非阻塞:指在不能马上获得结果以前,该函数不会阻塞当前线程,而会马上返回。epoll工做在非阻塞模式时,才会发挥做用。编程
咱们了解了socket以后已经知道,普通套接字实现的服务端的缺陷:一次只能服务一个客户端!服务器
而且,为了使一个客户端可以不断收发消息,咱们还要使用while循环来轮询,这极大地下降了咱们的效率多线程
accept阻塞!并发
在没有新的套接字来以前,不能处理已经创建链接的套接字的请求app
recv 阻塞!socket
在没有接受到客户端请求数据以前,不能与其余客户端创建链接函数
能够用非阻塞接口来尝试解决这个问题!spa
阻塞IO模型操作系统
阻塞IO(blocking IO)的特色:就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。线程
什么是阻塞呢?想象这种情形:你要去车站接朋友,到了车站以后发现车还没到站,你如今有两种选择:
很明显,大多数人都会选择第二种方案。
而在计算机世界,这两种情形就对应阻塞和非阻塞忙轮询。
非阻塞IO模型
非阻塞套接字和阻塞套接字的区别:
把套接字设置为非阻塞以后,若是没有获得想要的数据,就会抛出一个BlockingIOError的异常。咱们能够经过捕获处理这个异常,让程序正常完成
非阻塞式IO中,用户进程实际上是须要不断的主动询问kernel数据准备好了没有
非阻塞如何利用
编程范式:
服务端
import socket CONN_ADDR = ('127.0.0.1', 9999) conn_list = [] # 链接列表 server = socket.socket() # 开启socket server.setblocking(False) # 设置为非阻塞 server.bind(CONN_ADDR) # 绑定IP和端口到套接字 server.listen(5) # 监听,5表示最大挂起数 print('start listen') while True: try: conn,addr = server.accept() #等待客户端链接,没有就抛出BlockingIOError conn.setblocking(False) print('{}已链接'.format(addr)) conn_list.append(conn) except BlockingIOError: pass conn_list = [x for x in conn_list] for conn_socket in conn_list:#对已链接的套接字进行轮询 try: data = conn_socket.recv(1024) #若有客户端发送消息,则打印并返回 except BlockingIOError: pass else: #else在不报错的时候才执行 if data: #判断客户端发过来的是否是空 print(data.decode()) conn_socket.send(data) else: #若为空,表示客户端已断开 conn_socket.close() conn_list.remove(conn_socket) print('客户端数目:{}'.format(len(conn_list)))
客户端
import socket client = socket.socket() client.connect(('127.0.0.1',9999)) while True: data = input('>>>>>') if data == 'q': #按q退出 break client.send(data.encode()) response = client.recv(1024) print(response.decode())
非阻塞IO模型优势:实现了同时服务多个客户端,可以在等待任务完成的时间里干其余活了(包括提交其余任务,也就是 “后台” 能够有多个任务在“”同时“”执行)。
可是非阻塞IO模型毫不被推荐
非阻塞IO模型缺点:
如何解决:多路复用IO
什么是IO多路复用技术呢,简单来讲,就是咱们把套接字交给操做系统去监控。
使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操做,感受效率更差。
可是,使用select之后最大的优点是用户能够在一个线程内同时处理多个socket的IO请求。用户能够注册多个socket,而后不断地调用select读取被激活的socket,
便可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须经过多线程的方式才能达到这个目的。
epoll是目前Linux上效率最高的IO多路复用技术。
epoll是惰性的事件回调,惰性事件回调是由用户进程本身调用的,操做系统只起到通知的做用。
epoll实现并发服务器,处理多个客户端
import socket import selectors # 注册一个epllo事件参数 # 1. 须要操做系统监控的套接字 # 2.事件(可读仍是可写) # 3.回调函数 def recv_data(conn): data = conn.recv(1024) if data: print('接收的数据是:%s' % data.decode()) conn.send(data) else: print('断开链接',conn) e_poll.unregister(conn) conn.close() def accept_conn(p_server): conn, addr = p_server.accept() print('Connected by', addr) # 也要注册一个事件 e_poll.register(conn,selectors.EVENT_READ,recv_data) CONN_ADDR = ('127.0.0.1', 9999) server = socket.socket() server.bind(CONN_ADDR) server.listen(6) # 生成一个epllo选择器实例 I/O多路复用,监控多个socket链接 e_poll = selectors.DefaultSelector() # Linux是epoll,Windows是select e_poll.register(server, selectors.EVENT_READ, accept_conn) # 事件循环 while True: # 事件循环不断地调用select获取发生变化的socket events = e_poll.select() for key, mask in events: call_back = key.data #key.data就是回调函数 call_back(key.fileobj) #key.fileobj是套接字