非阻塞Socket接收数据后函数阻塞问题排查求助
问题:非阻塞Socket接收数据后陷入阻塞的排查
我写了一段Python代码,从非阻塞Socket读取数据(有数据就打印),但接收并打印数据后,进程会陷入阻塞,只有杀死发送数据的进程才会恢复。我确认Socket已经设为非阻塞,也觉得continue语句可能多余,但就是找不到问题——Python不是我的主力语言,应该是犯了低级错误。
Socket初始化与连接监听代码
# Open socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setblocking(False) # Make port non-blocking s.bind((HOST, PORT)) s.listen(MAX_CONN) #print("Listening on port %d" % PORT) connection_list = [] connection_list.append(s) while True: # Get the list sockets which are ready to be read through select readable, writable, errored = select.select(connection_list, [], [], 0) # 0 = No timeout / do not block for sock in readable: # New connection if sock == s: # New connection received through server_socket sockfd, addr = s.accept() connection_list.append(sockfd) print("Client (%s, %s) connected" % addr)
接收数据的处理函数(疑似问题所在)
def handle_replies(): global connection_list for sock in connection_list: if sock != s: # Ignore our open listening socket try: data = sock.recv(1024) except socket.error as e: err = e.args[0] if err == errno.EAGAIN or err == errno.EWOULDBLOCK: print("Would block") # Socket has no data for us else: # a "real" error occurred print("Error: %s" % e) continue # To next sock in list else: if data: print("<%s> sent %s" % (str(sock.getpeername()), data)) continue
现象
当用另一个进程发送数据时,会输出类似 <('127.0.0.1', 33196)> sent b'[HELLO:STATUS]',但之后程序就卡住,直到杀死发送进程才恢复。
发送端代码(GTK3 C语言)
GSocket *sock = g_socket_connection_get_socket(connection); g_socket_set_blocking( sock, FALSE); /* use the connection */ GInputStream * istream = g_io_stream_get_input_stream (G_IO_STREAM (connection)); GOutputStream * ostream = g_io_stream_get_output_stream (G_IO_STREAM (connection)); bytes = sprintf(buffer, "[HELLO:STATUS]"); g_output_stream_write (ostream, buffer, (gsize)bytes, NULL, NULL);
问题原因与解决方案
核心问题1:新连接的Socket未设置非阻塞
你只在监听Socket上设置了非阻塞,但accept()返回的新连接Socket默认是阻塞模式的!这就导致后续对这个Socket调用recv()时,会直接阻塞住进程,直到有数据或连接关闭。
核心问题2:handle_replies的调用逻辑错误
你的主循环里用select.select(..., 0)实现就绪检测,但你绕开了select的结果,单独调用handle_replies直接遍历所有连接Socket调用recv()——哪怕Socket没有数据就绪。对于非阻塞Socket会抛出EAGAIN,但对于默认阻塞的新连接Socket,就会直接卡住。
核心问题3:空数据处理缺失
当recv()返回空字节串b''时,说明客户端已经关闭了连接,此时你需要把这个Socket从connection_list中移除,否则后续会一直进行无效调用。
修正后的代码要点
- 给新连接Socket设置非阻塞:
sockfd, addr = s.accept() sockfd.setblocking(False) # 新增这一行! connection_list.append(sockfd)
- 把接收逻辑整合到主循环的
readable处理中:
移除单独的handle_replies函数,直接在select返回的可读Socket列表中处理接收,避免无效操作:
while True: readable, writable, errored = select.select(connection_list, [], [], 0.1) # 设小超时降低CPU占用 for sock in readable: if sock == s: # 处理新连接 sockfd, addr = s.accept() sockfd.setblocking(False) connection_list.append(sockfd) print("Client (%s, %s) connected" % addr) else: # 处理已有连接的接收 try: data = sock.recv(1024) except socket.error as e: err = e.args[0] if err == errno.EAGAIN or err == errno.EWOULDBLOCK: continue # 处理真实错误,移除并关闭Socket print("Error: %s" % e) connection_list.remove(sock) sock.close() else: if data: print("<%s> sent %s" % (str(sock.getpeername()), data)) else: # 客户端关闭连接,清理资源 print("Client (%s, %s) disconnected" % sock.getpeername()) connection_list.remove(sock) sock.close()
- 优化
select超时:
把select的超时从0改成0.1(或其他合理值),避免无意义的空轮询导致CPU占用过高。
内容的提问来源于stack exchange,提问作者John U
相关产品推荐
相关产品推荐

