包包版网络游戏大厅+桥牌系统 4.终于可以聊天了
有了上一章所搭建的网络通信框架,我们就可以自由发挥了。只要把握好HandShake的顺序,就可以了。比如说我下面要介绍的大厅里的聊天机制,就是通过实现了503和504协议的“有问必答”原理。
重构后的版本,代码在这里下载:PlayCard 2.2
聊天室的截图如下,以下是一个Server端和两个Client端(注意到,kitty是最后登录的,所以看不到先前的聊天信息):
详细介绍如下:
这里,503协议是Client端发送的聊天信息(Request),504协议是Server端将接受到的聊天信息转发(Response)给所有Client端(包括发送聊天信息的Client端,也就是说,即使是自己发送的消息,也要等Server端Response后才能显示)。
注:在这套源码的自定义协议中,单数协议为Client端发送的Request,双数协议为Server端发送的Response。
在CommonClassLibrary类库,添加503和504协议的实体类ChatMessage,其中包括发送用户名UserName和发送消息Message两个属性:

Client端修改:
登陆成功后,进入MainForm界面,此时会重新建立异步回调的循环
(Client.GetStream()).BeginRead(recByte, 0, 1024, GetMsgCallback, this);
这里的GetMsg回调方法和LoginForm的GetMsg方法基本相同,唯一区别在else分支,MainForm界面在处理完接收到的数据包后,继续侦听,于是多了以下两条语句:
{
AsyncCallback GetStreamMsgCallback = new AsyncCallback(GetMsg);
Client.GetStream().BeginRead(recByte, 0, 1024, GetStreamMsgCallback, this);
}
而LoginForm的else分支在处理完数据包后,也就是得到验证结果后,不再进行异步回调。说得详细些:验证成功,就跳转到MainForm界面,LoginForm不再继续侦听;验证失败,则立刻终止侦听,直到下一次点击登录按钮,才会重新建立Socket并进行侦听。
在点击Send按钮后,将携带聊天信息的503协议封装到ChatMessage实体,序列化后发送Request到Server端。
{
ChatMessage u = new ChatMessage();
u.Protocol = "503";
u.Message = txtMessage.Text.Trim();
SendText(SerializationFormatter.GetSerializationBytes(u));
txtMessage.Text = "";
}
而处理接收数据包的方法仍然是BuildText,这里是对504协议进行解析:
ChatMessage chat = (ChatMessage)obj;
string message = chat.UserName + " : " + chat.Message + ""r"n";
this.Invoke(new DisplayMessage(DisplayText), message);
break;
BuildText方法所在线程不属于MainForm窗体主线程,但凡是有多线程编程经验的都知道,BuildText方法是不可以直接操作MainForm的控件(DisplayText方法),只能使用this.Invoke技术回调DisplayText的方法指针,正如上面代码所示。
Server端修改
为消息事件添加MessageEventArgs类,将Client接收到的消息封装到MessageEventArgs参数后传递给MainThread类的方法:

Client类
添加MessageReceived事件
在BuildText方法中添加对503协议的处理
if (MessageReceived != null)
{
ChatMessage meg = (ChatMessage)obj;
MessageEventArgs e = new MessageEventArgs();
e.Message = meg.Message;
MessageReceived(this, e);
}
break;
MainThread类
将OnMessageReceived方法附属到新添加的事件MessageReceived上:
而添加OnMessageReceived方法如下,从而把这条聊天信息转发给所有在线用户:
{
//Message sender client
Client temp = (Client)sender;
AddLog(temp.UserName + " :" + e.Message);
ChatMessage chat = new ChatMessage();
chat.Protocol = "504";
chat.UserName = temp.UserName;
chat.Message = e.Message;
byte[] message = SerializationFormatter.GetSerializationBytes(chat);
Client tempClient;
DataTable dt = ClientList.Instance().GetUserList();
foreach (DataRow row in dt.Rows)
{
string uid = (string)row["UserID"];
tempClient = (Client)clientTable[uid];
tempClient.Send(message);
}
}
以上是Client端和Server端的修改,归结出“程咬金三板斧”,以后每次添加新协议都如法炮制:
1.携带新信息的协议,就在CommonClassLibrary类库添加相应的实体类,派生于CommonProtocol基类。
2.永远是Client端先发Request请求,也就是一个单数协议,如501(登录)、503(发聊天消息),以后还会有很多。这是一个主动的动作,来自UI的的操作,注意,这里只发送消息就可以,而不要等待结果——所谓异步编程的思路。让我们再来看一下点击发送按钮的方法:
{
ChatMessage u = new ChatMessage();
u.Protocol = "503";
u.Message = txtMessage.Text.Trim();
SendText(SerializationFormatter.GetSerializationBytes(u));
txtMessage.Text = "";
}
3.Server端永远是被动的接收来自Client的Request请求——一个单数协议,在BuildText方法中对其进行解析后,触发主线程的相应事件,于是在相应的方法中,发送偶数协议,也就是Response。这里,可能是发给原先Request的Client端(如502协议登录验证结果),也可能是群发给其他Client端(如504协议转发聊天信息)
4.无论Server端还是Client端,都是在BuildText方法中对接收到的数据包进行反序列化,然后根据协议的不同进行不同的处理。以后我们每次添加新协议,都要这里加上case分支语句,注意到Server端处理单数协议,Client处理偶数协议。
相应的,在这些处理模块中,要进行方法回调,从而操作主线程或UI。这里,Server端使用了事件机制;而Client端使用了委托回调机制。
补充:小赵指出我使用了HashTable这个老古董存储client对象不是很好,于是我将其改造为Dictionary<KValue, TValue>范型:
clientTable = new Dictionary<string, Client>();
也许有人会问,游戏大厅需要聊天么?是的,可以没有这个功能。我演示的目的是承上启下,介绍一下在我这个框架下如何轻松地开发新功能,制定一个套路,为下面的大厅通信打下基础。
此外,在Client端,目前还没有显示其他用户进入或离开的消息,以及显示用户列表的功能。
下一章,我要搭建游戏大厅,并对聊天功能进行改进。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架