循序渐进做项目系列(3):迷你QQ篇(1)——实现客户端互相聊天

时间:2021-09-30 08:54:55
《循序渐进做项目系列迷你QQ篇》将陆续介绍客户端聊天,文件传输,加好友,群聊,包括语音聊天,视频聊天,远程桌面等等需求如何实现,感兴趣的朋友可以持续关注。考虑到某些需求较为复杂,本系列采用成熟的通信框架ESFramework来做,而不是从socket做起,当然这与本人才疏学浅也有莫大的关系,如果大家不嫌弃小弟写得太“low”,还请捧个人场,顺便给予鼓励!

     言归正传,今天就是要实现一个最简单的功能:客户端互相聊天。

    循序渐进做项目系列(3):迷你QQ篇(1)——实现客户端互相聊天

一·部署通信设备

     参见 循序渐进做项目系列(1):最简单的C/S程序——让服务器来做加法

        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            IRapidPassiveEngine clientEngine = RapidEngineFactory.CreatePassiveEngine();//创建客户端通信引擎
            ClientHandler clientHandler = new ClientHandler();//创建客户端消息处理器       
            Form_Login loginForm = new Form_Login(clientEngine, clientHandler);       
            loginForm.ShowDialog();//登陆窗会话完成才能运行主窗体
            Form_Client clientForm = new Form_Client(clientEngine, loginForm.SelfUserID);
            clientHandler.ClientForm = clientForm;
            Application.Run(clientForm);
        }

二·登陆窗设计

    public partial class Form_Login : Form
    {
        private IRapidPassiveEngine clientEngine; 
       
        private  ClientHandler clientHandler;

        public string SelfUserID//本人用户ID,该属性主要是为了暴露给主窗体用来标识用户
        {           
get { return this.textBox_UserID.Text; } } public Form_Login(IRapidPassiveEngine _clientEngine, ClientHandler _clientHandler) { InitializeComponent(); //传入通信引擎和消息处理器主要是为了登陆的时候进行通信引擎初始化操作 this.clientEngine = _clientEngine; this.clientHandler = _clientHandler; } private void button_login_Click(object sender, EventArgs e) { //通信引擎初始化,与目标服务器建立TCP连接 LogonResponse logonResponse = this.clientEngine.Initialize(this.textBox_UserID.Text, this.textBox_Password.Text, "127.0.0.1", 2000, clientHandler); if (logonResponse.LogonResult == LogonResult.Succeed) { this.DialogResult = DialogResult.OK; } else { MessageBox.Show("登陆失败!" + logonResponse.LogonResult.ToString() + logonResponse.FailureCause); } } }

 三·客户端主窗体设计

    public partial class Form_Client : Form
    {
        private IRapidPassiveEngine clientEngine;

        private string selfUserID;

        public Form_Client(IRapidPassiveEngine _clientEngine,string _selfUserID)
        {
            this.clientEngine = _clientEngine;
            this.selfUserID = _selfUserID;
            this.Text = _selfUserID;//在窗体上显示本用户ID
            InitializeComponent();
        }

        private void button_Send_Click(object sender, EventArgs e)
        {
            string msg = this.textBox_Input.Text;//聊天消息
            Byte[] msgCode = Encoding.UTF8.GetBytes(msg);//转码以备发送
            string targetUserID = this.textBox_TargetUserID.Text;//从输入框读取待发送用户ID //利用通信引擎发送消息
            this.clientEngine.CustomizeOutter.Send(targetUserID, InformationType.Chat, msgCode);
            this.ShowChatMsg(this.selfUserID, msg);         
        }
        //将聊天消息按一定格式显示在界面上
        public void ShowChatMsg(string UserID, string msg)
        {
            this.richTextBox_Output.AppendText(UserID + "  " + DateTime.Now.ToString() + "\n");
            this.richTextBox_Output.AppendText(msg + "\n");
            this.richTextBox_Output.ScrollToCaret();
            this.textBox_Input.Text = "";
        }
    }

四·客户端收到消息后进行处理

 public class ClientHandler: ICustomizeHandler
    {
        private Form_Client clientForm;

        //该属性是为了在外部将clientForm注入,之所以不在构造函数中传入是因为当时clientForm尚未创建
        public Form_Client ClientForm
        {
            get { return clientForm; }
            set { clientForm = value; }
        }
       
        public void HandleInformation(string sourceUserID, int informationType, byte[] info)
        {
            if (informationType == InformationType.Chat)
           {
               if (this.clientForm.InvokeRequired) //不是主线程时
               {
                   //转换为主线程
                   this.clientForm.Invoke(new ESBasic.CbGeneric<string, int, byte[]>(this.HandleInformation), sourceUserID, informationType, info);
               }
               else
               {
                  string msg = Encoding.UTF8.GetString(info);//解析消息
                  this.clientForm.ShowChatMsg(sourceUserID, msg);//显示消息
               }
           }
        }

        public byte[] HandleQuery(string sourceUserID, int informationType, byte[] info)
        {
            throw new NotImplementedException();
        }

五·总结

    源码下载:MiniQQ1.0.zip

      由于代码中尽量做到规范命名,并做了详细的注释,所以没有再花更多的文字段落去说明,相信大家也能很容易看懂。整个逻辑十分简单,对于新手有两点需要注意一下:

1.各个类之间通过参数传递从而完成合作,传参方式有的是通过构造函数,有的是通过属性注入,新手朋友们可以自己比较一下两种方式的适用场合。

2.其中有一处重构,就是聊天的时候既要显示自己发的消息又要显示收到的消息,这时候代码基本上是一样的,所以我将其重构了。至于什么时候需要重构,如何重构,新手朋友们可以自己多多揣摩。

      这两点都是平时做项目中经常遇到的问题,是需要熟练掌握的。

      当然,最重要的一点还是循序渐进做项目咯!做的多了这些东西自然就会熟练了,写程序时就能够下笔如有神了!