学科分类
目录

select并发服务器

Python中通过select模块实现select并发服务器,该模块包含了一个select()函数,函数语法格式具体如下:

select(rlist, wlist, xlist[, timeout])

根据数据的类型,套接字分为三种状态,即可读状态、可写状态和异常状态。可读状态表示当前套接字接收到了其它套接字发来的数据;可写状态表示当前套接字已准备好向其它套接字发送数据;异常状态表示当前套接字获取了套接字使用过程中产生的异常信息。

select()函数可同时监测套接字的可读、可写和异常状态,该函数中的前三个参数都是列表,其中rlist表示等待读就绪的套接字列表;wlist表示等待写就绪的套接字列表;xlist表示等待异常出现的套接字列表。若select模式不监测某个套接字列表,可将对应参数设置为“[]”。select()函数中的第4个参数timeout是一个可选参数,用于指定等待时长,通常使用以秒为单位的浮点数表示,若该参数缺省,则等待永不会超时。

select()函数调用成功将会返回3个列表,依次为读就绪套接字列表、写就绪套接字列表和出现异常的套接字列表,用户可在程序中分别遍历这三个列表,获取每个套接字接收到的数据、要发送的数据和出现的异常信息。

下面将搭建一个基于select模式、可实现小写转大写功能的TCP服务器,具体代码如下。

 1  import select
 2  import socket
 3  import sys
 4  def main():
 5    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 6    server_socket.bind(('', 7788))
 7    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 8    server_socket.listen(5) 
 9    # 将服务器套接字加入等待读就绪的套接字列表
 10   inputs = [server_socket]       
 11   while True:
 12     # 调用select()函数,阻塞等待      
 13     readable, writeable, exceptional = select.select(inputs, [], [])
 14     # 数据抵达,循环
 15     for temp_socket in readable:
 16       # 监听到有新的连接
 17       if temp_socket == server_socket:
 18         new_socket, client_address = server_socket.accept()
 19         print("一个新的客户端到来:%s" % str(client_address))
 20         # select监听的temp_socket
 21         inputs.append(new_socket)
 22       # 有数据到达
 23       else:
 24         # 读取客户端连接发送的数据
 25         data = temp_socket.recv(1024)
 26         # 若有数据递达,对数据进行处理
 27         if data:
 28           data = data.upper()
 29           temp_socket.send(data)
 30         # 若未接收到数据,断开连接
 31         else:
 32           # 从监听列表中移除对应的temp_socket
 33           inputs.remove(temp_socket)
 34           temp_socket.close()
 35   server_socket.close()
 36 if __name__ == '__main__':
 37   main()

以上程序搭建的服务器只对读就绪套接字进行了处理:

程序第10行代码创建了等待读就绪的套接字列表inputs,并使用监测客户端连接请求的套接字server_socket初始化该列表。客户端的连接请求相当于客户端发送到服务器套接字server_socket中的数据,若有客户端发送了连接请求,服务器套接字server_socket中将会接收到数据,并转变为读就绪状态。

程序第13行代码调用select()监测服务器端各种套接字列表的状态,经此步骤后,readable列表存储读就绪的套接字,writeable列表存储写就绪的套接字,exceptional列表存储捕获到异常的套接字。

之后程序对可读套接字列表中的套接字逐个进行处理:若可读套接字列表中包含服务器套接字server_socket,说明有新的客户端连接请求到达,此时调用accept()方法对其进行处理,并将accept()方法返回的套接字new_socket添加到等待读就绪的套接字列表inputs中;若可读套接字列表中包含有非服务器套接字的其它套接字,说明有已建立连接的客户端程序向服务器发送了数据,此时服务器应通过该套接字接收客户端程序发送的数据,并根据数据的长度执行不同的操作。

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

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', 7788))
  # 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()

在启动服务器之后启动客户端,服务器中将打印客户端的地址信息。本次测试共启动了4个客户端,客户端都启动之后,服务器中打印的信息如下:

一个新的客户端到来:('172.16.43.31', 9668)
一个新的客户端到来:('172.16.43.31', 10269)
一个新的客户端到来:('172.16.43.31', 10275)
一个新的客户端到来:('172.16.43.31', 10281)

由以上信息可知, TCP服务器可与多个客户端建立连接。

使用客户端向服务器中发送数据,客户端中打印的信息如下所示:

-----待处理数据------
hello itheima
------处理结果-------
HELLO ITHEIMA
-----待处理数据------

由以上信息可知, TCP服务器可成功接收客户端发送的数据,并将数据的处理结果返回给客户端。

综上所述,基于select模型的TCP并发服务器实现成功。

select服务器先在遍历中获取到所有已就绪套接字列表,再对列表中的套接字进行操作;只有本次获取到的所有已就绪套接字处理完毕时,select服务器才会再次遍历所有连接。与单进程非阻塞服务器相比,若服务器中的不活跃连接较多,select可以避免大量无用轮询,以此提高服务器的效率。

需要说明的是,select服务器能监测的套接字数量相当有限,在Linux系统中,其上限一般为1024,用户可以通过修改宏定义、重新编译内核的方式打破这一限制,但select服务器的效率会随着其监测连接数量的增多而降低。

点击此处
隐藏目录