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的优点:

  1. 没有最大并发连接的限制,能打开的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]))

测试服务端输出效果

最新文章

  1. SQLSERVER将一个文件组的数据移动到另一个文件组
  2. 利用Python进行数据分析(13) pandas基础: 数据重塑/轴向旋转
  3. Android动画学习(一)——Android动画系统框架简介
  4. 小菜学习设计模式(三)—工厂方法(Factory Method)模式
  5. java中对List<Map<String,Object>>中的中文汉字排序
  6. golang的ssh例子
  7. Spring Cloud OAuth
  8. Discuz云平台站点信息同步失败,An unknown error occurred. May be DNS Error.
  9. 运行时动态修改webconfig
  10. 重新认识一个强大的 Gson
  11. 201521123099 《Java程序设计》 第10周学习总结
  12. linux下svn的安装与配置
  13. 访问vm中centos的web站点
  14. C语言第九次博客作业--指针
  15. Linux基本命令总结(四)
  16. Java-接口(interface)
  17. 今天遇到一件开心事,在eclipse编写的代码在命令窗口中编译后无法运行,提示 “错误: 找不到或无法加载主类”
  18. cordova 企业应用打包Archive的时候报 "#import <Cordova file not found"
  19. 网页静态处理技术FreeMarker概述
  20. '增量赋值(augmented assignment)', 多么痛的领悟!

热门文章

  1. bootstrap导入报错
  2. pechkin 导出https路径的图片
  3. Jmeter BlazeMeter实现web录制
  4. C#.NET 操作Windows服务(安装、卸载)
  5. 【2020五校联考NOIP #6】最佳观影
  6. Codeforces 1408I - Bitwise Magic(找性质+集合幂级数)
  7. c6和c7
  8. acid, acknowledge, acquaint
  9. 【讨论】APP的免填邀请码解决方案
  10. STL学习笔记1