欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#聊天程序服务端与客户端完整实例代码

程序员文章站 2024-02-20 23:03:58
本文所述为基于c#实现的多人聊天程序服务端与客户端完整代码。本实例省略了结构定义部分,服务端主要是逻辑处理部分代码,因此使用时需要完善一些窗体按钮之类的。 先看服务端代码...

本文所述为基于c#实现的多人聊天程序服务端与客户端完整代码。本实例省略了结构定义部分,服务端主要是逻辑处理部分代码,因此使用时需要完善一些窗体按钮之类的。

先看服务端代码如下:

using system;
using system.drawing;
using system.collections;
using system.componentmodel;
using system.windows.forms;
using system.data;
using system.net;
using system.net.sockets;
using system.threading;
namespace 多人聊天程序server端
{
 /// <summary>
 /// 应用程序的主入口点。
 /// </summary>
 [stathread]
 static void main()
 {
  application.run(new form1());
 }
 // 启动服务按钮
 private void button2_click(object sender, system.eventargs e)
 {
  try
  {
  // 必须填写端口
  if(txtport.text == "")
  {
   messagebox.show("请先填写服务端口号!", "提示");
   return;
  }
  int32 port = int32.parse(txtport.text); // 获得端口号
  // 创建侦听的socket
  mainsocket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp);
  ipendpoint localep = new ipendpoint(ipaddress.any, port);
  // 将 socket 绑定到本地的终结点上
  mainsocket.bind(localep);
  // 开始侦听,最大的连接数是 5
  mainsocket.listen(5);
  // 开始一个异步操作接受客户的连接请求
  mainsocket.beginaccept(new asynccallback(onclientconnect), null);
  // 启动服务按钮不可用,停止服务按钮可用
  updatecontrols(true);
  }
  catch(socketexception se)
  {
  messagebox.show(se.message, "提示");
  }
 }
 // 更新“启动服务按钮”和“停止服务”按钮的状态:是否可用;
 // 注意:两个按钮的状态是互斥的
 private void updatecontrols(bool onserve)
 {
  button2.enabled = !onserve;
  button3.enabled = onserve;
  if(onserve)
  {
  status.text = "已启动服务";
  }
  else
  {
  status.text = "未启动服务";
  }
 }
 // 回调函数,当客户连接上时,将会被调用
 public void onclientconnect(iasyncresult asyn)
 {
  try
  {
  // 调用endaccept完成beginaccept异步调用,返回一个新的socket处理与客户的通信
  socket workersocket = mainsocket.endaccept(asyn);
  // 增加客户数目
  interlocked.increment(ref clientnum);
  // 将 workersocket socket加入到 arraylist 中
  workersocketlist.add(workersocket);
  // 发送欢迎信息给连接上服务器的客户
  string msg = "欢迎客户 " + clientnum + " 登录服务器\n";
  sendwelcometoclient(msg, clientnum);
  // 在线客户数目改变,必须更新客户列表
  updateclientlistcontrol();
  // 连接上的客户接收数据
  waitfordata(workersocket, clientnum);
  // 主 socket 返回,继续等待其它的连接请求
  mainsocket.beginaccept(new asynccallback(onclientconnect), null);
  }
  catch(objectdisposedexception)
  {
  system.diagnostics.debugger.log(0,"1","\n onclientconnection: socket已经关闭!\n");
  }
  catch(socketexception se)
  {
  messagebox.show(se.message, "提示");
  }
 }
 // 发送欢迎信息给客户
 void sendwelcometoclient(string msg, int clientnumber)
 {
  // 用utf8格式来将string信息转化成byte数组形式
  byte[] bydata = system.text.encoding.utf8.getbytes(msg);
  // 获得客户clientnumber对应的socket
  socket workersocket = (socket)workersocketlist[clientnumber - 1];
  // 将数据发给客户
  workersocket.send(bydata);
 }
 // 该类保存当前的socket,它的客户号还有发送给服务器的数据
 public class socketpacket
 {
  public system.net.sockets.socket currentsocket; // 当前的socket
  public int clientnumber; // 客户号
  public byte[] databuffer = new byte[1024]; // 发给服务器的数据
  // 构造函数
  public socketpacket(system.net.sockets.socket socket, int clientnumber)
  {
  currentsocket = socket;
  this.clientnumber = clientnumber;
  }
 }
 // 开始等待客户发送数据
 public void waitfordata(system.net.sockets.socket socket, int clientnumber)
 {
  try
  {
  if(pfnworkercallback == null)
  {
   // 当连接上的客户有写的操作的时候,调用回调函数
   pfnworkercallback = new asynccallback(ondatareceived);
  }
  socketpacket socketpacket = new socketpacket(socket, clientnumber);
  socket.beginreceive(socketpacket.databuffer, 0, socketpacket.databuffer.length,
            socketflags.none, pfnworkercallback, socketpacket);
  }
  catch(socketexception se)
  {
  messagebox.show (se.message, "提示");
  }
 }
 // 当客户写数据时,调用以下方法
 public void ondatareceived(iasyncresult asyn)
 {
  socketpacket socketdata = (socketpacket)asyn.asyncstate ;
  try
  {
  // endreceive完成beginreceive异步调用,返回客户写入流的字节数
  int irx = socketdata.currentsocket.endreceive(asyn);
  // 加 1 是因为字符串以 '\0' 作为结束标志符
  char[] chars = new char[irx + 1];
  // 对客户发来的信息进行utf8解码,存入chars字符数组中
  system.text.decoder decoder = system.text.encoding.utf8.getdecoder();
  int charlen = decoder.getchars(socketdata.databuffer, 0, irx, chars, 0);
  system.string szdata = new system.string(chars);
  string msg = "客户 " + socketdata.clientnumber + " 发的信息:" + szdata;
  // 将客户发的数据加入到信息列表中
  appendtoricheditcontrol(msg);
  // 等待数据
  waitfordata(socketdata.currentsocket, socketdata.clientnumber);
  }
  catch (objectdisposedexception )
  {
  system.diagnostics.debugger.log(0,"1","\nondatareceived: socket已经关闭!\n");
  }
  catch(socketexception se)
  {
  if(se.errorcode == 10054)
  {
   // 将客户断开连接的信息写入信息列表中
   string msg = "客户 " + socketdata.clientnumber + " 已断开了连接!" + "\n";
   appendtoricheditcontrol(msg);
   // 移走已关闭的socket
   workersocketlist[socketdata.clientnumber - 1] = null;
   // 更新客户列表
   updateclientlistcontrol();
  }
  else
  {
   messagebox.show (se.message, "提示");
  }
  }
 }
 // 更新信息列表,该方法可由主线程或其他工作线程所调用
 private void appendtoricheditcontrol(string msg)
 {
  // 测试看是哪个线程调用了该方法
  if (invokerequired)
  {
  // we cannot update the gui on this thread.
  // all gui controls are to be updated by the main (gui) thread.
  // hence we will use the invoke method on the control which will
  // be called when the main thread is free
  // do ui update on ui thread
  object[] plist = {msg};
  txtrecvmsg.begininvoke(new updatericheditcallback(onupdaterichedit), plist);
  }
  else
  {
  // 创建该控件的主线程直接更新信息列表
  onupdaterichedit(msg);
  }
 }
 // 添加信息到 txtrecvmsg 中
 private void onupdaterichedit(string msg)
 {
 // txtrecvmsg.appendtext(msg);
  txtrecvmsg.text = txtrecvmsg.text + msg;
 }
 // 更新客户列表
 private void updateclientlistcontrol()
 {
  if (invokerequired) // is this called from a thread other than the one created
  // the control
  {
  clientlist.begininvoke(new updateclientlistcallback(updateclientlist), null);
  }
  else
  {
  // 创建该控件的主线程直接更新信息列表
  updateclientlist();
  }
 }
 // 更新客户列表
 void updateclientlist()
 {
  clientlist.items.clear(); // 清空客户列表
  for(int i = 0; i < workersocketlist.count; i++)
  {
  // 加1,是因为数组从下标0开始,而我们的客户标号是从1开始
  string clientkey = convert.tostring(i + 1);
  socket workersocket = (socket)workersocketlist[i];
  if(workersocket != null)
  {
   // 将连接着服务器的客户添加到客户列表中
   if(workersocket.connected)
   {
   clientlist.items.add(clientkey);
   }
  }
  }
 }
 // 停止服务按钮
 private void button3_click(object sender, system.eventargs e)
 {
  closesockets();
  updatecontrols(false);
  // 更新客户列表
  updateclientlistcontrol();
 }
 // 发送信息按钮
 private void button1_click(object sender, system.eventargs e)
 {
  // 如果在线客户列表不为空,则允许发送信息
  if (clientlist.items.count != 0 )
  {
  try
  {
   string msg = txtsendmsg.text;
   msg = "服务器信息: " + msg + "\n";
   byte[] bydata = system.text.encoding.utf8.getbytes(msg);
   socket workersocket = null;
   for(int i = 0; i < workersocketlist.count; i++)
   {
   workersocket = (socket)workersocketlist[i];
   if(workersocket!= null)
   {
    // 发给所有连接上服务器的客户
    if(workersocket.connected)
    {
    workersocket.send(bydata);
    }
   }
   }
  }
  catch(socketexception se)
  {
   messagebox.show(se.message, "提示");
  }
  }
  else
  {
  messagebox.show("没有在线客户,不能发送信息!", "提示");
  }
 }
 // 清空信息按钮
 private void button4_click(object sender, system.eventargs e)
 {
  txtrecvmsg.clear(); // 清空从客户发来的信息
 }
 // 关闭窗体按钮
 private void button5_click(object sender, system.eventargs e)
 {
  closesockets();
  close();
 }
 // 关闭socket
 void closesockets()
 {
  // 关闭主socket
  if(mainsocket != null)
  {
  mainsocket.close();
  }
  socket workersocket = null;
  // 关闭客户 socket 数组
  for(int i = 0; i < workersocketlist.count; i++)
  {
  workersocket = (socket)workersocketlist[i];
  if(workersocket != null)
  {
   workersocket.close();
   workersocket = null;
  }
  }
 }
 private void form1_load(object sender, system.eventargs e)
 {
  try
  {
  // 获得本机的ip地址
  txtip.text = dns.resolve(dns.gethostname()).addresslist[0].tostring();
  // 启动时,启动服务按钮可用,停止服务按钮不可用
  updatecontrols(false);
  }
  catch(exception exc)
  {
  messagebox.show(exc.message, "提示");
  }
 }
 }
}

