C# .NET 网络 & 串口通信(上位机全套完整版思维导图笔记)

中心主题:C# .NET 网络通信 + 串口通信(上位机开发全套) 四大主干:原生 Socket | TcpClient/TcpListener | UdpClient | 第三方 SDK 新增补充模块:串口 SerialPort、获取本机 IP、获取串口列表、端口操作、开发做题 / 项目标准顺序、全套可运行示例、踩坑清单

📋 前置命名空间汇总(所有代码通用)

csharp

运行

//网络通信必引
using System;
using System.Net;
using System.Net.Sockets;
//串口必引
using System.IO.Ports;
//其他辅助
using System.Linq;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;

✅ 项目开发标准做题顺序(重点!面试 / 做毕设必按这个流程)

不管做 TCP 服务端、UDP 网关、串口 Modbus、海康 SDK,固定开发顺序

  1. 需求分析:确定通信类型(TCP?UDP?串口?第三方 SDK)、协议(Modbus-RTU / 自定义帧)、交互时序
  2. 获取硬件 / 网络资源
    • 串口:枚举本机所有 COM 口
    • 网络:获取本机 IP、选择监听端口
  3. 日志模块优先实现(先写 LogHelper,SDK 类优先开启底层日志,日志放在最前面!
  4. 资源对象定义(全局:SerialPort / UdpClient / TcpListener / Socket / SDK 句柄、CancellationTokenSource 取消令牌)
  5. UI 绑定、参数校验(IP、端口、波特率非空判断)
  6. 通信初始化 + 开启监听 / 连接
  7. 异步接收循环(后台线程接收,严禁阻塞 UI)
  8. 协议解析(粘包处理 / CRC 校验 / 帧解析)
  9. 业务逻辑(云台、读写寄存器、截图录像等)
  10. 资源释放!(关闭串口、停止监听、Logout、Cleanup、Dispose)
  11. 异常捕获 + 写入日志
  12. 测试边界:断网、串口拔出、重复点击连接

口诀:日志先行 → 枚举资源 → 初始化 → 异步收 → 解析协议 → 业务 → 释放资源


分支 0【新增必备工具模块】上位机高频工具:串口枚举、本机 IP、端口

0.1 获取本机所有串口 COM 列表(SerialPort)

API

SerialPort.GetPortNames() → 返回string[],直接拿到本机 COM1、COM2...

完整示例(WinForm,加载到下拉框 cbbCom)

csharp

运行

/// <summary>获取本机所有串口,绑定到下拉框</summary>
private void LoadComList()
{
    //拿到所有COM名称数组
    string[] comArr = SerialPort.GetPortNames();
    cbbCOMPort.Items.Clear();
    cbbCOMPort.Items.AddRange(comArr);
    if(comArr.Length>0) cbbCOMPort.SelectedIndex = 0;
}
//窗体加载时调用
private void Form_Load(object sender,EventArgs e)
{
    LoadComList();
}

✅ 知识点:

  • 依赖System.IO.Ports
  • 插拔串口不会自动刷新,可加【刷新串口】按钮重新执行GetPortNames()
  • 不存在的 COM 直接 Open 会抛异常,必须 try-catch

0.2 SerialPort 串口全套 API(Modbus-RTU 串口核心)

表格

API 功能
SerialPort(端口,波特率,校验位,数据位,停止位) 构造串口对象
Open() 打开串口
Close() / Dispose() 关闭释放
Write(byte[],offset,count) 发送二进制(Modbus-RTU 字节)
Read(byte[],offset,count) 同步读取
DataReceived 串口接收事件(后台 IO 线程,禁止直接操作 UI!要用 Invoke)
BytesToRead 当前缓冲区字节数

最简 Modbus 串口示例

csharp

运行

SerialPort serial = new SerialPort("COM3",9600,Parity.None,8,StopBits.One);
serial.Open();
byte[] modbusFrame = {0x01,0x03,0x00,0x00,0x00,0x04,0x44,0x09};
serial.Write(modbusFrame,0,modbusFrame.Length);
//DataReceived事件接收设备应答

⚠️坑:DataReceived不在 UI 线程,更新控件必须Invoke

0.3 获取本机 IP 地址(上位机网关、服务端绑定 IP 用)

方式 1:获取本机第一个 IPv4(你之前 IPHelper 版本)

csharp

运行

public static IPAddress GetLocalIPv4()
{
    IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName());
    foreach(var ip in hostEntry.AddressList)
    {
        //只取IPv4,排除IPv6
        if(ip.AddressFamily == AddressFamily.InterNetwork)
        {
            return ip;
        }
    }
    return null;
}

