C#聊天程序服务端与客户端完整实例代码
程序员文章站
2023-12-18 16:24:22
本文所述为基于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); // 初始化时,只有连接按钮可用 } } }