Socket(插座)是網路程式設計中的核心抽象概念,表示網路通訊的端點(endpoint),由 IP 位址與埠號(Port)組成,用於在不同主機或程序間建立雙向資料通道。它封裝了底層 TCP/UDP 通訊細節,提供 connect()、send()、receive() 等 API,讓應用程式能像操作檔案般處理網路 I/O,是 Web 伺服器、即時通訊、遊戲伺服器等網路應用的基礎。
Socket 的本質與結構
Socket 是作業系統提供的介面,將網路協議轉換為應用程式可用的檔案描述符:
Socket = (IP 位址, 埠號, 通訊協定)
範例:(192.168.1.100, 8080, TCP)
四元組唯一識別連線:
(來源IP:埠, 目的IP:埠) = (192.168.1.100:54321, 93.184.216.34:80)
Socket 在 OS 中的角色:
應用程式 → socket() API → 核心網路堆疊 → 網卡 → 網路
檔案描述符(fd) ↔ sock 結構體 ↔ sk_buff 封包佇列
TCP vs UDP Socket 比較
| 特性 | TCP Socket | UDP Socket |
|---|---|---|
| 連線型態 | 有連線(三次握手) | 無連線(直接發送) |
| 資料保證 | 可靠、有序、重傳 | 盡力送達、無序 |
| 效能 | 較慢(狀態維護) | 快速(無狀態) |
| API | connect(), listen(), accept() |
sendto(), recvfrom() |
| 用途 | Web、郵件、檔案傳輸 | 串流、DNS、遊戲 |
TCP Socket 程式設計完整範例
服務端(Python)
import socket
# 1. 建立 Socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 綁定位址與埠號
server.bind(('0.0.0.0', 8080)) # 監聽所有介面
# 3. 監聽連線(連線佇列長度)
server.listen(5)
print("伺服器啟動,監聽 8080 埠...")
while True:
# 4. 接受連線,返回新 Socket 與客戶端位址
client_socket, addr = server.accept()
print(f"客戶端連線:{addr}")
# 5. 接收資料
data = client_socket.recv(1024)
print(f"收到:{data.decode()}")
# 6. 發送回應
client_socket.send(b"HTTP/1.1 200 OK
Hello World!")
# 7. 關閉連線
client_socket.close()
server.close()
客戶端(Python)
import socket
# 1. 建立 Socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 連線到伺服器
client.connect(('127.0.0.1', 8080))
# 3. 發送資料
client.send(b"GET / HTTP/1.1
Host: localhost
")
# 4. 接收回應
response = client.recv(4096)
print(response.decode())
# 5. 關閉 Socket
client.close()
Socket 生命週期
服務端生命週期:
socket() → bind() → listen() → [accept() → 處理 → close()] 重複 → close()
客戶端生命週期:
socket() → connect() → send()/recv() → close()
連線狀態機(TCP):
CLOSED → LISTEN(服務端)
↓ SYN
SYN_SENT(客戶端) ↔ SYN_RCVD(服務端)
↓ ACK
ESTABLISHED(資料傳輸)
↓ FIN
FIN_WAIT → CLOSE_WAIT → LAST_ACK → CLOSED
非阻塞與多工 Socket
同步阻塞(預設):
python
data = sock.recv(1024) # 阻塞直到有資料
非阻塞 Socket:
import socket
import select
sock.setblocking(False)
# I/O 多工:select/poll/epoll
readable, _, _ = select.select([sock], [], [], 1.0)
if sock in readable:
data = sock.recv(1024)
現代多工方案:
select():檔案描述符位元遮罩,O(n)
poll(): 陣列結構,無 1024 限制
epoll(): Linux 事件驅動,O(1),Node.js/io_uring 基礎
kqueue():BSD/Mac,高效事件通知
WebSocket vs 傳統 Socket
HTTP 輪詢問題:
客戶端 → Poll /messages → 無新訊息 → 1 秒後重試 → 重複
WebSocket 解決方案:
握手:HTTP Upgrade: websocket
資料:Frame[Opcode(FIN|Text|Binary)][Payload]
雙向:全雙工,伺服器主動推送
// 客戶端
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => console.log(event.data);
ws.send('Hello Server!');
// 服務端(Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
ws.on('message', msg => ws.send(`Echo: ${msg}`));
});
Socket 程式設計最佳實務
錯誤處理
try:
sock.connect((host, port))
except socket.timeout:
print("連線超時")
except ConnectionRefusedError:
print("連線被拒")
except OSError as e:
print(f"網路錯誤:{e}")
finally:
sock.close()
緩衝區管理
def recv_all(sock, chunk_size=1024):
data = b""
while True:
chunk = sock.recv(chunk_size)
if not chunk: # 連線關閉
break
data += chunk
return data
連線池與超時
sock.settimeout(5.0) # 5 秒超時
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
常見 Socket 應用場景
| 應用 | Socket 類型 | 埠號 | 範例 |
|---|---|---|---|
| HTTP 伺服器 | TCP | 80/443 | Nginx、Apache |
| SSH 遠端連線 | TCP | 22 | OpenSSH |
| 資料庫連線 | TCP | 5432(MySQL) | PostgreSQL |
| 即時通訊 | WebSocket | 自定义 | ChatGPT、Discord |
| 遊戲伺服器 | UDP/TCP | 7777 | Unity、Unreal |
跨語言 Socket 互通範例
Python 服務端 ↔ Go 客戶端:
// Go 客戶端
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil { panic(err) }
defer conn.Close()
conn.Write([]byte("Hello from Go!"))
reply := make([]byte, 1024)
n, _ := conn.Read(reply)
fmt.Println(string(reply[:n]))
Socket 是網路程式設計的基石,從簡單 TCP Echo 到複雜微服務網格,從 WebSocket 即時通訊到遊戲伺服器 UDP 廣播。理解 Socket API(bind/listen/accept/connect)與底層狀態機,就能打造任何網路應用。現代框架雖封裝細節,但核心仍是 Socket,掌握它等於掌握網路程式設計的本質!