(五)通过select监控多个描述符实现并发连接

概述

本文通过使用select改写之前的服务器程序通过监控多个套接字描述符来实现并发连接并加入了一些机制让程序更加健壮,不过我们所有的实验都是建立在单词发送数据不会超过1024字节,如果超过你需要做特殊处理。

代码实例

描述符就绪条件

套接字准备好读

以下条件满足之一则套接字准备好读

  • 套接字接收缓冲区中的数据长度大于0
  • 该连接读半部关闭,也就是本端的套接字收到FIN,也就是对方已经发送完数据并执行了四次断开的第一次发送FIN,这时候本端如果继续尝试读取将会得到一个EOF也就是得到空。
  • 套接字是一个监听套接字且已经完成的连接数量大于0,也就是如果监听套接字可读正面有新连接进来那么在连接套接字上条用accept将不会阻塞
  • 套接字产生错误需要进行处理,读取这样的套接字将返回一个错误

套接字准备好写

以下条件满足之一则套接字准备好写

  • 套接字发送缓冲区可以空间大于等于套接字发送缓冲区最低水位,也就是发送缓冲区没有空余空间或者空余空间不足以容纳一个TCP分组(1460-40=1420)。如果不够它就会等。当可以容纳了就表示套接字可写,这个可写是程序把数据发送到套接字发送缓冲区。
  • 该连接写半部关闭,
  • 使用非阻塞式connect的套接字已建立连接或者connect已经失败
  • 有一个错误套接字待处理

服务器端代码

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 # Author: rex.cheny
 4 # E-mail: [email protected]
 5
 6 import socket
 7 import select
 8
 9
10 def echoStr(readAbledSockFD, rList):
11     try:
12         bytesData = readAbledSockFD.recv(1024)
13         data = bytesData.decode(encoding="utf-8")
14         if data:
15             print("收到客户端 ", readAbledSockFD.getpeername(), " 消息:", data)
16             if data.upper() == "BYE":
17                 print("客户端 ", readAbledSockFD.getpeername(), " 主动断开连接。")
18                 rList.remove(readAbledSockFD)
19                 readAbledSockFD.close()
20             else:
21                 readAbledSockFD.send(data.encode(encoding="utf-8"))
22         else:
23             """
24             如果客户端进程意外终止,那么select将返回,因为该连接套接字收到FIN,所以readAbledSockFD读取的内容是‘‘就是空,数据长度是0
25             也就是你试图读取一个收到FIN的套接字会出现这种情况,通常的错误信息是 "server terminated prematurely"
26             """
27             print("客户端 ", readAbledSockFD.getpeername(), " 意外中断连接。")
28             rList.remove(readAbledSockFD)
29             readAbledSockFD.close()
30     except Exception as err:
31         """
32         这里如果抛出异常通常是因为当连接套接字收到RST之后调用 recv()函数产生的 "Connection reset by peer" 错误,
33         为什么套接字会收到RST,通常是向一个收到FIN的套接字执行写入操作导致的。
34         """
35         print("客户端 ", readAbledSockFD.getpeername(), " 意外中断连接。")
36         rList.remove(readAbledSockFD)
37         readAbledSockFD.close()
38
39
40 def main():
41     sockFd = socket.socket()
42     sockFd.bind(("", 5556))
43     sockFd.listen(5)
44
45     # 这里为什么要把这个监听套接字放入可读列表中呢?服务器监听套接字描述符如果有新连接进来那么该描述符可读
46     rList = [sockFd]
47     wList = []
48     eList = []
49
50     print("等待客户端连接......")
51     while True:
52         """
53         select(),有4个参数,前三个必须也就是感兴趣的描述符,第四个是超时时间
54         第一个参数:可读描述符列表
55         第二个参数:可写描述符列表
56         第三个参数:错误信息描述符列表
57         对于自己的套接字来说,输入表示可以读取,输出表示可以写入,套接字就相当于一个管道,对方的写入代表你的读取,你的写入代表对方的读取
58
59         select函数返回什么呢?你把感兴趣的描述符加入到列表中并交给select后,当有可读或者有可写或者错误这些描述符就绪后,select就会返回
60         哪些就绪的描述符,你需要做的就是遍历这些描述符逐一进行处理。
61         """
62         readSet, writeSet, errorSet = select.select(rList, wList, eList)
63
64         # 处理描述符可读
65         for readAbledSockFD in readSet:
66             if readAbledSockFD is sockFd:
67                 try:
68                     connFd, remAddr = sockFd.accept()
69                 except Exception as err:
70                     """
71                     这里处理当三次握手完成后,客户端意外发送了一个RST,这将导致一个服务器错误
72                     """
73                     print("")
74                     continue
75                 print("新连接:", connFd.getpeername())
76                 # 把新连接加入可读列表中
77                 rList.append(connFd)
78             else:
79                 echoStr(readAbledSockFD, rList)
80
81         # 处理描述符可写
82         for writeAbledSockFd in writeSet:
83             pass
84
85         # 处理错误描述符
86         for errAbled in errorSet:
87             pass
88
89
90 if __name__ == ‘__main__‘:
91     main()

客户端代码

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 # Author: rex.cheny
 4 # E-mail: [email protected]
 5
 6 import socket
 7 import select
 8 import sys
 9
