C#实现TCP串口转网口通信工具开发指南

一、概述


    在工业自动化和物联网领域,串口设备(如PLC、传感器等)常常需要通过网络进行远程监控和通信。基于TCP的串口转网口工具可以帮助我们实现这种需求,同时支持Modbus协议的读写操作,非常适合在具体工业场景中使用。本文将详细介绍基于C#的串口转网口工具的开发过程,包括功能设计、代码实现以及配置管理。

二、功能需求分析
串口通信支持:

配置串口参数(如波特率、数据位、停止位、校验位等)。
支持Modbus协议,读取串口设备中的寄存器数据。
网络通信支持:

配置TCP监听地址和端口。
支持多个客户端连接,实现数据转发功能。
心跳检测:

支持心跳包功能,确保网络连接的可靠性。
配置管理:

实现配置文件管理,保存和加载串口和TCP服务器的设置。
可视化界面:

提供用户界面,直观展示串口、网口状态和传输数据。

三、代码架构设计

1. 主要组件

串口通信模块:
使用SerialPort类管理串口通信。
使用Modbus.Device库处理Modbus协议。
TCP服务模块:
使用TcpListener和TcpClient实现TCP通信。
支持多客户端连接管理。
心跳包机制:
使用Timer控件定时发送心跳包。
配置管理:使用ConfigurationManager管理配置文件。

2. 数据流向
串口到网口:串口接收到数据后,转发给所有已连接的TCP客户端。
网口到串口:TCP客户端发送的数据通过串口发送。
Modbus协议处理:根据Modbus协议读取串口设备的数据,并转发给TCP客户端。


四、代码实现
1. 界面设计
界面分为以下部分:

指示灯:显示串口和网口的状态(绿色为正常,红色为异常)。
串口配置:设置串口参数。
服务器配置:设置TCP服务器的IP地址和端口。
心跳设置:设置心跳包的间隔、内容和HEX模式。
2. 代码实现
2.1 初始化与配置加载

private bool Init()
{
    try
    {
        // 加载串口配置
        for (int i = 1; i <= 8; i++)
        {
            cbPortName.Items.Add("COM" + i);
            cbBTL.Items.Add((1200 * Math.Pow(2, i - 1)).ToString());
            if (i >= 5)
            {
                cbSJW.Items.Add(i.ToString());
            }
            if (i >= 1 && i <= 5)
            {
                cbJYW.Items.Add(Enum.GetName(typeof(Parity), i - 1));
            }
            if (i >= 1 && i <= 4)
            {
                cbSZW.Items.Add(Enum.GetName(typeof(StopBits), i - 1));
            }
        }
        cbPortName.SelectedItem = ConfigurationManager.AppSettings["SerialPort"];
        cbBTL.SelectedItem = ConfigurationManager.AppSettings["BaudRate"];
        cbSJW.SelectedItem = ConfigurationManager.AppSettings["DataBits"];
        cbJYW.SelectedItem = ConfigurationManager.AppSettings["Parity"];
        cbSZW.SelectedItem = ConfigurationManager.AppSettings["StopBits"];

        // 配置串口
        serialPort = new SerialPort();
        serialPort.PortName = cbPortName.SelectedItem.ToString();
        serialPort.BaudRate = Convert.ToInt32(cbBTL.SelectedItem.ToString());
        serialPort.DataBits = Convert.ToInt32(cbSJW.SelectedItem);
        serialPort.Parity = (Parity)Enum.Parse(typeof(Parity), cbJYW.SelectedItem.ToString());
        serialPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cbSZW.SelectedItem.ToString());

        // 配置服务器
        tbIP.Text = ConfigurationManager.AppSettings["IPAddress"];
        tbDK.Text = ConfigurationManager.AppSettings["Port"];
        IPAddress ip = IPAddress.Parse(tbIP.Text.Trim());
        int port = Convert.ToInt32(tbDK.Text.Trim());
        tcpListener = new TcpListener(ip, port);

        // 心跳包配置
        radioButton1.Checked = bool.Parse(ConfigurationManager.AppSettings["HeartOn"]);
        radioButton2.Checked = !bool.Parse(ConfigurationManager.AppSettings["HeartOn"]);
        tbXTJG.Text = ConfigurationManager.AppSettings["HeartTime"];
        tbXTNR.Text = ConfigurationManager.AppSettings["HeartContent"];
        checkBox1.Checked = bool.Parse(ConfigurationManager.AppSettings["HeartHex"]);
        timer1.Interval = Convert.ToInt32(tbXTJG.Text);
        timer1.Tick += Timer1_Tick;

        return true;
    }
    catch (Exception ex)
    {
        MessageBox.Show($"出错了: {ex.Message}");
        return false;
    }
}

