使用通用接口与设备进行 串口(SerialPort)、网口(Socket)通信

时间:2024-04-06 16:47:40

软件与设备通信,一般有两种通信方式:

①网络(Socket,TCP)通信,如软件与PLC之间进行通信。

②串口(SerialPort,RS232)通信,如软件与测距仪之间进行通信。

新建控制台项目:CommunicationDemo。【.Net framework 4.5】

添加对SuperSocket客户端 SuperSocket.ClientEngine.dll 与 SuperSocket.ProtoBase.dll  的引用

第一步、新建枚举文件CommunicationCategory.cs,用于表示串口还是网络通信。源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CommunicationDemo
{
    /// <summary>
    /// 通信类型,是网口通信,还是串口通信
    /// </summary>
    public enum CommunicationCategory
    {
        /// <summary>
        /// 网络通信,网口,Ethernet以太网
        /// </summary>
        NetworkPort = 0,
        /// <summary>
        /// 串口通信 COM=cluster communication port串行通讯端口,一般指RS485或RS232等
        /// </summary>
        SerialPort = 1
    }
}

第二步、创建消息类型枚举文件MessageType.cs,用于区分通信时的消息类型。源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CommunicationDemo
{
    /// <summary>
    /// 消息类型,提示信息类型
    /// </summary>
    public enum MessageType
    {
        /// <summary>
        /// 正常的发送和接收
        /// </summary>
        Normal = 0,
        /// <summary>
        /// 连接成功
        /// </summary>
        Connected = 1,
        /// <summary>
        /// 连接已断开
        /// </summary>
        Closed = 2,
        /// <summary>
        /// 错误信息【连接失败,通讯出错】
        /// </summary>
        Error = 3
    }
}
 

第三步、创建统一入口类文件IEquipmentCommon.cs,用于接口统一定义。源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CommunicationDemo
{
    /// <summary>
    /// 设备通信 统一公共接口
    /// </summary>
    public interface IEquipmentCommon
    {
        /// <summary>
        /// 是否已连接
        /// </summary>
        bool IsConnected { get; }
        /// <summary>
        /// 通信类型:网络通信 还是 串口通信
        /// </summary>
        CommunicationCategory Category { get; }
        /// <summary>
        /// 通信时编码格式,默认为ASCII
        /// </summary>
        Encoding Encoding { get; set; }
        /// <summary>
        /// 连接到服务端 或 设备
        /// </summary>
        /// <param name="ipOrName">网口指的是IP地址,串口指的是串口名</param>
        /// <param name="port">网口指的是端口,串口指的是波特率</param>
        /// <returns>是否连接成功【打开端口成功】</returns>
        bool Connect(string ipOrName, int port);
        /// <summary>
        /// 关闭连接
        /// </summary>
        void Close();
        /// <summary>
        /// 发送消息,按照字符串
        /// </summary>
        /// <param name="command"></param>        
        void Send(string command);
        /// <summary>
        /// 发送消息,按照字节流
        /// </summary>
        /// <param name="buffer"></param>
        void Send(byte[] buffer);
        /// <summary>
        /// 接收消息,获取字符串数据
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        int Receive(out string content);
        /// <summary>
        /// 接收消息,获取字节流数据
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        int Receive(out byte[] buffer);

        /// <summary>
        /// 发送命令并同步等待结果,结果为字符串数据
        /// </summary>
        /// <param name="command">要发送的命令</param>
        /// <param name="timeout">接收超时时间,单位:毫秒</param>
        /// <param name="result">返回的结果</param>
        /// <returns>返回字符串长度</returns>
        int SendAndWaitResult(string command, int timeout, out string result);
        /// <summary>
        /// 发送命令并同步等待结果,结果为字节流数据
        /// </summary>
        /// <param name="buffer">要发送的命令</param>
        /// <param name="timeout">接收超时时间,单位:毫秒</param>
        /// <param name="resultBuffer">返回的结果</param>
        /// <returns>接收的数据长度</returns>
        int SendAndWaitResult(byte[] buffer, int timeout, out byte[] resultBuffer);
        /// <summary>
        /// 所有消息接收事件
        /// </summary>
        event Action<string, MessageType> DataReceived;
    }
}
 

