前段時間看到一篇博文《可在廣域網部署運行的即時通信系統 -- GGTalk總覽(附源碼下載)》,他是用C#實現的即時通信系統,功能強大,界面漂亮。 就想用golang重寫服務端,把代碼下載回來,發現通訊框架用的是ESFramework,我沒用過也不知道ESFramework的協議,重寫是不行的了,只能把原做者的客戶端界面扣出來,本身寫一個,客戶端是C#+protobuf, 服務端是golang+protobuf,動起手來才發現,功能細節實在太多了,沒精力搞下去了,就權當protobuf的學習例子吧。html
1、協議git
4個字節的長度 + 4個字節的包長 + protobuf數據包 // 登陸的請求和響應 LoginRequestCMD uint32 = 1 LoginResponseCMD uint32 = 2 // 註冊用戶的請求和響應 RegisterRequestCMD uint32 = 3 RegisterResponseCMD uint32 = 4 // 獲取朋友列表的請求和響應 GetFriendsRequestCMD uint32 = 5 GetFriendsResponseCMD uint32 = 6 // 用戶下線請求 和 通知 UserOfflineRequestCMD uint32 = 7 UserOfflineNoticeCMD uint32 = 8 // 用戶改變狀態請求 和 通知 ChangeStatusRequestCMD uint32 = 9 ChangeStatusNoticeCMD uint32 = 10 // 修改用戶資料請求 和 響應 UpdateUserInfoRequestCMD uint32 = 11 UpdateUserInfoResponseCMD uint32 = 12 // 好友聊天請求 和 通知 FriendChatRequestCMD uint32 = 13 FriendChatNoticeCMD uint32 = 14 // 獲取用戶列表請求 和 響應 GetUserListRequestCMD uint32 = 15 GetUserListResponseCMD uint32 = 16 // 增長好友請求 和 響應 AddFriendsRequestCMD uint32 = 17 AddFriendsResponseCMD uint32 = 18 // 心跳請求 和 響應 HeartbeatRequestCMD uint32 = 19 HeartbeatResponseCMD uint32 = 20 // 上傳文件請求 和 響應 UploadFileRequestCMD uint32 = 21 UploadFileResponseCMD uint32 = 22 // 獲取文件列表請求 和 響應 GetFileListRequestCMD uint32 = 23 GetFileListResponseCMD uint32 = 24
2、客戶端github
客戶端是使用c# + protobuf開發,只是簡單的實現了一個TCPClient類,其它的大可能是界面上的操做了:
TCPClien:golang
/// <summary> /// 發送protobuf對象 /// </summary> /// <param name="cmd">命令號</param> /// <param name="sendMessage">protobuf對象</param> /// <returns>已發送長度</returns> public Int32 SendProtoMessage(Int32 cmd, IMessage message) { } 組裝數據包,發送 /// <summary> /// 接收消息線程 /// </summary> /// <param name="state"></param> private void ReceiveThread(Object state) { } 啓動一個線程接收數據,把接收到的數據扔到隊列裏,再由DealQueueThread線程處理 在客戶端其實有一些場景並不合適使用異步模式,好比 登陸命令,註冊命令,使用同步模式處理會更方便 /// <summary> /// 處理消息線程 /// </summary> /// <param name="state"></param> private void DealQueueThread(Object state) { } 消息隊列處理線程,把接收到的消息用 ProtocolDecoder.Singleton.Decode 解包回調
ProtocolDecoder:c#
協議解析類,經過命令號把byte[]轉成相應的protobuf對象,並根據命令號回調Action,最終回調給界面 拿LoginRequest來舉例: 首先增長一個Login.proto文件,裏面有兩個協議結構,LoginRequest用於請求,LoginResponse用於響應, 用protoc --csharp_out=. Login.proto編譯成c#類文件,build.bat有編譯的命令 message LoginRequest { string UserID = 1; // 登錄的賬號 string Password = 2; // 密碼 int32 Status = 3; // 狀態 } message LoginResponse { Result Result = 1; // 返回值 UserInfo User = 2; // 用戶信息 } 在ProtocolDecoder類裏增長一個回調事件:public Action<LoginResponse> OnLoginResponse; 在GetMessage 和 OnEvent方法裏增長相關的代碼 在LoginForm_Load裏註冊事件回調方法ProtocolDecoder.Singleton.OnLoginResponse += OnLoginResponse; 最後,使用TCPClient.Singleton.SendProtoMessage(CMD.LoginRequestCMD, request);發送登陸命令後,會回調到OnLoginResponse方法。 注意一點哈:登陸方法其實使用同步模式更加方便合理,我這裏是使用異步,因此要增長一個BaseTask類用於修改按鈕的狀態
3、服務端框架
服務端使用golang + protobuf開發,通信組件使用 [zinx](http://github.com/aceld/zinx), zinx內部已經實現了 4個字節的長度 + 4個字節的包長 + 數據包 的協議 只要把命令號和處理對象綁定,並實現Handle方法就成了,其餘的就是業務代碼了
// 註冊路由 server.AddRouter(models.LoginRequestCMD, &network.LoginRequest{}) server.AddRouter(models.RegisterRequestCMD, &network.RegisterRequest{}) server.AddRouter(models.GetFriendsRequestCMD, &network.GetFriendsRequest{}) server.AddRouter(models.UserOfflineRequestCMD, &network.LogoutRequest{}) server.AddRouter(models.ChangeStatusRequestCMD, &network.ChangeStatusRequest{}) server.AddRouter(models.UpdateUserInfoRequestCMD, &network.UpdateUserInfoRequest{}) server.AddRouter(models.FriendChatRequestCMD, &network.FriendChatRequest{}) server.AddRouter(models.GetUserListRequestCMD, &network.GetUserListRequest{}) server.AddRouter(models.AddFriendsRequestCMD, &network.AddFriendsRequest{}) server.AddRouter(models.HeartbeatRequestCMD, &network.HeartbeatRequest{}) server.AddRouter(models.UploadFileRequestCMD, &network.UploadFileRequest{}) server.AddRouter(models.GetFileListRequestCMD, &network.GetFileListRequest{})
客戶端發送LoginRequest到服務後,服務端解析出 LoginRequestCMD 命令號,響應到network.LoginRequest{}裏,在network.LoginRequest{}的Handle()裏進行業務處理,最後使用LoginResponseCMD 和 pb.LoginResponse{}應答客戶端的請求。