及時通訊最佳實踐

描述

Lhttp是一個基於websocket服務端框架,提供一個相似http的協議去幫助開發者開發長鏈接的應用。javascript

使用Lhttp能夠大量減小服務端開發的工做量,實現很是好的模塊化和業務功能的解耦合。前端

能夠定製任何你想要的功能。java

項目地址git

特色

  • 使用簡單,功能強大github

  • 性能高,使用gnatsd消息隊列 publish 10000 條消息耗時0.04s(single-core CPU,1G memory).web

  • 支持集羣,橫向擴展,經過增長服務器來獲取更高的服務能力redis

  • 很是容器進行定製與擴展docker

  • 能夠很是好的與http服務協同工做,如利用http發送消息,將消息轉發給上游http服務器等。因此即使你不會go語言也能夠開發一些應用。json

聊天室demo

前端sdk後端

圖片描述

協議棧:

+--------------------+
|       lhttp        |
+--------------------+
|     websocket      |
+--------------------+
|        TCP         |
+--------------------+

系統架構

+---------------------------------------+
        |    message center cluster (gnatsd)    |
        +---------------------------------------+
 ........|.................|...............|..................
| +-------------+   +-------------+   +-------------+        | 
| |lhttp server |   |lhttp server |   |lhttp server |   ...  |  lhttp 服務集羣
| +-------------+   +-------------+   +-------------+        | 
 .....|..........._____|  |___.............|  |_________......
      |          |            |            |            |       <----使用websocket連接
 +--------+  +--------+   +--------+   +--------+   +--------+   
 | client |  | client |   | client |   | client |   | client |   
 +--------+  +--------+   +--------+   +--------+   +--------+

快速入門

go get github.com/nats-io/nats
go get github.com/fanux/lhttp

先啓動gnatsd:

cd bin
./gnatsd &
./lhttpd

打開另外一個終端,執行客戶端程序,輸入命令碼:

cd bin
./lhttpClient

使用docker快速體驗

$ docker build -t lhttp:latest .
$ docker run -p 9090:9090 -p 8081:8081 lhttp:latest

打開瀏覽器,訪問: http://localhost:9090.

打開兩個窗口就能夠聊起來了。

websocket 端口是 8081, 可使用本身的websocket客戶端去連 ws://localhost:8081

也能夠從dockerhub上下載鏡像:

$ docker run -p 9090:9090 -p 8081:8081 fanux/lhttp:latest

協議介紹

LHTTP/1.0 Command\r\n                --------起始行,協議名和版本,Command:很是重要,標識這條消息的命令碼是什麼,服務端也是根據命令碼註冊對應的處理器的。
Header1:value\r\n                    --------首部
Header2:value\r\n
\r\n
body                                 --------消息體

事例:

LHTTP/1.0 chat\r\n                  命令碼叫`chat`
content-type:json\r\n               消息體使用json編碼
publish:channel_jack\r\n            服務端請把這條消息publish給jack (jack訂閱了channel_jack)
\r\n
{
    to:jack,
    from:mike,
    message:hello jack,
    time:1990-1210 5:30:48
}

使用教程,只需三步

定義你的處理器,須要聚合 BaseProcessor

type ChatProcessor struct {
    *lhttp.BaseProcessor
}

實現三個接口,鏈接打開時幹嗎,關閉時幹嗎,消息到來時幹嗎。

type ChatProcessor struct {
}
func (p ChatProcessor)OnOpen(h *WsHandler) {
    //your logic
}
func (p ChatProcessor)OnClose(h *WsHandler) {
    //your logic
}
func (p ChatProcessor)OnMessage(h *WsHandler) {
    //your logic
}

註冊你的處理器,這裏的chat 與消息體中的chat對應,也就是這個處理器僅會處理LHTTP/1.0 chat\r\n....這類消息.

lhttp.Regist("chat",&ChatProcessor{&lhttp.BaseProcessor{}})

若是命令碼是 "chat" ChatProcessor 會處理它。

這裏好比收到消息就直接將消息返回:

func (p *ChatProcessor)OnMessage(h *WsHandler) {
    h.Send(h.GetBody())
}

啓動服務器

http.Handler("/echo",lhttp.Handler(lhttp.StartServer))
http.ListenAndServe(":8081")

一個完整的回射例子:

type ChatProcessor struct {
    *lhttp.BaseProcessor
}

func (p *ChatProcessor) OnMessage (h *lhttp.WsHandler) {
    log.Print("on message :", h.GetBody())
    h.Send(h.GetBody())
}

func main(){
    lhttp.Regist("chat", &ChatProcessor{&lhttp.BaseProcessor{}})

    http.Handle("/echo",lhttp.Handler(lhttp.StartServer))
    http.ListenAndServe(":8081",nil)
}