第四步、创建网络通信类NetworkCommunication.cs,实现接口IEquipmentCommon。源程序如下:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.ClientEngine;
using SuperSocket.ProtoBase;

namespace CommunicationDemo
{
    /// <summary>
    /// 网络通信【Socket套接字通信】
    /// </summary>
    public class NetworkCommunication : IEquipmentCommon
    {
        /// <summary>
        /// TCP客户端会话对象
        /// </summary>
        public AsyncTcpSession AsyncSession;
        /// <summary>
        /// 当前接收到的消息,注意 接收到数据每次最多接收4096字节【4KB】
        /// </summary>
        public string RecvNewMsg = string.Empty;
        /// <summary>
        /// 出错信息
        /// </summary>
        public string ErrorMsg = string.Empty;        
        /// <summary>
        /// 连接超时时间,单位:毫秒
        /// </summary>
        public int ConnectTimeOut { get; set; }
        /// <summary>
        /// 服务端描述
        /// </summary>
        public string ServerEndPoint = string.Empty;

        /// <summary>
        /// 接收的字节流数据【后续使用这个进行处理】
        /// </summary>
        public ArraySegment<byte> ReceiveBuffer;

        public NetworkCommunication()
        {
            AsyncSession = new AsyncTcpSession();
            ConnectTimeOut = 3000;
            this.Encoding = Encoding.GetEncoding("GBK");//简体中文
            //连接到服务器事件
            AsyncSession.Connected += Client_Connected;
            //连接断开事件
            AsyncSession.Closed += Client_Closed;
            //发生错误的处理
            AsyncSession.Error += Client_Error;
            //收到服务器数据事件:注意 接收消息每次接收4096字节【4KB】,如果发送的消息大于4096字节,将分多段发送,也就是触发多次DataReceived事件
            AsyncSession.DataReceived += Client_DataReceived;
        }

        private void Client_DataReceived(object sender, DataEventArgs e)
        {
            ReceiveBuffer = new ArraySegment<byte>(e.Data, e.Offset, e.Length);
            string msg = Encoding.GetString(e.Data, e.Offset, e.Length);
            this.RecvNewMsg = msg;
            DataReceived?.Invoke(msg, MessageType.Normal);
        }

        private void Client_Error(object sender, ErrorEventArgs e)
        {
            //连接失败,通讯出错
            string errorMsg = e.Exception.Message;
            this.ErrorMsg = errorMsg;
            DataReceived?.Invoke(errorMsg, MessageType.Error);
        }

        private void Client_Closed(object sender, EventArgs e)
        {
            string closeMsg = "通讯已断开";
            //服务端关闭,触发;
            //关闭连接,触发;
            DataReceived?.Invoke(closeMsg, MessageType.Closed);
        }

        private void Client_Connected(object sender, EventArgs e)
        {
            int commonPort = ((IPEndPoint)AsyncSession.LocalEndPoint).Port;
             //连接成功,触发;
             DataReceived?.Invoke($"建立连接成功.服务端:【{ServerEndPoint}】,公共通信端口:【{commonPort}】", MessageType.Connected);
        }

        #region 实现接口IEquipmentCommon
        /// <summary>
        /// 所有消息接收事件
        /// </summary>
        public event Action<string, MessageType> DataReceived;
        /// <summary>
        /// 是否已连接到服务端
        /// </summary>
        public bool IsConnected => AsyncSession == null ? false : AsyncSession.IsConnected;

        /// <summary>
        /// 通信类型:这里固定为NetworkPort【网口通信】
        /// </summary>
        public CommunicationCategory Category { get => CommunicationCategory.NetworkPort;  }

        /// <summary>
        /// 编码格式,默认为ASCII
        /// </summary>
        public Encoding Encoding { get; set; }

        /// <summary>
        /// 客户端关闭连接
        /// </summary>
        public void Close()
        {
            AsyncSession?.Close();
        }

