golang實現的長連線服務

simplejia發表於2016-12-07

我最近分享了我用golang實現的長連線服務,希望對大家有用。

connsvr 長連線服務(http://github.com/simplejia/connsvr)

功能

  • 支援tcp自定義協議長連線
  • 支援http協議長連線(long poll機制,ajax掛上去後等待資料返回)
  • 每個使用者建立一個連線,每個連線唯一對應一個使用者,使用者可以同時加入多個房間
  • 推送資料時,可以不給房間內特定的一個使用者推資料,適用於:前端假寫資料,長連線服務幫過濾掉這條訊息
  • 接收到上行資料後,同步轉發給相應業務處理服務,可通過conf/conf.json配置pubs節點,connsvr將資料通過http方式路由到後端業務處理服務,然後透傳結果到客戶端
  • 支援定期拉取遠端伺服器配置資料
  • 根據遠端配置資訊,可給客戶端下發訊息拉取方式的指令,目前支援:
    • 推送通知,然後客戶端主動拉後端服務,適用於:對訊息一致性要求高,這種方式,可以保證使用者拉到完整的訊息列表,不會由於推送失敗而丟訊息,後端服務需要提供客戶端資料拉取的完整功能
    • 推送整條訊息,客戶端不用拉,適用於:對訊息一致性要求不高,丟一兩條沒關係,這種方式,基本只需要有connsvr就夠了,而且對connsvr的要求不高
    • 推送通知,然後客戶端來connsvr拉訊息,適用於:對訊息一致性上有要求,但允許在瞬間訊息量比較大的情況下丟掉部分老的訊息,這種方式,基本只需要有connsvr就夠了,對connsvr要求較高

實現

  • 啟用一個協程用於接收後端push資料,啟用若干個協程用於管理房間使用者,使用者被hash到對應協程
  • 每個協程需要通過管道接收資料,包括:加入房間,退出房間,推送訊息
  • 每個使用者連線啟一個讀協程
  • 無鎖

特點

  • 通訊協議足夠簡單高效
  • 服務設計儘量簡化,通用性好

協議

http長連線

** 加入房間 **
http://xxx.xxx.com/enter%3Frid ... 3Dxxx
請求引數說明:
rid: 房間號
uid: 使用者id
sid: session_id,區分同一uid不同連線,[可選]
callback: jsonp回撥函式,[可選]

返回資料說明:
[callback(][json body][)]
示例如下: cb({"body":"hello world","cmd":"2","rid":"r1","sid":"","subcmd":"0","uid":"r2"})
** 拉取訊息 **
http://xxx.xxx.com/msgs%3Frid% ... 3Dxxx
請求引數說明:
rid: 房間號
uid: 使用者id
sid: session_id,區分同一uid不同連線,[可選]
subcmd: 用於區分不同業務,有效資料:1~255之間
mid: 客戶端讀到的最後一條訊息,沒有傳空
callback: jsonp回撥函式,[可選]

返回資料說明:
[callback(][json body][)]
示例如下: cb({"body":["hello world"],"cmd":"5","rid":"r1","sid":"","subcmd":"0","uid":"r2"})

test資料夾有個ajax長輪詢示例:ajax.html,使用方式如下:

  1. 首先配置host: 127.0.0.1 connsvr.com
  2. 啟動connsvr: ./connsvr -env dev
  3. 瀏覽器裡開啟ajax.html
  4. 執行包含push訊息的測試用例:go test -env dev -v -run=TestTcp

經過上面幾步,瀏覽器內容會更新成如下:

{"body":"hello world","cmd":"99","ext":"{\"GetMsgKind\":2}","rid":"r1","sid":"0.3209966821165452","subcmd":"0","uid":"u1"}  
refresh time: 上午2:05:59

重複執行測試用例,你能看到訊息在更新(refresh time顯示的時間在變)


tcp自定義協議長連線(包括收包,回包)

Sbyte+Length+Cmd+Subcmd+UidLen+Uid+SidLen+Sid+RidLen+Rid+BodyLen+Body+ExtLen+Ext+Ebyte

Sbyte: 1個位元組,固定值:0xfa,標識資料包開始
Length: 2個位元組(網路位元組序),包括自身在內整個資料包的長度
Cmd: 1個位元組,
  * 0x01:心跳 
  * 0x02:加入房間 
  * 0x03:退出房間 
  * 0x04:上行訊息 
  * 0x05:拉取訊息列表 
  * 0xff:標識服務異常
Subcmd: 1個位元組,路由不同的後端介面,見conf/conf.json pubs和msgs節點,
  * pubs代表上行訊息配置,中轉給業務方資料示例如下:uid=u1&rid=r1&cmd=99&subcmd=0&body=hello,直接把後端返回傳回client
  * msgs代表拉訊息列表配置,中轉給業務方資料示例如下:uid=u1&rid=r1&cmd=99&subcmd=0,返回給client示例如下:["xxx", "yyy"]
UidLen: 1個位元組,代表Uid長度
Uid: 使用者id,對於app,可以是裝置id,對於瀏覽器,可以是登陸使用者id
SidLen: 1個位元組,代表Sid長度
Sid: session_id,區分同一uid不同連線,對於瀏覽器,可以是生成的隨機串,瀏覽器多視窗,多標籤需單獨生成隨機串
RidLen: 1個位元組,代表Rid長度
Rid: 房間id
BodyLen: 2個位元組(網路位元組序),代表Body長度
Body: 和業務方對接,connsvr會中轉給業務方
ExtLen: 2個位元組(網路位元組序),代表Ext長度
Ext: 擴充套件欄位,當來自於connsvr時,目前支援如下:
{    
    "GetMsgKind": 1 // 1: 推送通知,然後客戶端主動拉後端服務  2: 推送整條訊息,客戶端不用拉 3: 推送通知,然後客戶端來connsvr拉訊息   
}
Ebyte: 1個位元組,固定值:0xfb,標識資料包結束

注1:上行資料包長度,即Length大小,限制4096位元組內(可配置),下行不限
注2:當connsvr服務處理異常,比如呼叫後端服務失敗,返回給client的資料包,Cmd:0xff
注3:當Cmd為0x05時,客戶端到connsvr拉取訊息列表,當connsvr訊息為空時,connsvr為根據conf/conf.json msgs節點配置路由到後端服務拉取訊息列表

後端push協議格式(udp)

Cmd+Subcmd+UidLen+Uid+SidLen+Sid+RidLen+Rid+BodyLen+Body+ExtLen+Ext:

Cmd: 1個位元組,經由connsvr直接轉發給client
Subcmd: 1個位元組,經由connsvr直接轉發給client
UidLen: 1個位元組,代表Uid長度
Uid: 指定排除的使用者uid
SidLen: 1個位元組,代表Sid長度
Sid: 指定排除的使用者session_id,當沒有傳入Sid時,只匹配uid
RidLen: 1個位元組,代表Rid長度
Rid: 房間id
BodyLen: 2個位元組(網路位元組序),代表Body長度
Body: 和業務方對接,connsvr會中轉給client
ExtLen: 2個位元組(網路位元組序),代表Ext長度
Ext: 擴充套件欄位,目前支援如下:
{    
    "MsgId": “1234” // 標識本條訊息id      
}
注:資料包長度限制50k內

使用方法

  • 配置檔案:conf.json (json格式,支援註釋),可以通過傳入自定義的env及conf引數來重定義配置檔案裡的引數,如:./connsvr -env dev -conf='hport=80;clog.mode=1',多個引數用;分隔
  • 建議用cmonitor做程式啟動管理
  • api資料夾提供的程式碼用於後端服務給connsvr推送訊息的,實際是通過clog服務分發的
  • connsvr的上報資料,比如本機ip定期上報(用於更新待推送伺服器列表),連線數、推送用時上報,等等,這些均是通過clog服務中轉實現,所以我提供了clog的handler,均在testdata目錄裡:相應要修改clog的conf.json部分如下:
"connsvr/logbusi_report": [
    {
        "handler": "connreporthandler",
        "params": {
            "redis": {"addrtype": "ip", "addr": ":6379"}
        }
    }
],
"connsvr/logbusi_stat": [
    {
        "handler": "connstathandler",
        "params": {}
    }
],
"demo/logbusi_push": [
    {
        "handler": "connpushhandler",
        "params": {
            "redis": {"addrtype": "ip", "addr": ":6379"}
        }
    }
]

相關文章