服務器與客戶端之間通訊模式:react
我以爲這個舉例是很恰當的。將服務器->客服總線,客戶端->客戶,新的客戶端->客服表明。編程
客服總線好比說400-xxxxxx這類的電話,一直處於等待狀態,當有新的客戶來電以後,總線接線員接到電話後,將客戶的電話切換給客服表明進行處理。這樣空出主線,以便總線接線員能夠繼續等待新的客戶電話。而此時以前接入的客戶及對應的客服表明,可以進行他們本身獨立的談話。當有新的客戶B進來以後,總線接線員會建立一個新的客服表明進行處理,總線接線員繼續進行等待。服務器
接下來咱們直接開始擼網絡
1.TCP鏈接多線程
TCP服務器端:框架
from socket import * from time import ctime HOST = '' # 對bind方法的標識,標識可使用任何可用的地址 PORT = 21567 # 隨機設置的端口號 BUFSIZE = 1024 # 緩衝區大小1KB ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) # 分配TCP服務器套接字 tcpSerSock.bind(ADDR) # 將套接字綁定到服務器地址 tcpSerSock.listen(5) # 開啓TCP監聽器 while True: print 'waiting fo connection...' # 無限循環中,等待客戶端的鏈接 tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr while True: data = tcpCliSock.recv(BUFSIZE) # 接收客戶端數據 if not data: # 若是接收的消息是空白數據,這覺得着客戶端已經退出,跳出循環,關閉當前的客戶端鏈接,繼續等待另外一個客戶端鏈接 break tcpCliSock.send('[%s] %s' % (ctime(), data)) # 接受到客戶端消息不爲空,則將其格式化並返回相同的數據,加上當前的時間戳前綴。 tcpCliSock.close() tcpSerSock.close() # 永遠不會執行,只是提醒你們,能夠用這種方式,關閉服務器套接字,退出服務
TCP客戶端:dom
# coding:utf-8 from socket import * HOST = 'localhost' # 本地鏈接通訊,若是要其他機器鏈接,可改爲服務器端IP PORT = 21567 # 隨機設置的端口號 BUFSIZE = 1024 # 緩衝區大小1KB ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) # 分配TCP客戶端套接字,主動調用並鏈接到服務器 tcpCliSock.connect(ADDR) while True: data = raw_input('> ') # 用戶輸入發送數據 if not data: break tcpCliSock.send(data) # 發送數據 data = tcpCliSock.recv(BUFSIZE) # 接收服務器端返回數據 if not data: break print data # 打印服務器端加了時間戳以後的返回數據,顯示 tcpCliSock.close()
若是須要IPv6地址:須要將HOST改成 HOST = '::1' ,同時請求套接字的AF_INET6家族。異步
接着爲了看他們的工做,先運行服務器端程序,而後啓動客戶端程序:socket
客戶端顯示以下:tcp
服務器端顯示以下:
waiting fo connection...
...connected from: ('127.0.0.1', 57551)
waiting fo connection...
經過以上的例子,給咱們展現了數據如何從客戶端到達服務器,並最後返回到客戶端。這裏服務器就是做爲了一個「時間服務器」,獲取服務器端的時間。
2.UDP鏈接
UDP鏈接與TCP鏈接通訊的一個顯著差別就是它不是面向鏈接的,無鏈接,無需監聽傳入的鏈接。這類服務器僅僅接受消息並有可能回覆數據。只有建立套接字並將其綁定到地址中,而後無限循環接受客戶端消息,處理,返回消息。
UDP服務器端:
# coding:utf-8 from socket import * from time import ctime HOST = '' # 對bind方法的標識,標識可使用任何可用的地址 PORT = 21567 # 隨機設置的端口號 BUFSIZE = 1024 # 緩衝區大小1KB ADDR = (HOST, PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM) # 分配TCP服務器套接字 udpSerSock.bind(ADDR) # 將套接字綁定到服務器地址 while True: print 'waiting for message...' data, addr = udpSerSock.recvfrom(BUFSIZE) # 接收客戶端數據 udpSerSock.sendto('[%s] %s' % (ctime(), data), addr) # 格式化客戶端數據,並返回給客戶端 print '... received from and returned tto:', addr udpSerSock.close() # 永遠不會執行,只是提醒你們,能夠用這種方式,關閉服務器套接字,退出服務
UDP客戶端:
# coding:utf-8 from socket import * HOST = 'localhost' # 對bind方法的標識,標識可使用任何可用的地址,使用本地鏈接localhost PORT = 21567 # 隨機設置的端口號 BUFSIZE = 1024 # 緩衝區大小1KB ADDR = (HOST, PORT) udpSockCli = socket(AF_INET, SOCK_DGRAM) # 分配udp客戶端套接字,主動調用並鏈接到服務器 while True: data = raw_input('> ') # 輸入數據 if not data: # 若是輸入數據爲空,默認退出鏈接 break udpSockCli.sendto(data, ADDR) # 客戶端發送輸入的數據到服務器 data, addr = udpSockCli.recvfrom(BUFSIZE) # 客戶端接收從服務器返回的數據 if not data: # 若是從服務器接收到的數據爲空,默認斷開鏈接 break print data udpSockCli.close() # 永遠不會執行,只是提醒你們,能夠用這種方式,關閉服務器套接字,退出服務
客戶端顯示:
服務器端顯示:
在使用TCP鏈接的時候,咱們必須先跑服務器端的程序,而後再起客戶端的程序。可是在用UDP鏈接的時候,就沒必要管這樣的啓動順序,能夠先啓動客戶端程序,而後再啓動服務器端程序進行通訊。
3.SocketServer模塊
服務器端:
# coding:utf-8 from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH) from time import ctime HOST = '' PORT = 21567 ADDR = (HOST, PORT) class MyRequestHandle(SRH): # 建立基於StreamRequestHandler的子類. def handle(self): # 重寫Handle方法 print '...connected from:', self.client_address ''' StreamRequestHandler類將輸入和輸出套接字看做相似文件的對象.所以 經過write發送字符串給到客戶端.readline獲取客戶端消息.由於採用的是 相似文件的處理方式,因此須要額外的回車和換行符,在客戶端進行處理. ''' self.wfile.write('[%s] %s' %(ctime(), self.rfile.readline())) tcpSSSer = TCP(ADDR, MyRequestHandle) # 創建鏈接服務 print 'waiting for connection ...' tcpSSSer.serve_forever() # 無限循環等待,服務客戶端請求
客戶端:
# coding:utf-8 from socket import * HOST = 'localhost' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) while True: tcpSSCliSock = socket(AF_INET, SOCK_STREAM) tcpSSCliSock.connect(ADDR) data = raw_input('> ') if not data: break ''' 由於採用的是相似文件的處理方式,因此須要額外的回車和換行符, 在客戶端進行處理. ''' tcpSSCliSock.send('%s\r\n' % data) data = tcpSSCliSock.recv(BUFSIZE) if not data: break print data.strip() tcpSSCliSock.close()
SocketServer請求處理程序的默認行爲是接受鏈接,獲取請求,而後關閉鏈接。因爲這個緣由,咱們不能在應用程序整個執行過程當中都保持鏈接,所以每次向服務器發送消息時,都須要建立一個新的套接字。
4.Twisted框架
服務器端:
# coding:utf-8 from twisted.internet import protocol, reactor from time import ctime PORT = 21567 # 定義鏈接端口 class TsSerProtocol(protocol.Protocol): # 定義基於protocol的子類 def connectionMade(self): # 重寫connectionMade方法,客戶端鏈接服務器默認調用該方法. clnt = self.clnt = self.transport.getPeer().host # 獲取主機信息 print '...connected from:', clnt def dataReceived(self, data): # 重寫dataReceived方法,客戶端經過網絡發送數據時默認調用該方法. self.transport.write('[%s] %s ' % (ctime(), data)) # 時間戳+發送的數據做爲返回數據. factory = protocol.Factory() # 協議工廠,每一個鏈接接入,製造一個協議實例 factory.protocol = TsSerProtocol print 'waiting for connection...' ''' 異步編程:這樣的異步模式稱爲Reactor模式 1.監聽事件 2.事件發生執行對應的回調函數 3.回調完成(可能產生新的事件添加進監聽隊列) 4.回到1,監聽事件 ''' reactor.listenTCP(PORT, factory) # reactor 安裝TCP監聽器,檢查服務請求,接收到請求後就建立一個TsSerProtocol實例來處理客戶端事物 reactor.run() # 運行事件管理器
reactor是事件管理器,用於註冊、註銷事件,運行事件循環,當事件發生時調用回調函數處理。關於reactor有下面幾個結論:
最後一條須要解釋清楚。在Twisted中,reactor是Singleton(也就是單例模式),即在一個程序中只能有一個reactor,而且只要你引入它就相應地建立一個。上面引入的方式這是twisted默認使用的方法。
客戶端:
# coding:utf-8 from twisted.internet import protocol, reactor import random from time import * HOST = 'localhost' # 定義本地127.0.0.1鏈接 PORT = 21567 # 定義默認端口 class TsCliProtocol(protocol.Protocol): # 定義基於protocol的子類 def sendData(self): # 添加默認的發送數據方法. # data = raw_input('> ') data = '123123' + str(random.randint(0, 1000)) # 爲觀察異步通訊,而選擇一直傳輸隨機字符串 sleep(3) if data: print '...sending %s ...' % data self.transport.write(data) # 發送數據給到服務器 else: self.transport.loseConnection() # 斷開客戶端與服務器鏈接,關閉套接字,調用工廠函數clientConnectionLost(),中止reactor事物監聽器 def connectionMade(self): # 創建鏈接 self.sendData() def dataReceived(self, data): # 接收到返回數據回調函數 print data self.sendData() class TsCliFactory(protocol.ClientFactory): # 客戶端工廠類,繼承於protocol.ClientFactory protocol = TsCliProtocol clientConnectionLost = clientConnectionFailed = lambda self, connector, reason: reactor.stop() reactor.connectTCP(HOST, PORT, TsCliFactory()) # 建立服務器TCP鏈接 reactor.run() # 運行事件管理器
運行上面的程序,一個服務器,兩個客戶端,咱們能夠看下以下情景:
服務器端:
客戶端:
經過上面的幾個嘗試,咱們不難發現單線程的通訊是很容易實現的,可是在實際工做中使用呢?
可能存在如下一些問題,同時咱們將在下一篇中進行探索:
1.異步通訊,也是在不斷的輪詢排隊處理中,若是採用服務器端多線程處理呢?
2.多線程與異步操做的異同
3.若是A - 服務器 -B該如何實現?