2.2串口与Modbus初始化

private void StartSerialPort()
{
    try
    {
        serialPort.Open();
        master = ModbusSerialMaster.CreateRtu(serialPort);
        panel1.BackColor = Color.Lime; // 状态灯显示绿色
    }
    catch (Exception ex)
    {
        panel1.BackColor = Color.Red; // 状态灯显示红色
        MessageBox.Show($"启动串口失败: {ex.Message}");
    }
}

2.4 数据转发逻辑

private void AcceptData()
{
    cts = new CancellationTokenSource();
    Task.Run(() =>
    {
        while (!cts.IsCancellationRequested)
        {
            try
            {
                TcpClient client = tcpListener.AcceptTcpClient();
                clients.Add(client);

                Task.Run(async () =>
                {
                    NetworkStream stream = client.GetStream();
                    while (!cts.IsCancellationRequested)
                    {
                        try
                        {
                            byte[] buffer = new byte[client.Available];
                            int length = await stream.ReadAsync(buffer, 0, buffer.Length);
                            string message = Encoding.UTF8.GetString(buffer, 0, length).Trim();

                            byte[] responseData = null;
                            switch (message)
                            {
                                case "温度值":
                                    responseData = ReadWD();
                                    break;
                                case "含水率":
                                    responseData = ReadHS();
                                    break;
                                case "电导率":
                                    responseData = ReadDD();
                                    break;
                                case "PH值":
                                    responseData = ReadPH();
                                    break;
                                case "盐度":
                                    responseData = ReadYD();
                                    break;
                                case "TDS":
                                    responseData = ReadTDS();
                                    break;
                                default:
                                    // 如果不是特定命令,将消息转发给串口
                                    serialPort.Write(buffer, 0, length);
                                    break;
                            }

                            if (responseData != null)
                            {
                                await stream.WriteAsync(responseData, 0, responseData.Length);
                            }

                            Invoke((Action)(async () =>
                            {
                                tbread.Text = (Convert.ToInt32(tbread.Text) + length).ToString();
                                panel3.BackColor = Color.Lime;
                                await Task.Delay(100);
                                panel3.BackColor = Color.White;
                            }));
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show($"出错: {ex.Message}");
                            break;
                        }
                    }
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show($"客户端连接异常: {ex.Message}");
            }
        }
    });
}

2.5 心跳包机制

private void Timer1_Tick(object sender, EventArgs e)
{
    if (clients.Count == 0)
    {
        return;
    }
    byte[] buffer;
    if (checkBox1.Checked) // 十六进制
    {
        string[] hexs = tbXTNR.Text.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
        buffer = new byte[hexs.Length];
        for (int i = 0; i < hexs.Length; i++)
        {
            try
            {
                buffer[i] = Convert.ToByte(hexs[i], 16);
            }
            catch (FormatException)
            {
                MessageBox.Show("十六进制格式错误");
                return;
            }
        }
    }
    else
    {
        buffer = Encoding.UTF8.GetBytes(tbXTNR.Text.Trim());
    }
    for (int i = 0; i < clients.Count; i++)
    {
        if (clients[i].Connected)
        {
            NetworkStream stream = clients[i].GetStream();
            stream.Write(buffer, 0, buffer.Length);
        }
    }
}

2.6 Modbus读取实现
 

private byte[] ReadWD()
{
    try
    {
        ushort[] data = master.ReadHoldingRegisters(1, 0x01, 1);
        float temperature = data[0] / 10F;
        return Encoding.UTF8.GetBytes(temperature.ToString("N1"));
    }
    catch (Exception ex)
    {
        MessageBox.Show($"读取温度值出错: {ex.Message}");
        return new byte[0];
    }
}

3. 配置文件管理
使用app.config文件保存设置:


    
        
        
        
        
        
        
        
        //这里可以更改为自己的网络ip地址
        
        
        
        
        
        
    

4.保存配置

private void btnSave_Click(object sender, EventArgs e)
{
    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    config.AppSettings.Settings["SerialPort"].Value = cbPortName.SelectedItem.ToString();
    config.AppSettings.Settings["BaudRate"].Value = cbBTL.SelectedItem.ToString();
    config.AppSettings.Settings["DataBits"].Value = cbSJW.SelectedItem.ToString();
    config.AppSettings.Settings["Parity"].Value = cbJYW.SelectedItem.ToString();
    config.AppSettings.Settings["StopBits"].Value = cbSZW.SelectedItem.ToString();
    config.Save(ConfigurationSaveMode.Modified);
    ConfigurationManager.RefreshSection("appSettings");
    MessageBox.Show("串口保存配置成功,重启查看效果");
}

五、功能测试
C#实现TCP串口转网口通信工具开发指南_第1张图片

注意串口配对和配置情况

例如(COM1->COM2) 两两一组

波特率、数据位等信息要保持一致

服务器的配置可以根据自己的网络ip进行更改

并在资源管理器中点击引用  添加这两个.netget包

C#实现TCP串口转网口通信工具开发指南_第2张图片

六、总结
本项目基于C#实现了一个完整的串口转网口工具,支持串口Modbus协议读写、TCP多客户端通信以及心跳机制,同时提供了配置文件管理功能。该工具适用于工业自动化、物联网等领域,具有较高的实用性和可扩展性。

完整的代码如下

using Modbus.Device;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 串口转网口
{
    public partial class Form1 : Form
    {
        //串口网口都支持
        IModbusMaster master;
        SerialPort serialPort;
        TcpListener tcpListener;
        CancellationTokenSource cts;
        List clients = new List();

        public Form1()
        {
            InitializeComponent();
        }

        #region 窗口加载负责对通信基础进行初始化

        private void Form1_Load(object sender, EventArgs e)
        {
            if (!Init())
            {
                // 关窗体
                Close();
                return;
            }
            // 启动串口
            StartSerialPort();
            StartTcpListener();
        }

        private void StartTcpListener()
        {
            try
            {
                tcpListener.Start();
                panel2.BackColor = Color.Lime;
                // 服务器启动后需要开启一个循环来接受新的客户端连接
                AcceptData();
                timer1.Enabled = radioButton1.Checked;
            }
            catch (Exception ex)
            {
                panel2.BackColor = Color.Red;
                MessageBox.Show("启动TCP监听器失败: " + ex.Message);
            }
        }

        private void AcceptData()
        {
            cts = new CancellationTokenSource();
            Task.Run(() =>
            {
                while (!cts.IsCancellationRequested)
                {
                    try
                    {
                        // 等待客户端连接
                        TcpClient client = tcpListener.AcceptTcpClient();
                        //这里添加到list里面是为了当串口收到消息时,能够找到在线的客户端,并发送消息
                        clients.Add(client);

                        //接收客户端的消息转发给串口
                        Task.Run(() =>
                        {
                            while (!cts.IsCancellationRequested)
                            {
                                try
                                {
                                    byte[] buffer = new byte[client.Available];
                                    NetworkStream stream = client.GetStream();
                                    int length = stream.Read(buffer, 0, buffer.Length);
                                    string message = Encoding.UTF8.GetString(buffer, 0, length).Trim();

                                    // 检查客户端发送的消息并读取相应的数据
                                    byte[] responseData = null;
                                    switch (message)
                                    {
                                        case "温度值":
                                            responseData = ReadWD();
                                            break;
                                        case "含水率":
                                            responseData = ReadHS();
                                            break;
                                        case "电导率":
                                            responseData = ReadDD();
                                            break;
                                        case "PH值":
                                            responseData = ReadPH();
                                            break;
                                        case "盐度":
                                            responseData = ReadYD();
                                            break;
                                        case "TDS":
                                            responseData = ReadTDS();
                                            break;
                                        default:
                                            // 如果不是特定命令,将消息转发给串口
                                            serialPort.Write(buffer, 0, length);
                                            break;
                                    }

                                    if (responseData != null)
                                    {
                                        stream.Write(responseData, 0, responseData.Length);
                                    }

                                    Invoke((Action)(async () =>
                                    {
                                        tbread.Text = (Convert.ToInt32(tbread.Text) + length).ToString();
                                        //模拟信号灯,有数据进来频闪
                                        panel3.BackColor = Color.Lime;
                                        await Task.Delay(100);
                                        panel3.BackColor = Color.White;
                                    }));
                                }
                                catch (Exception ex)
                                {
                                    MessageBox.Show("出错: " + ex.Message);
                                    break;
                                }
                            }
                        }, cts.Token);
                    }
                    catch (Exception ex)
                    {
                       
                    }
                }
            }, cts.Token);
        }

        private void StartSerialPort()
        {
            try
            {
                serialPort.Open();
                // 创建Modbus Master实例
                master = ModbusSerialMaster.CreateRtu(serialPort);
                panel1.BackColor = Color.Lime;
            }
            catch (Exception ex)
            {
                panel1.BackColor = Color.Red;
                MessageBox.Show("启动串口失败: " + ex.Message);
            }
        }

        private bool Init()
        {
            try
            {
                for (int i = 1; i <= 8; i++)
                {
                    cbPortName.Items.Add("COM" + i);
                    cbBTL.Items.Add((1200 * Math.Pow(2, i - 1)).ToString());
                    if (i >= 5)
                    {
                        cbSJW.Items.Add(i.ToString());
                    }
                    if (i >= 1 && i <= 5)
                    {
                        cbJYW.Items.Add(Enum.GetName(typeof(Parity), i - 1));
                    }
                    if (i >= 1 && i <= 4)
                    {
                        cbSZW.Items.Add(Enum.GetName(typeof(StopBits), i - 1));
                    }
                }
                cbPortName.SelectedItem = ConfigurationManager.AppSettings["SerialPort"];
                cbBTL.SelectedItem = ConfigurationManager.AppSettings["BaudRate"];
                cbSJW.SelectedItem = ConfigurationManager.AppSettings["DataBits"];
                cbJYW.SelectedItem = ConfigurationManager.AppSettings["Parity"];
                cbSZW.SelectedItem = ConfigurationManager.AppSettings["StopBits"];
                // 配置串口
                serialPort = new SerialPort();
                serialPort.PortName = cbPortName.SelectedItem.ToString();
                serialPort.BaudRate = Convert.ToInt32(cbBTL.SelectedItem.ToString());
                serialPort.DataBits = Convert.ToInt32(cbSJW.SelectedItem);
                serialPort.Parity = (Parity)Enum.Parse(typeof(Parity), cbJYW.SelectedItem.ToString());
                serialPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cbSZW.SelectedItem.ToString());

                // 配置服务器
                tbIP.Text = ConfigurationManager.AppSettings["IPAddress"];
                tbDK.Text = ConfigurationManager.AppSettings["Port"];
                // 初始化服务器信息
                IPAddress ip = IPAddress.Parse(tbIP.Text.Trim());
                int port = Convert.ToInt32(tbDK.Text.Trim());
                tcpListener = new TcpListener(ip, port);

                // 心跳包配置
                radioButton1.Checked = bool.Parse(ConfigurationManager.AppSettings["HeartOn"]);
                radioButton2.Checked = !bool.Parse(ConfigurationManager.AppSettings["HeartOn"]); // 确保两个单选按钮互斥
                tbXTJG.Text = ConfigurationManager.AppSettings["HeartTime"];
                tbXTNR.Text = ConfigurationManager.AppSettings["HeartContent"];
                checkBox1.Checked = bool.Parse(ConfigurationManager.AppSettings["HeartHex"]);
                // 心跳包通过Timer控件来控制
                timer1.Interval = Convert.ToInt32(tbXTJG.Text);
                timer1.Tick += Timer1_Tick;
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("出错了: " + ex.Message);
                return false;
            }
        }

        private void Timer1_Tick(object sender, EventArgs e)
        {
            if (clients.Count == 0)
            {
                return;
            }
            byte[] buffer;
            if (checkBox1.Checked) // 十六进制
            {
                string[] hexs = tbXTNR.Text.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                buffer = new byte[hexs.Length];
                for (int i = 0; i < hexs.Length; i++)
                {
                    try
                    {
                        buffer[i] = Convert.ToByte(hexs[i], 16);
                    }
                    catch (FormatException)
                    {
                        MessageBox.Show("十六进制格式错误");
                        return;
                    }
                }
            }
            else
            {
                // 心跳内容为字符串
                buffer = Encoding.UTF8.GetBytes(tbXTNR.Text.Trim());
            }
            for (int i = 0; i < clients.Count; i++)
            {
                if (clients[i].Connected)
                {
                    NetworkStream stream = clients[i].GetStream();
                    stream.Write(buffer, 0, buffer.Length);
                }
            }
        }

      

        private byte[] ReadWD()
        {
            try
            {
                
                ushort[] data = master.ReadHoldingRegisters(1, 0x01, 1);
                float temperature = data[0] / 10F;
                return Encoding.UTF8.GetBytes(temperature.ToString("N1"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("读取温度值出错: " + ex.Message);
                return new byte[0]; // 返回空字节数组
            }
        }

        private byte[] ReadHS()
        {
            try
            {
                ushort[] data = master.ReadHoldingRegisters(1, 0x00, 1);
                float humidity = data[0] / 10F;
                string debugMsg = $"读取含水率: 原始值={data[0]}, 计算值={humidity}";
                Console.WriteLine(debugMsg);  // 在输出窗口查看
                return Encoding.UTF8.GetBytes(humidity.ToString("N1"));
            }
            catch (Exception ex)
            {
                Console.WriteLine($"读取含水率出错: {ex.Message}");
                return new byte[0];
            }
        }

        private byte[] ReadDD()
        {
            try
            {
                // 假设电导率在Modbus地址0x02
                ushort[] data = master.ReadHoldingRegisters(1, 0x02, 1);
                float conductivity = data[0] / 10F;
                return Encoding.UTF8.GetBytes(conductivity.ToString("N1"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("读取电导率出错: " + ex.Message);
                return new byte[0]; // 返回空字节数组
            }
        }
        private byte[] ReadPH()
        {
            try
            {
                
                ushort[] data = master.ReadHoldingRegisters(1, 0x03, 1);
                float conductivity = data[0] / 10F;
                return Encoding.UTF8.GetBytes(conductivity.ToString("N1"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("读取电导率出错: " + ex.Message);
                return new byte[0]; // 返回空字节数组
            }
        }
        private byte[] ReadYD()
        {
            try
            {
                
                ushort[] data = master.ReadHoldingRegisters(1, 0x07, 1);
                float conductivity = data[0] / 10F;
                return Encoding.UTF8.GetBytes(conductivity.ToString("N1"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("读取电导率出错: " + ex.Message);
                return new byte[0]; // 返回空字节数组
            }
        }
        private byte[] ReadTDS()
        {
            try
            {
                
                ushort[] data = master.ReadHoldingRegisters(1, 0x08, 1);
                float conductivity = data[0] / 10F;
                return Encoding.UTF8.GetBytes(conductivity.ToString("N1"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("读取电导率出错: " + ex.Message);
                return new byte[0]; // 返回空字节数组
            }
        }
        #endregion

        #region 串口保存

        private void btnSave_Click(object sender, EventArgs e)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings["SerialPort"].Value = cbPortName.SelectedItem.ToString();
            config.AppSettings.Settings["BaudRate"].Value = cbBTL.SelectedItem.ToString();
            config.AppSettings.Settings["DataBits"].Value = cbSJW.SelectedItem.ToString();
            config.AppSettings.Settings["Parity"].Value = cbJYW.SelectedItem.ToString();
            config.AppSettings.Settings["StopBits"].Value = cbSZW.SelectedItem.ToString();
            config.Save(ConfigurationSaveMode.Modified);
            ConfigurationManager.RefreshSection("appSettings");
            MessageBox.Show("串口保存配置成功,重启查看效果");
        }

        private void btnSave2_Click(object sender, EventArgs e)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings["IPAddress"].Value = tbIP.Text.Trim();
            config.AppSettings.Settings["Port"].Value = tbDK.Text.Trim();
            config.Save();
            MessageBox.Show("服务器保存配置成功,重启查看效果");
        }

        private void btnSave3_Click(object sender, EventArgs e)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings["HeartOn"].Value = radioButton1.Checked ? "true" : "false";
            config.AppSettings.Settings["HeartTime"].Value = tbXTJG.Text.Trim();
            config.AppSettings.Settings["HeartContent"].Value = tbXTNR.Text.Trim();
            config.AppSettings.Settings["HeartHex"].Value = checkBox1.Checked.ToString();
            config.Save();
            MessageBox.Show("心跳包保存配置成功,重启查看效果");
        }

        #endregion

        #region 心跳开关

        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            timer1.Enabled = radioButton1.Checked;
        }

        private void radioButton2_CheckedChanged(object sender, EventArgs e)
        {
            timer1.Enabled = radioButton2.Checked;
        }

        #endregion

        private void btnread_Click(object sender, EventArgs e)
        {

        }
    }
}


     
        
    
	
		
		
		
		
		
		
		
		
		
		
		
		
		
		
	

你可能感兴趣的:(tcp/ip,网络协议,网络,c#)