python學習筆記之--網(wǎng)絡(luò)編程

基本知識

  • 計算機網(wǎng)絡(luò)知識,主要了解一下OSI的七層架構(gòu),以及TCP/IP的四層架構(gòu)。
  • 網(wǎng)絡(luò)通信中最基本的概念:套接字,也叫通信端點、socket
  • 網(wǎng)絡(luò)通信地址表示:一般是主機+端口號
  • 最常用的兩種連接方式:面向連接方式(TCP,SOCK_STREAM)和面向無連接方式(UDP, SOCK_DGRAM)

這些基本的知識,網(wǎng)上有很多相關(guān)介紹,不是本章重點,有需要可以在網(wǎng)上查找相關(guān)資料。
這里學習一下python語言對底層網(wǎng)絡(luò)編程的支持,這里只學習最常用的socket編程相關(guān)知識,詳細說明可以參考官方文檔:底層網(wǎng)絡(luò)接口

socket模塊

python有一個專門的socket模塊來支持底層網(wǎng)絡(luò)通信。是對Unix系統(tǒng)調(diào)用和套接字庫接口的直譯。

socket編程基本套路

  1. TCP服務(wù)端
    s = socket()  # 創(chuàng)建一個服務(wù)端socket
    s.bind()      # 綁定socket地址(ip+port)
    s.listen()    # 監(jiān)聽此地址是否有連接
    loop:
        cs = s.accept() # 阻塞等待客戶端連接,連接成功返回新的套接字對象,用于在此連接上收發(fā)數(shù)據(jù)
        comm_loop:
            cs.recv()/cs.send() # 通信:接收數(shù)據(jù)/發(fā)送數(shù)據(jù)
        cs.close() # 關(guān)閉通信套接字對象
    s.close() # 關(guān)閉服務(wù)端套接字對象
    
  2. TCP客戶端
    cs = socket()  # 創(chuàng)建一個客戶端socket
    cs.connect()      # 通過服務(wù)端地址(ip + port)嘗試連接服務(wù)端
    comm_loop:
        cs.send()/cs.recv() # 通信:發(fā)送數(shù)據(jù)/接收數(shù)據(jù)
    cs.close() # 關(guān)閉客戶端套接字對象
    
  3. UDP服務(wù)端
    ss = socket()  # 創(chuàng)建一個服務(wù)端socket
    ss.bind()      # 綁定服務(wù)端地址
    comm_loop:
        cs.recvfrom()/cs.sendto() # 通信:接收數(shù)據(jù)/發(fā)送數(shù)據(jù)
    cs.close() # 關(guān)閉客戶端套接字對象
    

說明: UDP無連接,不需要監(jiān)聽。先被動接收數(shù)據(jù),會返回客戶端的地址,后面就可以根據(jù)客戶端地址主動發(fā)送消息。

  1. UDP客戶端
    cs = socket()  # 創(chuàng)建一個客戶端socket
    comm_loop:
        cs.sendto()/cs.recvfrom() # 通信:發(fā)送數(shù)據(jù)/接收數(shù)據(jù)
    cs.close() # 關(guān)閉客戶端套接字對象
    

socket通信實例

先從一個TCP實例開始學習,功能比較簡單:

  1. 一個客戶端,一個服務(wù)器端,兩個python程序可以放在兩臺電腦上運行
  2. 客戶端給服務(wù)端發(fā)送一個消息;服務(wù)器端接收到消息之后,加上一個時間戳再返回給客戶端
  3. 無消息則結(jié)束
  • 服務(wù)端代碼

    from socket import *
    from time import ctime
    
    HOST = ''
    PORT = 21567
    BUFF = 1024
    
    ADDR = (HOST, PORT)
    
    tcpSvrSocket = socket(AF_INET, SOCK_STREAM)
    tcpSvrSocket.bind(ADDR)
    tcpSvrSocket.listen(5)
    
    while True:
        print("Waiting for connect...")
        tcpCliSocket, addr = tcpSvrSocket.accept()
        print(f"...connected from {addr}")
    
        while True:
            data = tcpCliSocket.recv(BUFF)
            if not data:
                break;
    
            data = data.decode('utf-8')
    
            print(f'recive data from client: {data}')
    
            resMsg = f'[{ctime()}] {data}'
            tcpCliSocket.send(bytes(resMsg, 'utf-8'))
    
        tcpCliSocket.close()
    
    tcpSvrSocket.close()
    
  • 客戶端代碼

    from socket import *
    
    HOST = ''
    PORT = 21567
    BUFF = 1024
    
    ADDR = (HOST, PORT)
    
    tcpCliSocket = socket(AF_INET, SOCK_STREAM)
    tcpCliSocket.connect(ADDR)
    
    while True:
        data = input(">>> ")
        if not data:
            break
    
        tcpCliSocket.send(bytes(data, 'utf-8'))
        data = tcpCliSocket.recv(BUFF)
        if not data:
            break
    
        print(data.decode('utf-8'))
    
    tcpCliSocket.close()
    

說明:socket類實現(xiàn)了__enter____exit__,可以使用with語句自動實現(xiàn)close操作,也即使用with語句可以不用主動調(diào)用socket.close()接口

模塊說明

  • 常量
    socket模塊定義了很多常量,最常使用的如下表
