C# TCP实现多个客户端与服务端 数据 与 文件的传输

时间:2021-03-22 04:10:34

C#菜鸟做这个东东竟然花了*天的时间了,真是菜,菜,菜~~~C# TCP实现多个客户端与服务端 数据 与 文件的传输

下面是我用C#写的 一个简单的TCP通信,主要的功能有:

(1) 多个客户端与服务器间的数据交流

(2)可以实现群发的功能

(3)客户端与服务端可以进行文件的传输

主要用到的知识: TCP里的 socket 、、、 多线程 Thread 、、、

下面的是界面:

C# TCP实现多个客户端与服务端 数据 与 文件的传输

C# TCP实现多个客户端与服务端 数据 与 文件的传输

下面分别是服务端和客户端的代码,如若借用,请标明出处~~~

服务端代码:

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

客户端代码:

    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;
    10. using System.Net.Sockets;
    11. using System.Threading;
    12. using System.IO;
    13. namespace _2222222
    14. {
    15. public partial class frmClient : Form
    16. {
    17. public frmClient()
    18. {
    19. InitializeComponent();
    20. TextBox.CheckForIllegalCrossThreadCalls = false;
    21. }
    22. Thread threadClient = null; // 创建用于接收服务端消息的 线程;
    23. Socket sockClient = null;
    24. private void btnConnect_Click(object sender, EventArgs e)
    25. {
    26. IPAddress ip = IPAddress.Parse(txtIp.Text.Trim());
    27. IPEndPoint endPoint=new IPEndPoint (ip,int.Parse(txtPort.Text.Trim()));
    28. sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    29. try
    30. {
    31. ShowMsg("与服务器连接中……");
    32. sockClient.Connect(endPoint);
    33. }
    34. catch (SocketException se)
    35. {
    36. MessageBox.Show(se.Message);
    37. return;
    38. //this.Close();
    39. }
    40. ShowMsg("与服务器连接成功!!!");
    41. threadClient = new Thread(RecMsg);
    42. threadClient.IsBackground = true;
    43. threadClient.Start();
    44. }
    45. void RecMsg()
    46. {
    47. while (true)
    48. {
    49. // 定义一个2M的缓存区;
    50. byte[] arrMsgRec = new byte[1024 * 1024 * 2];
    51. // 将接受到的数据存入到输入  arrMsgRec中;
    52. int length = -1;
    53. try
    54. {
    55. length = sockClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度;
    56. }
    57. catch (SocketException se)
    58. {
    59. ShowMsg("异常;" + se.Message);
    60. return;
    61. }
    62. catch (Exception e)
    63. {
    64. ShowMsg("异常:"+e.Message);
    65. return;
    66. }
    67. if (arrMsgRec[0] == 0) // 表示接收到的是消息数据;
    68. {
    69. string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1);// 将接受到的字节数据转化成字符串;
    70. ShowMsg(strMsg);
    71. }
    72. if (arrMsgRec[0] == 1) // 表示接收到的是文件数据;
    73. {
    74. try
    75. {
    76. SaveFileDialog sfd = new SaveFileDialog();
    77. if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
    78. {// 在上边的 sfd.ShowDialog() 的括号里边一定要加上 this 否则就不会弹出 另存为 的对话框,而弹出的是本类的其他窗口,,这个一定要注意!!!【解释:加了this的sfd.ShowDialog(this),“另存为”窗口的指针才能被SaveFileDialog的对象调用,若不加thisSaveFileDialog 的对象调用的是本类的其他窗口了,当然不弹出“另存为”窗口。】
    79. string fileSavePath = sfd.FileName;// 获得文件保存的路径;
    80. // 创建文件流,然后根据路径创建文件;
    81. using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
    82. {
    83. fs.Write(arrMsgRec, 1, length - 1);
    84. ShowMsg("文件保存成功:" + fileSavePath);
    85. }
    86. }
    87. }
    88. catch (Exception aaa)
    89. {
    90. MessageBox.Show(aaa.Message);
    91. }
    92. }
    93. }
    94. }
    95. void ShowMsg(string str)
    96. {
    97. txtMsg.AppendText(str + "\r\n");
    98. }
    99. // 发送消息;
    100. private void btnSendMsg_Click(object sender, EventArgs e)
    101. {
    102. string strMsg = txtName.Text.Trim()+"\r\n"+"    -->"+ txtSendMsg.Text.Trim()+ "\r\n";
    103. byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
    104. byte[] arrSendMsg = new byte[arrMsg.Length + 1];
    105. arrSendMsg[0] = 0; // 用来表示发送的是消息数据
    106. Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
    107. sockClient.Send(arrSendMsg); // 发送消息;
    108. ShowMsg(strMsg);
    109. txtSendMsg.Clear();
    110. }
    111. // 选择要发送的文件;
    112. private void btnSelectFile_Click(object sender, EventArgs e)
    113. {
    114. OpenFileDialog ofd = new OpenFileDialog();
    115. ofd.InitialDirectory = "D:\\";
    116. if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    117. {
    118. txtSelectFile.Text = ofd.FileName;
    119. }
    120. }
    121. //向服务器端发送文件
    122. private void btnSendFile_Click(object sender, EventArgs e)
    123. {
    124. if (string.IsNullOrEmpty(txtSelectFile.Text))
    125. {
    126. MessageBox.Show("请选择要发送的文件!!!");
    127. }
    128. else
    129. {
    130. // 用文件流打开用户要发送的文件;
    131. using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open))
    132. {
    133. //在发送文件以前先给好友发送这个文件的名字+扩展名,方便后面的保存操作;
    134. string fileName = System.IO.Path.GetFileName(txtSelectFile.Text);
    135. string fileExtension = System.IO.Path.GetExtension(txtSelectFile.Text);
    136. string strMsg = "我给你发送的文件为: " + fileName + "\r\n";
    137. byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
    138. byte[] arrSendMsg = new byte[arrMsg.Length + 1];
    139. arrSendMsg[0] = 0; // 用来表示发送的是消息数据
    140. Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
    141. sockClient.Send(arrSendMsg); // 发送消息;
    142. byte[] arrFile = new byte[1024 * 1024 * 2];
    143. int length = fs.Read(arrFile, 0, arrFile.Length);  // 将文件中的数据读到arrFile数组中;
    144. byte[] arrFileSend = new byte[length + 1];
    145. arrFileSend[0] = 1; // 用来表示发送的是文件数据;
    146. Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length);
    147. // 还有一个 CopyTo的方法,但是在这里不适合; 当然还可以用for循环自己转化;
    148. sockClient.Send(arrFileSend);// 发送数据到服务端;
    149. txtSelectFile.Clear();
    150. }
    151. }
    152. }
    153. }
    154. }  
      1. 转自http://blog.csdn.net/chwei_cson/article/details/7737766