学科分类
目录

单进程非阻塞服务器

单进程非阻塞服务器通过解阻塞的方式实现并发操作。Python中套接字默认以阻塞方式处理数据,若套接字调用accept()、recv()等方法时没有接收到数据,套接字就会阻塞等待数据递达。用户可通过套接字中提供的setblocking()方法将套接字设置为非阻塞模式,如此即便套接字中没有数据递达,套接字调用的方法也会立刻返回。

setblocking()的使用示例如下:

server_socket.setblocking(False)

setblocking()方法的默认参数为True,套接字默认工作在阻塞模式。以上示例中的参数False是一个实参,用于将套接字设置为非阻塞模式。

由分析可知,若要使服务器可以与多个客户端建立连接,需要保证在调用accept()方法时不会产生阻塞,即服务器套接字server_socket应被设置为非阻塞;若要使每个连接中的客户端都可随时向服务器中发送数据,则需保证在调用recv()方法时不产生阻塞,即新套接字new_socket应被设置为非阻塞。

下面以前面的TCP数据转换服务器为基础,实现一个TCP单进程非阻塞并发服务器,具体代码如下:

 1  import socket
 2  def main():
 3    # 1.创建socket
 4    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 5    # 重复使用绑定的信息
 6    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 7    local_addr = ('', 6789)
 8    # 2.绑定本地ip以及port
 9    server_socket.bind(local_addr)
 10   # 3.将socket变为监听(被动)套接字
 11   server_socket.listen(3)
 12   # 4.将服务器套接字server_socket设置为非阻塞
 13   server_socket.setblocking(False)
 14   # 用来保存所有已经连接的客户端的信息
 15   client_address_list = []            
 16   while True:
 17     # 等待一个新的客户端到来
 18     try:
 19       new_socket, client_address = server_socket.accept()
 20     except:
 21       pass
 22     else:
 23       print("一个新的客户端到来:%s"%str(client_address))
 24       # 将新套接字new_socket设置为非阻塞
 25       new_socket.setblocking(False)
 26       # 将本次建立连接后获取的套接字添加到已连接客户端列表中
 27       client_address_list.append((new_socket, client_address))
 28     for client_socket,client_address in client_address_list:
 29       try:
 30         recv_info = client_socket.recv(1024).decode('gb2312')
 31       except:
 32         pass
 33       else:
 34         if len(recv_info)>0:        
 35           # 数据处理
 36           print('待处理数据:%s'%recv_info)
 37           data = recv_info.upper()
 38           client_socket.send(data.encode('gb2312'))
 39           print('处理结果:%s'%data)
 40         else:
 41           # 断开连接
 42           client_socket.close()
 43           # 将套接字从已连接客户端列表移除
 44           client_address_list.remove((client_socket, 
 45                              client_address))
 46           print("%s 已断开连接"%str(client_address))
 47   server_socket.close()
 48 if __name__ == '__main__':
 49   main()

以上示例的第15行代码创建了一个列表client_address_list,该列表用于存储每次建立连接后获得的客户端信息,由于每次连接都会产生新的套接字和客户端地址,若不存储这些信息,这些信息将会被下次建立连接后获取到的套接字和客户端地址信息覆盖。

需要说明的是,在此段代码中为accept()方法和recv()方法的调用设置了异常处理,这是因为,若非阻塞套接字调用这两个方法时没有接收到数据,将会抛出如下所示的异常:

BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。

当客户端发送的数据长度为0字节时,表示客户端将断开连接,这种情况下关闭服务器端与该客户端进行数据交互的套接字,并将套接字从已连接客户端列表中移除。第34行代码对接收到的数据长度进行了判断,根据数据长度决定数据处理方式。

为了测试该程序的功能,下面实现一个用于发送数据和接收数据处理结果的客户端程序,客户端程序代码如下:

import socket
def main():
  # 1.创建套接字client_socket
  client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # 2.请求连接
  client_socket.connect(('172.16.43.31', 6789))
  # 3.发送数据
  while True:
     data = input("-----待处理数据------\n")
     client_socket.send(data.encode('gb2312'))
     recv_info = client_socket.recv(1024).decode('gb2312')
     print("------处理结果-------\n%s" % recv_info)
  # 4.关闭聊天室套接字server_socket
  server_socket.close()
if __name__ == '__main__':
  main()

启动服务器程序,在多个控制台窗口中分别执行客户端程序,服务器端中将依次打印建立连接的客户端地址信息,如下所示:

一个新的客户端到来:('172.16.43.31', 7836)
一个新的客户端到来:('172.16.43.31', 7844)
一个新的客户端到来:('172.16.43.31', 7852)
一个新的客户端到来:('172.16.43.31', 7861)

由此可知,服务器程序可同时与多个客户端建立连接。

使用客户端向服务器发送数据,客户端1、2与服务器端打印的信息分别如下。

客户端1:

----待处理数据------
hello itehima
------处理结果-------
HELLO ITEHIMA
-----待处理数据------

客户端2:

-----待处理数据------
hello world
------处理结果-------
HELLO WORLD
-----待处理数据------

服务器:

一个新的客户端到来:('172.16.43.31', 7836)
一个新的客户端到来:('172.16.43.31', 7844)
一个新的客户端到来:('172.16.43.31', 7852)
一个新的客户端到来:('172.16.43.31', 7861)
待处理数据:hello itehima
处理结果:HELLO ITEHIMA
待处理数据:hello world
处理结果:HELLO WORLD

由此可知,服务器程序可处理多个客户端请求。

综上所述,单进程非阻塞并发服务器实现成功。

点击此处
隐藏目录