网络编程socket 结合IO多路复用select; epool机制分别实现单线程并发TCP服务器
select版-TCP服务器
1. select 原理
在多路复用的模型中,比较常用的有select模型和epoll模型。这两个都是系统接口,由操作系统提供。当然,Python的select模块进行了更高级的封装。
网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。
这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。
2、select版本基于socket模块的TCP并发服务器代码示例
说明:服务端采用socket.AF_INET socket.SOCK_STREAM ;即IP/TCP协议
# 使用select ,阻塞等待监控哪些套接字有新数据
# select模块的select()是个函数,接收四个参数rlist, wlist, xlist, timeout=None
# 分别表示准备监控读套接字列表,准备监控写套接字列表,准备监控异常套接字列表,超时时间
# 返回值为可读套接字列表,可写套接字列表, 异常套接字列表
# 有任何套接字有变化,就会有返回值,否则就一直阻塞住
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# @Time: 2020/7/4 16:56
# @Author:zhangmingda
# @File: socket_select_study.py
# @Software: PyCharm
# Description:select io多路复用实现单线程并发TCP服务器 import socket
import sys
import select listenAddr = ('', 8080)
tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpServer.bind(listenAddr)
tcpServer.listen(5) # 设置可以并发建立的连接数 # 准备让select 模块监控是否有数据可以收的套接字列表
inputs = [tcpServer, ] # sys.stdin linux下还可以加入sys.stdin作为监控键盘的输入
# 准备一个装已建立连接的字典,键为连接实例,值为对应的客户端IP地址信息
established = {} running = True
while True:
# 使用select ,阻塞等待监控哪些套接字有新数据
# select模块的select()是个函数,接收四个参数rlist, wlist, xlist, timeout=None
# 分别表示准备监控读套接字列表,准备监控写套接字列表,准备监控异常套接字列表,超时时间
# 返回值为可读套接字列表,可写套接字列表, 异常套接字列表
# 有任何套接字有变化,就会有返回值,否则就一直阻塞住
print("使用select IO多路负用监控套接字状态")
readable, writeable, exceptional = select.select(inputs, [], [])
print("注意:被监控的套接字有变化") # 循环判断收数据的套接字是否有数据到达
for sock in readable:
# 当收到数据的套接字为tcpServer时,说明时新的一个客户端到了
if sock == tcpServer:
# 获取套接字中活动的连接对象和客户端地址
conn, addr = tcpServer.accept()
# 将活动的套接字对象添加到监控列表中
inputs.append(conn)
print("建立了一个新TCP连接:", addr)
established[conn] = addr # 收到数据的时 标准输入,即键盘
elif sock == sys.stdin:
# 获取输入内容
cmd = sys.stdin.readline()
print("获取到键盘输入指令:%s 退出" % cmd)
running = False
break # 收到的数据不是上面两个,则肯定时已建立链接的套接字有新数据或者断开连接了
else:
# 读取客户端发来的数据
data = sock.recv(1024)
# 如果数据存在,就原封返回去:做个echo服务器
if data:
print("收到并返回:",data.decode('gb2312'))
sock.send(data)
# 如果不存在数据,说明连接状态异常了,断开连接并从监控列表移除连接对象
else:
inputs.remove(sock)
sock.close()
# 移除记录的客户端地址信息
print(established.get(sock), "客户已断开")
established.pop(sock) # while 循环必须有个跳出循环的条件,否则while下面的代码有获取不到while上面变量的风险
if not running:
break tcpServer.close()
客户端使用"网络调试助手.exe"发包
windows 下pycharm服务端输出效果:
Linux下监控键盘输入结果
如上代码当处理逻辑中有sleep时while 循环仍会卡住。并没有实现并发处理
可以参考之前threading.Thread() 多线程方式处理;每个线程处理一次套接字变化,参考代码如下:
当一个请求卡住时,不影响第二个连接的处理逻辑
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# @Time: 2020/7/4 16:56
# @Author:zhangmingda
# @File: socket_select_study.py
# @Software: PyCharm
# Description:select io多路复用实现单线程并发TCP服务器 import socket
import sys
import select
import time
from threading import Thread listenAddr = ('', 8080)
tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpServer.bind(listenAddr)
tcpServer.listen(5) # 设置可以并发建立的连接数 # 准备让select 模块监控是否有数据可以收的套接字列表
inputs = [tcpServer, ] # sys.stdin linux下还可以加入sys.stdin作为监控键盘的输入
# 准备一个装已建立连接的字典,键为连接实例,值为对应的客户端IP地址信息
established = {} # 退出循环判断用变量
running = True def sendTask(sock, data):
print("收到并返回:", data.decode('gb2312'))
sock.send(data)
time.sleep(10) while True:
# 使用select ,阻塞等待监控哪些套接字有新数据
# select模块的select()是个函数,接收四个参数rlist, wlist, xlist, timeout=None
# 分别表示准备监控读套接字列表,准备监控写套接字列表,准备监控异常套接字列表,超时时间
# 返回值为可读套接字列表,可写套接字列表, 异常套接字列表
# 有任何套接字有变化,就会有返回值,否则就一直阻塞住
print("使用select IO多路负用监控套接字状态")
readable, writeable, exceptional = select.select(inputs, [], [])
print("注意:被监控的套接字有变化") # 循环判断收数据的套接字是否有数据到达
for sock in readable:
# 当收到数据的套接字为tcpServer时,说明时新的一个客户端到了
if sock == tcpServer:
# 获取套接字中活动的连接对象和客户端地址
conn, addr = tcpServer.accept()
# 将活动的套接字对象添加到监控列表中
inputs.append(conn)
print("建立了一个新TCP连接:", addr)
established[conn] = addr # 收到数据的时 标准输入,即键盘
elif sock == sys.stdin:
# 获取输入内容
cmd = sys.stdin.readline()
print("获取到键盘输入指令:%s 退出" % cmd)
running = False
break # 收到的数据不是上面两个,则肯定时已建立链接的套接字有新数据或者断开连接了
else:
# 读取客户端发来的数据
data = sock.recv(1024)
# 如果数据存在,就原封返回去:做个echo服务器
if data:
# print("收到并返回:",data.decode('gb2312'))
# sock.send(data)
# time.sleep(10)
# 可以考虑使用一个新线程处理新数据
t = Thread(target=sendTask, args=(sock, data))
t.start() # 如果不存在数据,说明连接状态异常了,断开连接并从监控列表移除连接对象
else:
inputs.remove(sock)
sock.close()
# 移除记录的客户端地址信息
print(established.get(sock), "客户已断开")
established.pop(sock) # while 循环必须有个跳出循环的条件,否则while下面的代码有获取不到while上面变量的风险
if not running:
break tcpServer.close()
并行处理请求效果
epoll版-TCP服务器
1. epoll的优点:
- 没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024
效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
2. epoll使用参考代码
说明:epoll 监控的是被注册的套接字所对应的文件描述符。
例如 sys.stdin文件描述符数字为0;
sys.stdout文件描述符数字为1;
sys.stderr文件描述符数字为2;
其它套接字的文件描述符例如网络io socket的 通过 套接字实例的.fineno()获取具体对应的文件描述符对应数字。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# @Time: 2020/7/5 11:32
# @Author:zhangmingda
# @File: socket_epoll_study.py
# @Software: PyCharm
# Description: 使用epoll 事件通知机制实现单线程并发服务器 import socket
import select
import time # 创建一个套接字作为TCP服务端
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置套接字,服务端主动断开后快速回收,无需等待2MSL时间
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1) # 绑定本机监听地址和断开
listenAddr = ('',7788)
s.bind(listenAddr) # 开始监听端口
s.listen(10) # 创建一个epoll对象,
# 注意:windows下没有epoll
epoll = select.epoll() # 将监听套接字的文件描述符注册到epoll中
# select.EPOLLIN 为只监听是否有新数据可读,
# select.EPOLLET 设置事件如果没有处理下次是否还进行通知
# epoll对文件描述符的操作有两种模式:
# select.EPOLLET:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
# select.EPOLLLT:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
epoll.register(s.fileno(),select.EPOLLIN|select.EPOLLET) # 准备已连接客户端的socket文件符和对应连接socket、客户端地址存储的字典
conn_established = {}
conn_client_addr = {} # 循环等待客户端来连接或者对方发送数据
while True:
# 反复检查已活动的文件描述符列表,有就返回,没有就阻塞
# 文件描述符列表的元素为文件描述符和事件组成的元组
epoll_list = epoll.poll()
print('epoll_list:',epoll_list) # 循环所有文件描述符列表内容做判断
for fd, events in epoll_list:
print('fd:', fd)
print('events:', events) # 如果是监听的套接字文件描述符活动,说明有新连接到了
if fd == s.fileno():
conn, client_addr = s.accept()
print("新连接:", client_addr)
# 将新连接的socket文件描述符注册到监控列表中
epoll.register(conn.fileno(), select.EPOLLIN|select.EPOLLET)
# 存储已连接的socket
conn_established[conn.fileno()] = conn
conn_client_addr[conn.fileno()] = client_addr
# 如果活动的不是上面的监听套接字文件描述符,
# 那么就是已连接的文件描述符有新数据
# 此处使用fd判断或者events判断都行,
# 如下用events判断之前fd对监听套接字的判断如果符和已经拦截单独处理
elif events == select.EPOLLIN:
# 到此肯定是新数据,获取新数据进行判断
recvData = conn_established.get(fd).recv(1024)
# 返回数据大于0 是新数据,否则一定是断开连接了
if len(recvData) > 0:
print("from client: %s" % recvData.decode('gb2312'))
# 如果做一个Echo服务器,原封不动返回给客户端
conn_established.get(fd).send(recvData)
else:
# 判断为客户端断开连接,从epoll中注销对应文件描述符
epoll.unregister(fd)
# 获取到服务端的socket进行关闭
conn_established[fd].close()
print("客户端 %s 已断开连接" % str(conn_client_addr[fd]))
测试服务端输出效果
网络编程socket 结合IO多路复用select; epool机制分别实现单线程并发TCP服务器的更多相关文章
- 第五十五节,IO多路复用select模块加socket模块,伪多线并发
IO多路复用select模块加socket模块,伪多线并发,并不是真正的多线程并发,实际通过循环等待还是一个一个处理的 IO多路复用,lo就是文件或数据的输入输出,IO多路复用就是可以多用户操作 IO ...
- IO多路复用select/poll/epoll详解以及在Python中的应用
IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...
- io多路复用-select()
参照<Unix网络编程>相关章节内容,实现了一个简单的单线程IO多路复用服务器与客户端. 普通迭代服务器,由于执行recvfrom则会发生阻塞,直到客户端发送数据并正确接收后才能够返回,一 ...
- java网络编程socket\server\TCP笔记(转)
java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04| 分类: Socket | 标签:java |举报|字号 订阅 1 TCP的开销 a ...
- Android 网络编程 Socket
1.服务端开发 创建一个Java程序 public class MyServer { // 定义保存所有的Socket,与客户端建立连接得到一个Socket public static List< ...
- 网络编程socket基本API详解(转)
网络编程socket基本API详解 socket socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信. socket ...
- 网络编程Socket之TCP之close/shutdown具体解释(续)
接着上一篇网络编程Socket之TCP之close/shutdown具体解释 如今我们看看对于不同情况的close的返回情况和可能遇到的一些问题: 1.默认操作的close 说明:我们已经知道writ ...
- 铁乐学Python_Day33_网络编程Socket模块1
铁乐学Python_Day33_网络编程Socket模块1 部份内容摘自授课老师的博客http://www.cnblogs.com/Eva-J/ 理解socket Socket是应用层与TCP/IP协 ...
- Linux IO多路复用 select
Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...
随机推荐
- 洛谷 P4292 - [WC2010]重建计划(长链剖分+线段树)
题面传送门 我!竟!然!独!立!A!C!了!这!道!题!incredible! 首先看到这类最大化某个分式的题目,可以套路地想到分数规划,考虑二分答案 \(mid\) 并检验是否存在合法的 \(S\) ...
- CF1493E Enormous XOR
题目传送门. 题意简述:给出长度为 \(n\) 的二进制数 \(l,r\),求 \(\max_{l\leq x\leq y\leq r}\oplus_{i=x}^yi\). 非常搞笑的题目,感觉难度远 ...
- php header下载文件 无法查看原因
php header下载文件 无法查看原因 php header下载文件 下方函数可以下载单个文件 function download($file_url){ if(!isset($file_url) ...
- 监督学习&非监督学习
监督学习 1 - 3 - Supervised Learning 在监督学习中,数据集中的每个例子,算法将预测得到例子的""正确答案"",像房子的价格,或者溜 ...
- 以DevExpress开发的WinFrom程序的多语言功能的实现
以DevExpress开发的WinFrom程序的多语言功能的实现 写在前面: 多语言切换功能在Winform程序中是经常遇到的需求,尤其是需要给国外客户使用的情况下,多语言功能是必不可少的.前一段时间 ...
- SpringBoot整合Shiro 四:认证+授权
搭建环境见: SpringBoot整合Shiro 一:搭建环境 shiro配置类见: SpringBoot整合Shiro 二:Shiro配置类 shiro整合Mybatis见:SpringBoot整合 ...
- 【模板】单源最短路径(Dijkstra)/洛谷P4779
题目链接 https://www.luogu.com.cn/problem/P4779 题目大意 给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个非负权值,求从 \(s\) 点出发,到 ...
- Kubernetes主机间cluster ip时通时不通
1.问题现象 测试部署了一个service,包括2个pod,分别在node1和node2上. $ kubectl get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) ...
- Phoenix二级索引
Phoenix Hbase适合存储大量的对关系运算要求低的NOSQL数据,受Hbase 设计上的限制不能直接使用原生的API执行在关系数据库中普遍使用的条件判断和聚合等操作.Hbase很优秀,一些团队 ...
- Hive相关知识点
---恢复内容开始--- 转载:Hive 性能优化 介绍 首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题? 数据量大不是问题,数据倾斜是个问题. jobs数比较多的作业运行效率相 ...