10
11 def echoStr(sockFd, connectionFailed):
12     try:
13         bytesData = sockFd.recv(1024)
14         data = bytesData.decode(encoding="utf-8")
15         if data:
16             print("服务器回复:", data)
17         else:
18             """
19             如果服务器进程意外终止,那么套接字也将返回,因为该连接套接字收到FIN,所以sockFd读取的内容是‘‘就是空,数据长度是0
20             也就是你试图读取一个收到FIN的套接字会出现这种情况,通常的错误信息是 "server terminated prematurely"
21             """
22             print("服务器 ", sockFd.getpeername(), " 意外中断连接。")
23             sockFd.close()
24             connectionFailed = True
25     except Exception as err:
26         """
27         这里如果抛出异常通常是因为当连接套接字收到RST之后调用 recv()函数产生的 "Connection reset by peer" 错误,
28         为什么套接字会收到RST,通常是向一个收到FIN的套接字执行写入操作导致的。
29         """
30         print("服务器 ", sockFd.getpeername(), " 意外中断连接。")
31         sockFd.close()
32         connectionFailed = True
33     return connectionFailed
34
35
36 def main():
37     sockFd = socket.socket()
38     sockFd.connect(("127.0.0.1", 5556))
39
40     # 用于判断服务器是否意外中断
41     connectionFailed = False
42     while True:
43         data = input("等待输入:")
44         if data == "Bye":
45             sockFd.send("Bye".encode(encoding="utf-8"))
46             """
47             shutdown就是主动触发关闭套接字,发送FIN,后面的参数是关闭写这一半,其实就是告诉服务器客户端不会再发送数据了。
48             """
49             sockFd.shutdown(socket.SHUT_WR)
50             break
51         else:
52             sockFd.send(data.encode(encoding="utf-8"))
53             if echoStr(sockFd, connectionFailed):
54                 break
55
56
57 if __name__ == ‘__main__‘:
58     main()

原文地址:https://www.cnblogs.com/yunxizhujing/p/9735264.html

时间: 2024-10-08 01:28:36

(五)通过select监控多个描述符实现并发连接的相关文章

文件描述符与socket连接

每个进程开启一个soeket连接,都会占用一个文件描述符. 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件. 文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作(包括网络socket操作)的系统调用都通过文件描述符. 程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果此时去打开一个新的文件,它的文件描述符会是3

通过UNIX域套接字传递文件描述符

传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程序设计范式 ??开发一个服务器程序,有较多的的程序设计范式可供选择,不同范式有其自身的特点和实用范围,明了不同范式的特性有助于我们服务器程序的开发.常见的TCP服务器程序设计范式有以下几种: 迭代服务器 并发服务器,每个客户请求fork一个子进程 预先派生子进程,每个子进程无保护地调用accept 预先

Select函数文件描述符集的准备条件

网络编程中,我们经常讨论等待某个描述符准备好I/O(读/写)或者等待其上发生一个待处理的异常条件.尽管可读性和可写性对于普通文件这样的描述符显而易见,然而对于引起诸如select返回套接字"就绪"的条件我们必须讨论的更明确些. 套接字Select函数原型: #include <sys/select.h> #include <sys/time.h> int select(int maxfdp1, fd_set *readset, fd_set *writeset,

五、基于文件描述符的文件操作(非缓冲)

1文件描述符 内核为每个进程维护一个已打开文件的记录表,文件描述符是一个较小的正整数(0—1023),它代表记录表的一项,通过文件描述符和一组基于文件描述符的文件操作函数,就可以实现对文件的读.写.创建.删除等操作. 常用基于文件描述符的函数有open(打开).creat(创建).close(关闭).read(读取).write(写入).ftruncate(改变文件大小).lseek(定位).fsync(同步).fstat(获取文件状态).fchmod(权限).flock(加锁).fcntl(控

为何select做多只支持1024个描述符

内核代码中,原因一览无余 每一位一个描述符,总共1024/8/4*32=1024个

操作系统学习(五) 、代码段和数据段描述符

一.代码段和数据段描述符格式 段描述符通用格式如下所示: 代码段和数据段描述符中各个位的含义如下所示: 二.代码段和数据段描述符类型 当段描述符中S标志位(描述符类型)被置位,则该描述符用于代码段或数据段.此时类型字段中最高比特位(第二个双字的位11)用于确定是数据段描述符(复位)还是代码段描述符(置位). 代码段和数据段描述符类型如下所示: 对于数据段描述符,类型字段的低3位(位8,9,10)分别用于表示已访问A,可写W,和扩展方向E,根据可写比特位W的设置,一个数据段可以是只读的,也可以是可

第十五节、韦伯局部描述符(WLB)

纹理作为一种重要的视觉线索,是图像中普遍存在而又难以描述的特征,图像的纹理特征一般是指图像上地物重复排列造成的灰度值有规则的分布.纹理特征的关键在于纹理特征的提取方法.目前,用于纹理特征提取的方法有很多,最具有代表性的是有基于二阶概率密度的灰度共生矩阵.符合人眼视觉特性的小波变换.纹理谱法以及基于图像结构基元的纹理元方法等.为了更有效地描述图像局部纹理特征,又先后提取了局部二值模式(Local Binary Pattern,LBP)和韦伯局部描述符(Weber Local Descriptor,

进程管理—进程描述符(task_struct)

http://blog.csdn.net/qq_26768741/article/details/54348586 当把一个程序加载到内存当中,此时,这个时候就有了进程,关于进程,有一个相关的叫做进程控制块(PCB),这个是系统为了方便进行管理进程所设置的一个数据结构,通过PCB,就可以记录进程的特征以及一些信息. 内核当中使用进程描述符task_struct. 这个task_struct就是一个定义的一个结构体,通过这个结构体,可以对进程的所有的相关的信息进行维护,对进程进行管理. 接下来我们

进程描述符task_struct

1.进程状态 [cpp] view plain copy volatile long state; int exit_state; state成员的可能取值如下: [cpp] view plain copy #define TASK_RUNNING        0 #define TASK_INTERRUPTIBLE  1 #define TASK_UNINTERRUPTIBLE    2 #define __TASK_STOPPED      4 #define __TASK_TRACED