C# TCP实现多个客户端与服务端 数据 与 文件的传输
下面是我用C#写的 一个简单的TCP通信,主要的功能有:
(1) 多个客户端与服务器间的数据交流
(2)可以实现群发的功能
(3)客户端与服务端可以进行文件的传输
主要用到的知识: TCP里的 socket 、、、 多线程 Thread 、、、
下面的是界面:
服务端代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using System.Net.Sockets; 10 using System.Net; // IP,IPAddress, IPEndPoint,端口等; 11 using System.Threading; 12 using System.IO; 13 14 namespace _11111 15 { 16 public partial class frm_server : Form 17 { 18 public frm_server() 19 { 20 InitializeComponent(); 21 TextBox.CheckForIllegalCrossThreadCalls = false; 22 } 23 24 Thread threadWatch = null; // 负责监听客户端连接请求的 线程; 25 Socket socketWatch = null; 26 27 Dictionary<string, Socket> dict = new Dictionary<string, Socket>(); 28 Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>(); 29 30 private void btnBeginListen_Click(object sender, EventArgs e) 31 { 32 // 创建负责监听的套接字,注意其中的参数; 33 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 34 // 获得文本框中的IP对象; 35 IPAddress address = IPAddress.Parse(txtIp.Text.Trim()); 36 // 创建包含ip和端口号的网络节点对象; 37 IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); 38 try 39 { 40 // 将负责监听的套接字绑定到唯一的ip和端口上; 41 socketWatch.Bind(endPoint); 42 } 43 catch (SocketException se) 44 { 45 MessageBox.Show("异常:"+se.Message); 46 return; 47 } 48 // 设置监听队列的长度; 49 socketWatch.Listen(10); 50 // 创建负责监听的线程; 51 threadWatch = new Thread(WatchConnecting); 52 threadWatch.IsBackground = true; 53 threadWatch.Start(); 54 ShowMsg("服务器启动监听成功!"); 55 //} 56 } 57 58 /// <summary> 59 /// 监听客户端请求的方法; 60 /// </summary> 61 void WatchConnecting() 62 { 63 while (true) // 持续不断的监听客户端的连接请求; 64 { 65 // 开始监听客户端连接请求,Accept方法会阻断当前的线程; 66 Socket sokConnection = socketWatch.Accept(); // 一旦监听到一个客户端的请求,就返回一个与该客户端通信的 套接字; 67 // 想列表控件中添加客户端的IP信息; 68 lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString()); 69 // 将与客户端连接的 套接字 对象添加到集合中; 70 dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection); 71 ShowMsg("客户端连接成功!"); 72 Thread thr = new Thread(RecMsg); 73 thr.IsBackground = true; 74 thr.Start(sokConnection); 75 dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr); // 将新建的线程 添加 到线程的集合中去。 76 } 77 } 78 79 void RecMsg(object sokConnectionparn) 80 { 81 Socket sokClient = sokConnectionparn as Socket; 82 while (true) 83 { 84 // 定义一个2M的缓存区; 85 byte[] arrMsgRec = new byte[1024 * 1024 * 2]; 86 // 将接受到的数据存入到输入 arrMsgRec中; 87 int length = -1; 88 try 89 { 90 length = sokClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度; 91 } 92 catch (SocketException se) 93 { 94 ShowMsg("异常:" + se.Message); 95 // 从 通信套接字 集合中删除被中断连接的通信套接字; 96 dict.Remove(sokClient.RemoteEndPoint.ToString()); 97 // 从通信线程集合中删除被中断连接的通信线程对象; 98 dictThread.Remove(sokClient.RemoteEndPoint.ToString()); 99 // 从列表中移除被中断的连接IP 100 lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); 101 break; 102 } 103 catch (Exception e) 104 { 105 ShowMsg("异常:" + e.Message); 106 // 从 通信套接字 集合中删除被中断连接的通信套接字; 107 dict.Remove(sokClient.RemoteEndPoint.ToString()); 108 // 从通信线程集合中删除被中断连接的通信线程对象; 109 dictThread.Remove(sokClient.RemoteEndPoint.ToString()); 110 // 从列表中移除被中断的连接IP 111 lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); 112 break; 113 } 114 if (arrMsgRec[0] == 0) // 表示接收到的是数据; 115 { 116 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec,1, length-1);// 将接受到的字节数据转化成字符串; 117 ShowMsg(strMsg); 118 } 119 if (arrMsgRec[0] == 1) // 表示接收到的是文件; 120 { 121 SaveFileDialog sfd = new SaveFileDialog(); 122 123 if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) 124 {// 在上边的 sfd.ShowDialog() 的括号里边一定要加上 this 否则就不会弹出 另存为 的对话框,而弹出的是本类的其他窗口,,这个一定要注意!!!【解释:加了this的sfd.ShowDialog(this),“另存为”窗口的指针才能被SaveFileDialog的对象调用,若不加thisSaveFileDialog 的对象调用的是本类的其他窗口了,当然不弹出“另存为”窗口。】 125 126 string fileSavePath = sfd.FileName;// 获得文件保存的路径; 127 // 创建文件流,然后根据路径创建文件; 128 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 129 { 130 fs.Write(arrMsgRec, 1, length - 1); 131 ShowMsg("文件保存成功:" + fileSavePath); 132 } 133 } 134 } 135 } 136 } 137 138 void ShowMsg(string str) 139 { 140 txtMsg.AppendText(str + "\r\n"); 141 } 142 143 // 发送消息 144 private void btnSend_Click(object sender, EventArgs e) 145 { 146 string strMsg = "服务器" + "\r\n" + " -->" + txtMsgSend.Text.Trim() + "\r\n"; 147 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 将要发送的字符串转换成Utf-8字节数组; 148 byte[] arrSendMsg=new byte[arrMsg.Length+1]; 149 arrSendMsg[0] = 0; // 表示发送的是消息数据 150 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 151 string strKey = ""; 152 strKey = lbOnline.Text.Trim(); 153 if (string.IsNullOrEmpty(strKey)) // 判断是不是选择了发送的对象; 154 { 155 MessageBox.Show("请选择你要发送的好友!!!"); 156 } 157 else 158 { 159 dict[strKey].Send(arrSendMsg);// 解决了 sokConnection是局部变量,不能再本函数中引用的问题; 160 ShowMsg(strMsg); 161 txtMsgSend.Clear(); 162 } 163 } 164 165 /// <summary> 166 /// 群发消息 167 /// </summary> 168 /// <param name="sender"></param> 169 /// <param name="e">消息</param> 170 private void btnSendToAll_Click(object sender, EventArgs e) 171 { 172 string strMsg = "服务器" + "\r\n" + " -->" + txtMsgSend.Text.Trim() + "\r\n"; 173 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 将要发送的字符串转换成Utf-8字节数组;
174 }
1 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; // 上次写的时候把这一段给弄掉了,实在是抱歉哈~ 用来标识发送是数据而不是文件,如果没有这一段的客户端就接收不到消息了~~~ 2 arrSendMsg[0] = 0; // 表示发送的是消息数据 3 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
1 foreach (Socket s in dict.Values) 2 { 3 s.Send(arrMsg); 4 } 5 ShowMsg(strMsg); 6 txtMsgSend.Clear(); 7 ShowMsg(" 群发完毕~~~"); 8 } 9 10 // 选择要发送的文件 11 private void btnSelectFile_Click_1(object sender, EventArgs e) 12 { 13 OpenFileDialog ofd = new OpenFileDialog(); 14 ofd.InitialDirectory = "D:\\"; 15 if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) 16 { 17 txtSelectFile.Text = ofd.FileName; 18 } 19 } 20 21 // 文件的发送 22 private void btnSendFile_Click_1(object sender, EventArgs e) 23 { 24 if (string.IsNullOrEmpty(txtSelectFile.Text)) 25 { 26 MessageBox.Show("请选择你要发送的文件!!!"); 27 } 28 else 29 { 30 // 用文件流打开用户要发送的文件; 31 using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open)) 32 { 33 string fileName=System.IO.Path.GetFileName(txtSelectFile.Text); 34 string fileExtension=System.IO.Path.GetExtension(txtSelectFile.Text); 35 string strMsg = "我给你发送的文件为: "+fileName+fileExtension+"\r\n"; 36 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 将要发送的字符串转换成Utf-8字节数组; 37 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 38 arrSendMsg[0] = 0; // 表示发送的是消息数据 39 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 40 bool fff = true; 41 string strKey = ""; 42 strKey = lbOnline.Text.Trim(); 43 if (string.IsNullOrEmpty(strKey)) // 判断是不是选择了发送的对象; 44 { 45 MessageBox.Show("请选择你要发送的好友!!!"); 46 } 47 else 48 { 49 dict[strKey].Send(arrSendMsg);// 解决了 sokConnection是局部变量,不能再本函数中引用的问题; 50 byte[] arrFile = new byte[1024 * 1024 * 2]; 51 int length = fs.Read(arrFile, 0, arrFile.Length); // 将文件中的数据读到arrFile数组中; 52 byte[] arrFileSend = new byte[length + 1]; 53 arrFileSend[0] = 1; // 用来表示发送的是文件数据; 54 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length); 55 // 还有一个 CopyTo的方法,但是在这里不适合; 当然还可以用for循环自己转化; 56 // sockClient.Send(arrFileSend);// 发送数据到服务端; 57 dict[strKey].Send(arrFileSend);// 解决了 sokConnection是局部变量,不能再本函数中引用的问题; 58 txtSelectFile.Clear(); 59 } 60 } 61 } 62 txtSelectFile.Clear(); 63 } 64 65 } 66 }
客户端代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 using System.Net; 11 using System.Net.Sockets; 12 using System.Threading; 13 using System.IO; 14 15 namespace _2222222 16 { 17 public partial class frmClient : Form 18 { 19 public frmClient() 20 { 21 InitializeComponent(); 22 TextBox.CheckForIllegalCrossThreadCalls = false; 23 } 24 25 Thread threadClient = null; // 创建用于接收服务端消息的 线程; 26 Socket sockClient = null; 27 private void btnConnect_Click(object sender, EventArgs e) 28 { 29 IPAddress ip = IPAddress.Parse(txtIp.Text.Trim()); 30 IPEndPoint endPoint=new IPEndPoint (ip,int.Parse(txtPort.Text.Trim())); 31 sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 32 try 33 { 34 ShowMsg("与服务器连接中……"); 35 sockClient.Connect(endPoint); 36 37 } 38 catch (SocketException se) 39 { 40 MessageBox.Show(se.Message); 41 return; 42 //this.Close(); 43 } 44 ShowMsg("与服务器连接成功!!!"); 45 threadClient = new Thread(RecMsg); 46 threadClient.IsBackground = true; 47 threadClient.Start(); 48 49 } 50 51 void RecMsg() 52 { 53 while (true) 54 { 55 // 定义一个2M的缓存区; 56 byte[] arrMsgRec = new byte[1024 * 1024 * 2]; 57 // 将接受到的数据存入到输入 arrMsgRec中; 58 int length = -1; 59 try 60 { 61 length = sockClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度; 62 } 63 catch (SocketException se) 64 { 65 ShowMsg("异常;" + se.Message); 66 return; 67 } 68 catch (Exception e) 69 { 70 ShowMsg("异常:"+e.Message); 71 return; 72 } 73 if (arrMsgRec[0] == 0) // 表示接收到的是消息数据; 74 { 75 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1);// 将接受到的字节数据转化成字符串; 76 ShowMsg(strMsg); 77 } 78 if (arrMsgRec[0] == 1) // 表示接收到的是文件数据; 79 { 80 81 try 82 { 83 SaveFileDialog sfd = new SaveFileDialog(); 84 85 if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) 86 {// 在上边的 sfd.ShowDialog() 的括号里边一定要加上 this 否则就不会弹出 另存为 的对话框,而弹出的是本类的其他窗口,,这个一定要注意!!!【解释:加了this的sfd.ShowDialog(this),“另存为”窗口的指针才能被SaveFileDialog的对象调用,若不加thisSaveFileDialog 的对象调用的是本类的其他窗口了,当然不弹出“另存为”窗口。】 87 88 string fileSavePath = sfd.FileName;// 获得文件保存的路径; 89 // 创建文件流,然后根据路径创建文件; 90 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 91 { 92 fs.Write(arrMsgRec, 1, length - 1); 93 ShowMsg("文件保存成功:" + fileSavePath); 94 } 95 } 96 } 97 catch (Exception aaa) 98 { 99 MessageBox.Show(aaa.Message); 100 } 101 } 102 } 103 } 104 void ShowMsg(string str) 105 { 106 txtMsg.AppendText(str + "\r\n"); 107 } 108 109 // 发送消息; 110 private void btnSendMsg_Click(object sender, EventArgs e) 111 { 112 string strMsg = txtName.Text.Trim()+"\r\n"+" -->"+ txtSendMsg.Text.Trim()+ "\r\n"; 113 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 114 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 115 arrSendMsg[0] = 0; // 用来表示发送的是消息数据 116 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 117 sockClient.Send(arrSendMsg); // 发送消息; 118 ShowMsg(strMsg); 119 txtSendMsg.Clear(); 120 } 121 122 // 选择要发送的文件; 123 private void btnSelectFile_Click(object sender, EventArgs e) 124 { 125 OpenFileDialog ofd = new OpenFileDialog(); 126 ofd.InitialDirectory = "D:\\"; 127 if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) 128 { 129 txtSelectFile.Text = ofd.FileName; 130 } 131 } 132 133 //向服务器端发送文件 134 private void btnSendFile_Click(object sender, EventArgs e) 135 { 136 if (string.IsNullOrEmpty(txtSelectFile.Text)) 137 { 138 MessageBox.Show("请选择要发送的文件!!!"); 139 } 140 else 141 { 142 // 用文件流打开用户要发送的文件; 143 using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open)) 144 { 145 //在发送文件以前先给好友发送这个文件的名字+扩展名,方便后面的保存操作; 146 string fileName = System.IO.Path.GetFileName(txtSelectFile.Text); 147 string fileExtension = System.IO.Path.GetExtension(txtSelectFile.Text); 148 string strMsg = "我给你发送的文件为: " + fileName + "\r\n"; 149 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 150 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 151 arrSendMsg[0] = 0; // 用来表示发送的是消息数据 152 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 153 sockClient.Send(arrSendMsg); // 发送消息; 154 155 byte[] arrFile = new byte[1024 * 1024 * 2]; 156 int length = fs.Read(arrFile, 0, arrFile.Length); // 将文件中的数据读到arrFile数组中; 157 byte[] arrFileSend = new byte[length + 1]; 158 arrFileSend[0] = 1; // 用来表示发送的是文件数据; 159 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length); 160 // 还有一个 CopyTo的方法,但是在这里不适合; 当然还可以用for循环自己转化; 161 sockClient.Send(arrFileSend);// 发送数据到服务端; 162 txtSelectFile.Clear(); 163 } 164 } 165 } 166 } 167 }
原文出自:https://blog.csdn.net/chwei_cson/article/details/7737766
对于不可控的事情,保持乐观;
对于可控的事情,保持谨慎