Go語言開發分佈式聊天室 原 薦

聲明

我是一個剛學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即可


鳴謝

感謝@CFC4N牛腩五花肉對本次項目的大力支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章