Context
在写一个Socket I/O模块,功能要求如下:
- 作为服务端,需要永远循环等待连接
- 建立TCP连接后可以收发数据
- 收发数据相互独立,不能阻塞
Trouble
代码如下
def run_server(send_queue, receive_queue):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print(f"[server] Connecting with {addr}")
with conn:
while True:
try:
m = send_queue.get(block=False)
except queue.Empty as e:
m = None
if m:
print(isinstance(m, AbstractMessage))
if isinstance(m, AbstractMessage):
send_bytes = message2bytes(m)
conn.sendall(send_bytes)
print(f"Send message is {type(m)} : {send_bytes}")
try:
data = conn.recv(4096)
except BlockingIOError as e:
data = None
if data:
print(f"data is {data}")
receive_message = bytes2message(data)
print(f"Receive message is {receive_message}")
receive_queue.put(receive_message)
BUS.push(receive_message)
调试时发现当Client没有发送数据时,Server会阻塞地等待接收数据,也就是data = conn.recv(4096)
这一行代码,导致无法发送数据。
Solution
查阅queue — A synchronized queue class
后,得知recv()
方法需要传入两个参数,bufsize
和flags
:
Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified by bufsize. See the Unix manual page recv(2) for the meaning of the optional argument flags; it defaults to zero.
文档内只描述了bufsize
的用法,关于flags
只是一笔带过。
在StackOverflow的When does socket.recv(recv_size) return?问题中@Ray的回答:
You can also call
recv()
as nonblocking if you give the right flag for it:socket.recv(10240, 0x40) # 0x40 = MSG_DONTWAIT a.k.a. O_NONBLOCK
Please note that you have to catch the [Errno 11] Resource temporarily unavailable exceptions when there is no input data.
得知通过flags
参数可以将recv()
方法设置为MSG_DONTWAIT
,通过try-except
写法可以实现非阻塞。
代码如下:
try:
data = conn.recv(4096, 0x40)
except BlockingIOError as e:
data = None
tips: 在查阅了recv(2) - Linux man page文档后依然没能找到0x40
和MSG_DONTWAIT
的对照表。
Sunmmary
Python的socket.recv()
方法可以通过传入flags=0x40
参数配合try-except
方法实现非阻塞。