python socket編程之select

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章