You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django的RobotDetail视图中集成Socket实现机器人通信

核心问题分析

  • 你当前的架构逻辑存在根本性错误:Socket服务端的监听逻辑被放在了form_valid方法中,该方法仅在用户提交表单更新机器人信息时才会触发执行,而Django视图是请求级别的,每次请求处理完成后资源就会被回收,你的Socket服务端根本不会常驻后台运行,自然会出现客户端连接被拒绝的问题。
  • start()方法内的while True死循环会直接阻塞当前Django请求,导致请求永远无法返回,浏览器会一直处于加载状态,端口也只会在提交表单的短暂时间内被绑定,后续客户端发起连接时服务端早就已经被销毁了。
  • BrokenPipe错误的触发原因是TCP连接的一端已经主动关闭连接,另一端还尝试向该连接发送数据,你的场景中就是Socket服务端逻辑被销毁后,残留的连接对象还尝试发送数据导致的。

正确实现方案

第一步:拆分Socket服务端为独立常驻进程

你需要把Socket服务端的逻辑单独抽离为独立的Python脚本,比如命名为socket_server.py,独立于Django进程运行,可以用supervisor或者systemd托管保证服务常驻后台:

import socket
import threading

HEADER = 64
PORT = 6060
# 绑定0.0.0.0允许所有地址的客户端接入
SERVER = "0.0.0.0"
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT!"

# 全局存储当前已连接的机器人客户端,方便后续定向下发指令
connected_clients = {}

def handle_client(conn, addr):
    print(f"[新连接] {addr} 已连接")
    robot_id = None
    connected = True
    while connected:
        try:
            msg_length = conn.recv(HEADER).decode(FORMAT)
            if msg_length:
                msg_length = int(msg_length)
                msg = conn.recv(msg_length).decode(FORMAT)
                if msg == DISCONNECT_MESSAGE:
                    connected = False
                # 约定首次连接时机器人上报自身ID,方便后续定向发指令
                if msg.startswith("ROBOT_ID:"):
                    robot_id = msg.split(":")[1]
                    connected_clients[robot_id] = conn
                # 处理Django侧下发的转发指令
                if msg.startswith("SEND_TO:"):
                    _, target_robot_id, command = msg.split(":", 2)
                    if target_robot_id in connected_clients:
                        target_conn = connected_clients[target_robot_id]
                        target_conn.send(command.encode(FORMAT))
                print(f"[{addr}] {msg}")
                conn.send("MSG Received".encode(FORMAT))
        except Exception as e:
            print(f"连接错误: {e}")
            connected = False
    if robot_id and robot_id in connected_clients:
        del connected_clients[robot_id]
    conn.close()

def start():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(ADDR)
    server.listen()
    print(f"[监听中] 服务端在 {SERVER}:{PORT} 监听")
    while True:
        conn, addr = server.accept()
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        thread.start()
        print(f"[活跃连接数] {threading.active_count() - 1}")

if __name__ == "__main__":
    start()

直接运行该脚本,Socket服务端就会常驻后台等待机器人和Django侧的连接。

第二步:Django侧仅保留指令下发逻辑

Django不需要承担Socket服务端的职责,只需要在需要下发指令的时候,作为Socket客户端和独立运行的Socket服务端通信即可,修改你的视图代码如下:

import socket
from django.views.generic.edit import UpdateView

HEADER = 64
PORT = 6060
# Socket服务端和Django同机的话直接填127.0.0.1即可
SERVER = "127.0.0.1"
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT!"

def send_command_to_robot(robot_id, command):
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(ADDR)
    # 约定指令格式:SEND_TO:<机器人ID>:<指令内容>
    msg = f"SEND_TO:{robot_id}:{command}"
    message = msg.encode(FORMAT)
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    send_length += b' ' * (HEADER - len(send_length))
    client.send(send_length)
    client.send(message)
    client.close()

class RobotDetail(UpdateView):
    model = Robot
    form_class = RobotUpdateForm
    template_name = 'dashboard/robotdetail.html'

    def form_valid(self, form):
        self.object = form.save()
        # 给当前操作的机器人下发指定路径的指令
        send_command_to_robot(str(self.object.id), self.object.path_options)
        return super().form_valid(form)

额外注意事项

  • 生产环境建议用Redis发布订阅或者消息队列来解耦Django和Socket服务端,比直接Socket通信稳定性更高
  • 要补充机器人断连重连、指令丢失重试的逻辑,避免出现指令下发失败的情况
  • 永远不要在Django视图中编写任何常驻阻塞逻辑,所有后台运行的任务都要拆分到独立进程中执行

内容的提问来源于stack exchange,提问作者Garberchov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 16:36:03