client: c#+protobuf, server: golang+protobuf

bccber發表於2020-10-13

前段時間看到一篇博文《可在廣域網部署執行的即時通訊系統 -- GGTalk總覽(附原始碼下載)》,他是用C#實現的即時通訊系統,功能強大,介面漂亮。 就想用golang重寫服務端,把程式碼下載回來,發現通訊框架用的是ESFramework,我沒用過也不知道ESFramework的協議,重寫是不行的了,只能把原作者的客戶端介面扣出來,自己寫一個,客戶端是C#+protobuf, 服務端是golang+protobuf,動起手來才發現,功能細節實在太多了,沒精力搞下去了,就權當protobuf的學習例子吧。

一、協議

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

二、客戶端

客戶端是使用c# + protobuf開發,只是簡單的實現了一個TCPClient類,其它的大多是介面上的操作了:

TCPClien:

/// <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:

協議解析類,通過命令號把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類用於修改按鈕的狀態

三、服務端

服務端使用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{}應答客戶端的請求。

原始碼:https://github.com/bccber/gogotalk

相關文章