客户端主要实现接收来自服务端返回的消息、实现发送消息的操作界面,创建socket实例,得到服务器的ip地址,更新控件,连接和断开等,具体代码如下:

using system;
using system.drawing;
using system.collections;
using system.componentmodel;
using system.windows.forms;
using system.data;
using system.net;
using system.net.sockets;
namespace 多人聊天程序client端
{
 public class form1 : system.windows.forms.form
 {
 private system.windows.forms.label label1;
 private system.windows.forms.textbox txtip;
 private system.windows.forms.label label2;
 private system.windows.forms.label label3;
 private system.windows.forms.richtextbox txtsendmsg;
 private system.windows.forms.label label4;
 private system.windows.forms.button button1;
 private system.windows.forms.button button2;
 private system.windows.forms.button button3;
 private system.windows.forms.button button4;
 private system.windows.forms.richtextbox txtrecvmsg;
 private system.windows.forms.textbox txtport;
 private system.windows.forms.button button5;
 private system.componentmodel.container components = null;
 byte[] m_databuffer = new byte[10];
 iasyncresult result;
 public asynccallback pfncallback ;
 private system.windows.forms.label status;
 private system.windows.forms.label label5;
 public socket clientsocket;
 public form1()
 {
  initializecomponent();
 }
 private void initializecomponent()
 {
  this.label1 = new system.windows.forms.label();
  this.txtip = new system.windows.forms.textbox();
  this.label2 = new system.windows.forms.label();
  this.txtport = new system.windows.forms.textbox();
  this.label3 = new system.windows.forms.label();
  this.txtsendmsg = new system.windows.forms.richtextbox();
  this.label4 = new system.windows.forms.label();
  this.status = new system.windows.forms.label();
  this.txtrecvmsg = new system.windows.forms.richtextbox();
  this.button1 = new system.windows.forms.button();
  this.button2 = new system.windows.forms.button();
  this.button3 = new system.windows.forms.button();
  this.button4 = new system.windows.forms.button();
  this.label5 = new system.windows.forms.label();
  this.button5 = new system.windows.forms.button();
  this.suspendlayout();
  // label1
  this.label1.autosize = true;
  this.label1.location = new system.drawing.point(16, 24);
  this.label1.name = "label1";
  this.label1.size = new system.drawing.size(60, 17);
  this.label1.tabindex = 0;
  this.label1.text = "服务器ip:";
  // txtip
  this.txtip.location = new system.drawing.point(80, 24);
  this.txtip.name = "txtip";
  this.txtip.size = new system.drawing.size(160, 21);
  this.txtip.tabindex = 1;
  this.txtip.text = "";
  // label2
  this.label2.autosize = true;
  this.label2.location = new system.drawing.point(16, 56);
  this.label2.name = "label2";
  this.label2.size = new system.drawing.size(35, 17);
  this.label2.tabindex = 2;
  this.label2.text = "端口:";
  // txtport
  this.txtport.location = new system.drawing.point(80, 56);
  this.txtport.name = "txtport";
  this.txtport.size = new system.drawing.size(40, 21);
  this.txtport.tabindex = 3;
  this.txtport.text = "";
  // label3
  this.label3.autosize = true;
  this.label3.location = new system.drawing.point(16, 96);
  this.label3.name = "label3";
  this.label3.size = new system.drawing.size(110, 17);
  this.label3.tabindex = 4;
  this.label3.text = "发送信息给服务器:";
  // txtsendmsg
  this.txtsendmsg.location = new system.drawing.point(16, 120);
  this.txtsendmsg.name = "txtsendmsg";
  this.txtsendmsg.size = new system.drawing.size(224, 88);
  this.txtsendmsg.tabindex = 5;
  this.txtsendmsg.text = "";
  // label4
  this.label4.autosize = true;
  this.label4.location = new system.drawing.point(16, 248);
  this.label4.name = "label4";
  this.label4.size = new system.drawing.size(60, 17);
  this.label4.tabindex = 6;
  this.label4.text = "连接状态:";
  // status
  this.status.location = new system.drawing.point(80, 248);
  this.status.name = "status";
  this.status.size = new system.drawing.size(192, 23);
  this.status.tabindex = 7;
  // txtrecvmsg
  this.txtrecvmsg.location = new system.drawing.point(264, 80);
  this.txtrecvmsg.name = "txtrecvmsg";
  this.txtrecvmsg.readonly = true;
  this.txtrecvmsg.size = new system.drawing.size(224, 144);
  this.txtrecvmsg.tabindex = 8;
  this.txtrecvmsg.text = "";
  // button1
  this.button1.location = new system.drawing.point(280, 16);
  this.button1.name = "button1";
  this.button1.size = new system.drawing.size(88, 32);
  this.button1.tabindex = 9;
  this.button1.text = "连接";
  this.button1.click += new system.eventhandler(this.button1_click);
  // button2
  this.button2.location = new system.drawing.point(384, 16);
  this.button2.name = "button2";
  this.button2.size = new system.drawing.size(88, 32);
  this.button2.tabindex = 10;
  this.button2.text = "断开";
  this.button2.click += new system.eventhandler(this.button2_click);
  // button3
  this.button3.location = new system.drawing.point(280, 232);
  this.button3.name = "button3";
  this.button3.size = new system.drawing.size(88, 32);
  this.button3.tabindex = 11;
  this.button3.text = "清空信息";
  this.button3.click += new system.eventhandler(this.button3_click);
  // button4
  this.button4.location = new system.drawing.point(384, 232);
  this.button4.name = "button4";
  this.button4.size = new system.drawing.size(88, 32);
  this.button4.tabindex = 12;
  this.button4.text = "关闭";
  this.button4.click += new system.eventhandler(this.button4_click);
  // label5
  this.label5.autosize = true;
  this.label5.location = new system.drawing.point(264, 64);
  this.label5.name = "label5";
  this.label5.size = new system.drawing.size(134, 17);
  this.label5.tabindex = 13;
  this.label5.text = "收到服务器发来的信息:";
  // button5
  this.button5.location = new system.drawing.point(16, 208);
  this.button5.name = "button5";
  this.button5.size = new system.drawing.size(224, 32);
  this.button5.tabindex = 14;
  this.button5.text = "发送信息";
  this.button5.click += new system.eventhandler(this.button5_click);
  // form1
  this.autoscalebasesize = new system.drawing.size(6, 14);
  this.clientsize = new system.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 += new system.eventhandler(this.form1_load);
  this.resumelayout(false);
 }
 #endregion
 [stathread]
 static void main()
 {
  application.run(new form1());
 }
 // 连接按钮
 private void button1_click(object sender, system.eventargs e)
 {
  // ip地址和端口号不能为空
  if(txtip.text == "" || txtport.text == "")
  {
  messagebox.show("请先完整填写服务器ip地址和端口号!", "提示");
  return;
  }
  try
  {
  // 创建socket实例
  clientsocket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp);
  // 得到服务器的ip地址
  ipaddress ipaddress = ipaddress.parse(txtip.text);
  int32 port = int32.parse(txtport.text);
  // 创建远程终结点
  ipendpoint remoteep = new ipendpoint(ipaddress, port);
  // 连接到远程服务器
  clientsocket.connect(remoteep);
  if(clientsocket.connected)
  {
   updatecontrols(true);
   waitfordata(); // 异步等待数据
  }
  }
  catch(socketexception se)
  {
  messagebox.show (se.message, "提示");
  updatecontrols(false);
  }
 }
 // 等待数据
 public void waitfordata()
 {
  try
  {
  if(pfncallback == null)
  {
   // 当连接上的客户有写的操作的时候,调用回调函数
   pfncallback = new asynccallback(ondatareceived);
  }
  socketpacket socketpacket = new socketpacket();
  socketpacket.thissocket = clientsocket;
  result = clientsocket.beginreceive(socketpacket.databuffer, 0, socketpacket.databuffer.length,
   socketflags.none, pfncallback, socketpacket);
  }
  catch(socketexception se)
  {
  messagebox.show(se.message, "提示");
  }
 }
 // 该类保存socket以及发送给服务器的数据
 public class socketpacket
 {
  public system.net.sockets.socket thissocket;
  public byte[] databuffer = new byte[1024]; // 发给服务器的数据
 }
 // 接收数据
 public void ondatareceived(iasyncresult asyn)
 {
  try
  {
  socketpacket thesockid = (socketpacket)asyn.asyncstate ;
  // endreceive完成beginreceive异步调用,返回服务器写入流的字节数
  int irx = thesockid.thissocket.endreceive(asyn);
  // 加 1 是因为字符串以 '\0' 作为结束标志符
  char[] chars = new char[irx + 1];
  // 用utf8格式来将string信息转化成byte数组形式
  system.text.decoder decoder = system.text.encoding.utf8.getdecoder();
  int charlen = decoder.getchars(thesockid.databuffer, 0, irx, chars, 0);
  system.string szdata = new system.string(chars);
  // 将收到的信息显示在信息列表中
  txtrecvmsg.text = txtrecvmsg.text + szdata;
  // 等待数据
  waitfordata();
  }
  catch (objectdisposedexception)
  {
  system.diagnostics.debugger.log(0,"1","\nondatareceived: socket已经关闭!\n");
  }
  catch(socketexception se)
  {
  if(se.errorcode == 10054)
  {
   string msg = "服务器" + "停止服务!" + "\n";
   txtrecvmsg.text = txtrecvmsg.text + msg;
   clientsocket.close();
   clientsocket = null;
   updatecontrols(false);
  }
  else
  {
   messagebox.show(se.message, "提示");
  }
  }
 }
 // 更新控件。连接和断开(发送信息)按钮的状态是互斥的
 private void updatecontrols(bool connected)
 {
  button1.enabled = !connected;
  button2.enabled = connected;
  button5.enabled = connected;
  if(connected)
  {
  status.text = "已连接";
  }
  else
  {
  status.text = "无连接";
  }
 }
 // 断开按钮
 private void button2_click(object sender, system.eventargs e)
 {
  // 关闭socket
  if(clientsocket != null)
  {
  clientsocket.close();
  clientsocket = null;
  updatecontrols(false);
  }
 }
 // 发送信息按钮
 private void button5_click(object sender, system.eventargs e)
 {
  try
  {
  // 如果客户与服务器有连接,则允许发送信息
  if(clientsocket.connected)
  {
   string msg = txtsendmsg.text + "\n";
   // 用utf8格式来将string信息转化成byte数组形式
   byte[] bydata = system.text.encoding.utf8.getbytes(msg);
   if(clientsocket != null)
   {
   // 发送数据
   clientsocket.send(bydata);
   }
  }
  }
  catch(exception se)
  {
  messagebox.show(se.message, "提示");
  }
 }
 // 清空按钮
 private void button3_click(object sender, system.eventargs e)
 {
  txtrecvmsg.clear(); // 清空信息列表
 }
 // 关闭按钮
 private void button4_click(object sender, system.eventargs e)
 {
  // 关闭socket
  if(clientsocket != null)
  {
  clientsocket.close();
  clientsocket = null;
  }
  close(); // 关闭窗体
 }
 private void form1_load(object sender, system.eventargs e)
 {
  updatecontrols(false); // 初始化时,只有连接按钮可用
 }
 }
}