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);//初始化时,只有连接按钮可用
}
}
}