Python3 Socket Server:如何确保conn.recv()的ACK在conn.close前发送?
我用WireShark抓包分析以下代码的TCP交互时发现,客户端关闭Socket后,服务器端的conn.recv()并没有发送ACK包。
抓包显示的交互流程为:
client >> server : FIN/ACK
server >> client : FIN/ACK
问题根源在于conn.close()在conn.recv()对应的ACK包发送之前就执行了。取消注释代码里的time.sleep(1)就能验证这一点——此时抓包流程变成:
client >> server : FIN/ACK
server >> client : ACK
server >> client : FIN/ACK
请问要怎么配置Socket,才能确保conn.recv()对应的ACK包在后续的conn.close()执行前就被发送出去?
相关代码
import socket # 补充原代码缺失的导入 class PLC_Server: def __init__(self, ip_address="localhost", port=5000): self.ip = ip_address self.port = port self.comm = {"address": self.ip + ":" + str(self.port)} self.buffer_size = 4096 self.client = None def _init_socket(self, listen_num=1): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.bind((self.ip, int(self.port))) self.sock.listen(listen_num) def run(self): self._init_socket() while True: conn, address = self.sock.accept() while True: recv_data = conn.recv(4096) if recv_data == b"": # FIN被conn.recv转换为长度为0的消息 print("Detect Client's socket close") # time.sleep(1) conn.close() break else: conn.send("OK") # 注意:这里需要传bytes类型,原代码会报错 if __name__ == '__main__': test = PLC_Server() test.run()
解决方案
方法1:设置TCP_QUICKACK选项
TCP协议默认有延迟ACK机制(通常200ms左右),用来合并ACK和后续的发送数据。我们可以通过开启TCP_QUICKACK选项,强制TCP栈立即发送ACK包,不需要等待延迟定时器。
修改后的代码片段:
if recv_data == b"": print("Detect Client's socket close") # 开启TCP_QUICKACK,强制立即发送ACK conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) conn.close() break else: conn.send(b"OK") # 修正为bytes类型
方法2:先shutdown再close
调用conn.shutdown(socket.SHUT_WR)会先关闭Socket的写方向,触发FIN包发送,但在此之前TCP栈会确保所有待发送的ACK都已发出。之后再调用close()释放Socket资源。
修改后的代码片段:
if recv_data == b"": print("Detect Client's socket close") # 先关闭写方向,确保ACK已发送 conn.shutdown(socket.SHUT_WR) # 可选:读取一次确认客户端的响应(如果需要) # conn.recv(4096) conn.close() break else: conn.send(b"OK") # 修正为bytes类型
方法3:发送空数据(不推荐)
虽然可以通过发送一个空的bytes对象conn.send(b"")来触发ACK,但这种方式不够优雅,还会产生额外的无效TCP段,因此不建议使用。
额外注意
原代码中的conn.send("OK")会抛出TypeError,因为send()方法要求传入bytes类型数据,必须改为conn.send(b"OK")。
内容的提问来源于stack exchange,提问作者orilyn

