引用: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

 

一、selectpollepoll三者区别

wKioL1YDs6Sjd8tqAAOiTLtzisA170.jpg



 

二、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_RAWraw套接字)。

 

 

2步,则是将socket绑定(指派)到指定地址上。

socket.bind((host,port))

 

3步,绑定后,必须准备好套接字,以便接受连接请求。

socket.listen(backlog)

acklog指定了最多连接数,至少为1,接到连接请求后,这些请求必须排队,如果队列已满,则拒绝请求。

 

 

4步,服务器套接字通过socketaccept方法等待客户请求一个连接:

connection,address=socket.accept()

调用accept方法时,socket会进入'waiting'(或阻塞)状态。客户请求连接时,方法建立连接并返回服务器。

accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址。

 

5步是处理阶段,服务器和客户通过sendrecv方法通信(传输数据)。

服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接受信息。

调用recv时,必须指定一个整数来控制本次调用所接受的最大数据量。recv方法在接受数据时会进入'blocket'状态,最后返回一个字符串,用它来表示收到的数据。如果发送的量超过recv所允许,数据会被截断。多余的数据将缓冲于接受端。以后调用recv时,多余的数据会从缓冲区删除。

 

6步,传输结束,服务器调用socketclose方法以关闭连接

2)建立一个简单客户连接则需要4个步骤。

 

1步,创建一个socket以连接服务器 socket=socket.socket(family,type)

 

2步,使用socketconnect方法连接服务器 socket.connect((host,port))

 

3步,客户和服务器通过sendrecv方法通信。

 

4步,结束后,客户通过调用socketclose方法来关闭连接。

 

 

三、select代码总体说明

3.1 使用select

python中,select函数是一个对底层操作系统的直接访问的接口。它用来监控socketsfilespipes,等待IO完成(Waiting for I/O completion)。当有可读、可写或是异常事件产生时,select可以很容易的监控到。 
select.selectrlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。 

3.2 select server端代码说明

一、select代码总结:

 

只要客户端发起一个新的socket过来,通过select监听这个句柄。

但是监听了之后,就用单线程处理了。

server端没有用到多线程。是监听有句柄(socket)过来(有连接活动过来)之后,

就用当前的这个处理了。它之所以看到的是异步的效果,是因为我们用了消息队列(Queue)减少阻塞。

但是所有的连接都是server端用单个线程处理的。

 

 

二、server端步骤:

1、该程序主要是利用socket进行通信,接收客户端发送过来的数据,然后再发还给客户端。 
2、首先建立一个TCP/IP socket,并将其设为非阻塞,然后进行bindlisten 
3、通过select函数获取到三种文件列表,分别对每个列表的每个元素进行轮询,对不同socket进行不同的处理,最外层循环直到inputs列表为空为止 

4、如果从客户端的一个socket接收到数据,设置个字典变量(message_queues)就把这个socket作为keyvalue为一个队列,以便装这个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.selectrlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。 

 

1)循环处理reatable列表:

情况1server 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本是个空字典

情况2server 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 in readable:
        '''
        情况1)server ready to connect client:准备建立连接--标识:
            readable列表里有 server(因为inputs之前已经有server,如果,循环readable里有server的话,那么说明准备和一个客户端建立连接。)
      '''
        if 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 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 in outputs:
                    #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
                    outputs.remove(s) #如果存在,就从活跃可写入的socket列表里删除
                print 'wriatable---before::',writable
                if 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 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 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 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 in socks:
    s.connect(server_address)

for message in messages:

    # Send messages on both sockets
    '''
    s.getpeername()
    返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
    s.getsockname()
    返回套接字自己的地址。通常是一个元组(ipaddr,port)
    '''
    for in socks:
        print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message)     #s.getsockname() 返回socket自己的地址,发送消息
        s.send(message)

    # Read responses on both sockets
    for 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
'''