首页 > 代码库 > 编写案例分别使用多进程、多路复用(select、epoll)实现tcp服务
编写案例分别使用多进程、多路复用(select、epoll)实现tcp服务
-------------------------------多进程的tcp服务器-------------------------------
通过为每个客户创建一个进程的方式,能够同时为多个客户进行服务器
当客户不是特别多的时候,这种方式还行,如果有几百上千个,就不可取了,因为每次创建进程等过程需要好较大的资源
python代码案例:
1 #coding=utf-8 2 3 #引用对应的包 4 from socket import * 5 6 from multiprocessing import Process 7 8 import sys 9 10 #进程函数:为客户提供tcp服务11 def tcpClient(newSocket,destAddr): 12 print("客户端(%s)以上线"%str(destAddr))13 while True:14 #数据的接受15 recvData=http://www.mamicode.com/newSocket.recv(1024)16 #模拟echo将数据回发服务器17 newSocket.send(recvData)18 19 #如果接收的数据长度为0,进行客户端的关闭操作20 if len(recvData) <= 0:21 print("------客户端(%s)已经下线-------"%str(destAddr))22 23 newSocket.close()24 break 25 print ("客户端(%s)传递过的数据为:%s"%(str(destAddr),recvData))26 27 28 #函数:main29 def main():30 #创建Tcp套接字31 socTcpSer=socket(AF_INET,SOCK_STREAM)32 33 socTcpSer.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)34 35 #创建ip和端口进行绑定36 local=("",int(sys.argv[1]))37 socTcpSer.bind(local)38 39 #开启监听程序40 socTcpSer.listen(5)41 42 print(‘---------等待客户端上线---------‘)43 44 #进行服务的开启,并循环为客户进行服务45 while True:46 47 #接受客户端响应信息48 newSocket,destAddr=socTcpSer.accept()49 50 #创建子进程51 pClient=Process(target=tcpClient,args=(newSocket,destAddr,))52 53 #子进程开始54 pClient.start()55 56 socTcpSer.close()57 58 59 #程序入口60 if __name__==‘__main__‘:61 main()
-------------------------------多路复用的服务器(select)-------------------------------
网络通信被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时间。
应用:
#import select:对应import包的引用操作
#readable,writeable,exceptinal=select.select(inputs,[],[]):使用select对套接字组成的列表进行遍历,过滤出对应的未堵塞的套接字
python代码示例:
1 #coding=utf-8 2 3 #引用对应的包 4 from socket import * 5 6 import select 7 8 import sys 9 10 #函数:main11 def main():12 #创建套接字13 serTcpSocket=socket(AF_INET,SOCK_STREAM)14 15 #绑定端口和ip,sys.argv[1] 运行时传递的参数16 serTcpSocket.bind(("",int(sys.argv[1])))17 18 #打开被动监听19 serTcpSocket.listen(5)20 21 print("-----服务器开启-----")22 23 #创建列表,存储对应的套接字24 inputs=[serTcpSocket]25 26 #循环27 while True:28 #使用select进行对应套接字的处理过滤29 readList,writeList,exceptList=select.select(inputs,[],[])30 31 #遍历readList列表进行操作32 for sockItem in readList:33 #如果为服务器套接字,进行accept()数据的接收34 if sockItem==serTcpSocket:35 #监听接受客户端传递过来的数据信息36 newSocket,destAddr=sockItem.accept()37 38 print("客户端(%s)以上线"%str(destAddr))39 40 #将用于与客户端通信的套接字进行存储41 inputs.append(newSocket)42 43 else:44 #进行客户端发送过来的数据的接收45 recvData=http://www.mamicode.com/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。
应用:
#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对象中注销该套接字
1 #coding=utf-8 2 3 #引用对应的包 4 from socket import * 5 6 import select 7 8 import sys 9 10 11 #函数:main12 def main():13 #创建服务器的套接字14 serTcpSocket=socket(AF_INET,SOCK_STREAM)15 16 #进行端口和ip的绑定操作17 serTcpSocket.bind(("",int(sys.argv[1])))18 19 #开启被动,进行监听20 serTcpSocket.listen(10)21 22 #创建一个epoll对象23 epoll=select.epoll()24 25 #使用epoll对套接字在操作系统中进行注册26 epoll.register(serTcpSocket.fileno(),select.EPOLLIN|select.EPOLLET)27 28 #创建两个字典29 #根据套接字的文件标识符对应套接字30 connection={}31 32 #根据套接字的文件标识符对应ip和端口元组信息33 address={}34 35 #提示服务开启36 print("------服务开启-----")37 38 #进行循环遍历39 while True:40 #接收对象中,未阻塞的套接字41 epollList = epoll.poll()42 43 for fd,event in epollList:44 #判断是否为服务器的套接字45 if fd==serTcpSocket.fileno():46 #接收客户端传递过来的数据信息47 newSocket,destAddr=serTcpSocket.accept()48 49 print("客户端(%s)以上线"%str(destAddr))50 51 #将对应的数据向字典中进行存储52 connection[newSocket.fileno()]=newSocket53 address[newSocket.fileno()]=destAddr54 55 #将套接字在对应的epoll对象中进行注册56 epoll.register(newSocket.fileno(),select.EPOLLIN|select.EPOLLET)57 58 elif event==select.EPOLLIN:59 #在字典中取出对应的套接字对象和地址(ip和端口)60 soc=connection[fd]61 addr=address[fd]62 63 #接收客户端发送过来的数据信息64 recvData=http://www.mamicode.com/soc.recv(1024)65 66 #判断客户端是否下线67 if len(recvData)>0:68 #进行打印69 print("客户端(%s):%s"%(str(addr),recvData))70 71 #echo服务,将信息回发到客户端72 soc.send(recvData)73 else:74 print("客户端(%s)以下线"%str(addr))75 76 #进行该套接字在epoll中的注销操作77 epoll.unregister(fd)78 79 #关闭该套接字80 soc.close()81 82 83 84 85 #程序入口86 if __name__=="__main__":87 main()
编写案例分别使用多进程、多路复用(select、epoll)实现tcp服务