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;
}
}
}