当前位置:   article > 正文

C#知识点-13(进程、多线程、使用Socket实现服务器与客户端通信)

C#知识点-13(进程、多线程、使用Socket实现服务器与客户端通信)

进程

定义:每一个正在运行的应用程序,都是一个进程 
进程不等于正在运行的应用程序。而是为应用程序的运行构建一个运行环境

  1. Process[] pros = Process.GetProcesses();//获取电脑中所有正在运行的进程
  2. //通过进程,直接打开文件
  3. //告诉进程,要打开的文件路径,通过PSI对象进行封装
  4. ProcessStartInfo psi = new ProcessStartInfo(@"C:\Users\ThinkPad\Desktop\1.txt");
  5. Process p = new Process();
  6. p.StartInfo = psi;
  7. p.Start();

多线程

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3. Test();
  4. }
  5. private void Test()
  6. {
  7. for (int i = 0; i < 100000; i++)
  8. {
  9. textBox1.Text = i.ToString();
  10. }
  11. }

这段代码在执行完成之前,程序会被卡死(不能操作程序,包括关闭窗口)。因为我们程序在做一些耗时操作的时候,如果主线程去执行某段代码,就没有其余的“精力”去完成其他的操作了。
这时候,我们就需要用到多线程,再新建一个线程来完成耗时操作

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3. Thread th = new Thread(Test);
  4. th.Start();
  5. }
  6. private void Test(object str)//如果线程执行的方法,需要参数,我们要求参数的类型必须是object类型
  7. {
  8. for (int i = 0; i < 100000; i++)
  9. {
  10. textBox1.Text = i.ToString();
  11. }
  12. }

但是使用多线程,也会有很多需要注意的地方。这段代码执行时会提示异常,显示“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”
因为创建textBox1的是主线程,而你创建了一个新的线程th,th调用Test方法会访问主线程创建的控件,这个操作默认是不允许的,我们不允许跨线程的访问,因为这是不安全的。

而如果强制要跨线程访问的话,使用下面这段代码在主窗体加载的时候

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3. //取消跨线程访问的检查
  4. Control.CheckForIllegalCrossThreadCalls = false;
  5. }

但是这样还是有问题,当你运行程序的时候,点击按钮开始计数。如果你在计数途中点击叉号关闭程序,程序还是再运行。这是因为你关闭了主线程(主窗体),但是另一个线程th还在执行。
这时候我们把th这个线程由前台线程转换为后台线程。
前台线程:只有所有的前台线程关闭,程序才关闭
后台线程:只要所有的前台线程结束,后台线程自动结束

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3. Thread th = new Thread(Test);
  4. th.IsBackground = true;//将th线程设置为后台线程
  5. th.Start();
  6. }

这时候,没有完成计数时关闭程序后会显示异常“创建窗口句柄时出错。”这是因为关闭程序的时候,主窗体这个前台线程关闭了,程序会调用Dispose这个方法进行线程的资源释放。但是Test方法可能还没执行完,还在使用主线程提供的textBox1这个空间资源,这时候Test方法发现找不到这个资源了,程序就会抛异常。
按理说不是已经把th变成后台线程了吗,不是只要所有的前台线程结束,后台线程就结束吗?为什么还是会抛这个异常。原因是我们的cpu不一定能及时的解决处理线程的操作,因为线程是告诉cpu,“我这个线程准备好了,随时可以操作”,但是具体啥时候操作,程序员说了不算,还要看cpu的“心情”。
我们想让程序不抛这个异常,只能是在关闭窗口的时候,强制关闭后台线程

  1. private void Form1_FormClosing(object sender, FormClosingEventArgs e)
  2. {
  3. th.Abort();//程序关闭时,强制后台线程关闭
  4. }

使用Socket实现服务器与客户端之间的通信

