包包版网络游戏大厅+桥牌系统 4.终于可以聊天了

返回目录

 

     有了上一章所搭建的网络通信框架,我们就可以自由发挥了。只要把握好HandShake的顺序,就可以了。比如说我下面要介绍的大厅里的聊天机制,就是通过实现了503504协议的“有问必答”原理。

     重构后的版本,代码在这里下载:PlayCard 2.2

     聊天室的截图如下,以下是一个Server端和两个Client端(注意到,kitty是最后登录的,所以看不到先前的聊天信息):

     

     详细介绍如下:     

     这里,503协议是Client端发送的聊天信息(Request),504协议是Server端将接受到的聊天信息转发(Response)给所有Client端(包括发送聊天信息的Client端,也就是说,即使是自己发送的消息,也要等ServerResponse后才能显示)。

     :在这套源码的自定义协议中,单数协议为Client端发送的Request,双数协议为Server端发送的Response。

     在CommonClassLibrary类库,添加503504协议的实体类ChatMessage,其中包括发送用户名UserName和发送消息Message两个属性:

Code

 

     Client端修改:

     登陆成功后,进入MainForm界面,此时会重新建立异步回调的循环

            AsyncCallback GetMsgCallback = new AsyncCallback(GetMsg);
            (Client.GetStream()).BeginRead(recByte, 
01024, GetMsgCallback, this);

     这里的GetMsg回调方法和LoginFormGetMsg方法基本相同,唯一区别在else分支,MainForm界面在处理完接收到的数据包后,继续侦听,于是多了以下两条语句:

                lock (Client.GetStream())
                {
                    AsyncCallback GetStreamMsgCallback 
= new AsyncCallback(GetMsg);
                    Client.GetStream().BeginRead(recByte, 
01024, GetStreamMsgCallback, this);
                }

     而LoginFormelse分支在处理完数据包后,也就是得到验证结果后,不再进行异步回调。说得详细些:验证成功,就跳转到MainForm界面,LoginForm不再继续侦听;验证失败,则立刻终止侦听,直到下一次点击登录按钮,才会重新建立Socket并进行侦听。

     在点击Send按钮后,将携带聊天信息的503协议封装到ChatMessage实体,序列化后发送RequestServer端。

            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类的方法:

Code

      Client

              添加MessageReceived事件

public event EventHandler<MessageEventArgs> 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端永远是被动的接收来自ClientRequest请求——一个单数协议,在BuildText方法中对其进行解析后,触发主线程的相应事件,于是在相应的方法中,发送偶数协议,也就是Response。这里,可能是发给原先RequestClient端(如502协议登录验证结果),也可能是群发给其他Client端(如504协议转发聊天信息)

       4.无论Server端还是Client端,都是在BuildText方法中对接收到的数据包进行反序列化,然后根据协议的不同进行不同的处理。以后我们每次添加新协议,都要这里加上case分支语句,注意到Server端处理单数协议,Client处理偶数协议。

       相应的,在这些处理模块中,要进行方法回调,从而操作主线程或UI。这里,Server端使用了事件机制;而Client端使用了委托回调机制。

 

     补充:小赵指出我使用了HashTable这个老古董存储client对象不是很好,于是我将其改造为Dictionary<KValue, TValue>范型:

        private Dictionary<string, Client> clientTable;
        clientTable 
= new Dictionary<string, Client>();

     也许有人会问,游戏大厅需要聊天么?是的,可以没有这个功能。我演示的目的是承上启下,介绍一下在我这个框架下如何轻松地开发新功能,制定一个套路,为下面的大厅通信打下基础。

     此外,在Client端,目前还没有显示其他用户进入或离开的消息,以及显示用户列表的功能。

 

     下一章,我要搭建游戏大厅,并对聊天功能进行改进。

 

posted @ 2008-07-31 17:51  包建强  Views(4483)  Comments(34Edit  收藏  举报