聲明
我是一個剛學go語言的菜鳥,還沒有資格談論什麼技術分享,只是爲了展示fooking的實際應用,同時把我用go寫的聊天室代碼貼出來供大家消遣,如果有入不了各位法眼的代碼,望輕噴。該聊天室基於fooking,而業務代碼是採用Go + Fastcgi。
完整的源代碼在 https://github.com/scgywx/fooking/blob/master/example/chat/gateway.go,全代碼200多行,去掉router部分代碼,實際邏輯代碼只有170來行,邏輯簡單,功能強大。
詳解
聊天服務器的入口main函數裏有3個IP和端口配置,分別是Chat服務器、Router服務器和Redis服務器。
func main() {
listener, _ := net.Listen("tcp", "0.0.0.0:9001")//Chat服務器配置
srv := newChatServer("test:9010", "test:6379");//Router配置與Redis配置
fcgi.Serve(listener, srv)
}
Chat服務器就是實現主要的聊天邏輯,Router服務器是用於轉發消息,Redis用來存儲用戶信息。上面我講過這個聊天室是基於fooking,所以客戶端不是直接與go通信,而是透過fooking來訪問的。我們使用go內置的fastcgi模塊來創建一個服務,然後處理請求即可,這跟http服務器非常像,只是協議規範不同而已,看下面的代碼就你知道創建一個fastcgi服務器有多簡單了。
func (s *ChatServer) ServeHTTP(rep http.ResponseWriter, req *http.Request) {
//短連接要調用一下這個,否則go不會主動斷開連接
//req.ParseForm();
req.Form = make(url.Values);
body, e := ioutil.ReadAll(req.Body)
if e != nil {
fmt.Printf("read request error\n")
}else{
sessionid := req.Header.Get("SESSIONID")
event := req.Header.Get("EVENT")
fmt.Printf("sid=%s, event=%s\n", sessionid, event);
//具體的業務邏輯處理
}
}
代碼中的sessionid就是當前發送請求的客戶端ID,當我們要做一些uid與客戶端id映射或者是要發消息給指定用戶的時候就可以使用這個ID。event就是當前請求的事件類型(0-表示請求,1-新連接,2-關閉連接),而聊天室只需要關心客戶端請求和斷開事件。請求可能是登陸、發消息或者是加入頻道,而斷開事件我們就需要把用戶信息刪除,並且把他的退出信息廣播給所有在聊天室的人。代碼如下:
switch event {
case "1"://新連接
//TODO
case "2"://連接關閉
s.logout(sessionid)
default://消息處理
if len(body) > 0 {
js, err := simplejson.NewJson(body)
if err != nil {
fmt.Printf("parse JSON error, data=")
fmt.Println(body)
}else{
r := s.handle(sessionid, js)
if len(r) > 0 {
rep.Header().Add("Content-Length", strconv.Itoa(len(r)))
rep.Write(r)
}else{
fmt.Println("no message response")
}
}
}
}
func (s *ChatServer) handle(sid string, req *simplejson.Json) []byte{
t, _ := req.Get("type").String()
switch t {
case "login": //登陸
.....
case "join": //加入房間
....
case "msg": //發送消息
.....
default:
fmt.Printf("invalid type")
}
return []byte("")
}
在上面的消息處理部分,有兩句rep.Header().Add和rep.Write,這表示如果需要返回數據給當前發請求的客戶端,可以添加Content-Length頭(表示要返回給客戶端的數據長度),然後調用rep.Write發送數據(類似Http的Request對應一個Response)。
代碼裏面的消息廣播、用戶分頻道都是在Router部分實現,他是fooking消息轉發與用戶數據維持的中間件。
爲什麼使用FastCGI
其實開發一個聊天室可以很簡單,可以直接使用websocket協議,讓客戶端跟服務器直接通信,簡單方便,通信代價低。並且做socket服務也不受協議限制,完全可以自定義或者是使用其它輕量級的協議,比如mqtt什麼的。然而爲什麼要使用fastcgi呢?主要是他的協議實現簡單,並且擴展性也非常強,目前世界上最強大的語言php的fpm就是使用該協議與nginx進行通信,如果你的服務使用fastcgi協議開發,那麼你可以完美的使用nginx與你的服務進行通信,這樣做的好處是寫socket服務能像寫web服務一樣調試,開發完一個功能你只需要簡單的執行如下命令即可(當然你可能需要添加少量的代碼來判斷來源是從nginx還是你自己的客戶端)。
curl -d '{"type":"login","name":"xxx"}' 'http://fooking/gateway.php?SESSIONID=aaa&EVENT=0'
網關與邏輯分離另一個好處是,當業務邏輯代碼需要更新,客戶端毫無察覺的,真正做到無痛更新。另外fastcgi協議本身已經支持多路複用(當然這個需要服務端的支持),這個功能可是大名鼎鼎的http到2.0才支持的喲。
協議詳細說明請見:http://www.fastcgi.com/drupal/node/6?q=node/22
爲什麼使用Fooking
文章開篇已經說了,我是一個Go語言的初學者,到這裏來不是爲了秀go技,而是爲了展示fooking與各語言的銜接。那麼爲什麼要使用fooking?我相信很多人都知道網關這個東西(可能在遊戲領域應用的更廣泛一些),他其主要的目的是用於承載客戶端的連接,把消息轉發與業務邏輯分開,後端開發人員只需要專心寫邏輯即可。這就好比我們寫web的時候,從來不需要自己去實現http server。那麼fooking也是這樣一個開源軟件,他將socket服務變的更簡單。更多的特性如下:
1 動態網關添加.
2 每個客戶端唯一SessionID.
3 組播(類似redis的pub/sub).
4 服務器狀態監控.
5 客戶端事件通知(如:新連接、關閉連接).
6 後端無語言限制(php, python, go, nodejs, etc...).
7 自定義消息協議.
8 後端長連接維持.
fooking的詳細介紹請參見: https://github.com/scgywx/fooking 或者 http://git.oschina.net/scgywx/fooking
使用方法
第一步(下載和編譯)
git clone https://github.com/scgywx/fooking.git
cd {$FOOKING_PATH}
make第二步(啓動Router)
cd src
./fooking ../router.lua第三步(啓動Gateway)
./fooking ../config.lua第四步(啓動Chat服務器)
go run gateway.go
第五步(測試) 修改example/chat/index.html文件的Websocket的服務器IP和端口(查找ws://)
然後用瀏覽器打開index.html即可