多路复用(select、epoll)实现tcp服务
网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。
缺点:
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.对socket进行扫描时是依次扫描的,即采用轮询的方法,效率较低。当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.对socket进行扫描时是依次扫描的,即采用轮询的方法,效率较低。当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。
应用:
#import select:对应import包的引用操作
#import select:对应import包的引用操作
#readable,writeable,exceptinal=select.select(inputs,[],[]):使用select对套接字组成的列表进行遍历,过滤出对应的未堵塞的套接字
python代码示例:
[AppleScript] 纯文本查看 复制代码
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
1 #coding=utf-8 2 3 #引用对应的包 4 from socket import * 5 6 import select 7 8 import sys 910 #函数:main11 def main():12 #创建套接字13 serTcpSocket=socket(AF_INET,SOCK_STREAM)1415 #绑定端口和ip,sys.argv[1] 运行时传递的参数16 serTcpSocket.bind(("",int(sys.argv[1])))1718 #打开被动监听19 serTcpSocket.listen(5)2021 print("-----服务器开启-----")2223 #创建列表,存储对应的套接字24 inputs=[serTcpSocket]2526 #循环27 while True:28 #使用select进行对应套接字的处理过滤29 readList,writeList,exceptList=select.select(inputs,[],[])3031 #遍历readList列表进行操作32 for sockItem in readList:33 #如果为服务器套接字,进行accept()数据的接收34 if sockItem==serTcpSocket:35 #监听接受客户端传递过来的数据信息36 newSocket,destAddr=sockItem.accept()3738 print("客户端(%s)以上线"%str(destAddr))3940 #将用于与客户端通信的套接字进行存储41 inputs.append(newSocket)4243 else:44 #进行客户端发送过来的数据的接收45 recvData=sockItem.recv(1024)46 47 #进行判断,如果传递过来的数据不为空48 if len(recvData)>0:49 #进行客户端发送过来的数据的打印操作50 print("客户端(%s):%s"%(str(destAddr),recvData))51 else:52 #表示客户端下线53 print("客户端(%s)以下线!"%str(destAddr))54 55 #进行对应该客户端套接字的关闭操作56 sockItem.close()57 58 #将该套接字从对应的列表中移除59 inputs.remove(sockItem)60 61 #程序入口62 if __name__=="__main__":63 main() |
-------------------------------多路复用的服务器(epoll)-------------------------------
优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select。
优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select。
应用:
#import select:引用select包
#epoll=select.epoll():创建epoll对象
#soc.fileno():获取套接字对应的文件描述符
#epoll.register(soc.fileno(),select.EPOLLIN|select.EPOLLET):将创建的套接字添加到epoll的事件监听
EPOLLIN (可读)
EPOLLOUT (可写)
EPOLLET (ET模式)
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
#import select:引用select包
#epoll=select.epoll():创建epoll对象
#soc.fileno():获取套接字对应的文件描述符
#epoll.register(soc.fileno(),select.EPOLLIN|select.EPOLLET):将创建的套接字添加到epoll的事件监听
EPOLLIN (可读)
EPOLLOUT (可写)
EPOLLET (ET模式)
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
#epollList=epoll.poll():进行已注册套接字的扫描操作
#epoll.unregister(soc.fileno()):从epoll对象中注销该套接字
#epoll.unregister(soc.fileno()):从epoll对象中注销该套接字
[AppleScript] 纯文本查看 复制代码
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
1 #coding=utf-8 2 3 #引用对应的包 4 from socket import * 5 6 import select 7 8 import sys 91011 #函数:main12 def main():13 #创建服务器的套接字14 serTcpSocket=socket(AF_INET,SOCK_STREAM)1516 #进行端口和ip的绑定操作17 serTcpSocket.bind(("",int(sys.argv[1])))1819 #开启被动,进行监听20 serTcpSocket.listen(10)2122 #创建一个epoll对象23 epoll=select.epoll()2425 #使用epoll对套接字在操作系统中进行注册26 epoll.register(serTcpSocket.fileno(),select.EPOLLIN|select.EPOLLET) |

浙公网安备 33010602011771号