单进程tcp服务器-epoll版

1. epoll的优点

1没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024

2)效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll

3)epoll采用的事件通知机制

2. 文件描述符

     1linux内核kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文       件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件

    2文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进         程   打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述           符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往        只适用于UNIXLinux这样的操作系统。Window没有。

    3习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误     ( standard error)是 2。尽管这种习惯并非Unix内核的特性,但是因为一些 shell 和很多应用程序都使用这种习       惯,因此,如果内核不遵循这种习惯的话,很多应用程序将不能使用。

3.水平触发 / 边缘触发

  水平触发:将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用epoll时将再次报       告 这些文件描述符,这种方式称为水平触发. 

  边缘触发:只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将       不 会再次告知,这种方式称为边缘触发

    理论上边缘触发的性能要更高一些,但是代码实现相当复杂

 4.   ET  /  LT

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。直到你出来为止。

ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。下次将不会在通知。

ET要比LT效率高


5 .  epoll使用代码


from socket import *
import select

def main():

   #创建tcp服务器套接字
   server_socket = socket(AF_INET,SOCK_STREAM)
   #设置端口可以重用
   server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
   #绑定端口
   server_socket.bind(("",9999))
   #设置监听
   server_socket.listen(5)

   #用epoll设置监听收数据
   epoll = select.epoll()
   #把server_socket注册到epoll的事件监听中,如果已经注册过会发生异常
   epoll.register(server_socket.fileno(),select.EPOLLIN|select.EPOLLET)
   #装socket列表
   socket_lists = {}
   #装socket对应的地址
   socket_address = {}
   while True:
      #返回套接字列表[(socket的文件描述符,select.EPOLLIN)],
      # 如果有新的链接,有数据发过来,断开链接等都会解除阻塞
      print("epoll.poll--111")
      epoll_list = epoll.poll()#[(文件描述符,注册的事件)]
      print("epoll.poll--222")
      print(epoll_list)
      for fd,event in epoll_list:
         #有新的链接
         if fd == server_socket.fileno():
            print("新的客户fd==%s" % fd)
            new_sokect,new_address = server_socket.accept()
            #往字典添加数据
            socket_lists[new_sokect.fileno()] = new_sokect
            socket_address[new_sokect.fileno()] = new_address
            #注册新的socket也注册到epoll的事件监听中
            epoll.register(new_sokect.fileno(), select.EPOLLIN | select.EPOLLET)
         elif event ==select.EPOLLIN:
            print("收到数据了")
            #根据文件操作符取出对应socket
            new_sokect = socket_lists[fd]
            address = socket_address[fd]
            recv_data = new_sokect.recv(1024)
            if len(recv_data) > 0:
               print("已经收到[%s]:%s" % (str(address),recv_data.decode("gb2312")))
            else:
               #客户端端口,取消监听
               epoll.unregister(fd)
               #关闭链接
               new_sokect.close()
               print("[%s]已经下线" % str(address))



   #关闭套接字链接
   server_socket.close()

if __name__ == "__main__":
   main()

猜你喜欢

转载自blog.csdn.net/fenglei0415/article/details/80094450