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