C#通讯转发万向节(C#串口、TCP客户端、服务端数据转发监控工具)
- ==Qt版字节转发高性能上线==
- 万向节不是万圣节
- 一、根本需求
- 二、概要设计
- 2.1 要想快,用VS
- 2.2 通讯底层:至少三种通讯类和公用接口类
- 2.3 转发的实质是两点一线,方便画线,一点多线,线和点要分离
- 2.4 端口开关用复选框,转发连线用表格,数据展示用富文本,添加删除用右键,弹出用通知图标
- 2.5 下次打开不用重新添加,ini、xml和json配置都太烦,直接序列化。单独一个exe,首次使用自动生成序列化文件,丢了也不怕。
- 三、详细设计
- 四、万向节下载
- 五、谢谢观看,我用一天(不带处理bug),深圳用几天?
Qt版字节转发高性能上线
传送门:BM 字节转发(C++方向)
万向节不是万圣节
在硬件调试中,需要连接各种各样的端口(还有串口,以下统称端口)交互数据,由于上位机软件的通讯端口类型限制,不能直接用现有电脑存在的或者虚拟的端口连接使用。同时,通讯的数据不能直观的展现成需要的格式日志。所以,参考测控软件,开发了一个类似于汽车万向节的电脑程序。
一、根本需求
需要干的实事
- 端口数据转发 ,串口转tcp服务端、tcp客户端转串口、串口转串口等排列组合;
- 用起来简单明了,
多一个按钮想删 - 坐标郑州,挑战深圳速度;
二、概要设计
2.1 要想快,用VS
2.2 通讯底层:至少三种通讯类和公用接口类
串口类:
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace tempJH1101
{
internal class SeriaPortCommunication : MyriadInterface
{
string dev = "";
public SerialPort MySerialProt;
string PortName = "";
int BaudRate=0;
public bool OnLine = false;
/// <summary>
/// 存字节数
/// </summary>
public byte[] Buffer = new byte[1024];
/// <summary>
/// 接受数据线程
/// </summary>
public Thread ReceiveThread = null;
public delegate void MessageDelegate(byte[] data);
public event MessageDelegate ReciveMessage = null;
public event EventHandler<byte[]> ShowData;
public SeriaPortCommunication(string name, int BaudRate)
{
PortName = name;
this.BaudRate = BaudRate;
MySerialProt = new SerialPort(PortName);
MySerialProt.BaudRate = BaudRate;
MySerialProt.DataBits = 8;
MySerialProt.StopBits = StopBits.One;
MySerialProt.Parity = Parity.Even;
MySerialProt.DataReceived += MySerialProt_DataReceived;
}
public event EventHandler<byte[]> ExportData;
public void OpenCom()
{
try
{
MySerialProt.Open();
}
catch (Exception ex)
{
//MessageBox.Show($"{PortName} 串口打开失败");
OnLine = false;
throw ex;
}
OnLine = true;
}
private void MySerialProt_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(200);
if (!MySerialProt.IsOpen) return;
int len = MySerialProt.BytesToRead;
byte[] b = new byte[len];
MySerialProt.Read(b, 0, len);
//if (ReciveMessage != null)
// ReciveMessage(b);
if (ExportData != null)
ExportData(this,b);
}
public void Send(byte[] dataBuffer, int tryCount = 3)
{
//while ((!MySerialProt.IsOpen) &&
// tryCount > 0)
//{
// tryCount--;
//}
if (MySerialProt.IsOpen)
{
MySerialProt.Write(dataBuffer, 0, dataBuffer.Length);
}
}
public void CloseCom()
{
MySerialProt.Close();
}
public void Open()
{
OpenCom();
}
public void Close()
{
CloseCom();
}
public void SendData(byte[] data)
{
Send(data);
}
string MyriadInterface.GetInfo()
{
return "串口号:"+PortName+":"+ BaudRate;
}
public void SetName(string str)
{
dev = str;
}
public string GetName()
{
return dev;
}
}
}
TCP客户端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace tempJH1101
{
public class Client : MyriadInterface
{
string dev = "";
string IP;
int Port;
TcpClient TcpClient = new TcpClient();
Thread ClientReceiveThread = null;
bool IsTrue = true;
public event EventHandler<byte[]> ExportData;
public Client(string ip, int port)
{
IP = ip;
Port = port;
}
private void MeterPortDataReceived()
{
IsTrue = true;
while (IsTrue)
{
int length = 0;
byte[] buffer = new byte[4 * 1024];
try
{
length = TcpClient.Client.Receive(buffer, 0, TcpClient.Client.Available, SocketFlags.None);
if (length < 1) continue;
byte[] b = buffer.Take(length).ToArray();
DealData(b);
}
catch (Exception)
{
IsTrue = false;
}
Thread.Sleep(200);
}
}
public void Open()
{
TcpClient = new TcpClient();
TcpClient.Connect(IP, Port);
ClientReceiveThread = new Thread(MeterPortDataReceived);
ClientReceiveThread.Start();
}
public void Close()
{
IsTrue = false;
TcpClient.Close();
if (ClientReceiveThread != null)
ClientReceiveThread.Abort();
}
public void SendBuf(byte[] b)
{
TcpClient.Client.Send(b);
}
void DealData(byte[] b)
{
if (ExportData != null)
ExportData(this, b);
}
public string GetInfo()
{
return "客户端:" + IP + ":" + Port;
}
public string GetName()
{
return dev;
}
public void SetName(string str)
{
dev = str;
}
public void SendData(byte[] data)
{
SendBuf(data);
}
}
}
TCP服务端(单客户段端模式,有新的就断开旧的,一次就只连一个):
namespace tempJH1101
{
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
public class Server:MyriadInterface
{
string dev;
public delegate void DelegateShowData(int rs, byte[] data);
public event DelegateShowData ShowData;
public event EventHandler<byte[]> ExportData;
public bool IsTrue = true;
string ServerIP; // 服务器端ip
int Port; // 服务器端口
TcpListener netListener = null;
Thread listenerThread = null; //监听线程,存在多个客户端,所以使用线程监听
Socket Client = null;
Thread ReceiveThread = null;//接收处理线程,只允许一个客户端连接
public int MeterNo = -1;
public Server(string ipString, int port)
{
ServerIP = ipString;
Port = port;
}
public void StartListen()
{
stop();
IsTrue = true;
listenerThread = new Thread(new ThreadStart(NetWaiter));
listenerThread.Start();
}
private void NetWaiter()
{
try
{
netListener = new TcpListener(IPAddress.Parse(ServerIP), Port);
netListener.Start();
}
catch (Exception error)
{
MessageBox.Show(error.Message + " IP:" + ServerIP);
return;
}
while (IsTrue) //循环监听
{
if (!netListener.Pending())
{
Thread.Sleep(1000);
continue;
}
Socket soc = netListener.AcceptSocket();
if (ReceiveThread != null)
{
IsTrue = false;
ReceiveThread.Abort();
Thread.Sleep(1000);
}
if (Client != null)
{
try
{
Client.Shutdown(SocketShutdown.Both);
}
catch (Exception ee)
{
}
finally
{
Client.Close();
}
}
Client = soc;
ReceiveThread = new Thread(new ParameterizedThreadStart(receiveData));// 在新的线程中接收客户端信息
ReceiveThread.Start(Client);
Thread.Sleep(1000); // 延时1秒后,接收连接请求
}
}
private void receiveData(object obj)
{
IsTrue = true;
while (IsTrue)
{
try
{
byte[] buf = Receive(Client);
if (buf != null && buf.Length > 1)
{
if (ExportData != null)
ExportData(this, buf);
DealReadData(buf);
Thread.Sleep(200); // 延时0.2秒后再接收客户端发送的消息
}
}
catch (Exception exception)
{
Client.Shutdown(SocketShutdown.Both);
Client.Close();
return;
}
Thread.Sleep(200); // 延时0.2秒后再接收客户端发送的消息
}
}
private void DealReadData(byte[] buf)
{
try
{
//byte[] b = brain.ProcessStdCmd(MeterNo, buf);
//Send(b);
}
catch (Exception ee)
{
}
}
private byte[] Receive(Socket socket)
{
byte[] bytes = null;
int len = socket.Available;
if (len > 0)
{
bytes = new byte[len];
int receiveNumber = socket.Receive(bytes);
}
return bytes;
}
/// <summary>
/// 只发送最后的连接
/// </summary>
/// <param name="data"></param>
public void Send(byte[] data)
{
try
{
if (ShowData != null)
ShowData(1, data);
Client.Send(data);
}
catch (Exception ee)
{
}
}
/// <summary>
/// 停止服务
/// </summary>
public void stop()
{
try
{
IsTrue = false;
if (netListener != null)
{
netListener.Stop();
netListener = null;
}
if (listenerThread != null)
{
listenerThread.Abort();
}
if (ReceiveThread != null)
{
ReceiveThread.Abort();
Thread.Sleep(1000);
}
if (Client != null)
{
Client.Shutdown(SocketShutdown.Both);
Client.Close();
}
}
catch (Exception ee)
{
//MessageBox.Show(ee.Message);
}
}
public string GetInfo()
{
return "服务端:" + ServerIP + ":" + Port;
}
public string GetName()
{
return dev;
}
public void SetName(string str)
{
dev = str;
}
public void Open()
{
StartListen();
}
public void Close()
{
stop();
}
public void SendData(byte[] data)
{
Send(data);
}
}
}
方便操作的公用接口类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace tempJH1101
{
public interface MyriadInterface
{
string GetInfo();
string GetName();
void SetName(string str);
void Open();
void Close();
void SendData(byte[] data);
event EventHandler<byte[]> ExportData;
}
}
2.3 转发的实质是两点一线,方便画线,一点多线,线和点要分离
数据的来是接收,去是发送。触发点是接收,发送不发送看有没有出去的线,都发送给谁那就找另一端。所以每个端口的接收事件都只注册一个公用的处理方法,简单,好调试。端口整整齐齐的放在数组里,我想发给谁,就直接叫索引,有一条线,我就发一次,有两条线我就发两次,反正for的是表格,循环的速度肯定比你删除的速度快,报不了错。
中枢大脑类:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace tempJH1101
{
public class Brain
{
FormMain main;
string file_path;
public Config config;
public event EventHandler<ShowTime> ShowBuf;
public Brain(FormMain a)
{
main = a;
file_path = Application.StartupPath + "\\" + Path.GetFileNameWithoutExtension(Application.ExecutablePath) + ".ini";
config = new Config();
if (File.Exists(file_path))
{
try
{
config = (Config)BinaryToObject(file_path);
}
catch (Exception)
{
}
}
else
{
SaveConfig();
}
}
public void DealData(string dev, byte[] b)
{
for (int i = 0; i < main.dataGridView1.Rows.Count; i++)
{
bool bc = Convert.ToBoolean(main.dataGridView1.Rows[i].Cells[4].Value);
if (!bc) continue;
int fasongid = FormMain.name.IndexOf(dev);
string now = main.dataGridView1.Rows[i].Cells[2].Value.ToString();
if (now == dev)
{
int jieshouid = FormMain.name.IndexOf(main.dataGridView1.Rows[i].Cells[3].Value.ToString());
FormMain.list[jieshouid].SendData(b);
bool ss = Convert.ToBoolean(main.dataGridView1.Rows[i].Cells[0].Value);
if (!ss) continue;
if (ShowBuf != null)
{
ShowTime time = new ShowTime();
time.line = main.dataGridView1.Rows[i].Cells[1].Value.ToString(); ;
time.rs = 1;
time.buf = b;
ShowBuf(this, time);
}
}
now = main.dataGridView1.Rows[i].Cells[3].Value.ToString();
if (now == dev)
{
int jieshouid = FormMain.name.IndexOf(main.dataGridView1.Rows[i].Cells[2].Value.ToString());
FormMain.list[jieshouid].SendData(b);
bool ss = Convert.ToBoolean(main.dataGridView1.Rows[i].Cells[0].Value);
if (!ss) continue;
if (ShowBuf != null)
{
ShowTime time = new ShowTime();
time.line = main.dataGridView1.Rows[i].Cells[1].Value.ToString(); ;
time.rs = 0;
time.buf = b;
ShowBuf(this, time);
}
}
}
}
public void SaveConfig()
{
ObjectToBinary(file_path, config);
}
// 传入需要序列化的对象,写成泛型的可以对一切对象通用
void ObjectToBinary<Config>(string path, Config u)
{
FileStream fs = new FileStream(path, FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, u);
fs.Close();
}
// 传入已经序列的文件,保存的硬盘地址
object BinaryToObject(string path)
{
FileStream fs = new FileStream(path, FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
object r = bf.Deserialize(fs);
fs.Close();
return r;
}
}
public struct ShowTime
{
public string line;
public int rs;
public byte[] buf;
}
[Serializable]
public class Config
{
public bool IsHEX = true;
public Dictionary<string, string> socket = new Dictionary<string, string>();
public Dictionary<string, string> link = new Dictionary<string, string>();
}
}
2.4 端口开关用复选框,转发连线用表格,数据展示用富文本,添加删除用右键,弹出用通知图标
主界面类(没有大骆驼小骆驼,一共就那几个骆驼):
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;
namespace tempJH1101
{
public partial class FormMain : Form
{
public Brain brain;
public static List<MyriadInterface> list = new List<MyriadInterface>();
public static List<string> name = new List<string>();
public delegate void MessageDelegate(ShowTime e);
public MessageDelegate showreal = null;
/// <summary>
/// 异步委托
/// </summary>
public IAsyncResult ProtDataReceivedResult = null;
public FormMain()
{
InitializeComponent();
brain = new Brain(this);
brain.ShowBuf += Brain_ShowBuf1;
showreal = new MessageDelegate(Brain_ShowBuf);
treeView1.Nodes.Clear();
}
private void Brain_ShowBuf1(object sender, ShowTime e)
{
ProtDataReceivedResult = this.BeginInvoke(showreal, new object[] { e });
}
private void Brain_ShowBuf(ShowTime e)
{
string hex = GetHexString(e.buf);
if (!cbHEX.Checked)
{
hex = Encoding.ASCII.GetString(e.buf);
}
hex = hex.Replace("\r", "").Replace("\n", "");
if (hex.Length < 1) return;
string str = DateTime.Now.ToString("HH:mm:ss.fff");
if (e.rs == 1)
{
str = $"{str}\t{e.line}\t发送\t{hex}";
}
else
{
str = $"{str}\t{e.line}\t接收\t{hex}";
}
str = str + "\r\n";
int intStart = richTextBox1.TextLength;
//fctbReceive.Focus();
richTextBox1.AppendText(str);
richTextBox1.Select(intStart, intStart + str.Length);
richTextBox1.SelectionColor = Color.LightGreen;
if (e.rs == 1) richTextBox1.SelectionColor = Color.Red;
richTextBox1.ScrollToCaret();
}
private string GetHexString(byte[] data)
{
int len = data.Length;
string sb = "";
for (int i = 0; i < len; i++)
{
sb += (data[i].ToString("X2") + " ");
}
return sb;
}
private void btnAdd_Click(object sender, EventArgs e)
{
MyriadInterface port = null;
list.Add(port);
FormNewPort f = new FormNewPort(this);
if (f.ShowDialog() == DialogResult.OK)
{
list[list.Count - 1].ExportData += Port_ExportData;
treeView1.Nodes.Add(name[name.Count - 1]);
treeView1.Nodes[name.Count - 1].ToolTipText = list[name.Count - 1].GetInfo();
list[name.Count - 1].SetName(name[name.Count - 1]);
}
else
{
list.RemoveAt(list.Count - 1);
}
//treeView1.Nodes.Clear();
//for (int i = 0; i < name.Count; i++)
//{
// treeView1.Nodes.Add(name[i]);
// treeView1.Nodes[i].ToolTipText = list[i].GetInfo();
// list[i].SetName(name[i]);
//}
}
private void Port_ExportData(object sender, byte[] e)
{
string dev = ((MyriadInterface)sender).GetName();
brain.DealData(dev, e);
}
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
int id = e.Node.Index;
if (e.Node.Checked)
{
list[id].Open();
}
else
{
list[id].Close();
}
}
private void btnLink_Click(object sender, EventArgs e)
{
FormNewLine f = new FormNewLine(this);
f.ShowDialog();
}
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 4 && e.RowIndex > -1)
{
//if (Convert.ToBoolean(dataGridView1.Rows[e.RowIndex].Cells[4]))
//{
//}
//else
//{
//}
}
}
private void btnClear_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
richTextBox1.Clear();
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (MessageBox.Show("确认退出万向节工具?", "警告", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
{
e.Cancel = true;
return;
}
for (int i = 0; i < list.Count; i++)
{
list[i].Close();
}
brain.config.IsHEX = cbHEX.Checked;
brain.config.socket = new Dictionary<string, string>();
for (int i = 0; i < list.Count; i++)
{
MyriadInterface myriad = list[i];
string name = myriad.GetName();
string link = myriad.GetInfo();
brain.config.socket.Add(name, link);
}
brain.config.link = new Dictionary<string, string>();
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
string a1 = dataGridView1.Rows[i].Cells[1].Value.ToString();
string a2 = dataGridView1.Rows[i].Cells[2].Value.ToString();
//string a20 = dataGridView1.Rows[i].Cells[2].Tag.ToString();
string a3 = dataGridView1.Rows[i].Cells[3].Value.ToString();
//string a30 = dataGridView1.Rows[i].Cells[3].Tag.ToString();
brain.config.link.Add(a1, a2 + "\r" + a3);
}
brain.SaveConfig();
notifyIcon1.Dispose();
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized)
{
ShowInTaskbar = false;
}
else
{
ShowInTaskbar = true;
}
}
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
{
WindowState = FormWindowState.Normal;
Activate();
}
private void cbTop_CheckedChanged(object sender, EventArgs e)
{
TopMost = cbTop.Checked;
}
private void btnBreak_Click(object sender, EventArgs e)
{
DataGridViewRow row = dataGridView1.CurrentRow;
string name = row.Cells[1].Value.ToString();
if (MessageBox.Show($"确认连线\t“{name}”\t删除吗?", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
row.Cells[4].Value = false;
row.Cells[0].Value = false;
dataGridView1.Rows.Remove(row);
}
private void btnDelete_Click(object sender, EventArgs e)
{
TreeNode node = treeView1.SelectedNode;
if (node == null) return;
string name1 = node.Text;
if (MessageBox.Show($"确认端口\t“{name1}”\t删除吗?", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
int id = name.IndexOf(name1);
list[id].Close();
for (int i = dataGridView1.Rows.Count - 1; i >= 0; i--)
{
DataGridViewRow row = dataGridView1.Rows[i];
string rname = row.Cells[2].Value.ToString();
if (rname == name1)
{
row.Cells[4].Value = false;
row.Cells[0].Value = false;
dataGridView1.Rows.Remove(row);
continue;
}
rname = row.Cells[3].Value.ToString();
if (rname == name1)
{
row.Cells[4].Value = false;
row.Cells[0].Value = false;
dataGridView1.Rows.Remove(row);
}
}
treeView1.Nodes.Remove(node);
name.Remove(name1);
list.RemoveAt(id);
}
private void FormMain_Load(object sender, EventArgs e)
{
if (brain.config.IsHEX)
{
cbHEX.Checked = true;
}
else
{
cbASCII.Checked = true;
}
for (int i = 0; i < brain.config.socket.Count; i++)
{
string name = brain.config.socket.ElementAt(i).Key;
string str = brain.config.socket.ElementAt(i).Value;
string tyoe1 = str.Split(':')[0];
string a2 = str.Split(':')[1];
MyriadInterface port = null;
FormMain.name.Add(name);
if (tyoe1 == "串口号")
{
port = new SeriaPortCommunication(a2.Split(':')[0], int.Parse(a2.Split(':')[1]));
}
else if (tyoe1 == "服务端")
{
port = new Server(a2.Split(':')[0], int.Parse(a2.Split(':')[1]));
}
else if (tyoe1 == "客户端")
{
port = new Client(a2.Split(':')[0], int.Parse(a2.Split(':')[1]));
}
port.SetName(name);
port.ExportData += Port_ExportData;
list.Add(port);
treeView1.Nodes.Add(name);
treeView1.Nodes[treeView1.Nodes.Count - 1].ToolTipText = port.GetInfo();
}
for (int i = 0; i < brain.config.link.Count; i++)
{
string name = brain.config.link.ElementAt(i).Key;
string str = brain.config.link.ElementAt(i).Value;
string[] arr = str.Split('\r');
dataGridView1.Rows.Add(false, name, arr[0], arr[1], false);
}
}
}
}
2.5 下次打开不用重新添加,ini、xml和json配置都太烦,直接序列化。单独一个exe,首次使用自动生成序列化文件,丢了也不怕。
怕丢放中枢大脑类文件里了
三、详细设计
上面就是问答题答案,全部代码见下期,关键是下期谁还看啊,直接上干货:
四、万向节下载