概述

本文通过使用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. C++入门笔记(二)变量和基本类型

    变量和基本类型 一.基本内置类型 1.除去布尔类型和扩展的字符型外,其他整型可以分为带符号的和无符号的. 2.与其他整型不同,字符型被分为了三种:char.signed char 和 unsigned ...

  2. 数据库SQLServr安装时出现--"需要更新以前的Visual Studio 2010实例"--状态失败

    在电脑中安装过Visual Studio比较低版本的软件的时候 将原本的Microsoft Visual Studio 2010 Service Pack 1进行了更改 导致sql比较高版本的不能很好 ...

  3. 用JDBC把Excel中的数据导入到Mysql数据库中

    步骤:0.在Mysql数据库中先建好table 1.从Excel表格读数据 2.用JDBC连接Mysql数据库 3.把读出的数据导入到Mysql数据库的相应表中 其中,步骤0的table我是先在Mys ...

  4. redis在windows和Linux系统下的下载、安装、配置

    1.下载redis安装包 在redis的官网只有Linux系统下的安装包,微软的GitHub上有提供windows版本的redis安装包 redis中文网:http://www.redis.cn/ 微 ...

  5. window下如何使用文本编辑器(如记事本)创建、编译和执行Java程序

    window下如何使用文本编辑器(如记事本)创建Java源代码文件,并编译执行 第一步:在一个英文目录下创建一个 .text 文件 第二步:编写代码 第三步:保存文件 方法一:选择 文件>另存为 ...

  6. Shell 脚本处理用户输入

    传递参数 跟踪参数 移动变量 处理选项 将选项标准化 获得用户的输入 bash shell提供了一些不同的方法来从用户处获取数据,包括命令行参数(添加在命令后数据),命令行选项(可以修改命令行为的单个 ...

  7. 清理 zabbix 历史数据, 缩减 mysql 空间

    zabbix 由于历史数据过大, 因此导致磁盘空间暴涨,  下面是结局方法步骤 1. 停止 ZABBIX SERER 操作 [root@gd02-qa-plxt2-nodomain-web-95 ~] ...

  8. 关于AngularJS学习整理---核心特性

    接触.学习AngularJS已经三个多月了,随着学习的深入,有些东西刚开始不明白,现在开始慢慢明白起来.于是,开始整理这几个月的学习成果.要不又要忘了...  初学Angular,是看到慕课网大漠穷秋 ...

  9. AWS MVC 详解

    由于新工作是在AWS PaaS平台上进行开发,为不耽误工作,先整理一下AWS MVS的使用规范,快速上手.对AWS PaaS平台的相关介绍留到以后再来补充.本文几乎是对官方学习文档的整理,有遗漏的后补 ...

  10. Python-数据类型1

    在Python中常见的数据类型有:整数(int).字符串(str).小数/浮点数(float).列表.元组.字典和布尔类型等,下面会进行一一介绍. 整数和小数,不用多介绍相信大家都有所了解,字符串是用 ...