包包版网络游戏大厅+桥牌系统 4.终于可以聊天了 | 您所在的位置:网站首页 › 手机桥牌游戏大厅 › 包包版网络游戏大厅+桥牌系统 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界面,此时会重新建立异步回调的循环 AsyncCallback GetMsgCallback = new AsyncCallback(GetMsg); (Client.GetStream()).BeginRead(recByte, 0, 1024, GetMsgCallback, this); 这里的GetMsg回调方法和LoginForm的GetMsg方法基本相同,唯一区别在else分支,MainForm界面在处理完接收到的数据包后,继续侦听,于是多了以下两条语句: lock (Client.GetStream()) { AsyncCallback GetStreamMsgCallback = new AsyncCallback(GetMsg); Client.GetStream().BeginRead(recByte, 0, 1024, GetStreamMsgCallback, this); } 而LoginForm的else分支在处理完数据包后,也就是得到验证结果后,不再进行异步回调。说得详细些:验证成功,就跳转到MainForm界面,LoginForm不再继续侦听;验证失败,则立刻终止侦听,直到下一次点击登录按钮,才会重新建立Socket并进行侦听。 在点击Send按钮后,将携带聊天信息的503协议封装到ChatMessage实体,序列化后发送Request到Server端。 if (txtMessage.Text.Trim() != "") { ChatMessage u = new ChatMessage(); u.Protocol = "503"; u.Message = txtMessage.Text.Trim(); SendText(SerializationFormatter.GetSerializationBytes(u)); txtMessage.Text = ""; } 而处理接收数据包的方法仍然是BuildText,这里是对504协议进行解析: case "504": //按Hall发送Client的聊天信息 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事件 public event EventHandler MessageReceived; 在BuildText方法中添加对503协议的处理 case "503": if (MessageReceived != null) { ChatMessage meg = (ChatMessage)obj; MessageEventArgs e = new MessageEventArgs(); e.Message = meg.Message; MessageReceived(this, e); } break; MainThread类 将OnMessageReceived方法附属到新添加的事件MessageReceived上: newClient.MessageReceived += OnMessageReceived; 而添加OnMessageReceived方法如下,从而把这条聊天信息转发给所有在线用户: public void OnMessageReceived(object sender, MessageEventArgs e) { //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的的操作,注意,这里只发送消息就可以,而不要等待结果——所谓异步编程的思路。让我们再来看一下点击发送按钮的方法: if (txtMessage.Text.Trim() != "") { 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范型: private Dictionary clientTable; clientTable = new Dictionary(); 也许有人会问,游戏大厅需要聊天么?是的,可以没有这个功能。我演示的目的是承上启下,介绍一下在我这个框架下如何轻松地开发新功能,制定一个套路,为下面的大厅通信打下基础。 此外,在Client端,目前还没有显示其他用户进入或离开的消息,以及显示用户列表的功能。 下一章,我要搭建游戏大厅,并对聊天功能进行改进。 |
CopyRight 2018-2019 实验室设备网 版权所有 |