C# 多人聊天程序


  上個星期,特別想寫一個點對點聊天的小程序,就上網查了一下有關C#網絡編程的知識,用到最多的就是TcpClient和TcpListener,使用這兩個類就可以完成主機之間的通信,當然,做這個程序的過程中也用到了多線程和事件與委托,這是我第一次將這些高級特性加入到程序中,通過參考

《C#和.net 3.0第一步》,我學會了如何使用事件,然后照個上面的例子寫出了這個多人聊天程序。

  

  定義一個客戶端類:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Windows.Forms;
using System.IO;
using System.Net;

namespace TCPClient
{
    class P2PClient
    {
        public TcpClient  tcpClientObj;//收發數據

        private Thread receiveThread; //接收數據的線程

        public delegate void receiveDelegate(string receiveData);//處理接收數據事件的方法類型

        public event receiveDelegate receiveEvent; //接收數據的事件

        public void SendConnection(string ip, int port)  //通過IP地址和端口號發送連接
        {
            IPAddress ipaddr = IPAddress.Parse(ip);//轉為IP地址后在連接會加快速度
            
            tcpClientObj = new TcpClient(); //連接客戶端 
           
            tcpClientObj.Connect(ipaddr, port);//連接    

            receiveThread = new Thread(Receiver); //啟動接收數據線程
            receiveThread.Start();  

        }

        public void Send(string message) //發送信息
        {
            if (tcpClientObj == null)
            {
                return;
            }
            NetworkStream ns = this.tcpClientObj.GetStream();//得到網絡流

            StreamWriter sw = new StreamWriter(ns);

            sw.WriteLine(message);//發送信息
            
            sw.Flush();//使所有的緩沖數據寫入基礎數據流  
            ns.Flush();
        }   

        private void Receiver()  //接收數據對應的線程(接收到數據以后觸發事件)
        {
            while (true) //一直接受
            {
                NetworkStream ns = this.tcpClientObj.GetStream();

                StreamReader sr = new StreamReader(ns);

                string receivedata = sr.ReadLine();

                receiveEvent(receivedata);//觸發事件
            }
        }  
    }
}

客戶端窗體對應代碼如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace TCPClient
{
    public partial class ClientForm : Form
    {
        private P2PClient ClientObj = new P2PClient(); //客戶對象,對於一個對象一定要new啊

        public ClientForm()
        {
            InitializeComponent();
        }

        private void ClientForm_Load(object sender, EventArgs e)
        {
            btnSend.Enabled = false; //沒有連接不允許發送數據
            this.AcceptButton = btnSend;

        }

        private void btnConnect_Click(object sender, EventArgs e)  
        {
            string nickName = tbName.Text;
            string ip =  tbIP.Text;
            string port = tbPort.Text;

            if(string.IsNullOrEmpty(nickName) || string.IsNullOrEmpty(ip) || string.IsNullOrEmpty(port))
            {
                MessageBox.Show("請將昵稱、IP填寫完整");
                return ;
            }

            try
            {           

                ClientObj.SendConnection(ip, Convert.ToInt32(port)); //連接  

                ClientObj.receiveEvent += new P2PClient.receiveDelegate(ClientObj_receiveEvent); //訂閱事件的處理方法

                ClientObj.Send(tbName.Text + "登陸成功!");

                btnSend.Enabled = true;
                btnConnect.Enabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show("連接時出錯:" + ex.Message);
                return;
            }
        }

        void ClientObj_receiveEvent(string receiveData)
        {
            try
            {
                if (this.InvokeRequired) //指示是否需要在這個線程上調用方法
                {
                    P2PClient.receiveDelegate update = new P2PClient.receiveDelegate(ClientObj_receiveEvent);//當把消息傳遞給控件線程時重復調用該方法就會調用else

                    this.Invoke(update, new object[] { receiveData });//將消息發送給控件線程處理
                }
                else
                {
                    lbMessage.Items.Add(receiveData);//添加數據
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("接收數據錯誤:" + ex.Message);
                return;
            }
            
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                if (string.IsNullOrEmpty(tbMessage.Text))
                {
                    return;
                }
                ClientObj.Send(tbName.Text + "說:" + tbMessage.Text);//發送信息
                tbMessage.Clear();//清除原來的文本
            }
            catch (Exception ex)
            {
                MessageBox.Show("發送數據出錯:" + ex.Message);
                return;
            }
        }

        private void ClientForm_FormClosing(object sender, FormClosingEventArgs e)
        {
           // ClientObj.Send(this.tbName.Text + "下線了");
            //ClientObj.tcpClientObj.Close();//關閉連接
            this.Close();//關閉窗體,讓程序自動釋放資源
        }
    }
}

客戶端完成,下面定義一個服務器端類

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Windows.Forms;
using System.IO;