        /// <summary>
        /// 以指定的IP和端口连接到服务端
        /// </summary>
        /// <param name="ipOrName"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public bool Connect(string ipOrName, int port)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            try
            {
                if (AsyncSession.IsConnected)
                {
                    Close();//ip,port更改的时候,需要断开。
                }
                ErrorMsg = string.Empty;
                ServerEndPoint = string.Empty;
                AsyncSession?.Connect(new IPEndPoint(IPAddress.Parse(ipOrName), port));
            }
            catch (Exception ex)
            {
                //连接失败:比如端口超过范围 比如端口 输入 87654
                DataReceived?.Invoke("连接失败:" + ex.Message, MessageType.Error);
                return false;
            }
            while (true)
            {
                if (AsyncSession.IsConnected || ErrorMsg != string.Empty)
                {
                    //确保连接过程已完成
                    ServerEndPoint = $"{ipOrName}:{port}";
                    break;
                }
                if (sw.ElapsedMilliseconds > ConnectTimeOut)
                {
                    //连接超时
                    sw.Stop();
                    break;
                }
            }
            return AsyncSession.IsConnected;
        }

        public int Receive(out string content)
        {
            content = RecvNewMsg;
            return RecvNewMsg.Length;
        }

        public int Receive(out byte[] buffer)
        {
            string content;
            int length = Receive(out content);
            buffer = Encoding.GetBytes(content);
            return length;
        }

        /// <summary>
        /// 发送命令:字符串
        /// </summary>
        /// <param name="command"></param>
        public void Send(string command)
        {
            Send(Encoding.GetBytes(command));
            DataReceived?.Invoke($"已发送命令数据:【{command}】", MessageType.Normal);
        }

        /// <summary>
        /// 发送命令:字节流
        /// </summary>
        /// <param name="buffer"></param>
        public void Send(byte[] buffer)
        {
            if (!AsyncSession.IsConnected)
            {
                DataReceived?.Invoke($"尚未连接服务端,无法发送数据", MessageType.Error);
                return;
            }
            AsyncSession?.Send(buffer, 0, buffer.Length);
            DataReceived?.Invoke($"已发送字节流数据:【{string.Join(",", buffer)}】", MessageType.Normal);
        }

        /// <summary>
        /// 发送命令并同步等待结果
        /// </summary>
        /// <param name="command">命令</param>
        /// <param name="timeout">超时时间,单位:毫秒。-1是无限等待</param>
        /// <param name="result">要返回的字符串结果</param>
        /// <returns></returns>
        public int SendAndWaitResult(string command, int timeout, out string result)
        {
            this.RecvNewMsg = string.Empty;
            this.Send(command);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            while (this.RecvNewMsg == string.Empty)
            {
                if (timeout != -1 && sw.ElapsedMilliseconds > timeout)
                {
                    sw.Stop();
                    result = string.Empty;
                    //超时返回
                    return 0;
                }
                System.Threading.Thread.Sleep(2);
            }
            sw.Stop();
            result = RecvNewMsg;
            return result.Length;
        }

        /// <summary>
        /// 发送字节流命令并同步等待结果
        /// </summary>
        /// <param name="buffer">发送的命令字节流</param>
        /// <param name="timeout">超时时间,单位:毫秒。-1是无限等待</param>
        /// <param name="resultBuffer">要获取的结果字节流</param>
        /// <returns></returns>
        public int SendAndWaitResult(byte[] buffer, int timeout, out byte[] resultBuffer)
        {
            this.RecvNewMsg = string.Empty;
            this.Send(buffer);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            while (this.RecvNewMsg == string.Empty)
            {
                if (timeout != -1 && sw.ElapsedMilliseconds > timeout)
                {
                    sw.Stop();
                    resultBuffer = new byte[0];
                    //超时返回空
                    return 0;
                }
                System.Threading.Thread.Sleep(2);
            }
            sw.Stop();
            resultBuffer = Encoding.GetBytes(this.RecvNewMsg);
            return resultBuffer.Length;
        }
        #endregion
    }

    
}
第五步、创建串行通信类SerialPortCommunication.cs,实现接口IEquipmentCommon。源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Threading;
using System.Diagnostics;

