基于socket、多线程的客户端服务器端聊天程序

服务器端:

基于socket、多线程的客户端服务器端聊天程序

  1. using System;
  2. using System.Windows.Forms;
  3. using System.Net.Sockets;
  4. using System.Net;//IPAddress,IPEndPoint(ip和端口)类
  5. using System.Threading;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. namespace MyChatRoomServer
  9. {
  10. public partial class Server : Form
  11. {
  12. public Server()
  13. {
  14. InitializeComponent();
  15. //一个线程正在访问当前UI线程,要关闭微软设置的对文本框操作的检查
  16. TextBox.CheckForIllegalCrossThreadCalls = false;
  17. }
  18. Thread threadWatch = null;//负责监听客户端连接请求的线程
  19. Socket socketWatch = null;//负责监听的套接字
  20. private void btnBeginListen_Click(object sender, EventArgs e)
  21. {
  22. //创建服务端负责监听的套接字,参数(IPV4寻址协议,使用流格式,使用Tcp传输协议)
  23. socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
  24. //获得文本框中IP地址对象
  25. IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
  26. //获取端口号
  27. IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
  28. //将负责监听的套接字绑定到唯一的IP和端口上
  29. socketWatch.Bind(endpoint);
  30. //设置监听队列的长度,意思就是同时发送请求只能10个
  31. socketWatch.Listen(10);
  32. //创建一个新的套接字专门负责跟客户端通信,注意:Accept方法会阻塞当前程序
  33. //Socket sockConnection = socketWatch.Accept();
  34. //创建负责监听的线程,并传入监听的方法
  35. threadWatch = new Thread(WatchConnecting);
  36. threadWatch.IsBackground = true;//设置为后台线程,只要前台的一结束,那么程序就结束
  37. threadWatch.Start();//启动线程
  38. ShowMsg("服务器启动监听成功!");
  39. }
  40. //保存了服务器端所有负责和客户端通信的套接字
  41. Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
  42. //保存了服务器端所有负责调用通信套接字Recive方法的线程
  43. Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();
  44. //Socket sokConnection = null;//负责通信的套接字
  45. /// <summary>
  46. /// 监听客户端请求的方法
  47. /// </summary>
  48. void WatchConnecting()
  49. {
  50. while (true) //加上循环持续不断的监听新的客户端的连接
  51. {
  52. //开始监听客户端连接请求,注意:Accept方法会阻断当时线程
  53. Socket sokConnection = socketWatch.Accept();
  54. //将列表控件中追加一个客户端的ip端口字符串,作为客户端的唯一标识
  55. lblOnline.Items.Add(sokConnection.RemoteEndPoint.ToString());
  56. //将与客户端通信的套接字对象sokConnection添加到键值对集合中,并以客户端IP端口作为键
  57. dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection);
  58. //创建一个委托
  59. ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg);
  60. //创建通信线程
  61. Thread thr = new Thread(pts);
  62. thr.IsBackground = true; //设置为后台
  63. thr.Start(sokConnection); //将线程的参数传入
  64. dictThread.Add(sokConnection.RemoteEndPoint.ToString(),thr);
  65. ShowMsg("客户端连接成功!"+sokConnection.RemoteEndPoint.ToString());
  66. }
  67. }
  68. /// <summary>
  69. /// 服务端负责监听客户端发来的数据方法
  70. /// </summary>
  71. void RecMsg(object socketClientPara)
  72. {
  73. Socket socketClient = socketClientPara as Socket;
  74. while (true)
  75. {
  76. //定义一个2M的接受数据的缓存区
  77. byte[] arrMsgRec = new byte[1024 * 1024 * 2];//手动准备2M空间
  78. //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度
  79. int length = socketClient.Receive(arrMsgRec);
  80. try
  81. {
  82. length = socketClient.Receive(arrMsgRec);
  83. }
  84. catch (SocketException ex)
  85. {
  86. ShowMsg("异常" + ex.Message+",RemoteEnd=" + socketClient.RemoteEndPoint.ToString());
  87. //从通信套接字集合中删除被中断连接的通信套接字
  88. dict.Remove(socketClient.RemoteEndPoint.ToString());//删除异常的端口和IP
  89. //从通信线程中删除被中断的连接通信线程对象
  90. dictThread.Remove(socketClient.RemoteEndPoint.ToString());//删除异常的线程
  91. //有异常则跳出,不执行后面的
  92. //从列表中删除被中断的连接IP:port
  93. lblOnline.Items.Remove(socketClient.RemoteEndPoint.ToString());
  94. break;
  95. //return;
  96. }
  97. catch (Exception ex)
  98. {
  99. ShowMsg("异常" + ex.Message);
  100. //有异常则跳出,不执行后面的
  101. return;
  102. }
  103. if (arrMsgRec[0] == 0)//判断传过来的第一个数据是0,则代表是文本数据
  104. {
  105. //将数组转成字符串,此时是将数组所有的元素都转成字符串,而真正接收到的只是服务器端传来的一些字符
  106. string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1);
  107. ShowMsg(strMsgRec);
  108. }
  109. //如果是1,则代表发送过来的是文件数据(文件/图片...)
  110. else if (arrMsgRec[0] == 1)
  111. {
  112. //保存文件选择框对象
  113. SaveFileDialog sfd = new SaveFileDialog();
  114. if(sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
  115. {
  116. string fileSavePath = sfd.FileName;//获得文件保存路径
  117. //创建文件流,让文件流来根据路径创建一个文件
  118. using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
  119. {
  120. fs.Write(arrMsgRec,1,length-1);
  121. ShowMsg("文件保存成功:"+fileSavePath);
  122. }
  123. }
  124. }
  125. }
  126. }
  127. void ShowMsg(string msg)
  128. {
  129. txtMsg.AppendText(msg + "\r\n");
  130. }
  131. private void btnCloseServer_Click(object sender, EventArgs e)
  132. {
  133. //threadWatch.Abort();//关闭线程
  134. //ShowMsg("服务器启动监听成功!");
  135. }
  136. //发送消息到客户端
  137. private void btnSend_Click(object sender, EventArgs e)
  138. {
  139. if (string.IsNullOrEmpty(lblOnline.Text))
  140. {
  141. MessageBox.Show("请在左侧选择要发送的好友");
  142. }
  143. else
  144. {
  145. string strMsg = txtMsgSend.Text.Trim();
  146. //将要发送的字符串转成utf8对应的字节数组
  147. byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
  148. string strClientKey = lblOnline.Text;
  149. //通过key找到字典集合中对应的与某个用户客户端通信的套接字的send方法,发数据给对方
  150. try
  151. {
  152. dict[strClientKey].Send(arrMsg);
  153. //一旦上面的出现异常,下面的就不执行
  154. ShowMsg("发送了数据出去:" + strMsg);
  155. }
  156. //sokConnection.Send(arrMsg);
  157. catch (SocketException ex)
  158. {
  159. ShowMsg("发送时异常:"+ex.Message);
  160. return;
  161. }
  162. catch (Exception ex)
  163. {
  164. ShowMsg("发送时异常:" + ex.Message);
  165. return;
  166. }
  167. }
  168. }
  169. //服务器群发消息
  170. private void btnSendToAll_Click(object sender, EventArgs e)
  171. {
  172. string strMsg = txtMsgSend.Text.Trim();
  173. //将要发送的字符串转成utf8对应的字节数组
  174. byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
  175. //便利当前字典里面所有的通信套接字
  176. foreach (Socket s in dict.Values)
  177. {
  178. s.Send(arrMsg);
  179. }
  180. ShowMsg("群发完毕!:)");
  181. }
  182. }
  183. }