訂閱/發佈

下面來看用Lhttp開發及時通訊應用有多簡單

假設有兩個客戶端,這裏的客戶端好比瀏覽器應用。

client1:

LHTTP/1.0 command\r\n
subscribe:channelID\r\n
\r\n
body optional

client1經過websocket向Lhttp發送如上字符串,就訂閱了channelId

client2:

LHTTP/1.0 command\r\n
publish:channelID\r\n
\r\n
body require

client2經過websocket向Lhttp發送如上字符串,就向channelID發佈了一條消息。 由於client1訂閱了channelID,因此client1會收到這條消息。

client1不想再收消息,那麼發以下字符串給服務端便可:

LHTTP/1.0 command\r\n
unsubscribe:channelID\r\n
\r\n
body optional

訂閱/發佈 是lhttp內置功能,服務端一行代碼不用寫便可獲取這種服務,只須要使用特定首部subscribe,publishunsubscribe

同時訂閱多個,如同時訂閱多個聊天室。

LHTTP/1.0 chat\r\n
subscribe:channelID1 channelID2 channelID3\r\n
\r\n

使用http發佈消息

URL: /publish .
方法: POST .
http body: 整個lhttp消息
for example I want send a message to who subscribe channel_test by HTTP.
如我想發送一條消息給訂閱了channel_test的人。

resp,err := http.POST("https://www.yourserver.com/publish", "text/plain",
    "LHTTP/1.0 chat\r\npublish:channel_test\r\n\r\nhello channel_test guys!")

這裏封裝好了一個更好用的工具 Publish tools.go

//func Publish(channelID []string, command string, header map[string]string, body string) (err error) {
//}
//send message to who subscribe mike.

Publish("mike", "yourCommand", nil, "hello mike!")

上游服務器

upstream首部可讓lhttp向上遊的http服務器發送一條消息。

LHTTP/1.0 command\r\n
upstream:POST http://www.xxx.com\r\n
\r\n
body

若是是POST方法,lhttp會把整個消息體看成http的body發送給 http://www.xxx.com
若是是GET,lhttp會忽略消息體

LHTTP/1.0 command\r\n
upstream:GET http://www.xxx.com?user=user_a&age=26\r\n
\r\n
body

upstream有什麼用:

如咱們不想改動lhttp的代碼,可是想存儲聊天記錄。

經過upstream能夠實現很好的解耦:

而且http server能夠用其它語言實現.

+----+                  +----+
        |jack|                  |mike|
        +----+                  +----+
         |_____________    _______|
                       |  |
                   +------------+
                   |lhttp server|
                   +------------+
                         |(http request with chat record)
                         V
                   +------------+
                   | http server|  upstream server(http://www.xxx.com/record)
                   +------------+
                   (save chat record)

jack:

LHTTP/1.0 chat\r\n
upstream:POST http://www.xxx.com/record\r\n
publish:channel_mike\r\n
\r\n
hello mike,I am jack

mike:

LHTTP/1.0 chat\r\n
subscribe:channel_mike\r\n
\r\n

這樣jack publish消息時不只mike能夠收到,後端的upstream server也能夠收到,咱們能夠在後端服務器中處理消息存儲的邏輯,如將消息

存儲到redis的有序集合中。

分塊消息

試想一下,一條消息中既有圖片也有文字還有語音怎麼辦? lhttp的multipart首部解決這個問題

LHTTP/1.0 upload\r\n
multipart:0 56\r\n
\r\n
content-type:text/json\r\n
\r\n
{filename:file.txt,fileLen:5}
content-type:text/plain\r\n
\r\n
hello
content-type:text/json\r\n\r\n{filename:file.txt,fileLen:5}content-type:text/plain\r\n\r\nhello
^                                                          ^
|<---------------------first part------------------------->|<---------second part------------>|
0                                                          56

http中是使用boundry實現的,lhttp使用偏移量標識分塊,這樣效率更高,不須要遍歷整個消息體。

如何獲取分塊消息

如客戶端消息以下:

LHTTP/1.0 upload\r\nmultipart:0 14\r\n\r\nk1:v1\r\n\r\nbody1k2:v2\r\n\r\nbody2

服務端代碼,消息存在鏈表中:

type UploadProcessor struct {
    *lhttp.BaseProcessor
}

func (*UploadProcessor) OnMessage(ws *lhttp.WsHandler) {
    for m := ws.GetMultipart(); m != nil; m = m.GetNext() {
        log.Print("multibody:", m.GetBody(), " headers:", m.GetHeaders())
    }
}

//don't forget to tegist your command processor

lhttp.Regist("upload", &UploadProcessor{&lhttp.BaseProcessor{}})

首部過濾模塊開發

相關文章
相關標籤/搜索