引用:Python Select 解析:http://www.cnblogs.com/alex3714/p/4372426.html
引用:Python网络编程中的select 和 poll I/O复用的简单使用:http://www.cnblogs.com/coser/archive/2012/01/06/2315216.html
引用:Python socket编程 http://blog.sina.com.cn/s/blog_523491650100hikg.html
一、select,poll,epoll三者区别
二、select 服务端和客户端基本步骤
引用:Python socket编程 http://blog.sina.com.cn/s/blog_523491650100hikg.html
1)建立服务器连接需要六个步骤。
第1步,是创建socket对象。调用socket构造函数。
socket=socket.socket(familly,type)
family的值可以是AF_UNIX(Unix域,用于同一台机器上的进程间通讯),也可以是AF_INET(对于IPV4协议的TCP和 UDP),
至于type参数,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。
第2步,则是将socket绑定(指派)到指定地址上。
socket.bind((host,port))
第3步,绑定后,必须准备好套接字,以便接受连接请求。
socket.listen(backlog)
acklog指定了最多连接数,至少为1,接到连接请求后,这些请求必须排队,如果队列已满,则拒绝请求。
第4步,服务器套接字通过socket的accept方法等待客户请求一个连接:
connection,address=socket.accept()
调用accept方法时,socket会进入'waiting'(或阻塞)状态。客户请求连接时,方法建立连接并返回服务器。
accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址。
第5步是处理阶段,服务器和客户通过send和recv方法通信(传输数据)。
服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接受信息。
调用recv时,必须指定一个整数来控制本次调用所接受的最大数据量。recv方法在接受数据时会进入'blocket'状态,最后返回一个字符串,用它来表示收到的数据。如果发送的量超过recv所允许,数据会被截断。多余的数据将缓冲于接受端。以后调用recv时,多余的数据会从缓冲区删除。
第6步,传输结束,服务器调用socket的close方法以关闭连接
2)建立一个简单客户连接则需要4个步骤。
第1步,创建一个socket以连接服务器 socket=socket.socket(family,type)
第2步,使用socket的connect方法连接服务器 socket.connect((host,port))
第3步,客户和服务器通过send和recv方法通信。
第4步,结束后,客户通过调用socket的close方法来关闭连接。
三、select代码总体说明
3.1 使用select
在python中,select函数是一个对底层操作系统的直接访问的接口。它用来监控sockets、files和pipes,等待IO完成(Waiting for I/O completion)。当有可读、可写或是异常事件产生时,select可以很容易的监控到。
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。
3.2 select server端代码说明
一、select代码总结:
只要客户端发起一个新的socket过来,通过select监听这个句柄。
但是监听了之后,就用单线程处理了。
server端没有用到多线程。是监听有句柄(socket)过来(有连接活动过来)之后,
就用当前的这个处理了。它之所以看到的是异步的效果,是因为我们用了消息队列(Queue)减少阻塞。
但是所有的连接都是server端用单个线程处理的。
二、server端步骤:
1、该程序主要是利用socket进行通信,接收客户端发送过来的数据,然后再发还给客户端。
2、首先建立一个TCP/IP socket,并将其设为非阻塞,然后进行bind和listen。
3、通过select函数获取到三种文件列表,分别对每个列表的每个元素进行轮询,对不同socket进行不同的处理,最外层循环直到inputs列表为空为止
4、如果从客户端的一个socket接收到数据,设置个字典变量(message_queues)就把这个socket作为key,value为一个队列,以便装这个socket的数据。
5、如果可以从活跃客户端的socket里读取数据:就往队列里put(从客户端socket接收到)的数据。
如果能可以把数据写入到客户端,就从队列里get_nowait(),无阻塞方式读取队列里的数据,并发送给客户端的socket。
5、当设置timeout参数时,如果发生了超时,select函数会返回三个空列表。 并从message_queues字典里删除这个socket。关闭连接。
三、详细步骤:
inputs:从客户端读取(readable)的活跃的socket列表,先默认添加server
outputs:可以写入客户端(writable)的活跃的socket列表
while True:
readable, writable, exceptional = select.select(inputs, outputs, inputs,timeout)#第一次循环的时候inputs列表里已经有server
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。
1)循环处理reatable列表:
情况1)server ready to connect client:准备建立连接--标识:
readable列表里有 server(因为inputs之前已经有server,如果,循环readable里有server的话,那么说明准备和一个客户端建立连接。)
connection, client_address = s.accept()
accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址
设成非阻塞模式:
connection.setblocking(0)
把inputs添加这个connection,也就是添加一个活跃可以读取数据的客户端socket。
把这个socket对象(connection),建一个我们以后可以发送数据的队列(queue)。
message_queues[connection] = Queue.Queue()#message_queues本是个空字典
情况2)server has connected:已经建立连接
接收数据
如果从可读取的客户端socket接收到数据的话:
我们就往队列里放个数据
message_queues[s].put(data),#这里的s其实就是情况1准备建立连接的connection。情况1的时候,已经建立好队列了。
假如客户端socket不在可读取的列表里,就加入到列表了。
情况3)就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。
并且,移除这个socket队列
2)循环处理writable列表:
对于writable list中的socket,也有几种状态:
如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态
3) 最后,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉。
3.3 select client端代码说明
Client端创建多个socket进行server链接,用于观察使用select函数的server端如何进行处理。
循环每个socket连接,给server发送和接收数据。
四、服务端完整代码及注释
代码,参见本地目录:E:\Python-10期\s10day7-biji\select_socket下的文件。
引用:Python Select 解析:http://www.cnblogs.com/alex3714/p/4372426.html
运行方法:在windows下运行server端程序,再开个窗口运行client端程序。然后观察server端和client端的输出。
#!/usr/bin/env python
#_*_coding:utf-8_*_
__author__ = 'WangQiaomei'
import select
import socket
import sys
import Queue
# Create a TCP/IP socket
'''
第1步是 创建socket对象。调用socket构造函数。
socket=socket.socket(familly,type)
family的值可以是AF_UNIX(Unix域,用于同一台机器上的进程间通讯),也可以是AF_INET(对于IPV4协议的TCP和 UDP),
至于type参数,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。
'''
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0) #设置为非阻塞模式
'''
第2步,则是将socket绑定(指派)到指定地址上。
socket.bind((host,port))
'''
# Bind the socket to the port
server_address = ('localhost', 10000)
print >>sys.stderr, 'starting up on %s port %s' % server_address
server.bind(server_address)
# Listen for incoming connections
server.listen(5)
'''
inputs:从客户端读取(readable)的活跃的socket列表,先默认添加server
outputs:可以写入客户端(writable)的活跃的socket列表
'''
inputs = [server ]
outputs = []
message_queues = {}
while inputs:
print >>sys.stderr, '\nwaiting for the next event'
timeout = 1
'''
#第一次循环的时候inputs列表里已经有server
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。
第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。
'''
readable,writable,exceptional = select.select(inputs, outputs, inputs,timeout)
# Handle inputs
#超时情况
if not(readable or writable or exceptional):
print >>sys.stderr, 'timed out , do something else here...'
continue
for s in readable:
'''
情况1)server ready to connect client:准备建立连接--标识:
readable列表里有 server(因为inputs之前已经有server,如果,循环readable里有server的话,那么说明准备和一个客户端建立连接。)
'''
if s is server:
# A "readable" server socket is ready to accept a connection
#accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址
connection, client_address = s.accept()
print >>sys.stderr, 'new connection from', client_address
connection.setblocking(0) #设成非阻塞模式:
inputs.append(connection) #把inputs添加这个connection,也就是添加一个活跃可以读取数据的客户端socket。
# Give the connection a queue for data we want to send
'''
#把这个socket对象(connection),建一个我们以后可以发送数据的队列(queue)。
#message_queues本是个空字典
'''
message_queues[connection] = Queue.Queue()
#情况2)server has connected:已经建立连接
else:
data = s.recv(1024) #接受数据
if data: #如果从可读取的客户端socket接收到数据的话:
# A readable client socket has data
print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
'''
我们就往队列里放个数据
message_queues[s].put(data),#这里的s其实就是情况1准备建立连接的connection。
情况1的时候,已经建立好队列了。
'''
message_queues[s].put(data)
# Add output channel for response
#假如客户端socket不在可读取的列表里,就加入到列表了。
if s not in outputs:
outputs.append(s)
#情况3)就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。
else:
# Interpret empty result as closed connection
print >>sys.stderr, 'closing', client_address, 'after reading no data'
# Stop listening for input on the connection
if s in outputs:
#既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
outputs.remove(s) #如果存在,就从活跃可写入的socket列表里删除
print 'wriatable---before::',writable
if s in writable:
writable.remove(s) #如果存在,就从写入的列表里删除
inputs.remove(s) #活跃可读的socket列表里删除
s.close() #把连接关闭
# Remove message queue
del message_queues[s] #移除这个socket队列
print message_queues
print 'wriatable:',writable
# Handle outputs
'''
循环处理可写socket列表。
如果有数据则发送回客户端,如果没数据则就把这个连接从output list中移除,outputs是活跃可写的socket列表。
这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态
'''
for s in writable:
try:
'''
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False) 非阻塞
'''
next_msg = message_queues[s].get_nowait()
except Queue.Empty: #假如队列为空
# No messages waiting so stop checking for writability.
print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty'
outputs.remove(s) #活跃可写socket列表删除此socket。
else: #假如队列有数据
print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername()) #客户端socket实例.getpeername()获取客户端的ip和端口
s.send(next_msg) #发送数据
#最后,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉。
# Handle "exceptional conditions"
for s in exceptional:
print >>sys.stderr, 'handling exceptional condition for', s.getpeername() #客户端socket实例.getpeername()获取客户端的ip和端口
# Stop listening for input on the connection
inputs.remove(s) #活跃可读socket列表删除此socket。
if s in outputs:
outputs.remove(s) #活跃可写socket列表删除此socket。
s.close() #连接关闭
# Remove message queue
del message_queues[s] #从队列里删除。
'''
Python Select 解析:http://www.cnblogs.com/alex3714/p/4372426.html
'''
五、客户端完整代码及注释
#!/usr/bin/env python
#_*_coding:utf-8_*_
import socket
import sys
messages = [ 'This is the message. ',
'It will be sent ',
'in parts.',
]
server_address = ('localhost', 10000)
# Create a TCP/IP 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
for s in socks:
s.connect(server_address)
for message in messages:
# Send messages on both sockets
'''
s.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)
'''
for s in socks:
print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message) #s.getsockname() 返回socket自己的地址,发送消息
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024) #默认最大文件描述符1024
print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data) #接受消息
if not data:
print >>sys.stderr, 'closing socket', s.getsockname()
s.close() #关闭链接。
'''
Python Select 解析:http://www.cnblogs.com/alex3714/p/4372426.html
'''