软件与设备通信,一般有两种通信方式:
①网络(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。窗体设计如图:
窗体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");
}));
}
}
}
程序运行结果如图: