【问题标题】:How to keep Python server connection open until client is done with it?如何在客户端完成之前保持 Python 服务器连接打开?
【发布时间】:2011-04-03 22:55:35
【问题描述】:

我有一个标准的分叉 TCPServer 设置,它接收传入的请求并将文件发送回客户端。服务器似乎正在发送所有数据,但我检查了客户端收到的字节数!= 发送的字节数。

经过进一步排查,接收方法客户端提示服务端提前关闭连接——导致接收失败。

然后我修改服务器在发送文件后休眠几秒钟 - 保持套接字打开足够长的时间让客户端接收然后关闭它。这行得通,但在我看来它非常hackish,因为很难预测线程在关闭套接字之前应该休眠多长时间。

我已尝试设置 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;
    }

【问题讨论】:

  • sendall 已经被阻止了。你的代码对我有用。你是如何尝试连接到这个的?
  • 是的,socket.sendall() 和 send socket.send() 在技术上应该阻塞,但是当我接收数据时我仍然收到“连接已被对等方重置”错误客户端 -所以它不会因为某种原因而阻塞,因为服务器在客户端获取所有数据之前关闭了连接。
  • 什么函数实际返回错误?
  • 客户端和服务器是否在 ISP 的不同端提供连接?出于完全难以理解的原因,一些脑残的 ISP 将重置注入数据包流。通常在路由器上阻止这些重置就足够了。
  • @winston 我刚刚添加了客户端接收部分。服务器发送一个具有文件大小的数据包,客户端 recv() 直到它获得所有块。 @TokenMacGuy 这很有趣。我从来不知道这是可能的。但是客户端和服务器在我的家庭网络中运行,不同的机器,相同的 ISP。

标签: python sockets networking


【解决方案1】:

我正在跳过您的代码部分。我只会专注于您的问题,接收完整的文件。一种很酷的方法是采用 HTTP 方式。首先,只需发送您要发送的字节数,以便接收端将从套接字接收到该数量。因此,只需向接收器发送少量额外数据即可完成工作..

发件人:

  • 发送要发送的文件大小
  • 发送文件数据

接收者:

  • 接收文件大小
  • 在 len(data)==size_received 之前接收数据

【讨论】:

    【解决方案2】:

    我不明白为什么睡觉可以解决任何问题。

    但我认为 python 在发送 5 个字节而 C++ 正在读取 8 个字节。? 在 python 中占用字节。 BOOL 我相信是类型定义为 int 并且可能需要 4 个字节。

    通常不鼓励对套接字进行读/写结构。

    【讨论】:

      猜你喜欢
      • 2012-05-16
      • 1970-01-01
      • 2018-06-18
      • 2020-10-20
      • 1970-01-01
      • 1970-01-01
      • 2018-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多