服务器:
服务器样式截图:

  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. namespace Server
  15. {
  16. public partial class Form1 : Form
  17. {
  18. public Form1()
  19. {
  20. InitializeComponent();
  21. }
  22. private void btnStart_Click(object sender, EventArgs e)
  23. {
  24. //1、创建一个监听连接的Socket对象socketWatch,使用IPv4,流式传输,Tcp协议
  25. Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  26. //2、创建一个ip地址
  27. IPAddress ip = IPAddress.Parse(txtServer.Text);
  28. //2.1、创建一个端口号
  29. IPEndPoint point = new IPEndPoint(ip,int.Parse(txtPort.Text));
  30. //3、将端口号绑定到socketWatch
  31. socketWatch.Bind(point);
  32. //4、设置监听队列(同一时刻最多有几台设备同时连接)
  33. socketWatch.Listen(10);
  34. ShowMsg("正在等待客户端的连接");
  35. //5、创建一个新线程th,创建线程用于使用新创建的Socket
  36. Thread th = new Thread(MyAccept);
  37. //6、设置th为后台线程
  38. th.IsBackground = true;
  39. //7、开启线程th
  40. th.Start(socketWatch);
  41. }
  42. //客户端的IP地址&端口号,服务器与客户端通讯的Socket
  43. Dictionary<string,Socket> dicSocket = new Dictionary<string,Socket>();
  44. /// <summary>
  45. /// 实现客户端与服务器的通讯
  46. /// </summary>
  47. /// <param name="o"></param>
  48. void MyAccept(object o)
  49. {
  50. //不停的接收客户端的连接
  51. while (true)
  52. {
  53. //o墙砖为Socket
  54. Socket socketWatch = o as Socket;
  55. //为新建连接创建新的与之通信的Socket
  56. Socket socketTX = socketWatch.Accept();
  57. //把客户端的IP地址&端口号和与客户端通信的Socket存储到键值对集合中
  58. dicSocket.Add(socketTX.RemoteEndPoint.ToString(), socketTX);
  59. //把客户端的ip地址和端口号,存储到下拉框中
  60. cboUsers.Items.Add(socketTX.RemoteEndPoint.ToString());
  61. //展示连接的ip地址和端口号
  62. ShowMsg(socketTX.RemoteEndPoint.ToString() + "连接成功");
  63. Thread th = new Thread(RecData);
  64. th.IsBackground = true;
  65. th.Start(socketTX);
  66. }
  67. }
  68. /// <summary>
  69. /// 不停的接收客户端的消息
  70. /// </summary>
  71. /// <param name="o"></param>
  72. void RecData(object o)
  73. {
  74. Socket socketTX = o as Socket;
  75. while(true)
  76. {
  77. //创建缓存区
  78. byte[] buffer = new byte[1024 * 1024 * 5];
  79. //r表示实际接受到的字节数
  80. int r = socketTX.Receive(buffer);
  81. //将接收到的字节数组使用系统默认编码格式转换为字符串
  82. string msg = Encoding.Default.GetString(buffer, 0, r);
  83. //展示接收到的信息
  84. ShowMsg(socketTX.RemoteEndPoint.ToString() + ":" + msg);
  85. }
  86. }
  87. /// <summary>
  88. /// 在文本框中展示信息
  89. /// </summary>
  90. /// <param name="msg"></param>
  91. public void ShowMsg(string msg)
  92. {
  93. txtLog.AppendText(msg + "\r\n");
  94. }
  95. private void Form1_Load(object sender, EventArgs e)
  96. {
  97. Control.CheckForIllegalCrossThreadCalls = false;
  98. }
  99. //服务器给客户端发消息
  100. private void btnSend_Click(object sender, EventArgs e)
  101. {
  102. string msg = txtMsg.Text.Trim();
  103. byte[] buffer = Encoding.Default.GetBytes(msg);
  104. //制作自己的协议 0:文字 1:文件 2:震动
  105. List<byte> listByte = new List<byte>();
  106. listByte.Add(0);
  107. listByte.AddRange(buffer);
  108. //以字节形式发送个客户端的数据,第一个字节是0代表发的是文字
  109. buffer = listByte.ToArray();
  110. //获取服务器选择的客户端的ip地址
  111. string ip = cboUsers.SelectedItem.ToString();
  112. //拿着ip去找对应的socket,然后发送
  113. dicSocket[ip].Send(buffer);
  114. }
  115. //发送震动
  116. private void btnZD_Click(object sender, EventArgs e)
  117. {
  118. byte[] buffer = new byte[1];
  119. buffer[0] = 2;
  120. string ip = cboUsers.SelectedItem.ToString();
  121. dicSocket[ip].Send(buffer);
  122. }
  123. //选择文件
  124. private void btnSelect_Click(object sender, EventArgs e)
  125. {
  126. //创建打开文件对话框
  127. OpenFileDialog ofd = new OpenFileDialog();
  128. ofd.Title = "请选择要发送的文件";
  129. ofd.Filter = "文本文件|*.txt|多媒体文件|*.wmv|所有文件|*.*";
  130. //初始化路径
  131. ofd.InitialDirectory = "E:\\123";
  132. //设置不允许多选
  133. ofd.Multiselect = false;
  134. ofd.ShowDialog();
  135. //获取用户选择文件的全路径
  136. string path = ofd.FileName;
  137. //放到窗体展示出来
  138. txtPath.Text = path;
  139. }
  140. //点击发送文件
  141. private void btnSendFile_Click(object sender, EventArgs e)
  142. {
  143. //获取要发送文件的路径
  144. string path = txtPath.Text.Trim();
  145. using (FileStream fsRead = new FileStream(path,FileMode.Open,FileAccess.Read))
  146. {
  147. try
  148. {
  149. byte[] buffer = new byte[1024 * 1024 * 5];
  150. int r = fsRead.Read(buffer, 0, buffer.Length);
  151. List<byte> list = new List<byte>();
  152. list.Add(1);
  153. list.AddRange(buffer);
  154. buffer = list.ToArray();
  155. //调用跟客户端通信的socket,发送字节数据
  156. string ip = cboUsers.SelectedItem.ToString();
  157. dicSocket[ip].Send(buffer, 0, r + 1, SocketFlags.None);
  158. }
  159. catch (Exception ex)
  160. {
  161. MessageBox.Show(ex.Message);
  162. }
  163. }
  164. }
  165. }
  166. }

