Python Socket通信需按CTRL-C才能完成服务端文件写入问题
故障根因
核心问题出在TCP字节流特性和代码逻辑的不匹配,附带几处显性语法/逻辑错误:
- 核心阻塞原因:TCP传输没有消息边界,
socket.recv()方法只有在对端主动关闭TCP连接时才会返回空字节。客户端发完PDF文件后没有关闭连接,直接进入等待服务端响应的逻辑,导致服务端receive_file函数里的while received := conn.recv(4096)循环永远等不到空返回值,持续阻塞在recv调用上,既不会打印接收完成提示,也不会正常关闭文件句柄——写入的文件内容一直停留在内存IO缓冲区中,只有按下Ctrl+C中断进程时,系统强制刷新缓冲区,文件才会落盘可见。 - 其他代码错误:
- 服务端程序入口判断错误:
_name_ == '_main_'使用单下划线包裹变量,Python的入口判断必须使用双下划线包裹的特殊变量__name__ == "__main__",否则服务端启动后不会执行main函数。 - 客户端接收逻辑类型不匹配:
s.recv()返回值是bytes类型,直接和字符串"stop!"做相等判断永远不会成立;且打开练习文件的写句柄后,错误调用f.read()尝试从只写句柄读数据,会直接触发IO异常,正确逻辑应该从socket读取数据写入文件。 - 全链路无消息边界约定:传输PDF、练习文件名、练习文件内容时都没有明确的结束标识,TCP粘包问题会导致后续消息解析完全混乱。
- 服务端程序入口判断错误:
修复方案
采用「固定8字节文件头传文件长度 + 按长度精准读取内容」的方式划定消息边界,不需要关闭连接就能判断单份文件/消息传输完成,同时修正所有显性代码错误。
需要提前引入struct模块处理固定长度的文件大小数值,采用网络字节序8字节无符号整数(支持最大16EB文件,完全满足常规传输需求)。
修复后服务端代码(server.py)
import socket import struct from threading import Thread s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) HOST = socket.gethostname() IP = socket.gethostbyname(HOST) PORT = 4243 s.bind(('', PORT)) s.listen(5) def receive_file(conn, i): f = open("exam.pdf", "wb") # 先读8字节文件长度 size_data = conn.recv(8) file_size = struct.unpack('!Q', size_data)[0] received_size = 0 while received_size < file_size: chunk = conn.recv(min(4096, file_size - received_size)) if not chunk: raise ConnectionError("连接意外中断") f.write(chunk) received_size += len(chunk) print("File is fully copied\n") f.close() def send_result(conn,i): while 1: nbr = str(input("which exercise? ")) if nbr == "stop!": conn.send(b"stop!") break file_path = f"exercise{nbr}.txt" f = open(file_path, "rb") f.seek(0, 2) file_size = f.tell() f.seek(0) # 先发文件名 conn.send(file_path.encode("utf-8")) # 先发8字节文件长度 conn.send(struct.pack('!Q', file_size)) # 再发文件内容 while data := f.read(4096): conn.send(data) f.close() def main(): try: while 1: i = 0 conn, addr = s.accept() print("Got connection from", addr) t = Thread(target=receive_file, args=(conn, i)) t.start() t.join() t = Thread(target=send_result, args=(conn, i)) t.start() t.join() except KeyboardInterrupt: print("interrupting \n") conn.close() s.close() if __name__ == "__main__": main()
修复后客户端代码(client.py)
import socket import struct # from threading import Thread s = socket.socket() # Create a socket object HOST = "101.xx.x.xxx" # public IP address PORT = 4243 # Reserve a port for your service. PDF_PATH = "exam.pdf" s.connect((HOST, PORT)) def send(): f = open(PDF_PATH, "rb") f.seek(0, 2) file_size = f.tell() f.seek(0) # 先发8字节文件长度 s.send(struct.pack('!Q', file_size)) # 再发文件内容 while data := f.read(4096): s.send(data) f.close() return def receive(): while 1: exercise = s.recv(4096) if exercise == b"stop!": s.close() break file_name = exercise.decode("utf-8") f = open(file_name, "wb") # 读8字节文件长度 size_data = s.recv(8) file_size = struct.unpack('!Q', size_data)[0] received_size = 0 # 从socket读数据写文件,不要对写句柄调用read while received_size < file_size: chunk = s.recv(min(4096, file_size - received_size)) if not chunk: raise ConnectionError("连接意外中断") f.write(chunk) received_size += len(chunk) f.close() return def main(): send() receive() if __name__ == "__main__": main()
额外说明
如果不需要客户端发完文件后保持连接等待响应,也可以在客户端发完PDF后调用s.shutdown(socket.SHUT_WR)关闭写方向的连接,服务端收完PDF后会读到空字节退出循环,但这种方式后续服务端无法再给客户端发消息,不符合当前业务逻辑,所以优先使用长度头的方案。
内容的提问来源于stack exchange,提问作者federikowsky
相关产品推荐
相关产品推荐

