概述

本文通过使用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监控多个描述符实现并发连接的更多相关文章

  1. Python中的属性访问与描述符

    Python中的属性访问与描述符 请给作者点赞--> 原文链接 在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个nam ...

  2. select限制之文件描述符限制

    1.一个进能够打开的最大文件描述符限制.可以通过两种方式修改ulimit -n :获取最大文件描述符个数ulimit -n 2048:修改为2048个 该限制的测试代码: 客户端程序: /* 1.se ...

  3. python高级编程之最佳实践,描述符与属性01

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #最佳实践 """ 为了避免前面所有的 ...

  4. Python 中的属性访问与描述符

    在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问.一般而言,点(. ...

  5. python基础----再看property、描述符(__get__,__set__,__delete__)

    一.再看property                                                                          一个静态属性property ...

  6. python之路(11)描述符

    前言 描述符是用于代理另一个类的属性,一般用于大型的框架中,在实际的开发项目中较少使用,本质是一个实现了__get__(),__set__(),__delete__()其中一个方法的新式类 __get ...

  7. python 异步 select pooll epoll

    概念: 首先列一下,sellect.poll.epoll三者的区别 select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当se ...

  8. Libev源码分析09:select突破处理描述符个数的限制

    众所周知,Linux下的多路复用函数select采用描述符集表示处理的描述符.描述符集的大小就是它所能处理的最大描述符限制.通常情况下该值为1024,等同于每个进程所能打开的描述符个数. 增大描述符集 ...

  9. Python 描述符(descriptor) 杂记

    转自:https://blog.tonyseek.com/post/notes-about-python-descriptor/ Python 引入的“描述符”(descriptor)语法特性真的很黄 ...

随机推荐

  1. 在NSMutableArray中添加空元素:NSNull类的使用

    有时需要将一些表示“空”的对象添加到array中.NSNull类正是基于这样的目的产生的.用NSNull表示一个占位符时,语句表达如下: [array addObject:[NSNull null]] ...

  2. [转]HTML5 script 标签的 crossorigin 属性到底有什么用?

    HTML5 script 标签的 crossorigin 属性到底有什么用? 最近Bootstrap 4已经正式发布了,可能已经有爱尝鲜的小伙伴在 alpha 阶段就尝试过 BS4.不过今天要说的不是 ...

  3. PBRT笔记(11)——光源

    自发光灯光 至今为止,人们发明了很多光源,现在被广泛使用的有: 白炽灯的钨丝很小.电流通过灯丝时,使得灯丝升温,从而使灯丝发出电磁波,其波长的分布取决于灯丝的温度.但大部分能量都被转化为热能而不是光能 ...

  4. [CF1138B]Circus

    Description: 给你2个长度为n的01串 从中选出\(n/2\)个,使得选出的数中第一排1的个数等于未选出数中第二排1的个数 输出一种方案即可,没有输出-1 Hint: \(n \le 50 ...

  5. [JZOJ3615]【NOI2014模拟】数列(平面几何+二维线段树)

    Description 给定一个长度为n的正整数数列a[i]. 定义2个位置的f值为两者位置差与数值差的和,即f(x,y)=|x-y|+|a[x]-a[y]|. 你需要写一个程序支持2种操作(k都是正 ...

  6. 页面的input唤醒软键盘再收起后,页面会出现软键盘高度的空白背景

    微信浏览器在版本6.7.4及以上会有这个bug:页面的input唤醒软键盘再收起后,页面会出现软键盘高度的空白背景,触摸到滚动条会消失恢复! 解决代码 后台框架嵌入iframe的情景,iframe内部 ...

  7. lvm快照备份数据库(Mysql5.7)

    备份的目的 能够防止由于机械故障以及人为误操作带来的数据丢失,例如将数据库文件保存在了其它地方. 备份的分类 以操作过程中服务的可用性分: 冷备份:cold backup mysql服务关闭,mysq ...

  8. 【类与对象】--------java基础学习第六天

    类与对象 1. 对于面向对象的开发来讲也分为三个过程: OOA(面向对象分析) OOD(面向对象设计) OOP(面向对象编程) 2. 面向对象的基本特征 2.1. 封装:保护内部操作(属性,方法)不被 ...

  9. vue + websocket 的使用

    阳光正好,我们正在努力前行. 一.引言 初始使用websocket ,一开始看文档的时候,觉得很简单,只需要创建websocket实例,然后有几个监听打开连接,监听关闭连接,监听连接异常等方法.但是, ...

  10. go语言指针理解