客户端:

基于socket、多线程的客户端服务器端聊天程序

    1. using System;
    2. using System.Windows.Forms;
    3. using System.Net;
    4. using System.Net.Sockets;
    5. using System.Threading;
    6. using System.IO;
    7. namespace MyChatRoomClient
    8. {
    9. public partial class FChatClient : Form
    10. {
    11. public FChatClient()
    12. {
    13. InitializeComponent();
    14. //一个线程正在访问当前UI线程,要关闭微软设置的对文本框操作的检查
    15. //关闭跨线程检查
    16. TextBox.CheckForIllegalCrossThreadCalls = false;
    17. }
    18. IPAddress address = null;
    19. IPEndPoint endpoint = null;
    20. Socket socketClient = null; //客户端套接字
    21. Thread threadClient = null;//客户端负责接受服务端发来的消息的线程
    22. //客户端发送请求到服务器
    23. private void btnConnect_Click(object sender, EventArgs e)
    24. {
    25. address = IPAddress.Parse(txtIP.Text.Trim());
    26. endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
    27. socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    28. try
    29. {
    30. socketClient.Connect(endpoint);
    31. //创建线程,监听服务器端发来的消息
    32. threadClient = new Thread(RecMsg);
    33. threadClient.IsBackground = true;
    34. threadClient.Start();
    35. }
    36. catch (Exception ee)
    37. {
    38. MessageBox.Show(ee.ToString());
    39. }
    40. }
    41. /// <summary>
    42. /// 监听服务器端发来的消息
    43. /// </summary>
    44. void RecMsg()
    45. {
    46. while (true)
    47. {
    48. //定义一个2M的接受数据的缓存区
    49. byte[] arrMsgRec = new byte[1024 * 1024 * 2];//手动准备2M空间
    50. //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度
    51. int length = socketClient.Receive(arrMsgRec);
    52. //将数组转成字符串,此时是将数组所有的元素都转成字符串,而真正接收到的只是服务器端传来的一些字符
    53. string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec,0,length);
    54. ShowMsg(strMsgRec);
    55. }
    56. }
    57. #region 在窗体文本框中显示消息-void ShowMsg(string msg)
    58. /// <summary>
    59. /// 在窗体文本框中显示消息
    60. /// </summary>
    61. /// <param name="msg">消息</param>
    62. void ShowMsg(string msg)
    63. {
    64. txtMsg.AppendText(msg + "\r\n");
    65. }
    66. #endregion
    67. #region 选择要发送的文件-btnChooseFile_Click
    68. //选择要发送的文件
    69. private void btnChooseFile_Click(object sender, EventArgs e)
    70. {
    71. OpenFileDialog ofd = new OpenFileDialog();
    72. if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    73. {
    74. txtFilePath.Text = ofd.FileName;
    75. }
    76. }
    77. #endregion
    78. //向服务器发送文本消息
    79. private void btnSendMsg_Click(object sender, EventArgs e)
    80. {
    81. string strMsg = txtMsgSend.Text.Trim();
    82. byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
    83. byte[] arrMsgSend = new byte[arrMsg.Length + 1];
    84. arrMsgSend[0] = 0;//设置标识位,0代表是文字
    85. Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1, arrMsg.Length);
    86. socketClient.Send(arrMsgSend);
    87. ShowMsg("我发送了:" + strMsg);
    88. }
    89. //向服务端发送文件
    90. private void btnSendFile_Click(object sender, EventArgs e)
    91. {
    92. //用文件流打开用户选择的文件
    93. using (FileStream fs = new FileStream(txtFilePath.Text, FileMode.Open))
    94. {
    95. byte[] arrFile = new byte[1024*1024*2];//定义一个2M缓存区
    96. //将文件数据读到数组arrFile中,并获得读取的真是数据长度
    97. int length = fs.Read(arrFile,0,arrFile.Length);
    98. byte[] arrFileSend = new byte[length + 1];
    99. arrFileSend[0] = 1;//代表发送的是文件数据
    100. //for (int i = 0; i < length; i++)
    101. //{
    102. //    arrFileSend[i + 1] = arrFile[i];
    103. //}
    104. //数据块的拷贝,将arrFile从第0个开始拷贝,拷贝到arrFileSend,从第一个开始存放
    105. Buffer.BlockCopy(arrFile,0,arrFileSend,1,length);
    106. //arrFile.CopyTo(arrFileSend,length);只能从0开始拷贝
    107. //发送了包含了标识位的新数据到服务端
    108. socketClient.Send(arrFileSend);
    109. }
    110. }
    111. }
    112. }
上一篇:通过一个例子学习Kubernetes里的PersistentVolumeClaim的用法


下一篇:PMF文件说明