基于C# Socket实现多人网络聊天室
首先不多说,最终实现界面如下,可以通过点击启动服务,开启TCP服务器:
开启TCP服务器之后,可以通过点击客户端,打开一个独立的TCP客户端,打开客户端之后,输入正确的IP地址和端口号,可以进行连接服务器,这里可以同时开启多个客户端:
每个客户端连接成功后,服务器的列表中会多出一个EndPoint,连接成功后,服务器和客户端之间就可以自由通话了,可以发送消息,也可以发送文件。
其实这就是QQ等即时通信工具的雏形,如果两个客户端之间需要通信,就通过服务器进行中转。
由于服务器代码量较大,下面上传一下客户端的代码,仅供参考:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Net; 9 using System.Net.Sockets; 10 using System.Text; 11 using System.Threading; 12 using System.Threading.Tasks; 13 using System.Windows.Forms; 14 15 namespace MyTCPServer 16 { 17 delegate void FileSaveDelegate(byte[] bt,int length); 18 19 public partial class FrmClient : Form 20 { 21 public FrmClient() 22 { 23 InitializeComponent(); 24 25 //委托对象绑定方法 26 MyShowMsg += ShowMsg; 27 28 MyFileSave += FileSave; 29 } 30 31 //运行标志位 32 private bool IsRun = true; 33 34 //创建连接服务器的Socket 35 Socket sockClient = null; 36 37 //创建接收服务器消息的线程 38 Thread thrClient = null; 39 40 //创建委托对象 41 ShowMsgDelegate MyShowMsg; 42 43 FileSaveDelegate MyFileSave; 44 45 private void btnConnect_Click(object sender, EventArgs e) 46 { 47 //获取IP对象 48 IPAddress address = IPAddress.Parse(this.txtIp.Text.Trim()); 49 50 //根据IP对象和端口号创建网络节点对象 51 IPEndPoint endPoint = new IPEndPoint(address, int.Parse(this.txtPort.Text.Trim())); 52 53 //创建负责连接的套接字,注意其中参数:[IPV4地址,字节流,TCP协议] 54 sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 55 56 try 57 { 58 Invoke(MyShowMsg, "与服务器连接中......"); 59 sockClient.Connect(endPoint); 60 } 61 catch (Exception ex) 62 { 63 MessageBox.Show("建立连接失败:" + ex.Message, "建立连接"); 64 return; 65 } 66 67 Invoke(MyShowMsg, "与服务器连接成功!"); 68 69 thrClient = new Thread(ReceiveMsg); 70 thrClient.IsBackground = true; 71 thrClient.Start(); 72 73 this.btnConnect.Enabled = false; 74 } 75 76 private void FileSave(byte[] arrMsgRec, int length) 77 { 78 try 79 { 80 SaveFileDialog sfd = new SaveFileDialog(); 81 82 if (sfd.ShowDialog(this) == DialogResult.OK) 83 { 84 85 string fileSavePath = sfd.FileName;// 获得文件保存的路径; 86 // 创建文件流,然后根据路径创建文件; 87 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 88 { 89 fs.Write(arrMsgRec, 1, length - 1); 90 Invoke(MyShowMsg, "文件保存成功:" + fileSavePath); 91 } 92 } 93 } 94 catch (Exception ex) 95 { 96 MessageBox.Show(ex.Message); 97 } 98 99 } 100 101 private void ReceiveMsg() 102 { 103 while(IsRun) 104 { 105 // 定义一个2M的缓存区 106 byte[] arrMsgRec = new byte[1024 * 1024 * 2]; 107 108 // 将接受到的数据存入到输入 arrMsgRec中 109 int length = -1; 110 try 111 { 112 length = sockClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度; 113 } 114 catch (SocketException) 115 { 116 return; 117 } 118 catch (Exception e) 119 { 120 Invoke(MyShowMsg, "连接断开:" + e.Message); 121 return; 122 } 123 if (arrMsgRec[0] == 0) // 表示接收到的是消息数据; 124 { 125 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length - 1);// 将接受到的字节数据转化成字符串; 126 Invoke(MyShowMsg, strMsg); 127 } 128 if (arrMsgRec[0] == 1) // 表示接收到的是文件数据; 129 { 130 Invoke(MyFileSave, arrMsgRec, length); 131 } 132 } 133 } 134 135 private void ShowMsg(string str) 136 { 137 txtMsg?.AppendText(str + Environment.NewLine); 138 } 139 140 private void btnSend_Click(object sender, EventArgs e) 141 { 142 string strMsg = txt_Name.Text.Trim() + Environment.NewLine + " -->" + txtMsgSend.Text.Trim() + Environment.NewLine; 143 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 144 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 145 arrSendMsg[0] = 0; // 用来表示发送的是消息数据 146 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 147 sockClient.Send(arrSendMsg); // 发送消息; 148 Invoke(MyShowMsg, strMsg); 149 txtMsgSend.Clear(); 150 } 151 152 private void btnSendFile_Click(object sender, EventArgs e) 153 { 154 if (string.IsNullOrEmpty(txtSelectFile.Text)) 155 { 156 MessageBox.Show("请选择要发送的文件!!!"); 157 } 158 else 159 { 160 // 用文件流打开用户要发送的文件; 161 using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open)) 162 { 163 //在发送文件以前先给好友发送这个文件的名字+扩展名,方便后面的保存操作; 164 string fileName = System.IO.Path.GetFileName(txtSelectFile.Text); 165 string fileExtension = System.IO.Path.GetExtension(txtSelectFile.Text); 166 string strMsg = "发送的文件为: " + fileName + Environment.NewLine; 167 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 168 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 169 arrSendMsg[0] = 0; // 用来表示发送的是消息数据 170 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 171 sockClient.Send(arrSendMsg); // 发送消息; 172 173 byte[] arrFile = new byte[1024 * 1024 * 2]; 174 int length = fs.Read(arrFile, 0, arrFile.Length); // 将文件中的数据读到arrFile数组中; 175 byte[] arrFileSend = new byte[length + 1]; 176 arrFileSend[0] = 1; // 用来表示发送的是文件数据; 177 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length); 178 // 还有一个 CopyTo的方法,但是在这里不适合; 当然还可以用for循环自己转化; 179 sockClient.Send(arrFileSend);// 发送数据到服务端; 180 txtSelectFile.Clear(); 181 } 182 } 183 } 184 185 private void btnSelectFile_Click(object sender, EventArgs e) 186 { 187 OpenFileDialog ofd = new OpenFileDialog(); 188 ofd.InitialDirectory = "D:\\"; 189 if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) 190 { 191 txtSelectFile.Text = ofd.FileName; 192 } 193 } 194 195 private void FrmClient_FormClosing(object sender, FormClosingEventArgs e) 196 { 197 IsRun = false; 198 sockClient?.Close(); 199 } 200 } 201 }
如果大家还有什么不明白的地方,可以关注一下微信公众号:dotNet工控上位机