基于TCP异步的聊天室程序

独自空忆成欢 提交于 2019-11-29 01:20:13

话说这个学期我们有一门课叫“中间件”,老师叫我们做一个基于TCP的聊天程序,主要结构如图

1.所有Client端需要与Server端连接(感觉这句话好白痴,TCP肯定要连接了才能工作)

2.Client端的功能是可以群发和私聊(用过QQ都应该知道什么是群发和私聊吧),但都必须经过Server端中转,也就是实现了类似通讯中间件的功能。

PS:开始写之前我是对网络编程这块完全没有认识的,上网找了几个TCP的程序,都是只能实现群发功能,或者只能实现client与server之间相互发的功能,

还没有哪个是可以实现上面所说的功能的程序的(如果有的请留言给我,我去下一个下来学习一下,O(∩_∩)O谢谢)。

实现方法有好多,用Socket类可以实现,用 TcpClient类和TcpListener类也可以实现,我就选择了后者,因为比较简单。

下面就列一下我用到的技术:

多线程,异步回调,委托,设计模式的观察者模式…………

先让大家看一下客户端和服务器端的界面先吧(本人不会做界面,而且界面上有很多Label是用来检查接收的情况,请大家选择性过滤掉)

server端的

client端的

1.Server端先启动服务,新建一个线程,绑定一个套接字,之后监听

2.Client端点击连接之后,就会与Server端建立连接。

3.每当有一个Client加入Server时,Server都会通知所有的Client更新用户列表(观察者模式)

4.点私聊和用户之后,就可以私聊;点群发,就发个所有用户。

代码解说,先看看解决方案

 

先说一下client端

