阻塞
在网络通信过程中,像recvfrom()/recv()/accept()等相关的函数,在进行网络相关数据的接收时,会默认阻塞的等待,这种通信方式叫做阻塞IO
非阻塞
非阻塞就是没有接收数据的时候,并没有继续等待,而是报出一个异常,这样程序就会执行到下个流程继续执行,不会影响到后面的操作
IO多路复用
多路IO好处就在于单个进程就可以同时处理多个网络连接的IO 通过一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回
轮询
就是在socket通信过程中,通过for/while死循环,监听每个socket的通信状态,并且执行效率和轮询的socket数量息息相关,一般在1024-2048左右
- EPOLLIN (可读)
- EPOLLOUT (可写)
- EPOLLET (ET模式)
epoll对文件描述符的操作有两种模式:
- LT(level trigger)--水平触发
- ET(edge trigger)--边界触发
LT模式是默认模式,LT模式与ET模式的区别如下:
- LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
- ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
epoll模型
由操作系统来负责监听每个任务(socket),当某个socket有数据时,操作系统就通知用户进程
import select
import socket
def main():
# 创建套接字 设置
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.setblocking(False)
# bind listen
server_socket.bind(('', 8080))
server_socket.listen(128)
# 创建epoll对象
epoll = select.epoll()
# socket {1:socket1,2:socket2}
socket_list = {}
# address {1:address, 2:address2}
address_list = {}
# 将服务器套接字 添加-注册到epoll对象的监视列表 fd = file descriptor
epoll.register(server_socket.fileno(), select.EPOLLIN)
while True:
# 获取监听结果清单列表 [()()()()()]
epoll_list = epoll.poll()
# 循环便利 清单列表 对其中客户端进行处理
# fd表示有事件到达的套接字的文件描述符 events代表事件
for fd, events in epoll_list:
# 如果有数据到达的是服务器套接字 说明 服务器收到了来自客户端的连接请求 accept
if fd == server_socket.fileno():
new_client_socket, new_client_addr = server_socket.accept()
print("接受到来自%s的连接请求" % str(new_client_addr))
# 设置套接字为非阻塞模式
new_client_socket.setblocking(False)
# 将新来的客户端添加到 epoll的监视列表中 和 本地的集合中
# 流式报文协议 -- 没有消息边界
epoll.register(new_client_socket.fileno(), select.EPOLLIN|select.EPOLLET)
# epoll.register(new_client_socket.fileno(), select.EPOLLIN)
# 将新来的客户端的套接字 地址信息保存在集合中
socket_list[new_client_socket.fileno()] = new_client_socket
address_list[new_client_socket.fileno()] = new_client_addr
# 不是 数据 recv
elif events == select.EPOLLIN: # 客户端
# 从集合中根据fd获取 对应的套接字信息
client_socket = socket_list[fd]
# 接收数据
recv_data = client_socket.recv(5)
#
if recv_data:
print("收到来自%s的数据:%s" % (str(address_list[fd]),recv_data))
else:
print("接收到来%s的断开请求" % str(address_list[fd]))
# 如果客户端关闭套接字 本地需要关闭套接字
# 从epoll监视列表中移除
epoll.unregister(fd)
# 从集合中删除客户端套接字对象 和 地址对象
del socket_list[fd]
del address_list[fd]
# 关闭客户端套接字
client_socket.close()
if __name__ == '__main__':
main()