(五)通过Python的select监控多个描述符实现并发连接
概述
本文通过使用select改写之前的服务器程序通过监控多个套接字描述符来实现并发连接并加入了一些机制让程序更加健壮,不过我们所有的实验都是建立在单词发送数据不会超过1024字节,如果超过你需要做特殊处理。
代码实例
描述符就绪条件
套接字准备好读
以下条件满足之一则套接字准备好读
- 套接字接收缓冲区中的数据长度大于0
- 该连接读半部关闭,也就是本端的套接字收到FIN,也就是对方已经发送完数据并执行了四次断开的第一次发送FIN,这时候本端如果继续尝试读取将会得到一个EOF也就是得到空。
- 套接字是一个监听套接字且已经完成的连接数量大于0,也就是如果监听套接字可读正面有新连接进来那么在连接套接字上条用accept将不会阻塞
- 套接字产生错误需要进行处理,读取这样的套接字将返回一个错误
套接字准备好写
以下条件满足之一则套接字准备好写
- 套接字发送缓冲区可以空间大于等于套接字发送缓冲区最低水位,也就是发送缓冲区没有空余空间或者空余空间不足以容纳一个TCP分组(1460-40=1420)。如果不够它就会等。当可以容纳了就表示套接字可写,这个可写是程序把数据发送到套接字发送缓冲区。
- 该连接写半部关闭,
- 使用非阻塞式connect的套接字已建立连接或者connect已经失败
- 有一个错误套接字待处理
服务器端代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com import socket
import select def echoStr(readAbledSockFD, rList):
try:
bytesData = readAbledSockFD.recv(1024)
data = bytesData.decode(encoding="utf-8")
if data:
print("收到客户端 ", readAbledSockFD.getpeername(), " 消息:", data)
if data.upper() == "BYE":
print("客户端 ", readAbledSockFD.getpeername(), " 主动断开连接。")
rList.remove(readAbledSockFD)
readAbledSockFD.close()
else:
readAbledSockFD.send(data.encode(encoding="utf-8"))
else:
"""
如果客户端进程意外终止,那么select将返回,因为该连接套接字收到FIN,所以readAbledSockFD读取的内容是''就是空,数据长度是0
也就是你试图读取一个收到FIN的套接字会出现这种情况,通常的错误信息是 "server terminated prematurely"
"""
print("客户端 ", readAbledSockFD.getsockname(), " 意外中断连接。")
rList.remove(readAbledSockFD)
readAbledSockFD.close()
except Exception as err:
"""
这里如果抛出异常通常是因为当连接套接字收到RST之后调用 recv()函数产生的 "Connection reset by peer" 错误,
为什么套接字会收到RST,通常是向一个收到FIN的套接字执行写入操作导致的。
"""
print("客户端 ", readAbledSockFD.getsockname(), " 意外中断连接。")
rList.remove(readAbledSockFD)
readAbledSockFD.close() def main():
sockFd = socket.socket()
sockFd.bind(("", 5556))
sockFd.listen(5) # 这里为什么要把这个监听套接字放入可读列表中呢?服务器监听套接字描述符如果有新连接进来那么该描述符可读
rList = [sockFd]
wList = []
eList = [] print("等待客户端连接......")
while True:
"""
select(),有4个参数,前三个必须也就是感兴趣的描述符,第四个是超时时间
第一个参数:可读描述符列表
第二个参数:可写描述符列表
第三个参数:错误信息描述符列表
对于自己的套接字来说,输入表示可以读取,输出表示可以写入,套接字就相当于一个管道,对方的写入代表你的读取,你的写入代表对方的读取 select函数返回什么呢?你把感兴趣的描述符加入到列表中并交给select后,当有可读或者有可写或者错误这些描述符就绪后,select就会返回
哪些就绪的描述符,你需要做的就是遍历这些描述符逐一进行处理。
"""
readSet, writeSet, errorSet = select.select(rList, wList, eList) # 处理描述符可读
for readAbledSockFD in readSet:
if readAbledSockFD is sockFd:
try:
connFd, remAddr = sockFd.accept()
except Exception as err:
"""
这里处理当三次握手完成后,客户端意外发送了一个RST,这将导致一个服务器错误
"""
print("")
continue
print("新连接:", connFd.getpeername())
# 把新连接加入可读列表中
rList.append(connFd)
else:
echoStr(readAbledSockFD, rList) # 处理描述符可写
for writeAbledSockFd in writeSet:
pass # 处理错误描述符
for errAbled in errorSet:
pass if __name__ == '__main__':
main()
客户端代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com import socket
import select
import sys def echoStr(sockFd, connectionFailed):
try:
bytesData = sockFd.recv(1024)
data = bytesData.decode(encoding="utf-8")
if data:
print("服务器回复:", data)
else:
"""
如果服务器进程意外终止,那么套接字也将返回,因为该连接套接字收到FIN,所以sockFd读取的内容是''就是空,数据长度是0
也就是你试图读取一个收到FIN的套接字会出现这种情况,通常的错误信息是 "server terminated prematurely"
"""
print("服务器 ", sockFd.getpeername(), " 意外中断连接。")
sockFd.close()
connectionFailed = True
except Exception as err:
"""
这里如果抛出异常通常是因为当连接套接字收到RST之后调用 recv()函数产生的 "Connection reset by peer" 错误,
为什么套接字会收到RST,通常是向一个收到FIN的套接字执行写入操作导致的。
"""
print("服务器 ", sockFd.getpeername(), " 意外中断连接。")
sockFd.close()
connectionFailed = True
return connectionFailed def main():
sockFd = socket.socket()
sockFd.connect(("127.0.0.1", 5556)) # 用于判断服务器是否意外中断
connectionFailed = False
while True:
data = input("等待输入:")
if data == "Bye":
sockFd.send("Bye".encode(encoding="utf-8"))
"""
shutdown就是主动触发关闭套接字,发送FIN,后面的参数是关闭写这一半,其实就是告诉服务器客户端不会再发送数据了。
"""
sockFd.shutdown(socket.SHUT_WR)
break
else:
sockFd.send(data.encode(encoding="utf-8"))
if echoStr(sockFd, connectionFailed):
break if __name__ == '__main__':
main()
改进的服务端代码
服务端代码没有做多少改动只是利用TCP机制减少了一些代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com import socket
import select def echoStr(readAbledSockFD, rList):
try:
bytesData = readAbledSockFD.recv(1024)
data = bytesData.decode(encoding="utf-8")
if data:
print("收到客户端 ", readAbledSockFD.getpeername(), " 消息:", data)
if data.upper() == "EXIT":
pass
else:
readAbledSockFD.send(data.encode(encoding="utf-8"))
else:
"""
如果客户端进程意外终止或者客户端主动断开,那么select将返回,因为该连接套接字收到FIN,所以readAbledSockFD读取的内容是''就是空,数据长度是0
,这种情况有两种可能:
1. 客户端主动断开,表示EOF,也就是资源无后续数据可以读取其实也就是连接关闭
2. 客户端进程崩溃,客户端内核还是会发送FIN,通常的错误信息是 "server terminated prematurely"
所以无论是哪种情况造成,这里也就是你试图读取一个收到FIN的套接字,我们统一视为关闭。
"""
print("客户端 ", readAbledSockFD.getpeername(), " *** EOF,主动断开连接。")
rList.remove(readAbledSockFD)
readAbledSockFD.close()
except Exception as err:
"""
这里如果抛出异常通常是因为当连接套接字收到RST之后调用 recv()函数产生的 "Connection reset by peer" 错误,
为什么套接字会收到RST,通常是向一个收到FIN的套接字执行写入操作导致的。
"""
print("客户端 ", readAbledSockFD.getsockname(), " 意外中断连接。")
rList.remove(readAbledSockFD)
readAbledSockFD.close() def main():
sockFd = socket.socket()
sockFd.bind(("", 5556))
sockFd.listen(5) # 这里为什么要把这个监听套接字放入可读列表中呢?服务器监听套接字描述符如果有新连接进来那么该描述符可读
rList = [sockFd]
wList = []
eList = [] print("等待客户端连接......")
while True:
"""
select(),有4个参数,前三个必须也就是感兴趣的描述符,第四个是超时时间
第一个参数:可读描述符列表
第二个参数:可写描述符列表
第三个参数:错误信息描述符列表
对于自己的套接字来说,输入表示可以读取,输出表示可以写入,套接字就相当于一个管道,对方的写入代表你的读取,你的写入代表对方的读取 select函数返回什么呢?你把感兴趣的描述符加入到列表中并交给select后,当有可读或者有可写或者错误这些描述符就绪后,select就会返回
哪些就绪的描述符,你需要做的就是遍历这些描述符逐一进行处理。
"""
readSet, writeSet, errorSet = select.select(rList, wList, eList) # 处理描述符可读
for readAbledSockFD in readSet:
if readAbledSockFD is sockFd:
try:
connFd, remAddr = sockFd.accept()
except Exception as err:
"""
这里处理当三次握手完成后,客户端意外发送了一个RST,这将导致一个服务器错误
"""
print("")
continue
print("新连接:", connFd.getpeername())
# 把新连接加入可读列表中
rList.append(connFd)
else:
echoStr(readAbledSockFD, rList) # 处理描述符可写
for writeAbledSockFd in writeSet:
pass # 处理错误描述符
for errAbled in errorSet:
pass if __name__ == '__main__':
main()
改进的客户端代码
客户端为什么改进?因为之前的客户端会阻塞在标准输入中,如果在等待客户端输入的时候服务端意外终止,那么此时客户端并不知道,只有发送数据的时候才会知道,这里我们改进的是客户端也使用select,它来监控套接字描述符和标准输入同时使程序不在被阻塞在标准输入上。你可以测试一下,当服务器启动后然后启动客户端,这时候客户端在等待输入,如果你把服务端终止那么在上面的版本中客户端并不知道虽然它得套接字已经收到FIN,但是在下面这版客户端程序中客户端会捕捉到这个变化从而直接终止客户端程序。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com """
解决了当服务器意外崩溃时客户端被阻塞在屏幕输入中,该版本程序使用了select。
""" import socket
import select
import sys def echoStr(sockFd, connectionFailed):
try:
bytesData = sockFd.recv(1024)
data = bytesData.decode(encoding="utf-8")
if data:
print("服务器回复:", data)
else:
"""
如果服务器进程意外终止,那么套接字也将返回,因为该连接套接字收到FIN,所以sockFd读取的内容是''就是空,数据长度是0
也就是你试图读取一个收到FIN的套接字会出现这种情况,通常的错误信息是 "server terminated prematurely"
"""
print("与服务器连接已断开。")
sockFd.close()
connectionFailed = True
except Exception as err:
"""
这里如果抛出异常通常是因为当连接套接字收到RST之后调用 recv()函数产生的 "Connection reset by peer" 错误,
为什么套接字会收到RST,通常是向一个收到FIN的套接字执行写入操作导致的。
"""
print("服务器 ", sockFd.getsockname(), " 意外中断连接。")
sockFd.close()
connectionFailed = True
return connectionFailed def main():
sockFd = socket.socket()
sockFd.connect(("127.0.0.1", 5556)) rList = [sockFd, sys.stdin]
wList = []
eList = [] # 用于判断服务器是否意外中断
connectionFailed = False
while True:
r, w, e = select.select(rList, wList, eList) if sockFd in r:
if echoStr(sockFd, connectionFailed):
break if sys.stdin in r:
x = sys.stdin.readline().strip()
if x.upper() == "EXIT":
sockFd.send(x.encode(encoding="utf-8"))
"""
shutdown就是主动触发关闭套接字,发送FIN,后面的参数是关闭写这一半,其实就是告诉服务器客户端不会再发送数据了。
为什么不直接close呢?这是因为假设此时还有服务器返回的数据在路上那么你还可以收到。
"""
sockFd.shutdown(socket.SHUT_WR)
else:
sockFd.send(x.encode(encoding="utf-8")) if __name__ == '__main__':
main()
(五)通过Python的select监控多个描述符实现并发连接的更多相关文章
- Python中的属性访问与描述符
Python中的属性访问与描述符 请给作者点赞--> 原文链接 在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个nam ...
- select限制之文件描述符限制
1.一个进能够打开的最大文件描述符限制.可以通过两种方式修改ulimit -n :获取最大文件描述符个数ulimit -n 2048:修改为2048个 该限制的测试代码: 客户端程序: /* 1.se ...
- python高级编程之最佳实践,描述符与属性01
# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #最佳实践 """ 为了避免前面所有的 ...
- Python 中的属性访问与描述符
在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问.一般而言,点(. ...
- python基础----再看property、描述符(__get__,__set__,__delete__)
一.再看property 一个静态属性property ...
- python之路(11)描述符
前言 描述符是用于代理另一个类的属性,一般用于大型的框架中,在实际的开发项目中较少使用,本质是一个实现了__get__(),__set__(),__delete__()其中一个方法的新式类 __get ...
- python 异步 select pooll epoll
概念: 首先列一下,sellect.poll.epoll三者的区别 select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当se ...
- Libev源码分析09:select突破处理描述符个数的限制
众所周知,Linux下的多路复用函数select采用描述符集表示处理的描述符.描述符集的大小就是它所能处理的最大描述符限制.通常情况下该值为1024,等同于每个进程所能打开的描述符个数. 增大描述符集 ...
- Python 描述符(descriptor) 杂记
转自:https://blog.tonyseek.com/post/notes-about-python-descriptor/ Python 引入的“描述符”(descriptor)语法特性真的很黄 ...
随机推荐
- mpvue中使用wxParse,解析a标签跳转问题
安装:npm i mpvue-wxparse js:import wxparse from "mpvue-wxparse"; css:@import url('~mpvue-wxp ...
- <玩转Django2.0>读书笔记:URL规则和视图
1. 带变量的URL #urls.py from django.urls import path from .view import * urlpatterns = [ path('',index_v ...
- centos7部署openstack-ocata
1.前言 本文旨在记录本人的一个实验过程,因为其中有一些坑,方便以后回顾查询. 其中限于篇幅(大部分是配置部分)有些内容省略掉了,官网都有,各位如果是安装部署的话可以参考官网,不建议使用本文. 以下是 ...
- C++输出
setiosflags 意思就是设置输入输出的标志iso::fixed 是操作符setiosflags 的参数之一,该参数指定的动作是以带小数点的形式表示浮点数,并且在允许的精度范围内尽可能的把数字移 ...
- iOS 获取app进程被杀死事件
程序被用户双击上滑杀死后,就对app做一些特殊的处理 下面的方法可以获取到用户双击上滑杀死的事件 - (void)applicationDidEnterBackground:(UIApplicatio ...
- sqlzoo:6
第一個例子列出球員姓氏為'Bender'的入球數據. * 表示列出表格的全部欄位,簡化了寫matchid, teamid, player, gtime語句. 修改此SQL以列出 賽事編號matchid ...
- 8.3-8.4NOIP模拟题总结
一:成绩 Day1 score=100+100+20 Day2 score=100+30+0 这成绩还是不行啊,仍需继续加油(抱怨一句暴力分有点少#滑稽) 二:题目分析 Day1 T1祖孙询问: 已知 ...
- Hibernate 检索方式之 HQL 检索方式
HQL(Hibernate Query Language) 是面向对象的查询语言,它和 SQL 查询语言有些相似.在 Hibernate 提供的各种检索方式中,HQL 是使用最广的一种检索方式,它有如 ...
- Shell 编程注意点
(一)判断语句 [$# -lt 4 ]判断语句,格式[空格 比较对象1 比较符号 比较对象2] $# 启动脚本时携带参数个数;参数个数总数. $1 代表第一个参数. $? 最后一次执行名命令的退出状态 ...
- KindEditor富文本编辑器, 从客户端中检测到有潜在危险的 Request.Form 值
在用富文本编辑器时经常会遇到的问题是asp.net报的”检测到有潜在危险的 Request.Form 值“一般的解法是在aspx页面 page 标签中加上 validaterequest='fa ...