声明变量
System.Collections.ArrayList clientlist = new System.Collections.ArrayList();        private bool isExit = false;        private delegate void SetListBoxCallBack(string str);        private SetListBoxCallBack setlistboxcallback;        private delegate void SetTextBoxReceiveCallBack(string str);        private SetTextBoxReceiveCallBack settextboxreceivecallback;        private delegate void SetComboBoxCallBack(string str);        private SetComboBoxCallBack setcomboboxcallback;        private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite);        private RemoveComboBoxItemsCallBack removecomboboxcallback;        private TcpClient client;        private NetworkStream ns;        private ManualResetEvent allDone = new ManualResetEvent(false);
构造函数
public ClientMain()        {            InitializeComponent();            setlistboxcallback = new SetListBoxCallBack(SetListBox);   //注册listbox回调函数            settextboxreceivecallback = new SetTextBoxReceiveCallBack(SetTextBoxReceive);  ////注册textbox回调函数            setcomboboxcallback = new SetComboBoxCallBack(SetComboBox);  ////注册combobox回调函数            removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems);        }    //注册combobox删除的回调函数
连接按钮
        //连接按钮事件        private void btn_Connect_Click(object sender, EventArgs e)        {            client = new TcpClient(AddressFamily.InterNetwork);            IPAddress serverip = IPAddress.Parse(txt_Server.Text);//采用的是异步方式            AsyncCallback receiveCallBack = new AsyncCallback(ReceiveCallBack);            allDone.Reset();            try            {//开始连接                client.BeginConnect(serverip, Convert.ToInt32(txt_Port.Text),receiveCallBack,client);                txt_ReceiveMsg.Invoke(settextboxreceivecallback, string.Format("本机终结点:{0}", client.Client.LocalEndPoint));                txt_ReceiveMsg.Invoke(settextboxreceivecallback, "开始与服务器连接");                allDone.WaitOne();                btn_Connect.Enabled = false;            }            catch            {                MessageBox.Show("登录服务器失败,请确认服务器是否正常工作!");            }        }
接收和回调
//接收回调函数        private void ReceiveCallBack(IAsyncResult iar)        {            allDone.Set();            try            {                client = (TcpClient)iar.AsyncState;                client.EndConnect(iar);                txt_ReceiveMsg.Invoke(settextboxreceivecallback, string.Format("与服务器{0}连接成功", client.Client.RemoteEndPoint));                ns = client.GetStream();                DataRead dataRead = new DataRead(ns, client.ReceiveBufferSize);                ns.BeginRead(dataRead.msg, 0, dataRead.msg.Length, ReadCallBack, dataRead);            }            catch            {                MessageBox.Show("已经与服务器断开连接!");                this.Close();            }        }        //读取回调函数        private void ReadCallBack(IAsyncResult iar)        {            try            {                DataRead dataread = (DataRead)iar.AsyncState;                int recv = dataread.ns.EndRead(iar);                lbluserlist.Text = Encoding.Unicode.GetString(dataread.msg, 0, recv);                string userlist1 = lbluserlist.Text;                if (userlist1.StartsWith("#"))                {                    lb_Users.Items.Clear();                    string[] abc = userlist1.Split(new char[] { '#' });                    for (int i = 1; i < abc.Length ; i++)                        lb_Users.Items.Add(abc[i]);                }                else                    txt_ReceiveMsg.Invoke(settextboxreceivecallback, Encoding.Unicode.GetString(dataread.msg, 0, recv));                                if (isExit == false)                {                                        dataread = new DataRead(ns, client.ReceiveBufferSize);                    ns.BeginRead(dataread.msg, 0, dataread.msg.Length, ReadCallBack, dataread);                }            }            catch (Exception e)            {                MessageBox.Show(e.Message);            }            finally            { }        }
发送功能
//发送按钮事件        private void btn_Send_Click(object sender, EventArgs e)        {//如果群发按钮被选中时            if (rbteam.Checked == true)            {                //lb_Users.SelectedItems.Clear();                SendString(txt_SendMsg.Text);            }//如果私聊按钮被选中时            else if (rbself.Checked == true)            {                if (lb_Users.SelectedItem != null)                    SendString("#" + lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text);                //label10.Text = "#" +  lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text;                else                    MessageBox.Show("你还没有选取要私聊的对象");            }                            //label9.Text =   "对大家说:" + txt_SendMsg.Text;                        txt_SendMsg.Clear();        }        //发送函数        private void SendString(string str)        {            try            {                byte[] bytesdata = Encoding.Unicode.GetBytes(str + "\r\n");                ns.BeginWrite(bytesdata, 0, bytesdata.Length, new AsyncCallback(SendCallBack), ns);                ns.Flush();            }            catch (Exception e)            {                MessageBox.Show(e.Message);            }            finally            { }        }        //发送回调函数        private void SendCallBack(IAsyncResult iar)        {            try            {                ns.EndWrite(iar);            }            catch (Exception e)            {                MessageBox.Show(e.Message);            }            finally            { }        }
回调函数的实现
//listbox回调函数        private void SetListBox(string str)        {            lb_Users.Items.Add(str);        }        //txtbox回调函数        private void SetTextBoxReceive(string str)        {            txt_ReceiveMsg.AppendText(str+"\r\n");        }

在看看Server端

开始阶段
        public ServerMain()        {            InitializeComponent();            setlistboxcallback = new SetListBoxCallBack(SetLbListBox);            setlistboxcallback2 = new SetListBoxCallBack(SetListBox);             removelistboxcallback = new RemoveListBoxCallBack(RemoveListBoxItems);            setcomboboxcallback = new SetComboBoxCallBack(SetComboBox);            removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems);        }               private bool isExit = false;        System.Collections.ArrayList clientlist = new System.Collections.ArrayList();        TcpListener listener;        private delegate void SetListBoxCallBack(string str);        private SetListBoxCallBack setlistboxcallback;        private SetListBoxCallBack setlistboxcallback2;        private delegate void RemoveListBoxCallBack(DataReadWrite datareadwrite);        private RemoveListBoxCallBack removelistboxcallback;        private ManualResetEvent allDone = new ManualResetEvent(false);        private delegate void SetComboBoxCallBack(string str);        private SetComboBoxCallBack setcomboboxcallback;        private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite);        private RemoveComboBoxItemsCallBack removecomboboxcallback;        //开始服务按钮        private void btn_Start_Click(object sender, EventArgs e)        {//新建线程接受connect            Thread myThread = new Thread(new ThreadStart(AcceptConnection));            myThread.Start();            btn_Start.Enabled = false;            btn_End.Enabled=true;        }        //线程AcceptConnection        private void AcceptConnection()        {            IPAddress[] ip = Dns.GetHostAddresses(Dns.GetHostName());            //IPAddress ipp = IPAddress.Parse("192.168.76.103");            listener = new TcpListener(ip[0], Convert.ToInt32(txt_ServerPort.Text));            listener.Start();            while (isExit == false)            {                try                {                    allDone.Reset();                    AsyncCallback callback = new AsyncCallback(AcceptTcpClientCallBack);                    lst_ServerList.Invoke(setlistboxcallback, "开始等待连接");                    listener.BeginAcceptTcpClient(callback, listener);                    allDone.WaitOne();                }                catch (Exception e)                {                    MessageBox.Show(e.Message);                    break;                }                finally                { }            }        }        private void AcceptTcpClientCallBack(IAsyncResult iar)        {            try            {                allDone.Set();                TcpListener mylistener = (TcpListener)iar.AsyncState;                TcpClient client = mylistener.EndAcceptTcpClient(iar);                lst_ServerList.Invoke(setlistboxcallback, "已接收客户连接" + client.Client.RemoteEndPoint);                listBox1.Invoke(setlistboxcallback2, client.Client.RemoteEndPoint.ToString());                comboBox1.Invoke(setcomboboxcallback, client.Client.RemoteEndPoint.ToString());                DataReadWrite datareadwrite = new DataReadWrite(client);                clientlist.Add(datareadwrite);                                //发送成员列表,这里用了观察者模式                SendList();                                   //与服务器连接后的不同的客户端的datareadwrite开始异步接收数据                    datareadwrite.ns.BeginRead(datareadwrite.read, 0, datareadwrite.read.Length, ReadCallBack, datareadwrite);            }            catch (Exception e)            {                lst_ServerList.Invoke(setlistboxcallback, e.Message);                //MessageBox.Show(e.Message);                return;            }            finally            { }        }        private void SendList()        {            string userlist = "";            for (int i = 0; i < clientlist.Count; i++)            {                userlist = userlist + "#" + ((DataReadWrite)clientlist[i]).client.Client.RemoteEndPoint.ToString();                label14.Text = userlist;            }            foreach (DataReadWrite drw in clientlist)                SendString(drw, userlist);        }
运行阶段
//读取消息回调函数        private void ReadCallBack(IAsyncResult iar)        {            DataReadWrite datareadwrite = (DataReadWrite)iar.AsyncState;            try            {                                int recv = datareadwrite.ns.EndRead(iar);                string aa = Encoding.Unicode.GetString(datareadwrite.read, 0, recv);                //string bb = "";                stringaa.Text = aa;                if (isExit == false)                {                    if (aa.Substring(0, 1) != "#")         //群发为#,没有则为单发                    {                        aa = aa.Insert(0, datareadwrite.client.Client.RemoteEndPoint.ToString() + "对大家说:");                        //stringbb.Text = aa.Substring(1, aa.Length-1);                        foreach (DataReadWrite drw in clientlist)                            SendString(drw, aa);                    }                    else         //私聊时截取ip地址,收到的信息为“#192.168.76.102:3315#消息”                    {                       int c=aa.LastIndexOf("#");                          //截取ip地址的位置index                       string ipaddress = aa.Substring(1, c - 1);         //截取ip地址                       for (int i = 0; i <= listBox1.Items.Count - 1; i++)                       {                           if (ipaddress == listBox1.Items[i].ToString())                           {                               //找到需要发送的对象                               DataReadWrite obj = (DataReadWrite)clientlist[i];                               //增加发送源的IP地址和端口号                               aa = aa.Insert(c + 1, datareadwrite.client.Client.RemoteEndPoint.ToString() + "跟你说:");                               SendString(obj, aa.Substring(c+1));                               break;                               //MessageBox.Show(aa.Substring(c, aa.Length - 2));                           }                       }                    }                    datareadwrite.ns.BeginRead(datareadwrite.read, 0, datareadwrite.read.Length, ReadCallBack, datareadwrite);                }            }            catch (Exception e)            {                lst_ServerList.Invoke(setlistboxcallback, e.Message);                listBox1.Invoke(removelistboxcallback, datareadwrite);                comboBox1.Invoke(removecomboboxcallback, datareadwrite);                //SendList();            }            finally            { }        }        //发送消息        private void SendString(DataReadWrite datareadwrite, string str)        {            try            {                datareadwrite.write = Encoding.Unicode.GetBytes(str + "\r\n");                datareadwrite.ns.BeginWrite(datareadwrite.write, 0, datareadwrite.write.Length, new AsyncCallback(SendCallBack), datareadwrite);                datareadwrite.ns.Flush();                lst_ServerList.Invoke(setlistboxcallback, string.Format("向{0}发送:{1}", datareadwrite.client.Client.RemoteEndPoint, str));            }            catch (Exception e)            {                lst_ServerList.Items.Add(e.Message);                //发送失败时,清除发送不成功的IP地址                listBox1.Invoke(removelistboxcallback, datareadwrite);                comboBox1.Invoke(removecomboboxcallback, datareadwrite);                //SendList();            }            finally            {            }        }        //发送回调        private void SendCallBack(IAsyncResult iar)        {            DataReadWrite datareadwrite = (DataReadWrite)iar.AsyncState;            try            {                datareadwrite.ns.EndRead(iar);            }            catch (Exception e)            {                lst_ServerList.Invoke(setlistboxcallback, e.Message);                listBox1.Invoke(removelistboxcallback, datareadwrite);                comboBox1.Invoke(removecomboboxcallback, datareadwrite);            }            finally            { }        }        //停止服务按钮        private void btn_End_Click(object sender, EventArgs e)        {            isExit = true;            allDone.Set();            btn_Start.Enabled = true;            btn_End.Enabled = false;            lst_ServerList.Items.Add("服务器于" + DateTime.Now.ToString() + "停止运行.");        }
回调函数实现
private void RemoveListBoxItems(DataReadWrite datareadwrite)        {            int index = clientlist.IndexOf(datareadwrite);            listBox1.Items.RemoveAt(index);        }        private void SetListBox(string str)        {            listBox1.Items.Add(str);        }        private void SetLbListBox(string str)        {            lst_ServerList.Items.Add(str);            lst_ServerList.SelectedIndex = listBox1.Items.Count - 1;            lst_ServerList.ClearSelected();                    }        private void RemoveComboBoxItems(DataReadWrite datareadwrite)        {            int index = clientlist.IndexOf(datareadwrite);            comboBox1.Items.RemoveAt(index);        }        private void SetComboBox(object obj)        {            comboBox1.Items.Add(obj);        }

