skynet網路部分剖析(一) socket的狀態
最近看了一些開源的網路庫原始碼,有libevent,muduo,redis,類nginx等等。再看skynet網路部分就覺得很容易了,因為他們都是基於reactor模式,套路都差不多。不過skynet的網路部分要稍微複雜點,因為他最終要面向的是lua邏輯端。為了讓lua socket api這塊設計的合理,好用,整個網路架構上相比前面介紹的網路框架會有點繞,比方說socket的狀態,比其他框架要多出幾種來。
socket狀態有哪些呢?檢視定義如下:
#define SOCKET_TYPE_INVALID 0 #define SOCKET_TYPE_RESERVE 1 #define SOCKET_TYPE_PLISTEN 2 #define SOCKET_TYPE_LISTEN 3 #define SOCKET_TYPE_CONNECTING 4 #define SOCKET_TYPE_CONNECTED 5 #define SOCKET_TYPE_HALFCLOSE 6 #define SOCKET_TYPE_PACCEPT 7 #define SOCKET_TYPE_BIND 8
我們看到listen之前還有個plisten,沒有accept,卻有paccept。這個字首p是什麼意思呢?
要想解釋這個,先得說說lua socket api如何設計和使用。一個典型的socket處理如下:
local id = socket.listen('127.0.0.1', 8876) local acceptFun = function(id, addr) print('connnect from '..addr..' '..id) socket.start(id) while true do local ret = socket.read(id) print('ret is ', ret) end end socket.start(id, acceptFun)
在lua邏輯中,我們不能像寫c伺服器那樣,listen之後呼叫在while迴圈中呼叫accept等待客戶端連線,我們必須善用指令碼語言的回撥。感謝雲風大俠設計出如此優秀的方案,在sockt.start中接受使用者連線的回撥函式。
這裡的socket.start相當於要打開了一個socket,可是按照一般的網路框架理解,listen不正是打開了一個監聽的socket嗎。所以雲風的設計方案是,listen底層確實建立了一個監聽socket,但是他還沒有納入epoll的監管之中。等到socket.start才將監聽socket納入epoll的監管,所以在此之前,監聽socket的狀態是SOCKET_TYPE_PLISTEN,等到socket.start之後變為SOCKET_TYPE_LISTEN。
同理,當有客戶端連線時,會生成一個新的連線socket,此時的狀態為SOCKET_TYPE_PACCEPT,注意此時也不會將該socket納入epoll的監管之中。等到呼叫socket.start之後狀態變為SOCKET_TYPE_CONNECTED,同時將該連線socket納入epoll的監管中。
總結一下,要想啟用socket服務,lua呼叫socket.start必不可少,他都是將監聽的socket或連線的socket納入epoll的監管之中,在lua邏輯端會使所在服務暫停,等待連線或傳送訊息。然後他會給所在的服務傳送型別為SKYNET_SOCKET_TYPE_CONNECT的socket訊息。lua邏輯收到該訊息的處理方式是恢復服務,服務得以繼續進行。
在lua邏輯中充當客戶端的角色時,要呼叫id = socket.open(ip, port),此時底層會去connect伺服器,成功後連線socket的狀態為SOCKET_TYPE_CONNECTED。這種情況可以不用呼叫socket.start(id),為什麼呢,難道不需要將socket納入epoll的監管嗎?其實在處理open訊息,建立新的socket時已經將其納入epoll的監管中了,new_fd(ss, id, sock, PROTOCOL_TCP, request->opaque, true),最後一個引數為true,表明要加入epoll中。
當socket收到訊息(關閉訊息也算)或有錯誤產生時,skynet則沒有給出socket的狀態,只是將收到的資料push到服務的訊息佇列。
歡迎加入QQ群 858791125 討論skynet,遊戲後臺開發,lua指令碼語言等問題。