调用:IPAddress localIp = GetLocalIPv4();

方式 2:枚举所有网卡全部 IPv4(多网卡电脑必备,推荐上位机使用)

csharp

运行

/// <summary>获取本机全部IPv4,返回列表</summary>
public static List<IPAddress> GetAllLocalIPv4()
{
    List<IPAddress> ipList = new List<IPAddress>();
    foreach(var netInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
    {
        //跳过回环、禁用、虚拟网卡
        if(netInterface.OperationalStatus != System.Net.NetworkInformation.OperationalStatus.Up) continue;
        foreach(var unicast in netInterface.GetIPProperties().UnicastAddresses)
        {
            if(unicast.Address.AddressFamily == AddressFamily.InterNetwork)
            {
                ipList.Add(unicast.Address);
            }
        }
    }
    return ipList;
}

✅适用:多网卡(有线 + 无线),下拉框给用户手动选择本机 IP

0.4 端口相关知识点

  1. 端口范围:1~65535;1-1023 知名端口,尽量不用,上位机建议用 8000、8080、9000 以上
  2. 端口占用判断(可选,启动服务前检测端口是否被占用)

csharp

运行

/// <summary>检测端口是否被占用</summary>
public static bool IsPortInUse(int port)
{
    try
    {
        //尝试绑定该端口,失败=占用
        using(Socket testSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp))
        {
            testSocket.Bind(new IPEndPoint(IPAddress.Any,port));
            return false;
        }
    }
    catch
    {
        return true;
    }
}

分支 1:原生 Socket(底层,TCP/UDP 两套)

命名空间:System.Net.Sockets 本质:操作系统原生套接字,TcpClient/UdpClient 内部都是封装 Socket,自由度最高

1.1 Socket TCP

核心 API

表格

API 说明
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp) TCP 构造
Bind() 服务端绑定 IP + 端口
Listen(int backlog) 开始监听,backlog = 等待连接队列长度
Accept() / AcceptAsync() 阻塞 / 异步接受客户端连接,返回新 Socket
Connect() / ConnectAsync() 客户端发起连接(三次握手)
Send() / SendAsync() 发送字节流
Receive() / ReceiveAsync() 接收字节流
Shutdown(SocketShutdown.Both) 优雅关闭
Close() / Dispose() 释放

✅特点:面向连接、可靠、有序、存在粘包 ✅TCP 服务端最简示例

csharp

运行

//1.获取本机IP、定义端口
IPAddress localIp = GetLocalIPv4();
int port = 8080;
IPEndPoint serverEp = new IPEndPoint(localIp,port);
//2.创建TCP Socket
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
serverSocket.Bind(serverEp);
serverSocket.Listen(10);
//3.等待客户端
Socket clientSocket = await serverSocket.AcceptAsync();
//收发
byte[] buffer = new byte[1024];
int recvLen = await clientSocket.ReceiveAsync(new ArraySegment<byte>(buffer),SocketFlags.None);

1.2 Socket UDP

核心 API

表格

API 说明
Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp) UDP 构造
Bind() UDP 服务端绑定本地端口
SendTo(byte[],SocketFlags,EndPoint) 指定远端端点发送
ReceiveFrom(byte[],SocketFlags,ref EndPoint) 接收,拿到发送方 RemoteEp(网关原路回包核心)

✅特点:无连接、不可靠、无粘包、支持广播 ✅UDP Socket 网关最简示例(Modbus UDP 转串口)

