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 + "
");
141         }
142  
143         // 发送消息
144         private void btnSend_Click(object sender, EventArgs e)
145         {
146             string strMsg = "服务器" + "
" + "   -->" + txtMsgSend.Text.Trim() + "
";
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 = "服务器" + "
" + "   -->" + txtMsgSend.Text.Trim() + "
";
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+"
";
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 + "
");
107         }
108  
109          // 发送消息;
110         private void btnSendMsg_Click(object sender, EventArgs e)
111         {
112             string strMsg = txtName.Text.Trim()+"
"+"    -->"+ txtSendMsg.Text.Trim()+ "
";
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 + "
";
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

对于不可控的事情,保持乐观; 对于可控的事情,保持谨慎
原文地址:https://www.cnblogs.com/baylor2019/p/13801563.html