C#聊天程序服务端与客户端完整实例代码
本文所述为基于C#实现的多人聊天程序服务端与客户端完整代码。本实例省略了结构定义部分,服务端主要是逻辑处理部分代码,因此使用时需要完善一些窗体按钮之类的。
先看服务端代码如下:
usingSystem; usingSystem.Drawing; usingSystem.Collections; usingSystem.ComponentModel; usingSystem.Windows.Forms; usingSystem.Data; usingSystem.Net; usingSystem.Net.Sockets; usingSystem.Threading; namespace多人聊天程序Server端 { ///<summary> ///应用程序的主入口点。 ///</summary> [STAThread] staticvoidMain() { Application.Run(newForm1()); } //启动服务按钮 privatevoidbutton2_Click(objectsender,System.EventArgse) { try { //必须填写端口 if(txtPort.Text=="") { MessageBox.Show("请先填写服务端口号!","提示"); return; } Int32port=Int32.Parse(txtPort.Text);//获得端口号 //创建侦听的Socket mainSocket=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); IPEndPointlocalEP=newIPEndPoint(IPAddress.Any,port); //将Socket绑定到本地的终结点上 mainSocket.Bind(localEP); //开始侦听,最大的连接数是5 mainSocket.Listen(5); //开始一个异步操作接受客户的连接请求 mainSocket.BeginAccept(newAsyncCallback(OnClientConnect),null); //启动服务按钮不可用,停止服务按钮可用 UpdateControls(true); } catch(SocketExceptionse) { MessageBox.Show(se.Message,"提示"); } } //更新“启动服务按钮”和“停止服务”按钮的状态:是否可用; //注意:两个按钮的状态是互斥的 privatevoidUpdateControls(boolonServe) { button2.Enabled=!onServe; button3.Enabled=onServe; if(onServe) { status.Text="已启动服务"; } else { status.Text="未启动服务"; } } //回调函数,当客户连接上时,将会被调用 publicvoidOnClientConnect(IAsyncResultasyn) { try { //调用EndAccept完成BeginAccept异步调用,返回一个新的Socket处理与客户的通信 SocketworkerSocket=mainSocket.EndAccept(asyn); //增加客户数目 Interlocked.Increment(refclientNum); //将workerSocketSocket加入到ArrayList中 workerSocketList.Add(workerSocket); //发送欢迎信息给连接上服务器的客户 stringmsg="欢迎客户"+clientNum+"登录服务器\n"; SendWelcomeToClient(msg,clientNum); //在线客户数目改变,必须更新客户列表 UpdateClientListControl(); //连接上的客户接收数据 WaitForData(workerSocket,clientNum); //主Socket返回,继续等待其它的连接请求 mainSocket.BeginAccept(newAsyncCallback(OnClientConnect),null); } catch(ObjectDisposedException) { System.Diagnostics.Debugger.Log(0,"1","\nOnClientConnection:Socket已经关闭!\n"); } catch(SocketExceptionse) { MessageBox.Show(se.Message,"提示"); } } //发送欢迎信息给客户 voidSendWelcomeToClient(stringmsg,intclientNumber) { //用UTF8格式来将string信息转化成byte数组形式 byte[]byData=System.Text.Encoding.UTF8.GetBytes(msg); //获得客户clientNumber对应的Socket SocketworkerSocket=(Socket)workerSocketList[clientNumber-1]; //将数据发给客户 workerSocket.Send(byData); } //该类保存当前的socket,它的客户号还有发送给服务器的数据 publicclassSocketPacket { publicSystem.Net.Sockets.SocketcurrentSocket;//当前的Socket publicintclientNumber;//客户号 publicbyte[]dataBuffer=newbyte[1024];//发给服务器的数据 //构造函数 publicSocketPacket(System.Net.Sockets.Socketsocket,intclientNumber) { currentSocket=socket; this.clientNumber=clientNumber; } } //开始等待客户发送数据 publicvoidWaitForData(System.Net.Sockets.Socketsocket,intclientNumber) { try { if(pfnWorkerCallBack==null) { //当连接上的客户有写的操作的时候,调用回调函数 pfnWorkerCallBack=newAsyncCallback(OnDataReceived); } SocketPacketsocketPacket=newSocketPacket(socket,clientNumber); socket.BeginReceive(socketPacket.dataBuffer,0,socketPacket.dataBuffer.Length, SocketFlags.None,pfnWorkerCallBack,socketPacket); } catch(SocketExceptionse) { MessageBox.Show(se.Message,"提示"); } } //当客户写数据时,调用以下方法 publicvoidOnDataReceived(IAsyncResultasyn) { SocketPacketsocketData=(SocketPacket)asyn.AsyncState; try { //EndReceive完成BeginReceive异步调用,返回客户写入流的字节数 intiRx=socketData.currentSocket.EndReceive(asyn); //加1是因为字符串以'\0'作为结束标志符 char[]chars=newchar[iRx+1]; //对客户发来的信息进行UTF8解码,存入chars字符数组中 System.Text.Decoderdecoder=System.Text.Encoding.UTF8.GetDecoder(); intcharLen=decoder.GetChars(socketData.dataBuffer,0,iRx,chars,0); System.StringszData=newSystem.String(chars); stringmsg="客户"+socketData.clientNumber+"发的信息:"+szData; //将客户发的数据加入到信息列表中 AppendToRichEditControl(msg); //等待数据 WaitForData(socketData.currentSocket,socketData.clientNumber); } catch(ObjectDisposedException) { System.Diagnostics.Debugger.Log(0,"1","\nOnDataReceived:Socket已经关闭!\n"); } catch(SocketExceptionse) { if(se.ErrorCode==10054) { //将客户断开连接的信息写入信息列表中 stringmsg="客户"+socketData.clientNumber+"已断开了连接!"+"\n"; AppendToRichEditControl(msg); //移走已关闭的socket workerSocketList[socketData.clientNumber-1]=null; //更新客户列表 UpdateClientListControl(); } else { MessageBox.Show(se.Message,"提示"); } } } //更新信息列表,该方法可由主线程或其他工作线程所调用 privatevoidAppendToRichEditControl(stringmsg) { //测试看是哪个线程调用了该方法 if(InvokeRequired) { //WecannotupdatetheGUIonthisthread. //AllGUIcontrolsaretobeupdatedbythemain(GUI)thread. //Hencewewillusetheinvokemethodonthecontrolwhichwill //becalledwhentheMainthreadisfree //DoUIupdateonUIthread object[]pList={msg}; txtRecvMsg.BeginInvoke(newUpdateRichEditCallback(OnUpdateRichEdit),pList); } else { //创建该控件的主线程直接更新信息列表 OnUpdateRichEdit(msg); } } //添加信息到txtRecvMsg中 privatevoidOnUpdateRichEdit(stringmsg) { //txtRecvMsg.AppendText(msg); txtRecvMsg.Text=txtRecvMsg.Text+msg; } //更新客户列表 privatevoidUpdateClientListControl() { if(InvokeRequired)//Isthiscalledfromathreadotherthantheonecreated //thecontrol { clientList.BeginInvoke(newUpdateClientListCallback(UpdateClientList),null); } else { //创建该控件的主线程直接更新信息列表 UpdateClientList(); } } //更新客户列表 voidUpdateClientList() { clientList.Items.Clear();//清空客户列表 for(inti=0;i<workerSocketList.Count;i++) { //加1,是因为数组从下标0开始,而我们的客户标号是从1开始 stringclientKey=Convert.ToString(i+1); SocketworkerSocket=(Socket)workerSocketList[i]; if(workerSocket!=null) { //将连接着服务器的客户添加到客户列表中 if(workerSocket.Connected) { clientList.Items.Add(clientKey); } } } } //停止服务按钮 privatevoidbutton3_Click(objectsender,System.EventArgse) { CloseSockets(); UpdateControls(false); //更新客户列表 UpdateClientListControl(); } //发送信息按钮 privatevoidbutton1_Click(objectsender,System.EventArgse) { //如果在线客户列表不为空,则允许发送信息 if(clientList.Items.Count!=0) { try { stringmsg=txtSendMsg.Text; msg="服务器信息:"+msg+"\n"; byte[]byData=System.Text.Encoding.UTF8.GetBytes(msg); SocketworkerSocket=null; for(inti=0;i<workerSocketList.Count;i++) { workerSocket=(Socket)workerSocketList[i]; if(workerSocket!=null) { //发给所有连接上服务器的客户 if(workerSocket.Connected) { workerSocket.Send(byData); } } } } catch(SocketExceptionse) { MessageBox.Show(se.Message,"提示"); } } else { MessageBox.Show("没有在线客户,不能发送信息!","提示"); } } //清空信息按钮 privatevoidbutton4_Click(objectsender,System.EventArgse) { txtRecvMsg.Clear();//清空从客户发来的信息 } //关闭窗体按钮 privatevoidbutton5_Click(objectsender,System.EventArgse) { CloseSockets(); Close(); } //关闭Socket voidCloseSockets() { //关闭主Socket if(mainSocket!=null) { mainSocket.Close(); } SocketworkerSocket=null; //关闭客户Socket数组 for(inti=0;i<workerSocketList.Count;i++) { workerSocket=(Socket)workerSocketList[i]; if(workerSocket!=null) { workerSocket.Close(); workerSocket=null; } } } privatevoidForm1_Load(objectsender,System.EventArgse) { try { //获得本机的IP地址 txtIP.Text=Dns.Resolve(Dns.GetHostName()).AddressList[0].ToString(); //启动时,启动服务按钮可用,停止服务按钮不可用 UpdateControls(false); } catch(Exceptionexc) { MessageBox.Show(exc.Message,"提示"); } } } }
客户端主要实现接收来自服务端返回的消息、实现发送消息的操作界面,创建Socket实例,得到服务器的IP地址,更新控件,连接和断开等,具体代码如下:
usingSystem; usingSystem.Drawing; usingSystem.Collections; usingSystem.ComponentModel; usingSystem.Windows.Forms; usingSystem.Data; usingSystem.Net; usingSystem.Net.Sockets; namespace多人聊天程序Client端 { publicclassForm1:System.Windows.Forms.Form { privateSystem.Windows.Forms.Labellabel1; privateSystem.Windows.Forms.TextBoxtxtIP; privateSystem.Windows.Forms.Labellabel2; privateSystem.Windows.Forms.Labellabel3; privateSystem.Windows.Forms.RichTextBoxtxtSendMsg; privateSystem.Windows.Forms.Labellabel4; privateSystem.Windows.Forms.Buttonbutton1; privateSystem.Windows.Forms.Buttonbutton2; privateSystem.Windows.Forms.Buttonbutton3; privateSystem.Windows.Forms.Buttonbutton4; privateSystem.Windows.Forms.RichTextBoxtxtRecvMsg; privateSystem.Windows.Forms.TextBoxtxtPort; privateSystem.Windows.Forms.Buttonbutton5; privateSystem.ComponentModel.Containercomponents=null; byte[]m_dataBuffer=newbyte[10]; IAsyncResultresult; publicAsyncCallbackpfnCallBack; privateSystem.Windows.Forms.Labelstatus; privateSystem.Windows.Forms.Labellabel5; publicSocketclientSocket; publicForm1() { InitializeComponent(); } privatevoidInitializeComponent() { this.label1=newSystem.Windows.Forms.Label(); this.txtIP=newSystem.Windows.Forms.TextBox(); this.label2=newSystem.Windows.Forms.Label(); this.txtPort=newSystem.Windows.Forms.TextBox(); this.label3=newSystem.Windows.Forms.Label(); this.txtSendMsg=newSystem.Windows.Forms.RichTextBox(); this.label4=newSystem.Windows.Forms.Label(); this.status=newSystem.Windows.Forms.Label(); this.txtRecvMsg=newSystem.Windows.Forms.RichTextBox(); this.button1=newSystem.Windows.Forms.Button(); this.button2=newSystem.Windows.Forms.Button(); this.button3=newSystem.Windows.Forms.Button(); this.button4=newSystem.Windows.Forms.Button(); this.label5=newSystem.Windows.Forms.Label(); this.button5=newSystem.Windows.Forms.Button(); this.SuspendLayout(); //label1 this.label1.AutoSize=true; this.label1.Location=newSystem.Drawing.Point(16,24); this.label1.Name="label1"; this.label1.Size=newSystem.Drawing.Size(60,17); this.label1.TabIndex=0; this.label1.Text="服务器IP:"; //txtIP this.txtIP.Location=newSystem.Drawing.Point(80,24); this.txtIP.Name="txtIP"; this.txtIP.Size=newSystem.Drawing.Size(160,21); this.txtIP.TabIndex=1; this.txtIP.Text=""; //label2 this.label2.AutoSize=true; this.label2.Location=newSystem.Drawing.Point(16,56); this.label2.Name="label2"; this.label2.Size=newSystem.Drawing.Size(35,17); this.label2.TabIndex=2; this.label2.Text="端口:"; //txtPort this.txtPort.Location=newSystem.Drawing.Point(80,56); this.txtPort.Name="txtPort"; this.txtPort.Size=newSystem.Drawing.Size(40,21); this.txtPort.TabIndex=3; this.txtPort.Text=""; //label3 this.label3.AutoSize=true; this.label3.Location=newSystem.Drawing.Point(16,96); this.label3.Name="label3"; this.label3.Size=newSystem.Drawing.Size(110,17); this.label3.TabIndex=4; this.label3.Text="发送信息给服务器:"; //txtSendMsg this.txtSendMsg.Location=newSystem.Drawing.Point(16,120); this.txtSendMsg.Name="txtSendMsg"; this.txtSendMsg.Size=newSystem.Drawing.Size(224,88); this.txtSendMsg.TabIndex=5; this.txtSendMsg.Text=""; //label4 this.label4.AutoSize=true; this.label4.Location=newSystem.Drawing.Point(16,248); this.label4.Name="label4"; this.label4.Size=newSystem.Drawing.Size(60,17); this.label4.TabIndex=6; this.label4.Text="连接状态:"; //status this.status.Location=newSystem.Drawing.Point(80,248); this.status.Name="status"; this.status.Size=newSystem.Drawing.Size(192,23); this.status.TabIndex=7; //txtRecvMsg this.txtRecvMsg.Location=newSystem.Drawing.Point(264,80); this.txtRecvMsg.Name="txtRecvMsg"; this.txtRecvMsg.ReadOnly=true; this.txtRecvMsg.Size=newSystem.Drawing.Size(224,144); this.txtRecvMsg.TabIndex=8; this.txtRecvMsg.Text=""; //button1 this.button1.Location=newSystem.Drawing.Point(280,16); this.button1.Name="button1"; this.button1.Size=newSystem.Drawing.Size(88,32); this.button1.TabIndex=9; this.button1.Text="连接"; this.button1.Click+=newSystem.EventHandler(this.button1_Click); //button2 this.button2.Location=newSystem.Drawing.Point(384,16); this.button2.Name="button2"; this.button2.Size=newSystem.Drawing.Size(88,32); this.button2.TabIndex=10; this.button2.Text="断开"; this.button2.Click+=newSystem.EventHandler(this.button2_Click); //button3 this.button3.Location=newSystem.Drawing.Point(280,232); this.button3.Name="button3"; this.button3.Size=newSystem.Drawing.Size(88,32); this.button3.TabIndex=11; this.button3.Text="清空信息"; this.button3.Click+=newSystem.EventHandler(this.button3_Click); //button4 this.button4.Location=newSystem.Drawing.Point(384,232); this.button4.Name="button4"; this.button4.Size=newSystem.Drawing.Size(88,32); this.button4.TabIndex=12; this.button4.Text="关闭"; this.button4.Click+=newSystem.EventHandler(this.button4_Click); //label5 this.label5.AutoSize=true; this.label5.Location=newSystem.Drawing.Point(264,64); this.label5.Name="label5"; this.label5.Size=newSystem.Drawing.Size(134,17); this.label5.TabIndex=13; this.label5.Text="收到服务器发来的信息:"; //button5 this.button5.Location=newSystem.Drawing.Point(16,208); this.button5.Name="button5"; this.button5.Size=newSystem.Drawing.Size(224,32); this.button5.TabIndex=14; this.button5.Text="发送信息"; this.button5.Click+=newSystem.EventHandler(this.button5_Click); //Form1 this.AutoScaleBaseSize=newSystem.Drawing.Size(6,14); this.ClientSize=newSystem.Drawing.Size(512,277); this.Controls.Add(this.button5); this.Controls.Add(this.label5); this.Controls.Add(this.button4); this.Controls.Add(this.button3); this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Controls.Add(this.txtRecvMsg); this.Controls.Add(this.status); this.Controls.Add(this.label4); this.Controls.Add(this.txtSendMsg); this.Controls.Add(this.label3); this.Controls.Add(this.txtPort); this.Controls.Add(this.label2); this.Controls.Add(this.txtIP); this.Controls.Add(this.label1); this.Name="Form1"; this.Text="多人聊天程序Client端"; this.Load+=newSystem.EventHandler(this.Form1_Load); this.ResumeLayout(false); } #endregion [STAThread] staticvoidMain() { Application.Run(newForm1()); } //连接按钮 privatevoidbutton1_Click(objectsender,System.EventArgse) { //IP地址和端口号不能为空 if(txtIP.Text==""||txtPort.Text=="") { MessageBox.Show("请先完整填写服务器IP地址和端口号!","提示"); return; } try { //创建Socket实例 clientSocket=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); //得到服务器的IP地址 IPAddressipAddress=IPAddress.Parse(txtIP.Text); Int32port=Int32.Parse(txtPort.Text); //创建远程终结点 IPEndPointremoteEP=newIPEndPoint(ipAddress,port); //连接到远程服务器 clientSocket.Connect(remoteEP); if(clientSocket.Connected) { UpdateControls(true); WaitForData();//异步等待数据 } } catch(SocketExceptionse) { MessageBox.Show(se.Message,"提示"); UpdateControls(false); } } //等待数据 publicvoidWaitForData() { try { if(pfnCallBack==null) { //当连接上的客户有写的操作的时候,调用回调函数 pfnCallBack=newAsyncCallback(OnDataReceived); } SocketPacketsocketPacket=newSocketPacket(); socketPacket.thisSocket=clientSocket; result=clientSocket.BeginReceive(socketPacket.dataBuffer,0,socketPacket.dataBuffer.Length, SocketFlags.None,pfnCallBack,socketPacket); } catch(SocketExceptionse) { MessageBox.Show(se.Message,"提示"); } } //该类保存Socket以及发送给服务器的数据 publicclassSocketPacket { publicSystem.Net.Sockets.SocketthisSocket; publicbyte[]dataBuffer=newbyte[1024];//发给服务器的数据 } //接收数据 publicvoidOnDataReceived(IAsyncResultasyn) { try { SocketPackettheSockId=(SocketPacket)asyn.AsyncState; //EndReceive完成BeginReceive异步调用,返回服务器写入流的字节数 intiRx=theSockId.thisSocket.EndReceive(asyn); //加1是因为字符串以'\0'作为结束标志符 char[]chars=newchar[iRx+1]; //用UTF8格式来将string信息转化成byte数组形式 System.Text.Decoderdecoder=System.Text.Encoding.UTF8.GetDecoder(); intcharLen=decoder.GetChars(theSockId.dataBuffer,0,iRx,chars,0); System.StringszData=newSystem.String(chars); //将收到的信息显示在信息列表中 txtRecvMsg.Text=txtRecvMsg.Text+szData; //等待数据 WaitForData(); } catch(ObjectDisposedException) { System.Diagnostics.Debugger.Log(0,"1","\nOnDataReceived:Socket已经关闭!\n"); } catch(SocketExceptionse) { if(se.ErrorCode==10054) { stringmsg="服务器"+"停止服务!"+"\n"; txtRecvMsg.Text=txtRecvMsg.Text+msg; clientSocket.Close(); clientSocket=null; UpdateControls(false); } else { MessageBox.Show(se.Message,"提示"); } } } //更新控件。连接和断开(发送信息)按钮的状态是互斥的 privatevoidUpdateControls(boolconnected) { button1.Enabled=!connected; button2.Enabled=connected; button5.Enabled=connected; if(connected) { status.Text="已连接"; } else { status.Text="无连接"; } } //断开按钮 privatevoidbutton2_Click(objectsender,System.EventArgse) { //关闭Socket if(clientSocket!=null) { clientSocket.Close(); clientSocket=null; UpdateControls(false); } } //发送信息按钮 privatevoidbutton5_Click(objectsender,System.EventArgse) { try { //如果客户与服务器有连接,则允许发送信息 if(clientSocket.Connected) { stringmsg=txtSendMsg.Text+"\n"; //用UTF8格式来将string信息转化成byte数组形式 byte[]byData=System.Text.Encoding.UTF8.GetBytes(msg); if(clientSocket!=null) { //发送数据 clientSocket.Send(byData); } } } catch(Exceptionse) { MessageBox.Show(se.Message,"提示"); } } //清空按钮 privatevoidbutton3_Click(objectsender,System.EventArgse) { txtRecvMsg.Clear();//清空信息列表 } //关闭按钮 privatevoidbutton4_Click(objectsender,System.EventArgse) { //关闭Socket if(clientSocket!=null) { clientSocket.Close(); clientSocket=null; } Close();//关闭窗体 } privatevoidForm1_Load(objectsender,System.EventArgse) { UpdateControls(false);//初始化时,只有连接按钮可用 } } }