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

进程

定义:每一个正在运行的应用程序,都是一个进程

进程不等于正在运行的应用程序。而是为应用程序的运行构建一个运行环境

            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;
        }
    }
}
相关推荐
字节程序员3 分钟前
四种自动化测试模型实例及优缺点详解
开发语言·javascript·ecmascript·集成测试·压力测试
程序员老冯头3 分钟前
第十六章 C++ 字符串
开发语言·c++
灵槐梦19 分钟前
【速成51单片机】2.点亮LED
c语言·开发语言·经验分享·笔记·单片机·51单片机
想睡觉 . 我也想睡觉 .20 分钟前
【C++算法】1.【模板】前缀和
开发语言·c++·算法
---wzy---29 分钟前
我的JAVA-Web基础(2)
java·开发语言
伟大无须多言40 分钟前
企业资源规划系统(ERP)服务器上线项目实施指南
开发语言·php
小码编匠41 分钟前
WPF 星空效果:创建逼真的宇宙背景
后端·c#·.net
逊嘘42 分钟前
【Java数据结构】LinkedList
java·开发语言·数据结构
周盛欢42 分钟前
云服务器yum无法解析mirrorlist.centos.org
开发语言·python
lxyzcm1 小时前
深入理解C++23的Deducing this特性(上):基础概念与语法详解
开发语言·c++·spring boot·设计模式·c++23