再介绍一下DataReadWrite类,这个类是实现将一个client的属性和功能集成到一个类中,主要是负责处理数据的,包括一个tcpclient对象,

一个网络流对象,2个负责读写的字节数组

DataReadWrite
    public class DataReadWrite    {        public TcpClient client;        public NetworkStream ns;        public byte[] read;        public byte[] write;        public DataReadWrite(TcpClient client)        {            this.client = client;            ns = client.GetStream();            read = new byte[client.ReceiveBufferSize];            write = new byte[client.SendBufferSize];        }        public void InitReadArray()        {            read = new byte[client.ReceiveBufferSize];        }        public void InitWriteArray()        {            write = new byte[client.SendBufferSize];        }    

在构造函数里得到连接上服务器的句柄,包含服务器的IP地址信息,从client对象上得到流对象,流对象就是用来在网络上进行流的读写。

以下是实现的截图

尝试了群发和私聊

由于是新手,所以里面用词方面可能有些不准确,而且程序这是初成品,还有很多方面的BUG。例如退出时会发生异常,当有人离开时,列表不会更新。

希望大家看了之后给点意见和建议,谢谢大家撒。有空多留言,谢谢

参考书籍:《visual c#网络编程技术与实践》和《c#网络应用高级编程》

ps:漏了源码,现在上传qqsocket.rar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!