如何在客户端完成之前保持Python服务器连接开放?

1 投票
2 回答
3050 浏览
提问于 2025-04-16 15:01

我有一个标准的分叉TCP服务器设置,它接收来自客户端的请求,并将一个文件发送回去。服务器看起来是把所有数据都发送了,但我在客户端检查后发现,接收到的字节数和发送的字节数不相等。

经过进一步调查,发现客户端的接收方法显示服务器过早关闭了连接,导致接收失败。

于是我修改了服务器,让它在发送文件后暂停几秒钟,保持连接打开,直到客户端接收完数据,然后再关闭。这种做法虽然有效,但我觉得很不靠谱,因为很难预测线程应该暂停多久才能安全关闭连接。

我还尝试在服务器端设置SO_LINGER来保持连接活着,但这并没有帮助,尽管我觉得应该有效。

我相信应该有更好的方法来确保客户端完全接收到文件之前,连接不会关闭。我需要做些什么才能保证在客户端接收到所有数据之前,连接不会关闭呢?

服务器

class ForkingTCPRequestHandler(SocketServer.BaseRequestHandler):
    def createSPP(self, dataLen, success):
        SPPStruct = struct.Struct('I?')
        values = (socket.htonl(dataLen), success,)
        packed_data = SPPStruct.pack(*values)       
        return packed_data

    def handle(self):
         """Enabling SO_LINGER to keep connection alive doesn't help"""
        self.request.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 5))

        """Send a packet to the client so it knows the length of incoming data""" 
        spp = self.createSPP(os.path.getsize(FILE_NAME), 1)
        self.request.sendall(spp)

        """Sending the file, finish() is automatically called after this.""" 
        f = open(FILE_NAME, 'rb')
        fileData = f.read()
        self.request.sendall(fileData)
        f.close()

    def finish(self):
        """Sleep until the file is fully received by the client.
        Sleeping keeps the connection open. BaseRequestHandler automatically
        closes the connection when finish() returns. This works but is not a
        robust solution."""
        time.sleep(5)

class ForkingTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
    pass

if __name__ == '__main__':  
    try:
        server = ForkingTCPServer((HOST, PORT), ForkingTCPRequestHandler)
    except socket.error as e:
        sys.exit(1)

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()
        sys.exit(0)

客户端连接到服务器

    // Establishes a standard TCP connection
    memset(&targetAddr, 0, sizeof(targetAddr));
    targetAddr.sin_family = AF_INET;
    targetAddr.sin_port = htons(atoi(port));
    bcopy(hostdetails->h_addr, (char *)&targetAddr.sin_addr, hostdetails->h_length);

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (socket < 0) {
            return -1;
    }

    rc = connect(sock, (struct sockaddr *)&targetAddr, sizeof(targetAddr));
    if (rc < 0) {
            close(sock);
            return -1;
    }

客户端接收数据

    // Receiving spp (server side) known as symProcPacket (client side)
    // symProcPacket contains the length of the file that will be sent next
    // Receiving this packet is always successful
    typedef struct SymProcessPacket {
        u_int32_t totalDataLen;
        BOOL processingSuccessful;
    } SymProcessPacket;

    tempBuf = (char *)malloc(sizeof(SymProcessPacket)); 
    recvBytes = recv(s, tempBuf, sizeof(SymProcessPacket), 0);
    if (recvBytes < 0) {
        goto processingError;       
    }

    memcpy(&symProcPacket, tempBuf, sizeof(SymProcessPacket));
    free(tempBuf);

    // Receiving the file
    // Receive chunks and put in a buffer until entire file is received
    tempBuf = (char*) malloc(sizeof(char)*ntohl(symProcPacket.totalDataLen));
    totalRecv = 0;
    recvBytes = 0;

    while (totalRecv < ntohl(symProcPacket.totalDataLen)) {
        recvBytes = recv(sock, tempBuf+totalRecv, (1<<14), 0);
        if (recvBytes < 0) {
            // RecvBytes returns -1, which is an error before getting all the data
            // It gets a "Connection was reset by peer" error here, unless the server
            // sleeps for a bit. It means the server closed the connection early.
            printf("Error: %s", errtostr(errno));
            goto errorImporting;
        }
        totalRecv += recvBytes;
    }

2 个回答

0

我搞不懂为什么睡觉能解决问题。

不过我觉得,Python在发送5个字节,而C++在读取8个字节。? 在Python中占用一个字节。我认为BOOL被定义为一个整数,可能占用4个字节。

一般来说,不建议直接把结构体读写到网络套接字里。

1

我不打算讨论你的代码部分,主要关注你遇到的问题,就是如何接收完整的文件。有一个很不错的方法是使用HTTP的方式。首先,发送你要发送的字节数,这样接收方就知道从连接中接收多少数据。其实,只需要多发送一点额外的信息给接收方就能解决问题。

发送方:

  • 先发送要发送的文件大小
  • 然后发送文件的数据

接收方:

  • 接收文件大小
  • 接收数据,直到接收到的数据长度等于已接收的大小

撰写回答