什么是 Socket?
Socket 的中文翻譯過來就是“套接字”,套接字是什么,我們先來看看它的英文含義:插座,
Socket 就像一個電話插座,負責連通兩端的電話,進行點對點通信,讓電話可以進行通信,埠就像插座上的孔,埠不能同時被其他行程占用,而我們建立連接就像把插頭插在這個插座上,創建一個 Socket 實體開始監聽后,這個電話插座就時刻監聽著訊息的傳入,誰撥通我這個“IP 地址和埠”,我就接通誰,
實際上,Socket 是在應用層和傳輸層之間的一個抽象層,它把 TCP/IP 層復雜的操作抽象為幾個簡單的介面,供應用層呼叫實作行程在網路中的通信,Socket 起源于 UNIX,在 UNIX 一切皆檔案的思想下,行程間通信就被冠名為檔案描述符(file descriptor),Socket 是一種“打開—讀/寫—關閉”模式的實作,服務器和客戶端各自維護一個“檔案”,在建立連接打開后,可以向檔案寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉檔案,
另外我們經常說到的Socket 所在位置如下圖:

Socket 通信程序
Socket 保證了不同計算機之間的通信,也就是網路通信,對于網站,通信模型是服務器與客戶端之間的通信,兩端都建立了一個 Socket 物件,然后通過 Socket 物件對資料進行傳輸,通常服務器處于一個無限回圈,等待客戶端的連接,
一圖勝千言,下面是面向連接的 TCP 時序圖:

客戶端程序:
客戶端的程序比較簡單,創建 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 = https://www.cnblogs.com/heyue0117/p/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 = https://www.cnblogs.com/heyue0117/p/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 地址及埠,data = https://www.cnblogs.com/heyue0117/p/conn.recv(1024)
接下來是處理階段,服務器和客戶端通過 send() 和 recv() 通信(傳輸資料),
服務器呼叫 send(),并采用字串形式向客戶端發送資訊,send() 回傳已發送的字符個數,
服務器呼叫 recv() 從客戶端接收資訊,呼叫 recv() 時,服務器必須指定一個整數,它對應于可通過本次方法呼叫來接收的最大資料量,recv() 在接收資料時會進入blocked狀態,最后回傳一個字串,用它表示收到的資料,如果發送的資料量超過了 recv() 所允許的,資料會被截短,多余的資料將緩沖于接收端,以后呼叫 recv() 時,會繼續讀剩余的位元組,如果有多余的資料會從緩沖區洗掉(以及自上次呼叫 recv() 以來,客戶端可能發送的其它任何資料),傳輸結束,服務器呼叫 Socket 的 close() 關閉連接,
從 TCP 連接的視角看 Socket 程序:
TCP 三次握手的 Socket 程序:

- 服務器呼叫
socket()、bind()、listen()完成初始化后,呼叫accept()阻塞等待; - 客戶端 Socket 物件呼叫
connect()向服務器發送了一個 SYN 并阻塞; - 服務器完成了第一次握手,即發送 SYN 和 ACK 應答;
- 客戶端收到服務端發送的應答之后,從
connect()回傳,再發送一個 ACK 給服務器; - 服務器 Socket 物件接收客戶端第三次握手 ACK 確認,此時服務端從
accept()回傳,建立連接,
接下來就是兩個端的連接物件互相收發資料,
TCP 四次揮手的 Socket 程序:

- 某個應用行程呼叫
close()主動關閉,發送一個 FIN; - 另一端接收到 FIN 后被動執行關閉,并發送 ACK 確認;
- 之后被動執行關閉的應用行程呼叫
close()關閉 Socket,并也發送一個 FIN; - 接收到這個 FIN 的一端向另一端 ACK 確認,
總結:
上面的代碼簡單地演示了 Socket 的基本函式使用,其實不管有多復雜的網路程式,這些基本函式都會用到,上面的服務端代碼只有處理完一個客戶端請求才會去處理下一個客戶端的請求,這樣的服務器處理能力很弱,而實際中服務器都需要有并發處理能力,為了達到并發處理,服務器就需要 fork 一個新的行程或者執行緒去處理請求,

學習思路這是很重要的

點此加入該群
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/121036.html
標籤:PHP