namespace TCPServer
{
    class P2PServer
    {
        public  TcpListener listenObj;//監聽對象
        public Dictionary<string,TcpClient> clientMem = new Dictionary<string,TcpClient>(); //客戶端列表一定要初始化,new

        private Thread listenThread; //監聽線程

        public delegate void ConnectDelegate(); //連接成功后處理事件的方法類型

        public event ConnectDelegate ConnectEvent;//連接事件

        public delegate void ReceiveDelegate(string message); //接收數據后處理事件方法類型

        public event ReceiveDelegate ReceiveEvent; //接收數據事件

        public void Listen(int port) //監聽方法,啟動監聽線程
        {
            IPAddress[] localIP = Dns.GetHostAddresses(Dns.GetHostName()); //通過主機名得到本地IP

            this.listenObj = new TcpListener(localIP[0], port); //監聽對象

            this.listenThread = new Thread(ListenClient); //這個線程僅僅用來監聽客戶        

            listenThread.Start();//啟動這個線程方法          
          
        }

        public void ListenClient()  //監聽線程對應的方法,監聽到信息后向所有的客戶端發送數據
        {
            while (true) //一直監聽,可以有多個客戶端請求連接
            {
                listenObj.Start();//開始偵聽請求 ;注意在線程start之后才可以。

                TcpClient acceptClientObj = listenObj.AcceptTcpClient();//接收掛起的連接請求,這是一個阻塞方法                  

                this.ConnectEvent();//觸發連接事件

                Thread receiveThread = new Thread(Receiver); //這個線程處理接收的數據

                string connectTime = DateTime.Now.ToString();

                receiveThread.Name = connectTime;//設置線程的名字

                this.clientMem.Add(connectTime, acceptClientObj); //將 客戶 添加到列表     

                receiveThread.Start();//接收到的連接包含數據               
            }            
        }

       
        public void Send(string message) //發送信息
        {
           
            foreach (KeyValuePair<string, TcpClient> var in clientMem) //向所有客戶發送數據
            {

                if (var.Value == null || var.Value.Connected == false)
                {
                    clientMem.Remove(var.Key);  //刪除斷開的連接???這個地方有待改進
                    continue;
                }             

                NetworkStream ns = var.Value.GetStream();//得到網絡流

                StreamWriter sw = new StreamWriter(ns);
                sw.WriteLine(message);

                sw.Flush();//刷新數據流  
                ns.Flush();
            }           
   
        }

        public void Receiver()  //接收 數據 對應的方法
        {   //所有的TcpClient都對應一個線程,用來接收客戶端發來的數據,通過線程名,找到對應的TcpClient
          
            while (true)
            {
                //收到一個TcpClient時,都有一個命名的Thread對應,
                NetworkStream ns = clientMem[Thread.CurrentThread.Name].GetStream();                 

                StreamReader sr = new StreamReader(ns);

                string message = sr.ReadLine();//讀取消息

                this.ReceiveEvent(message);//接收過數據 就觸發接收消息的事件            
            }
        }

    }
}

下面是服務器端窗體代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;

namespace TCPServer
{
    public partial class ServerForm : Form
    {
        P2PServer serverObj = new P2PServer();

        public ServerForm()
        {
           
            InitializeComponent();
        }

        private void ServerForm_Load(object sender, EventArgs e)
        {
            try
            {
                serverObj.ConnectEvent += new P2PServer.ConnectDelegate(serverObj_ConnectEvent);  //訂閱連接事件

                serverObj.ReceiveEvent += new P2PServer.ReceiveDelegate(serverObj_ReceiveEvent);  //訂閱接收數據事件

                serverObj.Listen(Convert.ToInt32(tbPort.Text)); //啟動監聽
            }
            catch (Exception ex)
            {
                MessageBox.Show("加載失敗:" + ex.Message);
                return;
            }

        }

        void serverObj_ReceiveEvent(string message)
        {
            try
            {
                if (this.InvokeRequired)
                {
                    P2PServer.ReceiveDelegate update = new P2PServer.ReceiveDelegate(serverObj_ReceiveEvent);

                    this.Invoke(update, new object[] { message });
                }
                else
                {
                    this.lbMessage.Items.Add(message);//添加到顯示欄

                    

                    serverObj.Send(message);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("處理事件方法錯誤:" + ex.Message);
                return;
            }
            
        }     

        void serverObj_ConnectEvent()
        {
            try
            {
                if (this.InvokeRequired)
                {
                    P2PServer.ConnectDelegate update = new P2PServer.ConnectDelegate(serverObj_ConnectEvent);

                    this.Invoke(update);
                }

                else
                {
                    this.lbMessage.Items.Add("連接成功");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("處理連接事件方法錯誤:" + ex.Message);
                return;
            }
        }

        private void ServerForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            
        }

        private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.Close();
        }
    }
}

運行效果如下:

 

顯示消息的是一個ListBox控件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM