Python自动化开发从浅入深-进阶(select,poll,epoll学习)
从select到poll,再到epoll是python在IO多路就绪通知方面的一个发展顺序。
Python中的IO多路复用主要有以下几个方面:
(1)当客户处理多个描述字时(一般是交互式输入和网络socket套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
select,poll,epoll三者的区别如下:
select
- 通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll
- 它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
epoll
- 直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
以下为对select的描述:
Windows Python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll
功能函数:
select()方法接收并监控3个通信列表:
- 第一个是所有的输入的data,就是指外部发过来的数据。
- 第二个是监控和接收所有要发出去的data(outgoing data)。
- 第三个监控错误信息。
select 是常用的异步socket 处理方法
一般用法:
# rlist,wlist,xlist 分别为需要异步处理的读socket队列, 写socket队列(一般不用), 和错误socket队列, 返回事件的读写和错误socket队列
il,ol,el = select(rlist,wlist,xlist[,timeout])
for sock in il:
#read the sock
for sock in ol:
#...
for sock in el:
#handle errors
def select(rlist, wlist, xlist, timeout=None): # real signature unknown; restored from __doc__
"""
select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
Wait until one or more file descriptors are ready for some kind of I/O.
The first three arguments are sequences of file descriptors to be waited for:
rlist -- wait until ready for reading
wlist -- wait until ready for writing
xlist -- wait for an ``exceptional condition''
If only one kind of condition is required, pass [] for the other lists.
A file descriptor is either a socket or file object, or a small integer
gotten from a fileno() method call on one of those.
The optional 4th argument specifies a timeout in seconds; it may be
a floating point number to specify fractions of seconds. If it is absent
or None, the call will never time out.
The return value is a tuple of three lists corresponding to the first three
arguments; each contains the subset of the corresponding file descriptors
that are ready.
*** IMPORTANT NOTICE ***
On Windows, only sockets are supported; on Unix, all file
descriptors can be used.
"""
pass
模块定义:
exception:
select.error(): 当错误发生时抛出, 会像C函数的perror()一样打印错误编号和描述字符串
functions:
select.epoll([sizehint=-1]): 返回一个edge polling 对象, 可以用来作为Edge or Level Triggered interface for I/O events.
select.poll(): 不是在所有系统上都支持, 返回一个polling 对象, 可以用来支持registering and unregistering file descriptors, 然后polling them for I/O events.
select.kqueue(): 只支持BSD, 返回一个kernel queue object.
select.kevent(ident,filter=KQ_FILTER_READ,flags=KQ_EV_ADD,fflags=0,data=0,udata=0): 只支持BSD,返回一个kernel queue object.
select.select(rlist,wlist,xlist[,timeout]): 这是一个straightforward interface to Unix select()系统调用, 前3个参数是一串可等待对象,
表示代表文件描述符的整数或者带有一个名为fileno()的无参方法返回的同样的整数;
参数: 1.rlist: 等待直到读准备好; 2.wlist: 等待直到写操作准备好; 3.xlist: 等待一个"exceptional condition" ;
允许空序列, 但是如果3个参数都为空的列表的话, 在Unix上可以, 但在Windows上不行, 与平台相关 .
当timeout参数被设定之后, 函数将blocks 知道至少一个文件描述符 is ready, 值为0 表示 a poll and never block.
返回值: triple of list of object that are ready. subsets of the first three arguments. 当time-out reached,
但还没有file descriptor ready, 返回 three empty lists.
os.popen()(这个命令可以用来执行系统调用, 相当于os.system(), 用法: os.popen("ping 192.168.1.1")) .
#服务器端: #===== #!/usr/bin/env python # -*- coding: utf-8 -*- #__author__ = 'jieli' #python 2.7调试 #注释:zhaohong __author__ = 'Alex Li' #注释:zhaohong import select #装入select模块,用于在socket中进行异步的IO import socket #装入socket模块,用于server和client建立连接和通讯 import sys #装入sys模块 import Queue #装入queue模块,用于队列处理 # Create a TCP/IP socket #创建一个TCP/IP socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#以intenet网络socket流的方式创建一个socket实例 server.setblocking(False)#对socket对象不进行阻塞 # Bind the socket to the port #为server socket实例绑定地址和端口 server_address = ('localhost', 7788)#绑定IP和端口 print('starting up on %s port %s' %( sys.stderr,server_address))#输出系统错误信息和地址与端口信息 server.bind(server_address)#为socket实例绑定地址和端口 # Listen for incoming connections server.listen(5)#监听5个客户端 #---------------------------------------------------------------------------------- # Sockets from which we expect to read #一个inputs列表,从列表中期望读到放入的socket数据 inputs = [ server ]#一个读列表,初始放入socket server实例,用于select中所有的读操作 # Sockets to which we expect to write #一个outputs列表,从列表中写入期望发出去的数据 outputs = [ ]#一个写列表,用于select中所有的写操作 #建立一个空的消息队列字典,用于缓冲读入或发出的数据。 #通过这个队列,socket读入或送出都通过这个队列进行。 message_queues = {} #循环等待读列表中需要处理的数据,至少在队列中有一个准备处理的数据 while inputs:#---------------------------------------------------------------------------------------------------------开始处理数据,第一次总是socket实例 # Wait for at least one of the sockets to be ready for processing print( '\nwaiting for the next event')#显示“等待下一个事件” #调用一个select,参数为:读处理列表,写处理列表,异常处理列表 #返回可读readable,可写writable,exceptional异常三个值 readable, writable, exceptional = select.select(inputs, outputs, inputs)#-------------定义select,参数:读列表,写列表(发出),异常 # Handle inputs #对readable列表进行遍历,遍历内容:server对象(准备阶段),或连接好的实例对象。第一次总是socket实例对象server,即开始接收数据。 for s in readable:#----------------------------------------------------------------------------------------------------------对收数据进行select处理 if s is server:#判断readable列表中有没有server,如果有,表示server socket实例处于就绪状态,准备好接收数据------准备收数据 # A "readable" server socket is ready to accept a connection connection, client_address = s.accept()#产生一个新的连接connection,socket准备读数据 print('new connection from', client_address)#显示客户端信息 connection.setblocking(False)#同样,需要将connection设置为非阻塞状态 inputs.append(connection)#将读数据连接放入select的读列表中,交给系统处理 # Give the connection a queue for data we want to send #产生一个队列,将读数据连接对象放入队列 message_queues[connection] = Queue.Queue()#以connection为key,建立一个数据队列。即数据就绪队列 else:#当readable列表中有接收好的数据,就开始进行接收-------------------------------------------------有数据来了,开始收数据 data = s.recv(1024)#收数据 if data:#有数据,以下处理 # A readable client socket has data print(sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) ) message_queues[s].put(data)#在key为connection的值(value)中put一个收取到的数据到to队列 # Add output channel for response if s not in outputs:#判断一下,所产生的连接实例是不是在写入(送出)列表中,如果不在,则放人outputs outputs.append(s) else:#-------------------------------------------------------------------------------------------没有数据,善后 # Interpret empty result as closed connection print('closing', client_address, 'after reading no data')#显示关闭信息 # Stop listening for input on the connection if s in outputs:#判断:如果连接实例在outputs列表中,则处理善后 #把连接实例从outputs列表中删掉 outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉 inputs.remove(s) #把inputs中的连接实例也删除掉 s.close() #关闭掉这个连接对象 # Remove message queue del message_queues[s]#把队列中的连接实例也删掉 # Handle outputs for s in writable:#对writeable列表进行遍历-------------------------------------------------------------------------对发送数据进行select处理 try:#到message_queues字典中找:有没有data next_msg = message_queues[s].get_nowait()#把数据收回来(非阻塞式接收) except Queue.Empty:#队列中没有数据,则善后 # No messages waiting so stop checking for writability. print('output queue for', s.getpeername(), 'is empty') outputs.remove(s) else:#把收到的数据发出去send print( 'sending "%s" to %s' % (next_msg, s.getpeername())) s.send(next_msg) # Handle "exceptional conditions" for s in exceptional:#---------------------------------------------------------------------------------------------对异常数据进行处理,即善后处理 print('handling exceptional condition for', s.getpeername() ) # Stop listening for input on the connection inputs.remove(s)#删掉readable列表中的这个数据 if s in outputs:#如果outputs列表中也有这个数据,把它也删掉 outputs.remove(s) s.close() # Remove message queue del message_queues[s]#从队列中删掉这个数据 ################################### #客户端端: #===== #!/usr/bin/env python # -*- coding: utf-8 -*- #__author__ = 'jieli' #注释:zhaohong #python 2.7调试 import socket import sys messages = [ 'This is the message. ', 'It will be sent ', 'in parts.', ] server_address = ('localhost', 7788) # Create a TCP/IP socket #建立一个列表,内置2个socket实例对象 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM), socket.socket(socket.AF_INET, socket.SOCK_STREAM), ] # Connect the socket to the port where the server is listening print(sys.stderr, 'connecting to %s port %s' % server_address) #遍历socket对象列表,分别进行连接 for s in socks: s.connect(server_address) #遍历message中的三条信息,进行逐条处理 for message in messages: # Send messages on both sockets #分别用socks列表的实例对象发出message信息 for s in socks: print(sys.stderr, '%s: sending "%s"' % (s.getsockname(), message)) s.send(message) # Read responses on both sockets #分别用socks列表的实例对象接收来自服务端的信息 for s in socks: data = s.recv(1024)#收数据 print (sys.stderr, '%s: received "%s"' % (s.getsockname(), data)) if not data:#没有数据,则关闭连接实例 print (sys.stderr, 'closing socket', s.getsockname()) s.close()
epoll精髓
(转自:http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html)
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
epoll的接口非常简单,一共就三个函数:
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
--------------------------------------------------------------------------------------------
从man手册中,得到ET和LT的具体描述如下
EPOLL事件有两种模型:
Edge Triggered (ET)
Level Triggered (LT)
假如有这样一个例子:
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)......
Edge Triggered 工作模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
i 基于非阻塞文件句柄
ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
Level Triggered 工作模式
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。
然后详细解释ET, LT:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。
在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。(未测试)
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:
while(rs)
{
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
if(buflen < 0)
{
// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
// 在这里就当作是该次事件已处理处.
if(errno == EAGAIN)
break;
else
return;
}
else if(buflen == 0)
{
// 这里表示对端的socket已正常关闭.
}
if(buflen == sizeof(buf)
rs = 1; // 需要再次读取
else
rs = 0;
}
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
{
ssize_t tmp;
size_t total = buflen;
const char *p = buffer;
while(1)
{
tmp = send(sockfd, p, total, 0);
if(tmp < 0)
{
// 当send收到信号时,可以继续写,但这里返回-1.
if(errno == EINTR)
return -1;
// 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,
// 在这里做延时后再重试.
if(errno == EAGAIN)
{
usleep(1000);
continue;
}
return -1;
}
if((size_t)tmp == total)
return buflen;
total -= tmp;
p += tmp;
}
return tmp;
}
python中使用epoll开发服务端程序
(转自:http://www.cnblogs.com/dkblog/archive/2011/03/25/1995755.html)
1 Python代码: 2 import socket, logging 3 import select, errno 4 5 logger = logging.getLogger("network-server") 6 7 def InitLog(): 8 logger.setLevel(logging.DEBUG) 9 10 fh = logging.FileHandler("network-server.log") 11 fh.setLevel(logging.DEBUG) 12 ch = logging.StreamHandler() 13 ch.setLevel(logging.ERROR) 14 15 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 16 ch.setFormatter(formatter) 17 fh.setFormatter(formatter) 18 19 logger.addHandler(fh) 20 logger.addHandler(ch) 21 22 23 if __name__ == "__main__": 24 InitLog() 25 26 try: 27 listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 28 except socket.error, msg: 29 logger.error("create a socket failed") 30 31 try: 32 listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 33 except socket.error, msg: 34 logger.error("setsocketopt error") 35 36 try: 37 listen_fd.bind(('', 2003)) 38 except socket.error, msg: 39 logger.error("listen file id bind ip error") 40 41 try: 42 listen_fd.listen(10) 43 except socket.error, msg: 44 logger.error(msg) 45 46 try: 47 epoll_fd = select.epoll() 48 epoll_fd.register(listen_fd.fileno(), select.EPOLLIN) 49 except select.error, msg: 50 logger.error(msg) 51 52 connections = {} 53 addresses = {} 54 datalist = {} 55 while True: 56 epoll_list = epoll_fd.poll() 57 for fd, events in epoll_list: 58 if fd == listen_fd.fileno(): 59 conn, addr = listen_fd.accept() 60 logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno())) 61 conn.setblocking(0) 62 epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET) 63 connections[conn.fileno()] = conn 64 addresses[conn.fileno()] = addr 65 elif select.EPOLLIN & events: 66 datas = '' 67 while True: 68 try: 69 data = connections[fd].recv(10) 70 if not data and not datas: 71 epoll_fd.unregister(fd) 72 connections[fd].close() 73 logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1])) 74 break 75 else: 76 datas += data 77 except socket.error, msg: 78 if msg.errno == errno.EAGAIN: 79 logger.debug("%s receive %s" % (fd, datas)) 80 datalist[fd] = datas 81 epoll_fd.modify(fd, select.EPOLLET | select.EPOLLOUT) 82 break 83 else: 84 epoll_fd.unregister(fd) 85 connections[fd].close() 86 logger.error(msg) 87 break 88 elif select.EPOLLHUP & events: 89 epoll_fd.unregister(fd) 90 connections[fd].close() 91 logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1])) 92 elif select.EPOLLOUT & events: 93 sendLen = 0 94 while True: 95 sendLen += connections[fd].send(datalist[fd][sendLen:]) 96 if sendLen == len(datalist[fd]): 97 break 98 epoll_fd.modify(fd, select.EPOLLIN | select.EPOLLET) 99 else: 100 continue 101 客户端程序,Python代码: 102 103 import socket 104 import time 105 import logging 106 107 logger = logging.getLogger("network-client") 108 logger.setLevel(logging.DEBUG) 109 110 fh = logging.FileHandler("network-client.log") 111 fh.setLevel(logging.DEBUG) 112 ch = logging.StreamHandler() 113 ch.setLevel(logging.ERROR) 114 115 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 116 ch.setFormatter(formatter) 117 fh.setFormatter(formatter) 118 119 logger.addHandler(fh) 120 logger.addHandler(ch) 121 122 if __name__ == "__main__": 123 try: 124 connFd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 125 except socket.error, msg: 126 logger.error(msg) 127 128 try: 129 connFd.connect(("192.168.31.226", 2003)) 130 logger.debug("connect to network server success") 131 except socket.error,msg: 132 logger.error(msg) 133 134 for i in range(1, 11): 135 data = "The Number is %d" % i 136 if connFd.send(data) != len(data): 137 logger.error("send data to network server failed") 138 break 139 readData = connFd.recv(1024) 140 print readData 141 time.sleep(1) 142 143 connFd.close()