Socket 的中文翻譯過來就是「套接字」。套接字是什麼,咱們先來看看它的英文含義:插座。python
Socket 就像一個電話插座,負責連通兩端的電話,進行點對點通訊,讓電話能夠進行通訊,端口就像插座上的孔,端口不能同時被其餘進程佔用。而咱們創建鏈接就像把插頭插在這個插座上,建立一個 Socket 實例開始監聽後,這個電話插座就時刻監聽着消息的傳入,誰撥通我這個「IP 地址和端口」,我就接通誰。服務器
實際上,Socket 是在應用層和傳輸層之間的一個抽象層,它把 TCP/IP 層複雜的操做抽象爲幾個簡單的接口,供應用層調用實現進程在網絡中的通訊。Socket 起源於 UNIX,在 UNIX 一切皆文件的思想下,進程間通訊就被冠名爲文件描述符(file descriptor)
,Socket 是一種「打開—讀/寫—關閉」模式的實現,服務器和客戶端各自維護一個「文件」,在創建鏈接打開後,能夠向文件寫入內容供對方讀取或者讀取對方內容,通信結束時關閉文件。網絡
另外咱們常常說到的Socket 所在位置以下圖:多線程
Socket 保證了不一樣計算機之間的通訊,也就是網絡通訊。對於網站,通訊模型是服務器與客戶端之間的通訊。兩端都創建了一個 Socket 對象,而後經過 Socket 對象對數據進行傳輸。一般服務器處於一個無限循環,等待客戶端的鏈接。併發
一圖勝千言,下面是面向鏈接的 TCP 時序圖:socket
客戶端的過程比較簡單,建立 Socket,鏈接服務器,將 Socket 與遠程主機鏈接(注意:只有 TCP 纔有「鏈接」的概念,一些 Socket 好比 UDP、ICMP 和 ARP 沒有「鏈接」的概念),發送數據,讀取響應數據,直到數據交換完畢,關閉鏈接,結束 TCP 對話。函數
import socket import sys if __name__ == '__main__': sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立 Socket 鏈接 sock.connect(('127.0.0.1', 8001)) # 鏈接服務器 while True: data = input('Please input data:') if not data: break try: sock.sendall(data) except socket.error as e: print('Send Failed...', e) sys.exit(0) print('Send Successfully') res = sock.recv(4096) # 獲取服務器返回的數據,還能夠用 recvfrom()、recv_into() 等 print(res) sock.close()
sock.sendall(data)
這裏也可用
send()
方法:不一樣在於sendall()
在返回前會嘗試發送全部數據,而且成功時返回 None,而send()
則返回發送的字節數量,失敗時都拋出異常。大數據
咱再來聊聊服務端的過程,服務端先初始化 Socket,創建流式套接字,與本機地址及端口進行綁定,而後通知 TCP,準備好接收鏈接,調用 accept()
阻塞,等待來自客戶端的鏈接。若是這時客戶端與服務器創建了鏈接,客戶端發送數據請求,服務器接收請求並處理請求,而後把響應數據發送給客戶端,客戶端讀取數據,直到數據交換完畢。最後關閉鏈接,交互結束。網站
import socket import sys if __name__ == '__main__': sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立 Socket 鏈接(TCP) print('Socket Created') try: sock.bind(('127.0.0.1', 8001)) # 配置 Socket,綁定 IP 地址和端口號 except socket.error as e: print('Bind Failed...', e) sys.exit(0) sock.listen(5) # 設置最大容許鏈接數,各鏈接和 Server 的通訊遵循 FIFO 原則 while True: # 循環輪詢 Socket 狀態,等待訪問 conn, addr = sock.accept() try: conn.settimeout(10) # 若是請求超過 10 秒沒有完成,就終止操做 # 若是要同時處理多個鏈接,則下面的語句塊應該用多線程來處理 while True: # 得到一個鏈接,而後開始循環處理這個鏈接發送的信息 data = conn.recv(1024) print('Get value ' + data, end='\n\n') if not data: print('Exit Server', end='\n\n') break conn.sendall('OK') # 返回數據 except socket.timeout: # 創建鏈接後,該鏈接在設定的時間內沒有數據發來,就會引起超時 print('Time out') conn.close() # 當一個鏈接監聽循環退出後,鏈接能夠關掉 sock.close()
conn, addr = sock.accept()
調用
accept()
時,Socket 會進入waiting狀態。客戶端請求鏈接時,方法創建鏈接並返回服務器。accept()
返回一個含有兩個元素的元組 (conn, addr)。第一個元素 conn 是新的 Socket 對象,服務器必須經過它與客戶端通訊;第二個元素 addr 是客戶端的 IP 地址及端口。spa
data = conn.recv(1024)
接下來是處理階段,服務器和客戶端經過
send()
和recv()
通訊(傳輸數據)。
服務器調用send()
,並採用字符串形式向客戶端發送信息,send()
返回已發送的字符個數。
服務器調用recv()
從客戶端接收信息。調用recv()
時,服務器必須指定一個整數,它對應於可經過本次方法調用來接收的最大數據量。recv()
在接收數據時會進入blocked狀態,最後返回一個字符串,用它表示收到的數據。若是發送的數據量超過了recv()
所容許的,數據會被截短。多餘的數據將緩衝於接收端,之後調用recv()
時,會繼續讀剩餘的字節,若是有多餘的數據會從緩衝區刪除(以及自上次調用recv()
以來,客戶端可能發送的其它任何數據)。傳輸結束,服務器調用 Socket 的close()
關閉鏈接。
TCP 三次握手的 Socket 過程:
socket()
、bind()
、listen()
完成初始化後,調用 accept()
阻塞等待;connect()
向服務器發送了一個 SYN 並阻塞;connect()
返回,再發送一個 ACK 給服務器;accept()
返回,創建鏈接。接下來就是兩個端的鏈接對象互相收發數據。
TCP 四次揮手的 Socket 過程:
close()
主動關閉,發送一個 FIN;close()
關閉 Socket,並也發送一個 FIN;上面的代碼簡單地演示了 Socket 的基本函數使用,其實無論有多複雜的網絡程序,這些基本函數都會用到。上面的服務端代碼只有處理完一個客戶端請求才會去處理下一個客戶端的請求,這樣的服務器處理能力很弱,而實際中服務器都須要有併發處理能力,爲了達到併發處理,服務器就須要 fork 一個新的進程或者線程去處理請求。