[TOC]python
socket: Python的底層網絡接口,通常狀況程序員不須要接觸到這個模塊。有更多的高級模塊,好比
requests
能夠直接使用。本文章試圖從Python的socket模塊和linux socket api的角度來對Python實現網絡通信方式進行分析,提升對TCP,UDP通信方式的理解。最後用Python實現一個hello/hi的簡單的網絡聊天程序。linux
在Python中若是想要使用一個本身的網絡應用層協議,或者說想使用純原生TCP,UDP來實現通信,就須要使用Python的socket
模塊。git
import socket
socket
模塊提供了訪問BSD套接字的接口。在全部現代Unix系統、Windows、macOS和其餘一些平臺上可用。程序員
# 使用socket()方法返回一個socket對象 s = socket.socket([family[, type, proto, fileno]])
重要參數:github
參數 | 描述 |
---|---|
family | |
socket.AF_INET(默認) | IPv4 |
socket.AF_INET6 | IPv6 |
socket.AF_UNIX | Unix系統進程間通訊 |
type | |
socket.SOCK_STREAM | 流式套接字,TCP |
socket.SOCK_DGRAM | 數據報套接字,UDP |
socket.SOCK_RAW | 原始套接字 |
socket方法與Linux Socket的
socket
函數對應編程
// socket(協議域,套接字類型,協議) int socket(int domain, int type, int protocol);
經過s = socket.socket()
方法,獲得了一個socket對象。api
Python中的socket對象的成員方法,是對套接字系統調用的高級實現,每每比C語言更高級。服務器
一般若是是服務器,須要綁定一個總所周知的地址用於提供服務,因此須要綁定一個(IP:PORT),客戶端能夠經過鏈接這個地址來得到服務。而客戶端則直接經過鏈接,由系統隨機分配一個端口號。網絡
python中bind()方法傳入一個地址和端口的元組dom
s.bind((host: str, port: int))
linux socket將套接字做爲對象,傳入一個套接字和地址結構體
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
開始監聽,backlog指定在拒絕鏈接以前,操做系統能夠掛起的最大鏈接數,默認爲1
s.listen(backlog: int)
linux socket一樣須要額外傳入套接字參數
int listen(int sockfd, int backlog);
connect
方法是客戶端用發起某個鏈接的,接受一個目標主機名和端口號的元組參數
# address -> (hostname, port) s.connect(address) # connect_ex是connect的擴展方法,不一樣在於返回錯誤代碼,而不是拋出錯誤 s.connect_ex(address)
linux socket中,參數分別爲客戶端套接字socket描述符,服務器socket地址,socket地址長度
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
服務器依次調用socket()
, bind()
, listen()
後就會監聽指定地址,客戶端經過connect()
向服務器發起鏈接請求。服務器監聽到請求後會調用accept()
函數接受請求。這樣端與端的鏈接就創建好了
python中,accept()
方法阻塞進程,等待鏈接,返回一個新的套接字對象和鏈接請求者地址信息。
# accept() -> (socket object, address info) s.accept()
linux socket中,第一個參數是服務器套接字描述符,第二個爲一個地址指針,用於返回客戶端協議地址,第三個參數是協議地址長度。若是鏈接成功,函數返回值爲內核自動生成的一個全新描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
服務器的監聽套接字通常只建立一個,而accept函數會返回一個鏈接套接字,服務器與客戶端之間的通訊是在鏈接套接字上進行。每來一個服務請求新建一個鏈接套接字與請求者通訊,而監聽套接字只有一個,完成服務後對應的鏈接套接字就會被關閉。(能夠理解成,監聽套接字是專門的接線員,只負責將電話轉接給別的部門)
send(data[, flags]) ->count
發送數據到socket,發送前要將數據轉換爲utf-8的二進制格式。返回發送數據長度,由於網絡可能繁忙,致使數據沒有所有發送完畢,因此要再次對剩下的數據進行發送。
python還有一個sendall()
sendall(data[, flags])
做用是不停調用send()
函數,直到全部數據發送完畢
linux socket中的send()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send()
函數先檢查協議是否正在發送緩衝區數據,等待協議發送完畢或則緩衝區已沒有數據,那麼send
比較sockfd緩衝區剩餘空間大小和發送數據的len。python中,從已鏈接套接字讀取數據的函數爲recv()
s.recv(bufsize: int)
從套接字接受數據,若是沒有數據到達套接字,將會阻塞直到來數據或則遠程鏈接關閉。
若是遠程鏈接關閉且數據已所有讀取,則拋出一個錯誤。
linux socket也有讀取數據函數recv()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recv()
等待s發送緩衝區發送完畢recv()
在無鏈接的狀況下,端到端須要使用另外的數據發送和接受方式
python中發送UDP數據,將數據data發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。
s.sendto(data,address)
linux socket: 因爲本地socket並無與遠端機器創建鏈接,因此在發送數據時應指明目的地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
該函數比send()函數多了兩個參數,dest_addr表示目地機的IP地址和端口號信息,而addrlen是地址長度。
s.recvfrom() -> (data, address)
接收UDP數據,與recv()相似,但返回值是(data,address)。其中data是包含接收的數據,address是發送數據的套接字地址。
linux socket: recvfrom()的狀況與sendto()相似,須要指針來存放發送數據的套接字地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
python和linux socket都須要對套接字關閉
python:
s.close()
linux socket:
int close(int socketfd)
#! /usr/bin/env python3 import socket from threading import Thread import traceback HOST = "127.0.0.1" PORT = 65432 def recv_from_client(conn): try: content = conn.recv(1024) return content except Exception: return None class ServiceThread(Thread): def __init__(self, conn, addr): super().__init__() self.conn = conn self.addr = addr def run(self): try: while True: content = recv_from_client(self.conn) if not content: break print(f"{self.addr}: {content.decode('utf-8')}") self.conn.sendall(content) self.conn.close() print(f"{self.addr[0]}:{self.addr[1]} leave.") except Exception: traceback.print_exc() if __name__ == "__main__": s = None try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen() print("Repeater server started successfully.") while True: conn, addr = s.accept() print(f"Connected from {addr}") service_thread = ServiceThread(conn, addr) service_thread.daemon = True service_thread.start() except Exception: traceback.print_exc() s.close()
#! /usr/bin/env python3 import socket from threading import Thread HOST = "127.0.0.1" PORT = 65432 class ReadFromConnThread(Thread): def __init__(self, conn): super().__init__() self.conn = conn def run(self): try: while True: content = self.conn.recv(1024) print(f"\n({HOST}:{PORT}): {content.decode('utf-8')}\nYOUR:", end="") except Exception: pass if __name__ == "__main__": s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) read_thread = ReadFromConnThread(s) read_thread.daemon = True read_thread.start() while True: content = input("YOUR:") if content == "quit": break s.sendall(content.encode("utf-8")) s.close()
做者:SA19225176,萬有引力丶
參考資料來源:USTC Socket網絡編程