大家好,欢迎来到IT知识分享网。
C#—串口和网口之间的通信
串口转网口、网口转串口,就是将网口发送来的数据包发送给串口设备,将串口设备返回的数据转发给网口客户端。
在C#中,将串口数据转换为网络数据并发送到网络,通常涉及以下步骤:
1. 创建一个TCP/UDP客户端,以连接到远程服务器。
2. 在串口接收中继站数据时,解析该数据以确定其格式和内容。
3. 将解析后的数据打包成符合网络协议的格式。
4. 使用网络客户端发送数据。
实现串口网口的转换实例
接下来就为大家介绍一下本次的实例:
1. 当串口事件接收到串口设备发来的消息时,将接收到的消息通过tcp进行发出给客户端,并且亮灯。
2. 当tcp服务器接收到网口客户端的消息时,将接收到的消息通过串口发出给串口设备,并且亮灯。
3. 项目中还有心跳机制,检测串口设备,将设备参数写入INI格式地址等操作。
提示: 本文中用到的serialPort1是winform的窗体控件
一、读取配置文件并设置串口网口对象
public partial class Form1 : Form { IniHelper Ini; string[] botlvs = new string[] { "1200", "4800", "9600" ,"13200"}; TcpListener listen; List<TcpClient> lists = new List<TcpClient>(); // 存放所有客户端 Dictionary<TcpClient, DateTime> HearDic = new Dictionary<TcpClient, DateTime>(); // 存放心跳 int heartTime=0,heartConnent=0; bool whetherHeart; public Form1() { InitializeComponent(); // 1 读取配置文件 string dirPath = Path.Combine(Application.StartupPath,"File"); string filePath = Path.Combine(dirPath, "Setting.ini"); Ini = new IniHelper(filePath); // 2 添加串口 comboBox1.Items.AddRange(SerialPort.GetPortNames()); // 获取所有串口 拼接在下拉框的items中 comboBox2.Items.AddRange(botlvs); // 添加波特率数组 comboBox2.Items.Add("自定义"); // 添加一个 comboBox3.Items.AddRange(new string[] { "5", "6", "7", "8" }); comboBox4.Items.AddRange(new string[] {"无","奇校检","偶效验"}); comboBox5.Items.AddRange(new string[] {"无","1","2","1.5"}); this.serialPort1.DataReceived += SerialPort1_DataReceived; // 3 处理界面默认值 readSetting(); // 4 开始串口通信 startChuanKou(); // 5 开始网口通信 startTCP(); } }
二、处理界面默认值
/// <summary> /// 配置串口默认值 /// </summary> void readSetting() { // 配置串口 comboBox1.SelectedItem = Ini.Read("Serialport", "name"); string botelv = Ini.Read("Serialport", "botelv", "9601"); int botelvIndex = Array.IndexOf(botlvs, botelv); // 获取botelv在数组里面的索引值 if (botelvIndex != -1) { comboBox2.SelectedIndex = botelvIndex; comboBox2.DropDownStyle = ComboBoxStyle.DropDownList; } else { // 波特率在数组里面 自定义波特率情况 comboBox2.DropDownStyle = ComboBoxStyle.DropDown; // 可编辑的下拉框 // DropDownList 不可编辑的下拉框 comboBox2.Text = botelv; } // 处理数据位 comboBox3.SelectedItem = Ini.Read("Serialport", "databit", "8"); // 处理奇偶校检位 comboBox4.SelectedIndex = Ini.Read("Serialport", "parity", 0); comboBox5.SelectedIndex = Ini.Read("Serialport", "stopbit", 0); comboBox1.DropDownStyle = ComboBoxStyle.DropDownList; comboBox3.DropDownStyle = ComboBoxStyle.DropDownList; comboBox4.DropDownStyle = ComboBoxStyle.DropDownList; comboBox5.DropDownStyle = ComboBoxStyle.DropDownList; // 网口数据的读取 textBox3.Text = Ini.Read("NetWork", "port", "8080"); if (Ini.Read("NetWork", "heartOn", false)) { radioButton1.Checked = true; } else { radioButton1.Checked = true; } textBox4.Text = Ini.Read("NetWork", "heartTime", "60000"); textBox5.Text = Ini.Read("NetWork", "heartData", ""); checkBox1.Checked = Ini.Read("NetWork", "heartHex", false); heartConnent = int.Parse(textBox5.Text); heartTime = int.Parse(textBox4.Text); whetherHeart = checkBox1.Checked; }
三、配置串口数据
/// <summary> /// 配置串口数据 /// </summary> void startChuanKou() { // 配置串口对象 try { this.serialPort1.PortName = comboBox1.Text; this.serialPort1.BaudRate = int.Parse(comboBox2.Text); this.serialPort1.DataBits = int.Parse(comboBox3.Text); this.serialPort1.StopBits = (StopBits)comboBox5.SelectedIndex; this.serialPort1.Parity = (Parity)comboBox4.SelectedIndex; this.serialPort1.Open(); // 亮灯 this.panel1.BackColor = Color.Green; } catch (Exception ex) { MessageBox.Show(ex.Message); if (this.serialPort1.IsOpen) { serialPort1.Close(); // 灭灯 this.panel1.BackColor = Color.DarkGray; } } }
四、实现tcp网口通信
/// <summary> /// TCP网口通信 /// </summary> void startTCP() { if (!int.TryParse(textBox3.Text,out int port) || port<1 || port>65563) { MessageBox.Show("请输入正确端口号!!!","提示"); } // 开启服务器 接收客户端 listen = new TcpListener(System.Net.IPAddress.Any, port); listen.Start(99); // 开启监听 panel2.BackColor = Color.Green; // 把多个客户端接收到数组里面 new Task(() => { while (true) { try { // 接收客户端 TcpClient c1 = listen.AcceptTcpClient(); if (!lists.Contains(c1) && !HearDic.ContainsKey(c1)) { // 把客户端添加到数里面 群发需要 if (whetherHeart) { lists.Add(c1); HearDic.Add(c1, DateTime.Now); } else lists.Add(c1); // 接受客户端发来的消息 tcpReceive(c1); } } catch (Exception ex) { MessageBox.Show(ex.Message); foreach (var c in lists) { c.Close(); } panel2.BackColor = Color.Green; } } }).Start(); } /// <summary> /// 接收网口消息 /// </summary> void tcpReceive(TcpClient client) { NetworkStream stream = client.GetStream(); Task.Run(() => { byte[] body = new byte[1024]; while (client.Connected) { try { int count = stream.Read(body, 0, body.Length); if (count != 0) { if (whetherHeart&& body[0] == heartConnent) { try { if (DateTime.Now - HearDic[client] <= new TimeSpan(0, 0, heartTime)) { HearDic[client] = DateTime.Now; } else { HearDic.Remove(client); lists.Remove(client); MessageBox.Show(client.Client.RemoteEndPoint.ToString() + "断开"); break; } } catch (Exception ex) { MessageBox.Show(ex.Message); break; } } else { serialPort1.Write(body, 0, body.Length); 网口接收消息亮灯(); } } else { HearDic.Remove(client); lists.Remove(client); } } catch (Exception ex) { listen.Start(); MessageBox.Show(ex.Message); break; } } }); } /// <summary> /// 是否设置心跳 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void radioButton1_CheckedChanged(object sender, EventArgs e) { if (checkBox1.Checked) whetherHeart = true; else whetherHeart = false; }
五、实现接收串口数据发送给网口并且亮灯
/// <summary> /// 接收串口数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { byte[] bs = new byte[serialPort1.WriteBufferSize]; // 定义一个字节数组 int count = serialPort1.Read(bs, 0, bs.Length); // 读取数据到字节数组里面 if (count == 0) { // 关闭串口 if (this.serialPort1.IsOpen) { serialPort1.Close(); // 灭灯 this.panel1.BackColor = Color.DarkGray; } } // 1 接收到1串口数据 需要让panel3更改颜色(亮灯) 封装方法控制效果 串口接收消息亮灯(); // 2 转发给所有客户端数据 串口转网口就是把串口数据通过网络转给其他客户端 foreach (var item in lists) { item.GetStream().Write(bs,0,bs.Length); } } catch (Exception ex) { MessageBox.Show(ex.Message); if (this.serialPort1.IsOpen) { serialPort1.Close(); // 灭灯 this.panel1.BackColor = Color.DarkGray; } } } /// <summary> /// 串口消息亮灯 /// </summary> void 串口接收消息亮灯() { this.Invoke(new Action(async () => { this.textBox1.Text = (int.Parse(this.textBox1.Text) + 1).ToString(); // 亮灯 panel3.BackColor = Color.Green; await Task.Delay(200); panel3.BackColor = Color.Gray; })); }
INI格式操作对象
public class IniHelper { public string FileName { get; set; } public IniHelper(string name) { this.FileName = name; } // 1 先导入C语言读取ini文件的方法 // GetPrivateProfileString() 获取ini配置文件的数据 // static 静态的变量 只能生命在当前文件中,不能在其他源文件进行使用。 // extern 用来声明外部的符号 可以跨文件使用。 [DllImport("Kernel32.dll")] //导入方法所在dll文件 public static extern Int32 GetPrivateProfileString( string sectionName, //段名 string keyName,//键名 string defaultName,//当键不存在默认值。 StringBuilder returnValue,// 读取到数据 uint size,// 读取数据的大小 string fileName// ini文件路径 ); // 2 添加加写入ini文件的C方法 [DllImport("Kernel32.dll")] public static extern Int32 WritePrivateProfileString( string sectionName, //段名 string keyName,//键名 string value,//设置的值 string fileName// ini文件路径 ); // 不直接使用上面的两个C语言的方法 再封装到方法中 // 读取ini数据的方法 // ReadDate("Second","B","Setting.ini") "10" public static string ReadDate(string sectionName, string keyName, string fileName) { StringBuilder sb = new StringBuilder(512); //512是存储的大小 GetPrivateProfileString(sectionName, keyName, string.Empty, sb, 512, fileName); return sb.ToString(); } /// <summary> /// 读取数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">键名</param> /// <returns>string</returns> public string Read(string sectionName, string keyName) { return ReadDate(sectionName, keyName, this.FileName); } /// <summary> /// 读取数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">键名</param> /// <param name="defaultValue">默认值</param> /// <returns>string</returns> public string Read(string sectionName, string keyName, string defaultValue) { string v = Read(sectionName, keyName); if (string.IsNullOrEmpty(v)) { return defaultValue; } return v; } /// <summary> /// 读取数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">键名</param> /// <param name="defaultValue">默认值</param> /// <returns>int</returns> public int Read(string sectionName, string keyName, int defaultValue) { string v = Read(sectionName, keyName); if (int.TryParse(v, out int value)) { return value; } else { return defaultValue; } } /// <summary> /// 读取数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">键名</param> /// <param name="defaultValue">默认值</param> /// <returns>bool</returns> public bool Read(string sectionName, string keyName, bool defaultValue) { string v = Read(sectionName, keyName); if (bool.TryParse(v, out bool value)) { return value; } else { // 不能转成bool if (v == "1" || v == "OK" || v == "ON" || v == "YES") { return true; } else if (v == "0" || v == "NO" || v == "OFF" || v.ToUpper() == "OFF") { // 如果值为以上几种情况 值为false return false; } else { return defaultValue; } } } /// <summary> /// 读取数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">键名</param> /// <param name="defaultValue">默认值</param> /// <returns>double</returns> public double Read(string sectionName, string keyName, double defaultValue) { string v = Read(sectionName, keyName); if (double.TryParse(v, out double value)) { return value; } else { return defaultValue; } } public static int WriteData(string s1, string k1, string v, string file) { return WritePrivateProfileString(s1, k1, v, file); } /// <summary> /// 写入数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">健名</param> /// <param name="value">值</param> /// <returns>int</returns> public int Write(string sectionName, string keyName, string value) { return WriteData(sectionName, keyName, value, this.FileName); } /// <summary> /// 写入数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">健名</param> /// <param name="value">值</param> /// <returns>int</returns> public int Write(string sectionName, string keyName, int value) { return WriteData(sectionName, keyName, value.ToString(), this.FileName); } /// <summary> /// 写入数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">健名</param> /// <param name="value">值</param> /// <returns>int</returns> public int Write(string sectionName, string keyName, bool value) { return WriteData(sectionName, keyName, value ? "1" : "0", this.FileName); } /// <summary> /// 写入数据 /// </summary> /// <param name="sectionName">段名</param> /// <param name="keyName">健名</param> /// <param name="value">值</param> /// <returns>int</returns> public int Write(string sectionName, string keyName, DateTime value) { return WriteData(sectionName, keyName, value.ToString("yyyy-MM-dd HH-mm:ss"), this.FileName); } }
完整代码
public partial class Form1 : Form { IniHelper Ini; string[] botlvs = new string[] { "1200", "4800", "9600" ,"13200"}; TcpListener listen; List<TcpClient> lists = new List<TcpClient>(); // 存放所有客户端 Dictionary<TcpClient, DateTime> HearDic = new Dictionary<TcpClient, DateTime>(); // 存放心跳 int heartTime=0,heartConnent=0; bool whetherHeart; public Form1() { InitializeComponent(); // 1 读取配置文件 string dirPath = Path.Combine(Application.StartupPath,"File"); string filePath = Path.Combine(dirPath, "Setting.ini"); Ini = new IniHelper(filePath); // 2 添加串口 comboBox1.Items.AddRange(SerialPort.GetPortNames()); // 获取所有串口 拼接在下拉框的items中 comboBox2.Items.AddRange(botlvs); // 添加波特率数组 comboBox2.Items.Add("自定义"); // 添加一个 comboBox3.Items.AddRange(new string[] { "5", "6", "7", "8" }); comboBox4.Items.AddRange(new string[] {"无","奇校检","偶效验"}); comboBox5.Items.AddRange(new string[] {"无","1","2","1.5"}); this.serialPort1.DataReceived += SerialPort1_DataReceived; // 3 处理界面默认值 readSetting(); // 4 开始串口通信 startChuanKou(); // 5 开始网口通信 startTCP(); } /// <summary> /// TCP网口通信 /// </summary> void startTCP() { if (!int.TryParse(textBox3.Text,out int port) || port<1 || port>65563) { MessageBox.Show("请输入正确端口号!!!","提示"); } // 开启服务器 接收客户端 listen = new TcpListener(System.Net.IPAddress.Any, port); listen.Start(99); // 开启监听 panel2.BackColor = Color.Green; // 把多个客户端接收到数组里面 new Task(() => { while (true) { try { // 接收客户端 TcpClient c1 = listen.AcceptTcpClient(); if (!lists.Contains(c1) && !HearDic.ContainsKey(c1)) { // 把客户端添加到数里面 群发需要 if (whetherHeart) { lists.Add(c1); HearDic.Add(c1, DateTime.Now); } else lists.Add(c1); // 接受客户端发来的消息 tcpReceive(c1); } } catch (Exception ex) { MessageBox.Show(ex.Message); foreach (var c in lists) { c.Close(); } panel2.BackColor = Color.Green; } } }).Start(); } /// <summary> /// 接收网口消息 /// </summary> void tcpReceive(TcpClient client) { NetworkStream stream = client.GetStream(); Task.Run(() => { byte[] body = new byte[1024]; while (client.Connected) { try { int count = stream.Read(body, 0, body.Length); if (count != 0) { if (whetherHeart&& body[0] == heartConnent) { try { if (DateTime.Now - HearDic[client] <= new TimeSpan(0, 0, heartTime)) { HearDic[client] = DateTime.Now; } else { HearDic.Remove(client); lists.Remove(client); MessageBox.Show(client.Client.RemoteEndPoint.ToString() + "断开"); break; } } catch (Exception ex) { MessageBox.Show(ex.Message); break; } } else { serialPort1.Write(body, 0, body.Length); 网口接收消息亮灯(); } } else { HearDic.Remove(client); lists.Remove(client); } } catch (Exception ex) { listen.Start(); MessageBox.Show(ex.Message); break; } } }); } /// <summary> /// 配置串口数据 /// </summary> void startChuanKou() { // 配置串口对象 try { this.serialPort1.PortName = comboBox1.Text; this.serialPort1.BaudRate = int.Parse(comboBox2.Text); this.serialPort1.DataBits = int.Parse(comboBox3.Text); this.serialPort1.StopBits = (StopBits)comboBox5.SelectedIndex; this.serialPort1.Parity = (Parity)comboBox4.SelectedIndex; this.serialPort1.Open(); // 亮灯 this.panel1.BackColor = Color.Green; } catch (Exception ex) { MessageBox.Show(ex.Message); if (this.serialPort1.IsOpen) { serialPort1.Close(); // 灭灯 this.panel1.BackColor = Color.DarkGray; } } } /// <summary> /// 配置串口默认值 /// </summary> void readSetting() { // 配置串口 comboBox1.SelectedItem = Ini.Read("Serialport", "name"); string botelv = Ini.Read("Serialport", "botelv", "9601"); int botelvIndex = Array.IndexOf(botlvs, botelv); // 获取botelv在数组里面的索引值 if (botelvIndex != -1) { comboBox2.SelectedIndex = botelvIndex; comboBox2.DropDownStyle = ComboBoxStyle.DropDownList; } else { // 波特率在数组里面 自定义波特率情况 comboBox2.DropDownStyle = ComboBoxStyle.DropDown; // 可编辑的下拉框 // DropDownList 不可编辑的下拉框 comboBox2.Text = botelv; } // 处理数据位 comboBox3.SelectedItem = Ini.Read("Serialport", "databit", "8"); // 处理奇偶校检位 comboBox4.SelectedIndex = Ini.Read("Serialport", "parity", 0); comboBox5.SelectedIndex = Ini.Read("Serialport", "stopbit", 0); comboBox1.DropDownStyle = ComboBoxStyle.DropDownList; comboBox3.DropDownStyle = ComboBoxStyle.DropDownList; comboBox4.DropDownStyle = ComboBoxStyle.DropDownList; comboBox5.DropDownStyle = ComboBoxStyle.DropDownList; // 网口数据的读取 textBox3.Text = Ini.Read("NetWork", "port", "8080"); if (Ini.Read("NetWork", "heartOn", false)) { radioButton1.Checked = true; } else { radioButton1.Checked = true; } textBox4.Text = Ini.Read("NetWork", "heartTime", "60000"); textBox5.Text = Ini.Read("NetWork", "heartData", ""); checkBox1.Checked = Ini.Read("NetWork", "heartHex", false); heartConnent = int.Parse(textBox5.Text); heartTime = int.Parse(textBox4.Text); whetherHeart = checkBox1.Checked; } /// <summary> /// 接收串口数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { byte[] bs = new byte[serialPort1.WriteBufferSize]; // 定义一个字节数组 int count = serialPort1.Read(bs, 0, bs.Length); // 读取数据到字节数组里面 if (count == 0) { // 关闭串口 if (this.serialPort1.IsOpen) { serialPort1.Close(); // 灭灯 this.panel1.BackColor = Color.DarkGray; } } // 1 接收到1串口数据 需要让panel3更改颜色(亮灯) 封装方法控制效果 串口接收消息亮灯(); // 2 转发给所有客户端数据 串口转网口就是把串口数据通过网络转给其他客户端 foreach (var item in lists) { item.GetStream().Write(bs,0,bs.Length); } } catch (Exception ex) { MessageBox.Show(ex.Message); if (this.serialPort1.IsOpen) { serialPort1.Close(); // 灭灯 this.panel1.BackColor = Color.DarkGray; } } } /// <summary> /// 串口消息亮灯 /// </summary> void 串口接收消息亮灯() { this.Invoke(new Action(async () => { this.textBox1.Text = (int.Parse(this.textBox1.Text) + 1).ToString(); // 亮灯 panel3.BackColor = Color.Green; await Task.Delay(200); panel3.BackColor = Color.Gray; })); } /// <summary> /// 网口消息亮灯 /// </summary> void 网口接收消息亮灯() { this.Invoke(new Action(async () => { this.textBox2.Text = (int.Parse(this.textBox2.Text) + 1).ToString(); // 亮灯 panel4.BackColor = Color.Green; await Task.Delay(200); panel4.BackColor = Color.Gray; })); } /// <summary> /// 是否设置心跳 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void radioButton1_CheckedChanged(object sender, EventArgs e) { if (checkBox1.Checked) whetherHeart = true; else whetherHeart = false; } /// <summary> /// 重启 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void button1_Click(object sender, EventArgs e) { // 关闭串口 if (this.serialPort1.IsOpen) { serialPort1.Close(); } // 灭灯 this.panel1.BackColor = Color.Gray; // 停掉TCP foreach (var c in lists) { c.Close(); } listen.Stop(); panel2.BackColor = Color.Gray; await Task.Delay(1000); // 开启TCP startTCP(); // 开启串口 startChuanKou(); } /// <summary> /// 保存 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { } /// <summary> /// 波特率下拉框触发变化的时候调用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void comboBox2_SelectedIndexChanged(object sender, EventArgs e) { if (comboBox2.SelectedItem.ToString() == "自定义") { // 切换到自定义选项上 comboBox2.DropDownStyle = ComboBoxStyle.DropDown; } else { comboBox2.DropDownStyle = ComboBoxStyle.DropDownList; } } }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/147601.html