csharp

运行

Socket udpSocket = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
IPEndPoint localEp = new IPEndPoint(IPAddress.Any,8080);
udpSocket.Bind(localEp);
IPEndPoint remoteEp = new IPEndPoint(IPAddress.Any,0);
byte[] buf = new byte[1024];
int len = udpSocket.ReceiveFrom(buf,SocketFlags.None,ref remoteEp);
//下发串口 → 拿到应答resp → SendTo原路返回
udpSocket.SendTo(resp,SocketFlags.None,remoteEp);

分支 2:TcpClient / TcpListener(TCP 封装,上位机 TCP 首选)

仅支持 TCP!封装 Socket,代码简洁,适合 ModbusTCP、TCP 设备

2.1 TcpListener(TCP 服务端)API

表格

API 功能
TcpListener(IPAddress localIp,int port) 构造监听对象
Start() 启动监听(内部 Bind+Listen)
AcceptTcpClient() / AcceptTcpClientAsync() 等待客户端,返回 TcpClient
Stop() 停止监听

2.2 TcpClient(TCP 客户端)API

表格

API 功能
TcpClient() 构造
Connect(ip,port) / ConnectAsync 建立 TCP 连接
NetworkStream GetStream() 获取流,Write 写、Read 读
Close() / Dispose() 释放

✅示例 TCP 客户端

csharp

运行

TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync("127.0.0.1",8080);
NetworkStream stream = tcpClient.GetStream();
byte[] sendData = Encoding.UTF8.GetBytes("test");
await stream.WriteAsync(sendData,0,sendData.Length);

⚠️重点坑:TCP一定会粘包!自定义协议必须加【帧头 + 长度】做分包解析


分支 3:UdpClient(UDP 封装,你的 UDP-Modbus 网关项目)

封装 Socket Dgram,专门简化 UDP,无连接

3.1 核心 API

表格

API 功能
UdpClient(int port) 服务端:绑定本地端口
UdpClient() UDP 客户端
Receive(ref IPEndPoint) / ReceiveAsync() 接收,自动拿到远端 RemoteEp
Send(byte[],int,IPEndPoint) / SendAsync 指定远端发送
Connect(ip,port) 可选:锁定默认远端,后续 Send 不用传 ep
Close() / Dispose() 释放

✅示例 UDP 网关(和你项目一模一样)

csharp

运行

UdpClient udpServer = new UdpClient(8080);
IPEndPoint remoteEp = null;
//异步接收
UdpReceiveResult result = await udpServer.ReceiveAsync();
byte[] recvFrame = result.Buffer;
remoteEp = result.RemoteEndPoint;
//串口下发 → 获取应答respBuf
udpServer.Send(respBuf, respBuf.Length, remoteEp);

✅关键点:UDP 应答必须使用收到包时拿到的 RemoteEp 原路返回


分支 4:第三方 SDK(海康摄像头、工业相机、NModbus 等)

不直接操作 Socket,厂商封装底层网络、码流、私有协议,C# 通过DllImport调用 C 库

4.1 海康 CHCNetSDK(你的项目全套 API)

表格

API 调用顺序说明
NET_DVR_SetLogToFile 最先调用,必须在 Init 之前,开启 SDK 底层日志
NET_DVR_Init SDK 全局初始化
NET_DVR_Login_V40 登录摄像头设备,返回 UserID
NET_DVR_RealPlay_V40 预览取流,返回 RealHandle
NET_DVR_PTZControlWithSpeed 云台控制
NET_DVR_CaptureJPEGPicture JPEG 抓图
NET_DVR_SaveRealData 开始录像
NET_DVR_StopSaveRealData 停止录像
NET_DVR_StopRealPlay 停止预览
NET_DVR_Logout 退出登录
NET_DVR_Cleanup 程序退出最后执行,释放 SDK
NET_DVR_GetLastError 获取错误码,配合日志

✅标准调用顺序:SetLogToFile → Init → Login → 预览/云台/抓图 → Stop → Logout → Cleanup

