Python 網絡編程操做TCP/UDP 初探(一)

服務器與客戶端之間通訊模式: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只有經過調用reactor.run()來啓動。
  • reactor循環是在其開始的進程中運行,也就是運行在主進程中。
  • 一旦啓動,就會一直運行下去。reactor就會在程序的控制下(或者具體在一個啓動它的線程的控制下)。
  • reactor循環並不會消耗任何CPU的資源。
  • 並不須要顯式的建立reactor,只須要引入就OK了。

最後一條須要解釋清楚。在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該如何實現?

相關文章
相關標籤/搜索