单进程tcp服务器-select版

1. select 原理

io多路复用没有使用多进程和多线程的情况下完成多个套接字的使用

select 能够完成一些套接字的检查,从头到尾检查一遍后,标记哪些套接字是否可以收数据,返回的时候,就返回能接收数据的套接字,返回的是列表。select是由操作系统提供的,效率要高些,非常快的方式检测哪些套接字可以接收数据。select是跨平台的,在window也可以用。

网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。

这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。

  select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

2. 缺点

1)select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048

2)对socket进行扫描时是依次扫描的,即采用轮询的方法,效率较低

3)当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。


from socket import AF_INET,socket,SO_REUSEADDR,SOCK_STREAM,SOL_SOCKET
from select import select
import sys

def main():

   #创建tcp的socket套接字
   server_socket = socket(AF_INET,SOCK_STREAM)
   server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

   #绑定端口
   server_socket.bind(("",9999))

   #设置监听
   server_socket.listen(5)

   #客户端列表
   socket_lists = [server_socket,sys.stdin]
   wirte_list = []
   #是否退出
   is_run = False
   try:

      while True:
         #检测列表client_lists那些socket可以接收数据,
         #检测列表[]那些套接字(socket)可否发送数据
         #检测列表[]那些套接字(socket)是否产生了异常
         print("select--111")
         #这个select函数默认是堵塞,当有客户端链接的时候解除阻塞,
         # 当有数据可以接收的时候解除阻塞,当客户端断开的时候解除阻塞
         readable, wirteable,excep = select(socket_lists,wirte_list,[])
         # print("select--2222")
         # print(111)
         for sock in wirteable:
            #这个会一直发送,因为他是处于已经发的状态
            sock.send("thank you!".encode("gb2312"))
         for sock in readable:
            #接收数据
            if sock == server_socket:
               print("sock == server_socket")
               #有新的客户端链接进来
               new_socket,new_address = sock.accept()
               #新的socket添加到列表中,便于下次socket的时候能检查到
               socket_lists.append(new_socket)
            elif sock == sys.stdin:
               cmd = sys.stdin.readline()
               print(cmd)
               is_run = cmd
            else:
               # print("sock.recv(1024)....")
               #此时的套接字sock是直接可以取数据的
               recv_data = sock.recv(1024)
               if len(recv_data) > 0:
                  print("从[%s]:%s" % (str(new_address),recv_data))
                  sock.send(recv_data)
                  #把链接上有消息接收的socket添加到监听写的列表中
                  wirte_list.append(sock)
               else:
                  print("客户端已经断开")
                  #客户端已经断开,要移除
                  sock.close()
                  socket_lists.remove(sock)

         #是否退出程序
         if is_run:
            break

   finally:
      #关闭套接字
      server_socket.close()


if __name__ == "__main__":
   main()

3.  其他

select -->最多1024个套接字-->采用轮询方式进程检测套接字是否可以接收等

poll -->解决了支持套接字上线问题-->采用轮询方式进程检测

epoll-->解决支持上限问题-->采用的是事件通知




猜你喜欢

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