常量名 說明
socket.AF_INET 一種用于ipv6的地址族,一個四元組(host, port, flowinfo, scope_id)表示地址
socket.AF_INET6 一種socket地址族(用于ipv4,也是目前常用的)使用一個(ip, port)元組表示地址
socket.SOCK_STREAM 面向連接(TCP)的socket類型
socket.SOCK_DGRAM 面向無連接(UDP)的socket類型
  • 接口
    socket模塊中有一些函數(shù)可以使用,但是最常用的還是socket類生成socket對象,以及類中的接口。類原型如下:
    class socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
    

常用接口信息如下表

接口名 連接方式 說明
socket.bind(address) TCP/UDP 服務(wù)端socket綁定到地址address
socket.listen([backlog]) TCP 啟動一個服務(wù)器用于接受連接。backlog表示系統(tǒng)允許暫未 accept 的連接數(shù),超過后將拒絕新連接。最低為 0(小于 0 會被置為 0),未指定則自動設(shè)為合理的默認值。
socket.accept() TCP 接受一個連接。此 socket 必須綁定到一個地址上并且監(jiān)聽連接。返回一個 (conn, address) 對:其中 conn 是一個新的套接字對象,用于在此連接上收發(fā)數(shù)據(jù),address 是連接另一端的套接字所綁定的地址。
socket.connect(address) TCP/UDP 連接到 address 處的遠程套接字。
socket.recv(bufsize[, flags]) TCP 從套接字接收數(shù)據(jù)。返回值是一個字節(jié)對象,表示接收到的數(shù)據(jù)。bufsize 指定一次接收的最大數(shù)據(jù)量??蛇x參數(shù) flags 的含義請參閱 Unix 手冊頁 recv(2),它默認為零。
socket.send(bytes[, flags]) TCP 發(fā)送數(shù)據(jù)給套接字。本套接字必須已連接到遠程套接字??蛇x參數(shù) flags 的含義與上述 recv() 中的相同。
socket.recvfrom(bufsize[, flags]) UDP 從套接字接收數(shù)據(jù)。返回值是一對 (bytes, address),其中 bytes 是字節(jié)對象,表示接收到的數(shù)據(jù),address 是發(fā)送端套接字的地址??蛇x參數(shù) flags 的含義請參閱 Unix 手冊頁 recv(2),它默認為零。
socket.sendto(bytes, address) UDP 發(fā)送數(shù)據(jù)給套接字。由 address 指定目標套接字??蛇x參數(shù) flags 的含義與上述 recv() 中的相同。
  • 異常
異常名 說明
socket.error 一個被棄用的 OSError 的別名。
socket.herror OSError的子類,本異常通常表示與地址相關(guān)的錯誤。
socket.gaierror OSError的子類,來自 getaddrinfo()getnameinfo(),表示與地址相關(guān)的錯誤。附帶的值是一對 (error, string),代表庫調(diào)用返回的錯誤。
socket.timeout OSError的子類,當套接字發(fā)生超時,且事先已調(diào)用過 settimeout()(或隱式地通過 setdefaulttimeout())啟用了超時,則會拋出此異常。

SocketServer模塊

socketserver模塊是一個用于網(wǎng)絡(luò)服務(wù)器編寫的框架,簡化了編寫網(wǎng)絡(luò)服務(wù)器的任務(wù)。例如上面TCP服務(wù)器端代碼可以使用此模塊改寫如下:

import socketserver

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile is a file-like object created by the handler;
        # we can now use e.g. readline() instead of raw recv() calls
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # Likewise, self.wfile is a file-like object used to write back
        # to the client
        self.wfile.write(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 21567

    # Create the server, binding to localhost on port 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

模塊說明

常用實體服務(wù)器類:

  • TCPServer類,該類使用互聯(lián)網(wǎng) TCP 協(xié)議,它可以提供客戶端與服務(wù)器之間的連續(xù)數(shù)據(jù)流。函數(shù)原型如下:
    class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
  • UDPServer類,該類使用數(shù)據(jù)包,即一系列離散的信息分包,它們可能會無序地到達或在傳輸中丟失。函數(shù)原型如下:
    class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

請求處理句柄對象:

  • BaseRequestHandler類,這是所有請求處理句柄對象的超類。 它定義了下文列出的接口。 一個實體請求處理句柄子類必須定義新的 handle()方法,并可重載任何其他方法。 對于每個請求都會創(chuàng)建一個新的子類的實例。

    接口名 說明
    setup() 會在 handle() 方法之前被調(diào)用以執(zhí)行任何必要的初始化操作。 默認實現(xiàn)不執(zhí)行任何操作。
    handle() 此函數(shù)必須執(zhí)行為請求提供服務(wù)所需的全部操作。 默認實現(xiàn)不執(zhí)行任何操作。 它有幾個可用的實例屬性;請求為 self.request;客戶端地址為 self.client_address;服務(wù)器實例為 self.server,如果它需要訪問特定服務(wù)器信息的話。
    finish() handle()方法之后調(diào)用以執(zhí)行任何需要的清理操作。 默認實現(xiàn)不執(zhí)行任何操作。
  • StreamRequestHandler和DatagramRequestHandler類,是BaseRequestHandler的子類,重載了 setup()finish()方法,并提供了 self.rfile(讀取以獲取請求數(shù)據(jù)) 和 self.wfile(寫入以將數(shù)據(jù)返回給客戶端) 屬性。

當然此框架還提供了更加強大的功能,具體可以參考官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容