namespace CommunicationDemo
{
    /// <summary>
    /// 串口通信【RS232或485通信】
    /// </summary>
    public class SerialPortCommunication : IEquipmentCommon
    {
        /// <summary>
        /// 串行端口资源对象
        /// </summary>
        public SerialPort serialPort = null;

        /// <summary>
        /// 接收超时时间:单位毫秒
        /// </summary>
        public int ReceiveTimeout { get; set; }

        /// <summary>
        /// 接收的字节流数据
        /// </summary>
        public byte[] ReceiveBuffer;

        public SerialPortCommunication()
        {
            serialPort = new SerialPort();
            ReceiveTimeout = 100;
            this.Encoding = Encoding.GetEncoding("GBK");
            serialPort.Encoding = this.Encoding;
            serialPort.DataReceived += SerialPort_DataReceived;
            serialPort.ErrorReceived += SerialPort_ErrorReceived;
        }

        private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            DataReceived?.Invoke("在端口上发生错误类型:" + e.EventType, MessageType.Error);
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            //Console.WriteLine("接收类型:" + e.EventType);            
            Thread.SpinWait(ReceiveTimeout);
            //可获得的字节数
            int availableCount = serialPort.BytesToRead;
            byte[] buffer = new byte[availableCount];
            int readCount = serialPort.Read(buffer, 0, availableCount);
            ReceiveBuffer = buffer;
            DataReceived?.Invoke(Encoding.GetString(buffer), MessageType.Normal);
        }

        #region 实现接口IEquipmentCommon
        /// <summary>
        /// 所有消息接收事件
        /// </summary>
        public event Action<string, MessageType> DataReceived;
        /// <summary>
        /// 是否已连接到串口
        /// </summary>
        public bool IsConnected => serialPort == null ? false : serialPort.IsOpen;

        /// <summary>
        /// 通信类型:这里固定为SerialPort【串口通信】
        /// </summary>
        public CommunicationCategory Category { get => CommunicationCategory.SerialPort; }

        /// <summary>
        /// 编码格式,默认为ASCII
        /// </summary>
        public Encoding Encoding { get; set; }

        /// <summary>
        /// 关闭串口连接
        /// </summary>
        public void Close()
        {
            serialPort?.Close();
        }

        /// <summary>
        /// 以串口名和波特率来打开串口资源
        /// </summary>
        /// <param name="ipOrName"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public bool Connect(string ipOrName, int port)
        {
            try
            {
                serialPort.PortName = ipOrName;
                serialPort.BaudRate = port;
                serialPort.Open();
                DataReceived?.Invoke($"连接串口成功.串口:【{ipOrName}】,波特率:【{port}】" , MessageType.Connected);
                return true;
            }
            catch (Exception ex)
            {
                DataReceived?.Invoke($"打开串口失败:{ ex.Message}", MessageType.Error);
                return false;
            }
        }

        public int Receive(out string content)
        {
            byte[] buffer;
            int length = Receive(out buffer);
            content = string.Empty;
            if (buffer != null)
            {
                content = Encoding.GetString(buffer);
            }
            return content.Length;
        }

        public int Receive(out byte[] buffer)
        {
            buffer = ReceiveBuffer;
            if (buffer != null)
            {
                return buffer.Length;
            }
            return 0;
        }

        public void Send(string command)
        {
            byte[] buffer = Encoding.GetBytes(command);
            Send(buffer);
        }

        public void Send(byte[] buffer)
        {
            if (!IsConnected)
            {
                DataReceived?.Invoke($"串口未打开或已关闭,无法发送数据", MessageType.Error);
                return;
            }
            //增加清除缓冲区数据:清除输出,清除串行驱动程序发送缓冲区的数据
            serialPort.DiscardOutBuffer();
            serialPort.Write(buffer, 0, buffer.Length);
            DataReceived?.Invoke($"已发送字节流数据:【{string.Join(",", buffer)}】", MessageType.Normal);
        }

        public int SendAndWaitResult(string command, int timeout, out string result)
        {
            byte[] buffer = Encoding.GetBytes(command);
            byte[] resultBuffer;
            SendAndWaitResult(buffer, timeout, out resultBuffer);
            result = Encoding.GetString(resultBuffer);
            return result.Length;
        }

