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服务器的效率会随着其监测连接数量的增多而降低。