项目经验 | 后端与判题端的socket通信和调度

本项目的后端是用Django开发的,而我主要做的是判题端的通信调度,判题端是用Python手撸的,并且采用socket进行通信。

总体流程图

遇到的问题:

  • 多个socket进行通信的架构
    之前没有遇到过创建多个socket来通信的结构,这里记录下代码。
    判题端用socketserver创建服务器,后端用socket创建多个客户端连接多个判题端。
class Clients:
    def __init__(self):
        self.judgers = Judger.objects.all()
        self.clients = {}
        for judger in self.judgers:
            self.clients[judger.ip] = socket.socket(
                socket.AF_INET, socket.SOCK_STREAM)
            try:
                self.clients[judger.ip].connect((judger.ip, JUDGER_PORT))
                logger.info('clients add %s' % (judger.ip))
            except:
                del self.clients[judger.ip]
        if not self.clients:
            raise ConnectionError('[client] 当前没有判题机在线')
  • 外部文件import Django内部文件的model问题

外部文件import django内部文件的model会出现引用失败的情况,需要加上如下代码

pathname = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, pathname)
sys.path.insert(0, os.path.abspath(os.path.join(pathname, '..')))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "OnlineJudge.settings")
django.setup()
  • Django ORM断连问题

这个搞得我头大,之前用过隔一个小时发一个wakeup包的方法,现在看到一个原生的方法还不错,在执行orm出错的语句前加上它就能无视6小时诅咒了。

db.close_old_connections()
  • 大文件传输问题

TCP传递大文件会遇到问题,这里的方法是,把需要传递的包切成1024等分的包数组,然后传包数提醒判题服务器,再把每个小包传过去,这里传递每个包后可以收下判题服务器的返回'ok',防止粘包。

body_list = [body[i:i+1024] for i in range(0, len(body), 1024)]
body_list_lenght = str(len(body_list)).encode('utf-8')
def send_data(self, client, body_list, body_list_lenght):
    client.send(body_list_lenght)
    logger.info('send body_list_lenght: %d ' %len(body_list) )
    for item in body_list:
        client.send(item)
        res = str(client.recv(10)) # 防止粘包
        logger.debug('send: %d | %s' % (len(item),res) )
    res = str(client.recv(10))
    logger.info('send body: %s'%res)
  • 判题端的结构

第一次写不用框架的python独立应用哈,还是前端菜鸟写的,结构有点奇葩轻喷
数据库初始化封装成 model.py
对model的操作封装成 model_api.py
对接客户端的服务端封装成 server.py
分配任务处理数据开辟线程的封装成 controller.py
判题端实际上是从原来后端里分离出来的,所以依然有 judge.pyJudger.py
docker的单例化文件 docker_client.py
文件夹有 log存放日志, question存放输入输出样例、 code存放临时代码

  • 多线程判题

开辟线程前必须要注意对数据库的活动容器记录必须在主线程,不然的话绝对出神奇的错误,然后用threading.Thread().start()启动线程,并且配合信号量sem = Semaphore(),在使用的时候python有语法糖,只需要 with sem: 就行啦

你可能感兴趣的:(项目经验 | 后端与判题端的socket通信和调度)