        public int SendAndWaitResult(byte[] buffer, int timeout, out byte[] resultBuffer)
        {
            this.ReceiveBuffer = null;
            this.Send(buffer);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            while (this.ReceiveBuffer == null)
            {
                if (timeout != -1 && sw.ElapsedMilliseconds > timeout)
                {
                    sw.Stop();
                    resultBuffer = new byte[0];
                    //超时返回空
                    return 0;
                }
                System.Threading.Thread.Sleep(2);
            }
            sw.Stop();
            resultBuffer = this.ReceiveBuffer;
            return resultBuffer.Length;
        }
        #endregion
    }
}
 

创建Windows窗体测试程序CommunicationWindowsApp,添加对控制台项目CommunicationDemo的引用,并设置CommunicationWindowsApp为启动项目。重命名默认的Form1窗体为 FormCommunicationDemo。窗体设计如图:

使用通用接口与设备进行 串口(SerialPort)、网口(Socket)通信

窗体FormCommunicationDemo关键源代码如下(忽略设计器自动生成的代码):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using CommunicationDemo;

namespace CommunicationWindowsApp
{
    public partial class FormCommunicationDemo : Form
    {
        /// <summary>
        /// 定义设备网络、串口统一接口
        /// </summary>
        IEquipmentCommon equipmentCommon;
        public FormCommunicationDemo()
        {
            InitializeComponent();
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            equipmentCommon = new NetworkCommunication();
            equipmentCommon.DataReceived -= EquipmentCommon_DataReceived;
            equipmentCommon.DataReceived += EquipmentCommon_DataReceived;
            equipmentCommon.Connect(txtServerIP.Text,int.Parse(txtPort.Text));
        }

        private void EquipmentCommon_DataReceived(string message, MessageType messageType)
        {
            DisplayMessage($"【{messageType}】{message}");
        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            equipmentCommon.Close();
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            equipmentCommon.Send(rtxtSendMessage.Text);
        }

        private void btnSendAndWait_Click(object sender, EventArgs e)
        {
            DisplayMessage($"【网口】测试发送命令并接收,准备发送命令:【{rtxtSendMessage.Text}】");
            Application.DoEvents();
            string result;
            int length = equipmentCommon.SendAndWaitResult(rtxtSendMessage.Text, 6000, out result);
            DisplayMessage($"【网口】超时时间6秒,发送命令获取到的结果是【{result}】,接收到的数据长度【{length}】");
        }

        private void btnConnectCom_Click(object sender, EventArgs e)
        {
            equipmentCommon = new SerialPortCommunication();
            equipmentCommon.DataReceived -= EquipmentCommon_DataReceived;
            equipmentCommon.DataReceived += EquipmentCommon_DataReceived;
            equipmentCommon.Connect(txtPortName.Text, int.Parse(txtBaudRate.Text));
        }

        private void btnCloseCom_Click(object sender, EventArgs e)
        {
            equipmentCommon.Close();
        }

        private void btnSendCom_Click(object sender, EventArgs e)
        {
            equipmentCommon.Send(rtxtSendMessageCom.Text);
        }

        private void btnSendAndWaitCom_Click(object sender, EventArgs e)
        {
            DisplayMessage($"【串口】测试发送命令并接收,准备发送命令:【{rtxtSendMessageCom.Text}】");
            Application.DoEvents();
            string result;
            int length = equipmentCommon.SendAndWaitResult(rtxtSendMessageCom.Text, 6000, out result);
            DisplayMessage($"【串口】超时时间6秒,发送命令获取到的结果是【{result}】,接收到的数据长度【{length}】");
        }

        /// <summary>
        /// 显示富文本框的消息
        /// </summary>
        /// <param name="message"></param>
        private void DisplayMessage(string message)
        {
            this.BeginInvoke(new Action(() =>
            {
                if (rtxtDisplay.TextLength >= 10240)
                {
                    rtxtDisplay.Clear();
                }
                rtxtDisplay.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} --> {message}\n");
            }));
        }
    }
}
 

程序运行结果如图:

使用通用接口与设备进行 串口(SerialPort)、网口(Socket)通信