4.2 第三方 SDK 通用知识点

  1. 大多是非托管 C++dll,需要[DllImport]导入
  2. 句柄 / UserId 必须妥善保存,用完必须释放,极易内存泄漏
  3. 优先开启 SDK 自带底层日志,再叠加自己业务 LogHelper

📌 全模块横向对比总表(背诵)

表格

类型 底层 连接 粘包 典型场景
Socket(TCP) 原生套接字 ✅会粘包 高性能自定义 TCP 协议
Socket(UDP) 原生套接字 ❌无 UDP 广播、极简网关
TcpClient/TcpListener 封装 Socket (TCP) ✅会粘包 ModbusTCP、普通 TCP 上位机
UdpClient 封装 Socket (UDP) ❌无 UDP-ModbusRTU 网关
SerialPort 串口 系统串口驱动 ❌按字节流 Modbus-RTU485/232
第三方 SDK 厂商封装 SDK 内部处理 SDK 内部处理 海康摄像头、工业相机

📌 通用高频坑汇总(上位机必避)

  1. TCP:粘包!必须设计帧结构(例:0xAA 0xBB + 长度 + 数据 + CRC)
  2. UDP:记住 RemoteEp,应答原路回包;不保证送达
  3. SerialPort:DataReceived 事件不在 UI 线程 → Invoke 更新控件;用完 Close
  4. 本机 IP:多网卡不要写死 127.0.0.1,优先枚举网卡选择
  5. 端口:启动前判断占用;防火墙放行
  6. 海康 SDK:SetLogToFile必须在 Init 前面;窗体关闭必须逐级释放句柄
  7. 所有异步循环接收:配合CancellationTokenSource取消,窗体关闭停止循环,防止后台死循环

📌 配套工具类整合(一次性复制,直接放到项目)

csharp

运行

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.IO.Ports;

namespace _1
{
    public static class NetSerialHelper
    {
        #region IP工具
        /// <summary>获取本机第一个IPv4</summary>
        public static IPAddress GetLocalIPv4()
        {
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            return host.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
        }
        /// <summary>获取本机全部网卡IPv4</summary>
        public static List<IPAddress> GetAllLocalIPv4()
        {
            List<IPAddress> list = new List<IPAddress>();
            foreach(var ni in NetworkInterface.GetAllNetworkInterfaces())
            {
                if(ni.OperationalStatus != OperationalStatus.Up) continue;
                foreach(var addr in ni.GetIPProperties().UnicastAddresses)
                {
                    if(addr.Address.AddressFamily == AddressFamily.InterNetwork)
                        list.Add(addr.Address);
                }
            }
            return list;
        }
        /// <summary>检测TCP端口是否占用</summary>
        public static bool IsTcpPortUsed(int port)
        {
            try
            {
                using(Socket s = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp))
                {
                    s.Bind(new IPEndPoint(IPAddress.Any,port));
                    return false;
                }
            }
            catch { return true; }
        }
        #endregion

        #region 串口工具
        /// <summary>获取本机所有COM口</summary>
        public static string[] GetAllComPort()
        {
            return SerialPort.GetPortNames();
        }
        #endregion
    }

    //日志帮助类(业务日志,你海康项目在用)
    public static class LogHelper
    {
        public static void WriteInfo(string msg)=>Write(msg,false);
        public static void WriteError(string msg)=>Write(msg,true);
        public static void WriteHkError(string op,uint errCode)=>Write($"【{op}】海康错误码:{errCode}",true);

        private static void Write(string msg,bool isError)
        {
            try
            {
                string dir = Path.Combine(Application.StartupPath,"AppLog");
                if(!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                string file = Path.Combine(dir,DateTime.Now.ToString("yyyy-MM-dd")+".txt");
                string level = isError ? "[ERROR]" : "[INFO]";
                string line = $"{DateTime.Now:HH:mm:ss} {level} {msg}{Environment.NewLine}";
                File.AppendAllText(file,line,Encoding.UTF8);
            }catch{}
        }
    }
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