Bootstrap

C# 进阶7 | TCP通讯,多线程接收、处理、显示,Queue缓存及跨线程访问(线程安全问题)

一、背景

  • 多线程处理通讯数据: 通常TCP通讯,开启三个线程,把接收、处理、显示分割来提高软件运行效率。
  • 应用场景: 在数据采集领域,往往需要快速通讯保证数据实时性。如图像采集上位机、工业温控上位机、某些连续波形采集显示等等。

二、TCP通讯

1.声明

private Socket socketClient = null;
public Thread  ThreadClient = null; //tcp主动接收线程
public string ipNum = "127.0.0.1";
public string portNum = "5678";

2.连接与断开

  /// <summary>
    /// 客户端套接字连接到网络节点上
    /// </summary>
    /// <returns></returns>
    private int ConnectTCP()
    {
        if (SocketClient != null)
        {
            SocketClient.Dispose(); //释放资源
        }
        SocketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream,  ProtocolType.Tcp); //因为关闭时需要释放对象,所以在连接前重新创建一个对象
        try
        {
            IPAddress ip = StrToIPv4(ipNum);
            if (ip == null)
            {
                return 1002; //IP地址错误
            }
            IPEndPoint ipe = new IPEndPoint(ip, Convert.ToInt32(portNum));
            //客户端套接字连接到网络节点上,用的是Connect
            SocketClient.Connect(ipe);
            //开启线程
            ThreadClient = new Thread(Recv);
            ThreadClient.IsBackground = true;
            ThreadClient.Start();        
        }
        catch (Exception e)
        {
            DisconnetTCP();
            isConnect = false;
            return 1001; //1001连接网口失败
        }
        isConnect = true;
        return 0;
    }

    /// <summary>
    /// 断开TCP
    /// </summary>
    public void DisconnetTCP()
    {
        if (SocketClient != null)
        {
            SocketClient.Dispose(); //释放资源
        }
        if (ThreadClient != null)
        {
            ThreadClient.Abort(); //终止线程
        }
        isConnect = false;
    }

3.TCP接收线程函数

 //接收服务端发来信息的方法  
    private void Recv()
    {
        //持续监听服务端发来的消息
        while (true)
        {
            try
            {
                //定义一个1M的内存缓冲区,用于临时性存储接收到的消息
                byte[] arrRecvmsg = new byte[1024 * 1024];
                //将客户端套接字接收到的数据存入内存缓冲区,并获取长度
                int lenghtData= SocketClient.Receive(arrRecvmsg);
                byte[] ReceiveData = new byte[lenghtData];
                //将套接字获取到的字符数组转换为人可以看懂的字符串
                // string strRevMsg = Encoding.UTF8.GetString(arrRecvmsg, 0, lenghtData);
                Array.Copy(arrRecvmsg, ReceiveData, lenghtData);
                //解析
                AnalysisRead(strRevMsg);
            }
            catch (Exception ex)
            {
                continue;
            }
        }
    }

以上是我们常用的Socket通讯方式。当解析函数AnalysisRead存在大量的计算、并显示结果到界面时,如果不另外开线程,数据实时性很难保证。

三、多线程接收、处理、显示

1.接收函数AnalysisRead

object AnsysPdLock = new object();
Semaphore TaskSemaphoreTCP; //TCP缓存区
private void AnalysisRead(byte[] revData)
{
    try
    {
        lock (AnsysPdLock)
        {
            tcpRevQueue.Enqueue(revData); //入列
        }
        // 每添加一个任务,释放一个资源
        TaskSemaphoreTCP.Release();
    }
    catch (Exception)
    {
    }
}

2.数据解析

Thread ThreadTCPAnalysis; //波形解析线程(TCP连接成功后开启线程)
ThreadTCPAnalysis = new Thread(AnalysisRevTCP);
ThreadTCPAnalysis.IsBackground = true;
ThreadTCPAnalysis.Start();

/// <summary>
/// 解析线程函数
/// </summary>
private void AnalysisRevTCP()
{
    byte[] tepData;
    while (true)
    {
        try
        {
            TaskSemaphoreTCP.WaitOne(); //等待并消耗一个信号
            lock (AnsysPdLock)
            {
                if (tcpRevQueue.Count > 0)
                {
                    tepData = tcpRevQueue.Dequeue(); //出列
                }
                else
                {
                    continue;
                }
            }
           AnalysisUWav(tepData); //解析函数
        }
        catch (Exception ex)
        {
        }
    }
}


private List<double> uWavVol = new List<double>(); //连续采集波形图电压值
private Queue<List<double>> pdQueue = new Queue<List<double>>(); //PD值缓存区
Semaphore TaskSemaphoreWav; //波形线程缓存区
object TCPReciveLock = new object();
/// <summary>
/// 解析连续PD
/// </summary>
/// <param name="revData"></param>
private void AnalysisUWav(List<byte> revData)
{
    int dataLength = revData.Count / 2;
    try
    {
        for (int i = 0; i < dataLength; i++)
        {
            double vol = Convert.ToDouble((Convert.ToInt32(revData[i * 2] <<  8) + revData[1 + i * 2])) / 1000;
            uWavVol.Add(vol);
            uWavVol.RemoveAt(0);
        }
        lock (TCPReciveLock)
        {
            pdQueue.Enqueue(uWavVol); //入列
        }
        // 每添加一个任务,释放一个资源
        TaskSemaphoreWav.Release();
    }
    catch (Exception ex)
    {
        //LogWrite.logWrite(ex.Message, ex.StackTrace); //写入日志
    }
}

3.数据显示

	Thread ThreadRefreshWav; //波形显示线程
    ThreadRefreshWav = new Thread(FunRefreshWav);
    ThreadRefreshWav.Name = "RefreshWav";
    ThreadRefreshWav.IsBackground = true;
    ThreadRefreshWav.Start();
    
    /// <summary>
    /// 波形显示函数
    /// </summary>
    private void FunRefreshWav()
    {
        while (true)
        {
            List<double> uWav = new List<double>();
            try
            {
                TaskSemaphoreWav.WaitOne(); //等待并消耗一个信号
                lock (AnsysPdLock)
                {
                    if (pdQueue.Count > 0)
                    {
                        uWav = pdQueue.Dequeue(); //出列
                    }
                    else
                    {
                        continue;
                    }
                }
                if (chartDebugWav.InvokeRequired)
                {
                    chartDebugWav.Invoke(new Action(() => {
                        chartDebugWav.Series[0].Points.DataBindY(uWav);
                    }));
                }
                else
                {
                    chartDebugWav.Series[0].Points.DataBindY(uWav);
                }
            }
            catch (Exception ex)
            {
            }
            
        }
    }

四、总结

1.整体的思路:

  • 开启三个线程和2个队列缓存
  • 线程1负责实时监控TCP接收数据,收到数据后立马给队列1进站该数据
  • 线程2实时监控队列1,当队列1存在数据立马取出并解析,把解析结果放进队列2
  • 线程3实时监控队列2,当队列2有数据立马取出显示到界面

2.Queue:

  • 队列作为一种基础且实用的数据结构,遵循“先进先出”(First-In, First-Out, FIFO)原则,广泛应用于各种编程场景。
  • Queue不是线程安全列表,因此需要借用信号量Semaphore来实现同步。

注:
转载本文需要标明出处!
六七彭(原谷子彭):[email protected]

;