客户端
客户端样式截图:

  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.Media;
  9. using System.Net;
  10. using System.Net.Sockets;
  11. using System.Text;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. using System.Windows.Forms;
  15. namespace Client
  16. {
  17. public partial class Form1 : Form
  18. {
  19. public Form1()
  20. {
  21. InitializeComponent();
  22. }
  23. Socket socket;
  24. private void btnStart_Click(object sender, EventArgs e)
  25. {
  26. socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  27. IPAddress ip = IPAddress.Parse(txtServer.Text);
  28. IPEndPoint point = new IPEndPoint(ip,int.Parse(txtPort.Text));
  29. socket.Connect(point);
  30. ShowMsg("连接成功");
  31. //不停的接收服务器发送过来的消息
  32. Thread th = new Thread(RecServerData);
  33. th.IsBackground = true;
  34. th.Start();
  35. }
  36. //不停地接收服务器发送过来的消息
  37. void RecServerData()
  38. {
  39. while (true)
  40. {
  41. //连接成功后,接收服务器发送过来的消息
  42. byte[] buffer = new byte[1024 * 1024 * 5];
  43. int r = socket.Receive(buffer);
  44. byte b = buffer[0];
  45. //对面发送过来的是文字
  46. if (b==0)
  47. {
  48. string msg = Encoding.Default.GetString(buffer,1,r-1);
  49. ShowMsg(msg);
  50. }
  51. else if (b==2)//对面发送过来的是震动
  52. {
  53. ZhenDong();
  54. SoundPlayer sp = new SoundPlayer();
  55. sp.Play();
  56. }
  57. else if (b == 1)//对面发送过来的是文件
  58. {
  59. //1、弹出来一个保存文件的对话框
  60. SaveFileDialog sfd = new SaveFileDialog();
  61. sfd.InitialDirectory = @"E:\123";
  62. sfd.Title = "请选择要保存的文件路径";
  63. sfd.Filter = "文本文件|*.txt|媒体文件|*.wmv|所有文件|*.*";
  64. sfd.ShowDialog(this);//展示保存对话框
  65. //2、用户在对话框中选择要保存文件的路径
  66. string savePath = sfd.FileName;
  67. //3、FileStream把数据写入到指定的路径下
  68. using (FileStream fsWrite = new FileStream(savePath, FileMode.Create, FileAccess.Write))
  69. {
  70. fsWrite.Write(buffer, 1, r - 1);
  71. MessageBox.Show("保存成功!!!!");
  72. }
  73. }
  74. }
  75. }
  76. //窗体震动
  77. void ZhenDong()
  78. {
  79. for (int i = 0; i < 1000; i++)
  80. {
  81. this.Location = new Point(300, 300);
  82. this.Location = new Point(320, 320);
  83. }
  84. }
  85. void ShowMsg(string msg)
  86. {
  87. txtLog.AppendText(msg+"\r\n");
  88. }
  89. //客户端给服务器发送消息
  90. private void btnSend_Click(object sender, EventArgs e)
  91. {
  92. string msg = txtMsg.Text.Trim();
  93. byte[] buffer = Encoding.Default.GetBytes(msg);
  94. socket.Send(buffer);
  95. }
  96. private void Form1_Load(object sender, EventArgs e)
  97. {
  98. Control.CheckForIllegalCrossThreadCalls = false;
  99. }
  100. }
  101. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/145592
推荐阅读
相关标签
  

闽ICP备14008679号