进程
定义:每一个正在运行的应用程序,都是一个进程
进程不等于正在运行的应用程序。而是为应用程序的运行构建一个运行环境
Process[] pros = Process.GetProcesses();//获取电脑中所有正在运行的进程 //通过进程,直接打开文件 //告诉进程,要打开的文件路径,通过PSI对象进行封装 ProcessStartInfo psi = new ProcessStartInfo(@"C:\Users\ThinkPad\Desktop\1.txt"); Process p = new Process(); p.StartInfo = psi; p.Start();
多线程
private void button1_Click(object sender, EventArgs e) { Test(); } private void Test() { for (int i = 0; i < 100000; i++) { textBox1.Text = i.ToString(); } }
这段代码在执行完成之前,程序会被卡死(不能操作程序,包括关闭窗口)。因为我们程序在做一些耗时操作的时候,如果主线程去执行某段代码,就没有其余的“精力”去完成其他的操作了。
这时候,我们就需要用到多线程,再新建一个线程来完成耗时操作
private void button1_Click(object sender, EventArgs e) { Thread th = new Thread(Test); th.Start(); } private void Test(object str)//如果线程执行的方法,需要参数,我们要求参数的类型必须是object类型 { for (int i = 0; i < 100000; i++) { textBox1.Text = i.ToString(); } }
但是使用多线程,也会有很多需要注意的地方。这段代码执行时会提示异常,显示“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”
因为创建textBox1的是主线程,而你创建了一个新的线程th,th调用Test方法会访问主线程创建的控件,这个操作默认是不允许的,我们不允许跨线程的访问,因为这是不安全的。
而如果强制要跨线程访问的话,使用下面这段代码在主窗体加载的时候
private void Form1_Load(object sender, EventArgs e) { //取消跨线程访问的检查 Control.CheckForIllegalCrossThreadCalls = false; }
但是这样还是有问题,当你运行程序的时候,点击按钮开始计数。如果你在计数途中点击叉号关闭程序,程序还是再运行。这是因为你关闭了主线程(主窗体),但是另一个线程th还在执行。
这时候我们把th这个线程由前台线程转换为后台线程。
前台线程:只有所有的前台线程关闭,程序才关闭
后台线程:只要所有的前台线程结束,后台线程自动结束
private void button1_Click(object sender, EventArgs e) { Thread th = new Thread(Test); th.IsBackground = true;//将th线程设置为后台线程 th.Start(); }
这时候,没有完成计数时关闭程序后会显示异常“创建窗口句柄时出错。”这是因为关闭程序的时候,主窗体这个前台线程关闭了,程序会调用Dispose这个方法进行线程的资源释放。但是Test方法可能还没执行完,还在使用主线程提供的textBox1这个空间资源,这时候Test方法发现找不到这个资源了,程序就会抛异常。
按理说不是已经把th变成后台线程了吗,不是只要所有的前台线程结束,后台线程就结束吗?为什么还是会抛这个异常。原因是我们的cpu不一定能及时的解决处理线程的操作,因为线程是告诉cpu,“我这个线程准备好了,随时可以操作”,但是具体啥时候操作,程序员说了不算,还要看cpu的“心情”。
我们想让程序不抛这个异常,只能是在关闭窗口的时候,强制关闭后台线程
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { th.Abort();//程序关闭时,强制后台线程关闭 }
使用Socket实现服务器与客户端之间的通信
服务器:
服务器样式截图:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Server { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnStart_Click(object sender, EventArgs e) { //1、创建一个监听连接的Socket对象socketWatch,使用IPv4,流式传输,Tcp协议 Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2、创建一个ip地址 IPAddress ip = IPAddress.Parse(txtServer.Text); //2.1、创建一个端口号 IPEndPoint point = new IPEndPoint(ip,int.Parse(txtPort.Text)); //3、将端口号绑定到socketWatch socketWatch.Bind(point); //4、设置监听队列(同一时刻最多有几台设备同时连接) socketWatch.Listen(10); ShowMsg("正在等待客户端的连接"); //5、创建一个新线程th,创建线程用于使用新创建的Socket Thread th = new Thread(MyAccept); //6、设置th为后台线程 th.IsBackground = true; //7、开启线程th th.Start(socketWatch); } //客户端的IP地址&端口号,服务器与客户端通讯的Socket Dictionary<string,Socket> dicSocket = new Dictionary<string,Socket>(); /// <summary> /// 实现客户端与服务器的通讯 /// </summary> /// <param name="o"></param> void MyAccept(object o) { //不停的接收客户端的连接 while (true) { //o墙砖为Socket Socket socketWatch = o as Socket; //为新建连接创建新的与之通信的Socket Socket socketTX = socketWatch.Accept(); //把客户端的IP地址&端口号和与客户端通信的Socket存储到键值对集合中 dicSocket.Add(socketTX.RemoteEndPoint.ToString(), socketTX); //把客户端的ip地址和端口号,存储到下拉框中 cboUsers.Items.Add(socketTX.RemoteEndPoint.ToString()); //展示连接的ip地址和端口号 ShowMsg(socketTX.RemoteEndPoint.ToString() + "连接成功"); Thread th = new Thread(RecData); th.IsBackground = true; th.Start(socketTX); } } /// <summary> /// 不停的接收客户端的消息 /// </summary> /// <param name="o"></param> void RecData(object o) { Socket socketTX = o as Socket; while(true) { //创建缓存区 byte[] buffer = new byte[1024 * 1024 * 5]; //r表示实际接受到的字节数 int r = socketTX.Receive(buffer); //将接收到的字节数组使用系统默认编码格式转换为字符串 string msg = Encoding.Default.GetString(buffer, 0, r); //展示接收到的信息 ShowMsg(socketTX.RemoteEndPoint.ToString() + ":" + msg); } } /// <summary> /// 在文本框中展示信息 /// </summary> /// <param name="msg"></param> public void ShowMsg(string msg) { txtLog.AppendText(msg + "\r\n"); } private void Form1_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } //服务器给客户端发消息 private void btnSend_Click(object sender, EventArgs e) { string msg = txtMsg.Text.Trim(); byte[] buffer = Encoding.Default.GetBytes(msg); //制作自己的协议 0:文字 1:文件 2:震动 List<byte> listByte = new List<byte>(); listByte.Add(0); listByte.AddRange(buffer); //以字节形式发送个客户端的数据,第一个字节是0代表发的是文字 buffer = listByte.ToArray(); //获取服务器选择的客户端的ip地址 string ip = cboUsers.SelectedItem.ToString(); //拿着ip去找对应的socket,然后发送 dicSocket[ip].Send(buffer); } //发送震动 private void btnZD_Click(object sender, EventArgs e) { byte[] buffer = new byte[1]; buffer[0] = 2; string ip = cboUsers.SelectedItem.ToString(); dicSocket[ip].Send(buffer); } //选择文件 private void btnSelect_Click(object sender, EventArgs e) { //创建打开文件对话框 OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "请选择要发送的文件"; ofd.Filter = "文本文件|*.txt|多媒体文件|*.wmv|所有文件|*.*"; //初始化路径 ofd.InitialDirectory = "E:\\123"; //设置不允许多选 ofd.Multiselect = false; ofd.ShowDialog(); //获取用户选择文件的全路径 string path = ofd.FileName; //放到窗体展示出来 txtPath.Text = path; } //点击发送文件 private void btnSendFile_Click(object sender, EventArgs e) { //获取要发送文件的路径 string path = txtPath.Text.Trim(); using (FileStream fsRead = new FileStream(path,FileMode.Open,FileAccess.Read)) { try { byte[] buffer = new byte[1024 * 1024 * 5]; int r = fsRead.Read(buffer, 0, buffer.Length); List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); buffer = list.ToArray(); //调用跟客户端通信的socket,发送字节数据 string ip = cboUsers.SelectedItem.ToString(); dicSocket[ip].Send(buffer, 0, r + 1, SocketFlags.None); } catch (Exception ex) { MessageBox.Show(ex.Message); } } } } }
客户端
客户端样式截图:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Client { public partial class Form1 : Form { public Form1() { InitializeComponent(); } Socket socket; private void btnStart_Click(object sender, EventArgs e) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse(txtServer.Text); IPEndPoint point = new IPEndPoint(ip,int.Parse(txtPort.Text)); socket.Connect(point); ShowMsg("连接成功"); //不停的接收服务器发送过来的消息 Thread th = new Thread(RecServerData); th.IsBackground = true; th.Start(); } //不停地接收服务器发送过来的消息 void RecServerData() { while (true) { //连接成功后,接收服务器发送过来的消息 byte[] buffer = new byte[1024 * 1024 * 5]; int r = socket.Receive(buffer); byte b = buffer[0]; //对面发送过来的是文字 if (b==0) { string msg = Encoding.Default.GetString(buffer,1,r-1); ShowMsg(msg); } else if (b==2)//对面发送过来的是震动 { ZhenDong(); SoundPlayer sp = new SoundPlayer(); sp.Play(); } else if (b == 1)//对面发送过来的是文件 { //1、弹出来一个保存文件的对话框 SaveFileDialog sfd = new SaveFileDialog(); sfd.InitialDirectory = @"E:\123"; sfd.Title = "请选择要保存的文件路径"; sfd.Filter = "文本文件|*.txt|媒体文件|*.wmv|所有文件|*.*"; sfd.ShowDialog(this);//展示保存对话框 //2、用户在对话框中选择要保存文件的路径 string savePath = sfd.FileName; //3、FileStream把数据写入到指定的路径下 using (FileStream fsWrite = new FileStream(savePath, FileMode.Create, FileAccess.Write)) { fsWrite.Write(buffer, 1, r - 1); MessageBox.Show("保存成功!!!!"); } } } } //窗体震动 void ZhenDong() { for (int i = 0; i < 1000; i++) { this.Location = new Point(300, 300); this.Location = new Point(320, 320); } } void ShowMsg(string msg) { txtLog.AppendText(msg+"\r\n"); } //客户端给服务器发送消息 private void btnSend_Click(object sender, EventArgs e) { string msg = txtMsg.Text.Trim(); byte[] buffer = Encoding.Default.GetBytes(msg); socket.Send(buffer); } private void Form1_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } } }