目錄
- Skynet.socket 函數族使用詳解
- 核心功能分類
- 一、TCP 連接管理
- 1. 監聽端口
- 2. 建立連接
- 3. 關閉連接
- 二、數據讀寫操作
- 1. 阻塞式讀取
- 2. 寫入數據
- 2.1 `socket.write(fd, data)` 的返回值
- 2.2 示例代碼
- 2.3 關鍵注意事項
- 2.4 與其他函數的區別
- 2.5 底層原理
- 2.6 總結
- 三、UDP 處理
- 1. 創建 UDP 句柄
- 2. 發送 UDP 數據
- 四、高級控制與監控
- 1. 緩沖區過載警告
- 2. 域名解析
- 五、SocketChannel 封裝
- 1. 創建 Channel 對象
- 2. 發送請求
- 六、最佳實踐與注意事項
- 總結
Skynet.socket 函數族使用詳解
Skynet 的 skynet.socket
模塊提供了 TCP/UDP 網絡通信的核心 API,結合協程機制實現了阻塞式調用模型,簡化了異步網絡編程。本文詳細解析其核心函數、使用場景及最佳實踐。
核心功能分類
- TCP 連接管理(監聽、連接、關閉)
- 數據讀寫(阻塞式讀寫、分包處理)
- UDP 支持(數據包收發、地址管理)
- 高級控制(緩沖區警告、域名解析、過載處理)
一、TCP 連接管理
1. 監聽端口
local socket = require "skynet.socket"-- 啟動 TCP 服務器
skynet.start(function()local listen_fd = socket.listen("0.0.0.0", 8888) -- 監聽 8888 端口socket.start(listen_fd, function(client_fd, addr)-- 新連接回調,處理客戶端請求socket.start(client_fd)-- ... 處理數據邏輯end)
end)
socket.listen(host, port [, backlog])
返回監聽套接字的文件描述符listen_fd
。
backlog
:等待連接隊列的最大長度(可選,默認 SOMAXCONN)。
2. 建立連接
local client_fd = socket.open("127.0.0.1", 6379) -- 連接 Redis
if client_fd thensocket.start(client_fd)socket.write(client_fd, "PING\r\n")
end
socket.open(host, port)
同步阻塞連接目標地址,返回客戶端套接字client_fd
。
3. 關閉連接
socket.close(client_fd) -- 安全關閉,等待未完成讀寫
socket.close_fd(client_fd) -- 強制立即關閉(慎用)
socket.shutdown(client_fd) -- 強制關閉(適用于 __gc 元方法)
- 區別:
close
:等待其他協程完成讀寫后關閉。close_fd
/shutdown
:直接關閉,可能導致未處理數據丟失。
二、數據讀寫操作
1. 阻塞式讀取
-- 讀取固定字節
local data, partial = socket.read(client_fd, 1024) -- 讀 1024 字節
if data thenprint("完整數據:", data)
elseprint("部分數據:", partial) -- 連接已關閉
end-- 讀取一行(默認以 \n 分割)
local line = socket.readline(client_fd, "\r\n") -- 自定義分隔符
socket.read(fd, sz)
sz
為nil
時讀取盡可能多的數據(至少 1 字節)。- 返回完整數據或
false + 已讀部分數據
(連接關閉時)。
2. 寫入數據
socket.write(client_fd, "Hello Skynet!\r\n") -- 高優先級寫入
socket.lwrite(client_fd, "Low priority data\r\n") -- 低優先級寫入
- 優先級區別:
write
:數據進入高優先級隊列,優先發送。lwrite
:數據進入低優先級隊列,高優先級隊列為空時發送。
在 Skynet 框架中,socket.write
方法的返回值取決于數據是否成功寫入內核的發送緩沖區。以下是具體說明:
2.1 socket.write(fd, data)
的返回值
-
成功時:
- 返回
true
,表示數據已成功加入內核的發送隊列,不保證對端已接收。 - 注意:返回值僅表示數據成功提交到操作系統的網絡協議棧,實際網絡傳輸是異步的。
- 返回
-
失敗時:
- 返回
nil
+ 錯誤信息(如"closed"
表示連接已關閉)。 - 常見錯誤:
"closed"
: 連接已關閉。"timeout"
: 發送超時(需結合socketdriver.settimeout
設置)。"error"
: 其他底層錯誤。
- 返回
2.2 示例代碼
local skynet = require "skynet"
local socket = require "skynet.socket"local fd = ... -- 假設 fd 是已建立的客戶端連接-- 嘗試發送數據
local ok, err = socket.write(fd, "Hello World")
if not ok thenskynet.error("Send failed:", err)socket.close(fd) -- 關閉失效連接
end
2.3 關鍵注意事項
- 異步發送:
socket.write
是非阻塞的,數據可能仍在發送隊列中未實際傳輸。 - 流量控制:若發送速度超過網絡帶寬或對端接收速度,可能導致緩沖區積壓,最終觸發錯誤。
- 錯誤處理:務必檢查返回值,及時關閉失效的
fd
,避免資源泄漏。 - 大包分片:單次寫入數據過大可能被系統拆分,需結合業務邏輯處理完整性(如添加長度頭)。
2.4 與其他函數的區別
socket.send
:與socket.write
行為一致,兩者是別名關系。socket.lwrite
:專用于發送 Lua 字符串(內部優化),行為相同。
2.5 底層原理
Skynet 的 socket.write
最終調用操作系統的 send
系統調用,但通過非阻塞模式封裝。若內核發送緩沖區已滿,數據會排隊等待,此時返回 true
;若連接已異常(如對端關閉),則直接返回錯誤。
2.6 總結
- 返回值意義:
true
表示數據提交成功,nil + err
表示失敗。 - 必須處理錯誤:尤其要捕獲
"closed"
錯誤,及時清理連接狀態。 - 性能影響:高頻發送時建議結合
socketdriver.setqueue_max
控制緩沖區大小,避免內存暴漲。
三、UDP 處理
1. 創建 UDP 句柄
local udp_fd = socket.udp(function(data, from)print("收到 UDP 數據:", data, "來源:", socket.udp_address(from))
end, "0.0.0.0", 9999) -- 綁定 9999 端口
socket.udp(callback [, host, port])
創建 UDP 句柄并綁定回調,收到數據時觸發callback(data, from)
。
2. 發送 UDP 數據
socket.sendto(udp_fd, from_address, "ACK") -- 發送到指定地址
socket.write(udp_fd, "Ping") -- 若已設置默認地址,直接寫入
socket.sendto(fd, from, data)
from
為接收到的來源地址字符串,不可手動構造。
四、高級控制與監控
1. 緩沖區過載警告
socket.warning(client_fd, function(fd, size)if size > 0 thenprint("警告:待發數據超過", size, "KB")elseprint("緩沖區已清空")end
end)
socket.warning(fd, callback)
監控待發數據量,超過 1MB 觸發回調(默認每超 64KB 打印錯誤日志)。
2. 域名解析
local dns = require "skynet.dns"
dns.server("8.8.8.8") -- 設置 DNS 服務器
local ip, all_ips = dns.resolve("www.example.com") -- 解析域名
dns.resolve(name [, ipv6])
返回解析到的 IP 地址及所有 IP 列表,避免阻塞 socket 線程。
五、SocketChannel 封裝
1. 創建 Channel 對象
local sc = require "skynet.socketchannel"
local channel = sc.channel {host = "127.0.0.1",port = 6379,response = function(sock)return true, sock:readline("\r\n") -- 解析 Redis 響應end,
}
- 模式選擇:
- 提供
response
函數則進入 Session 模式(如 MongoDB)。 - 否則為 請求-回應模式(如 Redis)。
- 提供
2. 發送請求
local resp = channel:request("PING\r\n") -- 請求并等待響應
local resp2 = channel:request("GET key\r\n", function(sock)return true, sock:read(5) -- 自定義響應解析
end)
channel:request(req [, response | session])
發送請求并自動匹配響應,支持自定義解析邏輯。
六、最佳實踐與注意事項
-
連接生命周期管理
- 使用
socket.close
確保安全關閉。 - 避免在
__gc
中使用阻塞操作,優先用shutdown
。
- 使用
-
協程調度優化
- 高頻讀寫時,合理使用
socket.lwrite
避免阻塞關鍵數據。 - 結合
skynet.fork
處理并發請求。
- 高頻讀寫時,合理使用
-
錯誤處理
- 所有讀寫操作需包裹在
pcall
中捕獲異常。 - UDP 需處理亂序和丟包,不可依賴時序。
- 所有讀寫操作需包裹在
-
性能監控
- 使用
socket.warning
監控緩沖區,防止內存溢出。 - 避免頻繁 DNS 查詢,通過緩存或獨立服務處理。
- 使用
總結
skynet.socket
通過協程化阻塞 API 簡化了網絡編程復雜度,結合 socketchannel
可高效處理復雜協議。開發者需注意:
- 連接安全性:合理關閉連接,避免資源泄漏。
- 協議適配:根據場景選擇基礎 API 或高級封裝。
- 性能調優:監控緩沖區,平衡吞吐量與內存消耗。
通過閱讀 lualib/socket.lua
和參考 service/gate.lua
,可深入理解底層實現機制。