为什么我的socket.makefile对象在关闭后仍然阻塞?
如果我使用 socket.makefile,然后关闭文件对象和底层的socket,那么后续调用 read
时会抛出一个异常,这正是我想要的。例如,下面的代码按我预期的那样工作:
import socket
from time import sleep
from threading import Thread
ADDR = ("localhost", 4321)
def listener(sock):
client,addr = sock.accept()
sleep(1)
client.close()
sock.close()
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
Thread(target=listener, args=[server_sock]).start()
sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
f.close()
sock.close()
f.read(8) # throws an exception, as I'd expect
但是,如果在文件/socket仍然打开的情况下调用 read
,那么这个调用会被阻塞,接着如果我关闭socket,read
方法仍然不会返回。实际上,它会一直挂着,直到另一端关闭socket。下面的代码展示了这种让人烦恼的行为:
import socket
from time import sleep
from threading import Thread
ADDR = ("localhost", 4321)
def reader(f):
print("about to read")
print("we read %r" % f.read(8))
print("finished reading")
def listener(sock):
client, addr = sock.accept()
sleep(3)
client.close()
sock.close()
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
Thread(target=listener, args=[server_sock]).start()
sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
Thread(target=reader, args=[f]).start()
sleep(1)
print("closing pseudo-file and socket")
f.close()
sock.close()
sleep(1)
print("we still haven't finished reading!")
这对我来说是个严重的问题,因为我想在一个线程中进行阻塞调用 f.read
,但同时又希望能够关闭socket,让这个线程从调用中返回(可能通过抛出异常)并退出。然而,发生的情况是,只要另一端不关闭socket,这个调用就会永远阻塞。
那么,有没有办法让 Thread1
在通过 socket.makefile
创建的类文件对象上调用 read
,然后让 Thread2
关闭socket,从而使 Thread1
停止在 read
调用上阻塞呢?
编辑:我尝试重写我的程序,完全使用 gevent
及其socket和Greenlet的多线程方法,但我的程序仍然表现得完全一样:
from gevent import sleep, socket, spawn
ADDR = ("localhost", 4321)
def reader(f):
print("about to read")
print("we read %r" % f.read(8))
print("finished reading")
def listener(sock):
client, addr = sock.accept()
sleep(3)
client.close()
sock.close()
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
spawn(listener, server_sock)
sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
spawn(reader, f)
sleep(1)
print("closing pseudo-file and socket")
f.close()
sock.close()
sleep(1)
print("we still haven't finished reading!")
sleep(2)
我很惊讶地发现,即使使用 gevent
sockets,调用 read
仍然会阻塞,即使底层的socket已经关闭。如果没有办法防止这种情况,我可能只能接受Thomas那句令人沮丧的“这是不可能的”回答 :(
2 个回答
是的,线程在处理一些事情时会表现得很糟糕,尤其是当多个线程同时操作像套接字这样的东西时。这种情况是很难让它正常工作的。你无法强制一个正在读取数据的线程中断这个读取操作或者结束它——在它还在读取的时候关闭套接字,反而更可能导致你的整个程序崩溃(或者更糟糕的情况),而不是达到你想要的效果。
处理这种情况的正确方法是使用非阻塞读取。像Twisted这样的事件框架可以帮助你解决这个问题。