skynet 的 1.0 版已經(jīng)發(fā)布了 3 個 alpha 版,等穩(wěn)定以后將發(fā)布 beta 版本。
最近的問題主要集中在一些我們在老項目中沒有使用到的特性上面。尤其是 sproto 這個模塊,我希望它將來作為 skynet 推薦的通訊協(xié)議,但我們老的項目開始的比 sproto 的項目早,所以早期項目全部使用的是 google protocol buffers (以及我自己做的實現(xiàn))。 隨著新項目的開展,我們公司內(nèi)部開始大面積使用 sproto ,也就發(fā)現(xiàn)了一些 bug ,在最近集中修復(fù)。
由于 skynet 使用多 lua VM 結(jié)構(gòu),為了不在每個 VM 里重復(fù)加載 sproto 協(xié)議,最近增加了 sproto 協(xié)議對象的共享。這個作為未寫入 sproto 文檔的特性提供,當然也不影響 sproto 在其它領(lǐng)域的使用。不過加這個特性比較匆忙,第一次提交時在 gc 方面遺留了一個 bug ,有可能導(dǎo)致多個 VM 重復(fù)釋放 C 對象,問題已經(jīng)在倉庫最新的提交中修復(fù)。
另一個為了 skynet 的應(yīng)用而特別加上的特性是讓 sproto 的 decode 可以接收指針(lightuserdata)。固然只接受 string 會讓實現(xiàn)更穩(wěn)固一些,不過在 skynet 里很多地方 string 和 lightuserdata + size 是通用的,所以就順帶支持了。這樣可以減少一次內(nèi)存拷貝。
根據(jù)使用的同學(xué)的需求,在 sproto 的 lua bingding 里增加了更為詳細的出錯提示,這可以幫助實際使用時的錯誤定位。另外,還增加更為嚴格的類型檢查。缺少這些檢查應(yīng)該算是 bug ,因為使用 sproto 而不是 json 這種的無格式的協(xié)議,就是為了可以多做一些類型檢查的。復(fù)雜類型(在 lua 里用 table 實現(xiàn))不檢查還會導(dǎo)致進程掛掉,這是絕對不可以接受的。
最后一個嚴重的 bug 是設(shè)計上的。
sproto 的 encode C API 采用的是 callback 的方式。由使用者(通常是其它語言的 binding )提供一個 callback 函數(shù),C 核心根據(jù) sproto 協(xié)議,每個字段調(diào)用一次這個函數(shù)。
如果它返回 -1 表示編碼錯誤(一般是 buffer 不夠大),會讓 C 核心的編碼過程錯誤返回。
如果它返回 0 表示這個字段不存在。這是因為 sproto 是允許字段不存在的,不存在的字段不會被編碼進最終的串。另外,對數(shù)組的編碼也依賴它。如果在編碼一個數(shù)組時返回 0 ,表示數(shù)組結(jié)束。
其它情況應(yīng)返回一個正數(shù),表示當前需要編碼的對象的長度。
對于簡單類型,如 boolean ,integer ,一般返回的是固定值。boolean 返回 4 ,integer 返回 4 或 8 (提示 C 核心這個整數(shù)是 32bit 還是 64bit 的)。最終編碼不一定按這個數(shù)字來,且 callback 函數(shù)得到的寫入地址也并非最終 buffer 的地址。C 核心會提供一個地址對齊的地址,然后根據(jù) sproto 的編碼協(xié)議來轉(zhuǎn)換到最終 buffer 中,同時還要處理大小端問題。
對于不定長類型,如 string 或自定義類型。這個長度會幫助 C 核心了解應(yīng)該將 buffer 指針后移多少字節(jié)。callback 函數(shù)將直接把數(shù)據(jù)寫入最終的 buffer 。而問題就出在這里。
當 string 是一個空串時,由于空串的長度為 0 ,會讓 C 核心誤會這個字段并不存在,這導(dǎo)致所有的空串無法編碼。更嚴重的是,如果是字符串數(shù)組,碰到空串就會停止編碼這個數(shù)組。最終的修補方案是,約定在編碼 string 的時候,應(yīng)該返回字符串長度 + 1 。這屬于一個設(shè)計問題,所以除了 lua binding 之外,別的語言的 binding 也需要修改。好在目前已知的 python binding 也是我們公司的同學(xué)實現(xiàn)的,應(yīng)該馬上能改過來。
對于用戶類型,沒有 string 這個問題。即使是空的對象,也有一個數(shù)據(jù)頭。所以不可能為 0 。對于空對象,不在數(shù)組中時,目前的 lua binding 會返回 0 ,讓 C 核心跳過這個字段,而在數(shù)組中時,則會返回一個空的數(shù)據(jù)頭。
利用 sproto 實現(xiàn)的 sharemap 也被查出一個 bug ,不過這個 bug 不屬于 sproto 。它的 metatable 被不小心循環(huán)引用了,如果一個字段不存在會導(dǎo)致 lua 檢測出 metatable 循環(huán)引用而出錯。
還有一個問題是在使用 httpc 時發(fā)現(xiàn)的,雖然已經(jīng)知道,但因為用的不多也沒有特別在意。這次在正式版發(fā)布前,還是給出解決方案:
skynet 的 socket 層在處理域名的時候直接調(diào)用了系統(tǒng) api getaddrinfo ,這會阻塞住線程。由于 skynet 的 socket 是單線程的,所以一旦做域名查詢,會導(dǎo)致 skynet 所有的 socket 消息處理阻塞。一般我們不會使用域名,即使用,也是在數(shù)據(jù)庫第一次連接的時候,通常發(fā)生在 skynet 進程啟動的時候,所以影響不大。但一旦使用 httpc 模塊,就很容易向外連接一個域名了。
由于系統(tǒng)并不提供異步的域名解析方法,很多其它網(wǎng)絡(luò)庫的做法是使用額外的線程去查詢域名。我并不想針對這個需求而大幅度修改已經(jīng)穩(wěn)定了的 skynet socket 層,所以提供了獨立的解決方案:那就是在上層自己使用 dns 協(xié)議發(fā)送 udp 包查詢。為了讓 httpc 模塊可以使用它,對其也做了一點改變,允許用戶連接一個 IP 地址,而自己在 http header 里填寫 host 字段。
另外,在充當 http 客戶端時,http 服務(wù)器往往會在返回的 header 中填寫多個 Set-Cookie 字段,之前對同名的 header 中字段沒有正確的處理,現(xiàn)在做了修正。(多個同名字段會生成一個 table )
更多信息請查看IT技術(shù)專欄