VS2008 + .NET3.5 环境、加热台TCP通讯场景

结合VS2008 + .NET3.5 环境、加热台TCP通讯场景,逐段拆解代码、讲清作用、运行流程、调用方式,全程通俗,不讲晦涩术语。

整体前置认知

1 程序整体作用

这是一套 C# TCP 客户端代码 ,专门和 129.168.1.133:9000 加热台通讯:

  • 建立网络连接 → 下发指令 → 接收设备返回数据 → 解析数据 → 断开连接
  • 封装了设备手册里全部指令:读写功率/电流/电压、IO引脚控制、IIC读写、修改设备IP等
  • 兼容老旧 VS2008,全程使用方法重载,不使用新版语法
csharp 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;

namespace VP_Communication.Communication.Heater
{
    /// <summary>
    /// SFP EVB加热台 TCP 全功能控制类
    /// 适配 VS2008 + .NET 3.5
    /// 完整覆盖Excel指令表所有功能
    /// </summary>
    public class SFP_EVB_Heater
    {
        private readonly object _lock = new object();
        private Socket Client;

        // 设备配置属性
        public string DefaultIP { get; set; }
        public int DefaultPort { get; set; }
        public int DefaultTimeout { get; set; }

        // 连接状态只读属性
        public bool IsOpen
        {
            get
            {
                return Client != null && Client.Connected;
            }
        }

        // 构造函数:初始化默认参数
        public SFP_EVB_Heater()
        {
            DefaultIP = "129.168.1.133"; // 你的设备真实IP
            DefaultPort = 9000;
            DefaultTimeout = 5000; // 已调整为5秒,避免超时
            Client = null;
        }

        #region 连接方法重载(适配VS2008,无默认参数)
        public bool Open()
        {
            return Open(DefaultIP, DefaultPort, DefaultTimeout);
        }

        public bool Open(string ipAddress)
        {
            return Open(ipAddress, DefaultPort, DefaultTimeout);
        }

        public bool Open(string ipAddress, int port)
        {
            return Open(ipAddress, port, DefaultTimeout);
        }
        #endregion

        /// <summary>
        /// 核心连接方法
        /// </summary>
        public bool Open(string ipAddress, int port, int timeOut)
        {
            if (ipAddress == null)
                ipAddress = DefaultIP;
            if (port <= 0)
                port = DefaultPort;
            if (timeOut <= 0)
                timeOut = DefaultTimeout;

            lock (_lock)
            {
                if (IsOpen)
                {
                    return true;
                }

                try
                {
                    Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    Client.ReceiveTimeout = timeOut;
                    Client.SendTimeout = timeOut;

                    IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
                    Client.Connect(endPoint);
                    Console.WriteLine("设备连接成功");
                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(string.Format("连接失败:{0}", ex.Message));
                    if (Client != null)
                    {
                        Client.Close();
                        Client = null;
                    }
                    return false;
                }
            }
        }

        /// <summary>
        /// 断开设备连接
        /// </summary>
        public void Close()
        {
            lock (_lock)
            {
                if (IsOpen)
                {
                    Client.Close();
                    Client = null;
                    Console.WriteLine("连接已断开");
                }
            }
        }

        #region 通用指令收发方法重载
        public string SendCommand(string cmd)
        {
            return SendCommand(cmd, 300); // 已调整为300ms延迟,适配设备响应
        }
        #endregion

        /// <summary>
        /// 核心指令收发方法
        /// </summary>
        public string SendCommand(string cmd, int delay)
        {
            lock (_lock)
            {
                if (!IsOpen)
                {
                    return null;
                }

                try
                {
                    byte[] wbuf = Encoding.UTF8.GetBytes(cmd + "\r\n");
                    Client.Send(wbuf, SocketFlags.None);
                    Thread.Sleep(delay);

                    byte[] rbuf = new byte[1024];
                    int count = Client.Receive(rbuf, SocketFlags.None);
                    if (count > 0)
                    {
                        string res = Encoding.UTF8.GetString(rbuf, 0, count).Trim();
                        Console.WriteLine(string.Format("发送指令:{0}\n设备返回:{1}", cmd, res));
                        return res;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(string.Format("指令异常:{0}", ex.Message));
                }
                return null;
            }
        }

        #region 一、IIC 读写功能(完整覆盖Excel指令)
        #region IIC_Set 重载
        public bool IIC_Set(int slot)
        {
            return IIC_Set(slot, "a0", "0", "5", "a1,9,1b,2c,3d");
        }

        public bool IIC_Set(int slot, string deviceAddr)
        {
            return IIC_Set(slot, deviceAddr, "0", "5", "a1,9,1b,2c,3d");
        }

        public bool IIC_Set(int slot, string deviceAddr, string regAddr)
        {
            return IIC_Set(slot, deviceAddr, regAddr, "5", "a1,9,1b,2c,3d");
        }

        public bool IIC_Set(int slot, string deviceAddr, string regAddr, string dataLength)
        {
            return IIC_Set(slot, deviceAddr, regAddr, dataLength, "a1,9,1b,2c,3d");
        }
        #endregion

        /// <summary>
        /// IIC 写入数据
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="deviceAddr">器件地址</param>
        /// <param name="regAddr">寄存器地址</param>
        /// <param name="dataLength">数据长度</param>
        /// <param name="data">要写入的16进制数据,逗号分隔</param>
        /// <returns>true=写入成功</returns>
        public bool IIC_Set(int slot, string deviceAddr, string regAddr, string dataLength, string data)
        {
            string cmd = string.Format("IIC{0}:set {1},{2},{3},{4}", slot, deviceAddr, regAddr, dataLength, data);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains("iic set ok");
        }

        #region IIC_Get 重载
        public string IIC_Get(int slot)
        {
            return IIC_Get(slot, "a0", "0", "9");
        }

        public string IIC_Get(int slot, string deviceAddr)
        {
            return IIC_Get(slot, deviceAddr, "0", "9");
        }

        public string IIC_Get(int slot, string deviceAddr, string regAddr)
        {
            return IIC_Get(slot, deviceAddr, regAddr, "9");
        }
        #endregion

        /// <summary>
        /// IIC 读取数据
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="deviceAddr">器件地址</param>
        /// <param name="regAddr">寄存器地址</param>
        /// <param name="dataLength">要读取的数据长度</param>
        /// <returns>设备返回的完整数据</returns>
        public string IIC_Get(int slot, string deviceAddr, string regAddr, string dataLength)
        {
            string cmd = string.Format("IIC{0}:get {1},{2},{3}", slot, deviceAddr, regAddr, dataLength);
            return SendCommand(cmd);
        }
        #endregion

        #region 二、EVB 功率/电流/电压 读写功能(完整覆盖Excel指令)
        #region GetPower 重载
        public string GetPower()
        {
            return GetPower(1);
        }
        #endregion

        /// <summary>
        /// 查询槽位功率
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>功率数值(单位mW)</returns>
        public string GetPower(int slot)
        {
            string cmd = string.Format("evb{0}:getpower?", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res))
                return null;

            string numberOnly = Regex.Replace(res, @"[^0-9]", "");
            return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
        }

        #region GetCurrent 重载
        public string GetCurrent()
        {
            return GetCurrent(1);
        }
        #endregion

        /// <summary>
        /// 查询槽位电流
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>电流数值(单位uA)</returns>
        public string GetCurrent(int slot)
        {
            string cmd = string.Format("evb{0}:getcurrent?", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res))
                return null;

            string numberOnly = Regex.Replace(res, @"[^0-9]", "");
            return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
        }

        #region GetVoltage 重载
        public string GetVoltage()
        {
            return GetVoltage(1);
        }
        #endregion

        /// <summary>
        /// 查询槽位电压
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>电压数值(单位mV)</returns>
        public string GetVoltage(int slot)
        {
            string cmd = string.Format("evb{0}:getvoltage?", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res))
                return null;

            string numberOnly = Regex.Replace(res, @"[^0-9]", "");
            return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
        }

        #region SetVoltage 重载
        public bool SetVoltage(int slot)
        {
            return SetVoltage(slot, 3.3);
        }
        #endregion

        /// <summary>
        /// 设置槽位电压(范围3.15~3.3V)
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="voltage">电压值(单位V,如3.3)</param>
        /// <returns>true=设置成功</returns>
        public bool SetVoltage(int slot, double voltage)
        {
            string cmd = string.Format("evb{0}:setvoltage {1}", slot, voltage);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains("set Voltage ok!");
        }
        #endregion

        #region 三、IO 引脚控制功能(完整覆盖Excel指令)
        #region 1. PowerEN 模块使能引脚
        #region SetPowerEN 重载
        public bool SetPowerEN(int slot)
        {
            return SetPowerEN(slot, 1);
        }
        #endregion

        /// <summary>
        /// 设置模块使能引脚
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="state">1=高电平使能,0=低电平禁用</param>
        /// <returns>true=设置成功</returns>
        public bool SetPowerEN(int slot, int state)
        {
            string cmd = string.Format("IO{0}:setPowerEN {1}", slot, state);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains(string.Format("POWER_EN:{0}", state));
        }

        #region GetPowerEN 重载
        public string GetPowerEN()
        {
            return GetPowerEN(1);
        }
        #endregion

        /// <summary>
        /// 查询模块使能引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetPowerEN(int slot)
        {
            string cmd = string.Format("IO{0}:getPowerEN", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("POWER_EN:"))
                return null;

            return res.Split(':')[1].Trim();
        }
        #endregion

        #region 2. TxDis 引脚
        #region SetTxDis 重载
        public bool SetTxDis(int slot)
        {
            return SetTxDis(slot, 0);
        }
        #endregion

        /// <summary>
        /// 设置TX_DIS引脚
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="state">1=高电平,0=低电平</param>
        /// <returns>true=设置成功</returns>
        public bool SetTxDis(int slot, int state)
        {
            string cmd = string.Format("IO{0}:setTxDis {1}", slot, state);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains(string.Format("TX_DIS:{0}", state));
        }

        #region GetTxDis 重载
        public string GetTxDis()
        {
            return GetTxDis(1);
        }
        #endregion

        /// <summary>
        /// 查询TX_DIS引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetTxDis(int slot)
        {
            string cmd = string.Format("IO{0}:getTxDis", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("TX_DIS:"))
                return null;

            return res.Split(':')[1].Trim();
        }
        #endregion

        #region 3. Rs0High 引脚
        #region SetRs0High 重载
        public bool SetRs0High(int slot)
        {
            return SetRs0High(slot, 1);
        }
        #endregion

        /// <summary>
        /// 设置RS0_HIGH引脚
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="state">1=高电平,0=低电平</param>
        /// <returns>true=设置成功</returns>
        public bool SetRs0High(int slot, int state)
        {
            string cmd = string.Format("IO{0}:setRs0High {1}", slot, state);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains(string.Format("RS0_HIGH:{0}", state));
        }

        #region GetRs0High 重载
        public string GetRs0High()
        {
            return GetRs0High(1);
        }
        #endregion

        /// <summary>
        /// 查询RS0_HIGH引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetRs0High(int slot)
        {
            string cmd = string.Format("IO{0}:getRs0High", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("RS0_HIGH:"))
                return null;

            return res.Split(':')[1].Trim();
        }
        #endregion

        #region 4. Rs1High 引脚
        #region SetRs1High 重载
        public bool SetRs1High(int slot)
        {
            return SetRs1High(slot, 1);
        }
        #endregion

        /// <summary>
        /// 设置RS1_HIGH引脚
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <param name="state">1=高电平,0=低电平</param>
        /// <returns>true=设置成功</returns>
        public bool SetRs1High(int slot, int state)
        {
            string cmd = string.Format("IO{0}:setRs1High {1}", slot, state);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains(string.Format("RS1_HIGH:{0}", state));
        }

        #region GetRs1High 重载
        public string GetRs1High()
        {
            return GetRs1High(1);
        }
        #endregion

        /// <summary>
        /// 查询RS1_HIGH引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetRs1High(int slot)
        {
            string cmd = string.Format("IO{0}:getRs1High", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("RS1_HIGH:"))
                return null;

            return res.Split(':')[1].Trim();
        }
        #endregion

        #region 5. 其他只读引脚查询
        #region GetTxFalu 重载
        public string GetTxFalu()
        {
            return GetTxFalu(1);
        }
        #endregion

        /// <summary>
        /// 查询TX_FALU引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetTxFalu(int slot)
        {
            string cmd = string.Format("IO{0}:getTxFalu", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("TX_FALU:"))
                return null;

            return res.Split(':')[1].Trim();
        }

        #region GetRxLos 重载
        public string GetRxLos()
        {
            return GetRxLos(1);
        }
        #endregion

        /// <summary>
        /// 查询RX_LOS引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetRxLos(int slot)
        {
            string cmd = string.Format("IO{0}:getRxLos", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("RX_LOS:"))
                return null;

            return res.Split(':')[1].Trim();
        }

        #region GetABS 重载
        public string GetABS()
        {
            return GetABS(1);
        }
        #endregion

        /// <summary>
        /// 查询ABS引脚状态
        /// </summary>
        /// <param name="slot">槽位号</param>
        /// <returns>1=高电平,0=低电平</returns>
        public string GetABS(int slot)
        {
            string cmd = string.Format("IO{0}:getABS", slot);
            string res = SendCommand(cmd);

            if (string.IsNullOrEmpty(res) || !res.Contains("ABS:"))
                return null;

            return res.Split(':')[1].Trim();
        }
        #endregion
        #endregion
        #endregion

        #region 四、系统功能(完整覆盖Excel指令)
        /// <summary>
        /// 设置设备IP地址
        /// </summary>
        /// <param name="newIp">新的IP地址,格式129.168.1.xxx</param>
        /// <returns>true=设置成功</returns>
        public bool SetDeviceIP(string newIp)
        {
            string cmd = string.Format("setip {0}", newIp);
            string res = SendCommand(cmd);
            return !string.IsNullOrEmpty(res) && res.Contains(string.Format("setip:{0}", newIp));
        }

        /// <summary>
        /// 查询设备支持的所有命令
        /// </summary>
        /// <returns>设备返回的帮助信息</returns>
        public string GetHelp()
        {
            return SendCommand("help");
        }
        #endregion
    }

    /// <summary>
    /// 测试入口:完整功能演示
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            SFP_EVB_Heater heater = new SFP_EVB_Heater();

            // 1. 连接设备
            Console.WriteLine("===== 正在连接设备 =====");
            if (!heater.Open())
            {
                Console.WriteLine("按任意键退出...");
                Console.ReadKey();
                return;
            }

            // 2. 基础信息查询演示
            Console.WriteLine("\n===== 基础信息查询 =====");
            string power = heater.GetPower();
            string current = heater.GetCurrent();
            string voltage = heater.GetVoltage();

            if (!string.IsNullOrEmpty(power))
                Console.WriteLine("当前功率:" + power + " mW");
            if (!string.IsNullOrEmpty(current))
                Console.WriteLine("当前电流:" + current + " uA");
            if (!string.IsNullOrEmpty(voltage))
                Console.WriteLine("当前电压:" + voltage + " mV");

            // 3. 引脚状态查询演示
            Console.WriteLine("\n===== 引脚状态查询 =====");
            string powerEN = heater.GetPowerEN();
            string txDis = heater.GetTxDis();
            string rs0High = heater.GetRs0High();

            if (!string.IsNullOrEmpty(powerEN))
                Console.WriteLine("模块使能状态:" + powerEN);
            if (!string.IsNullOrEmpty(txDis))
                Console.WriteLine("TX_DIS状态:" + txDis);
            if (!string.IsNullOrEmpty(rs0High))
                Console.WriteLine("RS0_HIGH状态:" + rs0High);

            // 4. 断开连接
            Console.WriteLine("\n===== 测试完成 =====");
            heater.Close();

            Console.WriteLine("\n按任意键关闭窗口...");
            Console.ReadKey();
        }
    }
}

2 整体运行顺序(先记大流程)

程序启动 → 进入Main入口

  1. 创建加热台控制对象
  2. 发起TCP连接
  3. 依次查询功率、电流、电压
  4. 依次查询多个硬件引脚状态
  5. 断开TCP连接
  6. 窗口停留,按按键退出

第一部分:头部引用区

csharp 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
  • using:引入系统自带工具包,相当于"提前拿好要用的工具"
  1. System:基础功能(控制台打印、异常报错、字符串处理)
  2. System.Net / System.Net.SocketsTCP网络核心 ,提供Socket网络管道
  3. System.Text:字符串 ↔ 网络字节 互相转换(网络只能传二进制)
  4. System.Threading:线程功能,这里只用 Thread.Sleep(暂停等待)
  5. System.Text.RegularExpressions:正则表达式,用来提取数字(解析功率/电流)

第二部分:命名空间

csharp 复制代码
namespace VP_Communication.Communication.Heater
{
    // 所有代码都写在这个大括号内
}
  • namespace:代码分类文件夹
  • 作用:把加热台相关代码统一归类,和你公司现有项目结构保持一致,避免代码重名。

第三部分:核心类 SFP_EVB_Heater

csharp 复制代码
public class SFP_EVB_Heater
{
    // 类内部所有成员
}
  • class:定义一个功能模板,你可以理解为「加热台遥控器」
  • 里面封装了:连接、发指令、解析数据、硬件控制所有功能

3.1 私有锁 & 网络管道

csharp 复制代码
private readonly object _lock = new object();
private Socket Client;
  1. private readonly object _lock
    • private:仅当前类内部能用,外部访问不到
    • _lock:程序锁,防止多个操作同时抢网络通道,造成通讯错乱
    • 小白:固定写法,不用修改,保留即可
  2. private Socket Client
    • Socket:电脑和加热台之间的虚拟网线(网络管道)
    • Client 为空 = 网线未接通;有值 = 网线已创建

3.2 设备配置属性

csharp 复制代码
public string DefaultIP { get; set; }
public int DefaultPort { get; set; }
public int DefaultTimeout { get; set; }
  • public:外部代码可以读取/修改
  • 三个属性用来存设备固定参数:
    1. DefaultIP:设备IP地址(文本格式)
    2. DefaultPort:设备通讯端口(数字)
    3. DefaultTimeout:超时时间(单位:毫秒)
  • { get; set; }:可读可写;VS2008 不支持直接赋值,统一在构造函数初始化

3.3 连接状态(只读属性)

csharp 复制代码
public bool IsOpen
{
    get
    {
        return Client != null && Client.Connected;
    }
}
  • bool:布尔类型,只有 true(是) / false(否)
  • 只有 get只读,只能查询状态,不能手动修改
  • 逻辑拆解:
    • Client != null:虚拟网线已创建
    • Client.Connected:网线真正连上设备
    • && 并且:两个条件同时成立 → true(已连接),否则未连接

3.4 构造函数(初始化参数)

csharp 复制代码
public SFP_EVB_Heater()
{
    DefaultIP = "129.168.1.133";
    DefaultPort = 9000;
    DefaultTimeout = 5000;
    Client = null;
}
  1. 构造函数规则:和类名同名、没有返回值
  2. 执行时机 :代码 new SFP_EVB_Heater() 创建遥控器时,自动第一时间执行
  3. 作用:给设备参数赋默认值
    • IP = 加热台真实地址
    • 端口 = 9000(设备固定端口)
    • 超时 = 5000毫秒(5秒),5秒连不上/收不到数据就判定失败
    • Client = null:初始状态,虚拟网线为空

3.5 连接方法组(Open 重载)

知识点:VS2008 不支持「方法默认参数」,所以用方法重载(同名、不同参数),实现灵活调用。

csharp 复制代码
#region 连接方法重载
public bool Open()
{
    return Open(DefaultIP, DefaultPort, DefaultTimeout);
}

public bool Open(string ipAddress)
{
    return Open(ipAddress, DefaultPort, DefaultTimeout);
}

public bool Open(string ipAddress, int port)
{
    return Open(ipAddress, port, DefaultTimeout);
}
#endregion
  • #region / #endregion:代码折叠标记,只影响编辑器显示,不运行代码
  • 三个简易Open方法,最终都会调用下面完整版Open
    1. Open() 不传参 → 使用默认IP/端口/超时
    2. Open("129.168.1.133") 只传IP → 其余用默认
    3. Open("129.168.1.133",9000) 传IP+端口 → 超时用默认

完整版 Open(核心连接逻辑)

csharp 复制代码
public bool Open(string ipAddress, int port, int timeOut)
{
    // 非法参数兜底
    if (ipAddress == null)
        ipAddress = DefaultIP;
    if (port <= 0)
        port = DefaultPort;
    if (timeOut <= 0)
        timeOut = DefaultTimeout;

    lock (_lock) // 上锁,防止并发冲突
    {
        if (IsOpen) // 已经连接,直接返回成功
        {
            return true;
        }

        try // 尝试执行连接逻辑
        {
            // 创建TCP虚拟网线
            Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            // 设置收发超时
            Client.ReceiveTimeout = timeOut;
            Client.SendTimeout = timeOut;
            // 把IP+端口打包成设备地址
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
            // 真正发起网络连接
            Client.Connect(endPoint);

            Console.WriteLine("设备连接成功");
            return true; // 连接成功返回true
        }
        catch (Exception ex) // 连接出错(断网/关机/IP错误)
        {
            Console.WriteLine(string.Format("连接失败:{0}", ex.Message));
            if (Client != null)
            {
                Client.Close(); // 关闭无效网线
                Client = null;
            }
            return false; // 连接失败返回false
        }
    }
}

逐句作用:

  1. 参数兜底:调用方传空/错误值,自动改用默认配置
  2. lock:上锁,同一时间只允许一次连接操作
  3. if (IsOpen):避免重复连接
  4. try-catch:异常捕获,连接报错不会直接崩溃程序
  5. new Socket(...):固定写法,创建TCP网络管道
  6. IPEndPoint:拼接设备完整网络地址
  7. Client.Connect()核心联网动作
  8. 成功/失败分别打印提示、返回对应结果

3.6 Close 断开连接

csharp 复制代码
public void Close()
{
    lock (_lock)
    {
        if (IsOpen) // 仅已连接时才执行断开
        {
            Client.Close();    // 关闭网络管道
            Client = null;     // 清空对象,释放资源
            Console.WriteLine("连接已断开");
        }
    }
}
  • void:方法无返回值
  • 作用:用完设备,主动断开TCP连接,释放网络资源

3.7 通用指令发送(SendCommand 重载+核心)

所有和设备收发数据,全部走这个方法,是TCP通讯核心。

csharp 复制代码
#region 通用指令收发方法重载
public string SendCommand(string cmd)
{
    return SendCommand(cmd, 300); // 默认等待300毫秒
}
#endregion
  • 只传指令、不传延迟,默认等待300ms(给设备处理时间)

完整版 SendCommand

csharp 复制代码
public string SendCommand(string cmd, int delay)
{
    lock (_lock)
    {
        if (!IsOpen) // 未连接设备,直接返回空
        {
            return null;
        }

        try
        {
            // 1. 字符串指令 → 转网络字节(网络只能传字节)
            byte[] wbuf = Encoding.UTF8.GetBytes(cmd + "\r\n");
            // 2. 发送字节数据到设备
            Client.Send(wbuf, SocketFlags.None);
            // 3. 程序暂停,等待设备响应
            Thread.Sleep(delay);
            // 4. 开辟内存空间,接收设备返回数据
            byte[] rbuf = new byte[1024];
            // 5. 接收设备传回的字节
            int count = Client.Receive(rbuf, SocketFlags.None);

            if (count > 0) // 收到有效数据
            {
                // 字节 → 转回可读字符串
                string res = Encoding.UTF8.GetString(rbuf, 0, count).Trim();
                Console.WriteLine(string.Format("发送指令:{0}\n设备返回:{1}", cmd, res));
                return res; // 返回设备原始数据
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(string.Format("指令异常:{0}", ex.Message));
        }
        return null; // 收发失败返回空
    }
}

TCP固定四步(记下来,所有网络设备通用)

  1. 文字指令 → 转二进制字节
  2. 通过Socket发送字节
  3. 等待设备响应
  4. 接收字节 → 转回文字

补充:cmd + "\r\n" 是设备协议要求,指令末尾必须加换行,设备才能识别。


第四部分:业务功能方法(对应设备指令手册)

下面所有方法,都是基于上面 SendCommand 封装的业务功能,调用即可实现对应操作。

4.1 IIC 读写功能

IIC_Set 写入数据

csharp 复制代码
#region IIC_Set 重载
public bool IIC_Set(int slot)
{
    return IIC_Set(slot, "a0", "0", "5", "a1,9,1b,2c,3d");
}
public bool IIC_Set(int slot, string deviceAddr)
{
    return IIC_Set(slot, deviceAddr, "0", "5", "a1,9,1b,2c,3d");
}
public bool IIC_Set(int slot, string deviceAddr, string regAddr)
{
    return IIC_Set(slot, deviceAddr, regAddr, "5", "a1,9,1b,2c,3d");
}
#endregion

public bool IIC_Set(int slot, string deviceAddr, string regAddr, string dataLength, string data)
{
    string cmd = string.Format("IIC{0}:set {1},{2},{3},{4}", slot, deviceAddr, regAddr, dataLength, data);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains("iic set ok");
}
  • slot:槽位号(1/2/3...)
  • 功能:向设备IIC总线写入数据
  • 返回 true = 写入成功;false = 失败

IIC_Get 读取数据

csharp 复制代码
#region IIC_Get 重载
public string IIC_Get(int slot)
{
    return IIC_Get(slot, "a0", "0", "9");
}
public string IIC_Get(int slot, string deviceAddr)
{
    return IIC_Get(slot, deviceAddr, "0", "9");
}
#endregion

public string IIC_Get(int slot, string deviceAddr, string regAddr, string dataLength)
{
    string cmd = string.Format("IIC{0}:get {1},{2},{3}", slot, deviceAddr, regAddr, dataLength);
    return SendCommand(cmd);
}
  • 功能:读取设备IIC总线数据
  • 返回值:设备返回的原始字符串

4.2 功率/电流/电压 读写(最常用)

GetPower 查询功率

csharp 复制代码
#region GetPower 重载
public string GetPower()
{
    return GetPower(1); // 默认1号槽位
}
#endregion

public string GetPower(int slot)
{
    string cmd = string.Format("evb{0}:getpower?", slot);
    string res = SendCommand(cmd);
    if (string.IsNullOrEmpty(res))
        return null;
    // 正则:只保留数字,过滤文字、单位
    string numberOnly = Regex.Replace(res, @"[^0-9]", "");
    return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
}
  • 调用示例:heater.GetPower() → 查询1号槽功率
  • 解析逻辑:自动剔除 Power : 42mW 里的文字,只返回数字

GetCurrent 查询电流

逻辑和GetPower完全一致,只是指令不同:

csharp 复制代码
public string GetCurrent(int slot)
{
    string cmd = string.Format("evb{0}:getcurrent?", slot);
    string res = SendCommand(cmd);
    if (string.IsNullOrEmpty(res))
        return null;
    string numberOnly = Regex.Replace(res, @"[^0-9]", "");
    return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
}

GetVoltage 查询电压

同理:

csharp 复制代码
public string GetVoltage(int slot)
{
    string cmd = string.Format("evb{0}:getvoltage?", slot);
    string res = SendCommand(cmd);
    if (string.IsNullOrEmpty(res))
        return null;
    string numberOnly = Regex.Replace(res, @"[^0-9]", "");
    return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
}

SetVoltage 设置电压

csharp 复制代码
public bool SetVoltage(int slot, double voltage)
{
    string cmd = string.Format("evb{0}:setvoltage {1}", slot, voltage);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains("set Voltage ok!");
}
  • 参数:slot槽位,voltage电压值(范围3.15~3.3V)
  • 示例:heater.SetVoltage(1,3.3) 设置1号槽电压3.3V
  • 返回true = 设置成功

4.3 IO 引脚控制(所有引脚统一规则)

SetPowerEN / GetPowerEN 举例,其余TxDis、Rs0High、Rs1High逻辑完全一样

  1. SetXXX:设置引脚状态(1高电平 / 0低电平),返回bool判断是否成功
  2. GetXXX:查询引脚状态,返回0/1字符串

示例:

csharp 复制代码
// 设置模块使能 1号槽 高电平
public bool SetPowerEN(int slot, int state)
{
    string cmd = string.Format("IO{0}:setPowerEN {1}", slot, state);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains(string.Format("POWER_EN:{0}", state));
}

// 查询模块使能状态
public string GetPowerEN(int slot)
{
    string cmd = string.Format("IO{0}:getPowerEN", slot);
    string res = SendCommand(cmd);
    if (string.IsNullOrEmpty(res) || !res.Contains("POWER_EN:"))
        return null;
    return res.Split(':')[1].Trim(); // 按冒号分割,取出状态值
}

其余引脚(TxDis、Rs0High、Rs1High、TxFalu、RxLos、ABS)写法一模一样,只是指令名称不同。

4.4 系统功能

csharp 复制代码
// 修改设备IP
public bool SetDeviceIP(string newIp)
{
    string cmd = string.Format("setip {0}", newIp);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains(string.Format("setip:{0}", newIp));
}

// 查询设备所有支持指令
public string GetHelp()
{
    return SendCommand("help");
}

第五部分:程序入口 Program + Main

整个程序的启动入口,程序运行从这里开始。

csharp 复制代码
class Program
{
    static void Main(string[] args)
    {
        // 1. 创建遥控器对象,自动执行构造函数初始化IP/端口
        SFP_EVB_Heater heater = new SFP_EVB_Heater();

        Console.WriteLine("===== 正在连接设备 =====");
        // 2. 连接设备,连接失败直接退出
        if (!heater.Open())
        {
            Console.WriteLine("按任意键退出...");
            Console.ReadKey();
            return;
        }

        // 3. 批量查询基础参数:功率、电流、电压
        Console.WriteLine("\n===== 基础信息查询 =====");
        string power = heater.GetPower();
        string current = heater.GetCurrent();
        string voltage = heater.GetVoltage();

        // 打印结果
        if (!string.IsNullOrEmpty(power))
            Console.WriteLine("当前功率:" + power + " mW");
        if (!string.IsNullOrEmpty(current))
            Console.WriteLine("当前电流:" + current + " uA");
        if (!string.IsNullOrEmpty(voltage))
            Console.WriteLine("当前电压:" + voltage + " mV");

        // 4. 批量查询引脚状态
        Console.WriteLine("\n===== 引脚状态查询 =====");
        string powerEN = heater.GetPowerEN();
        string txDis = heater.GetTxDis();
        string rs0High = heater.GetRs0High();

        if (!string.IsNullOrEmpty(powerEN))
            Console.WriteLine("模块使能状态:" + powerEN);
        if (!string.IsNullOrEmpty(txDis))
            Console.WriteLine("TX_DIS状态:" + txDis);
        if (!string.IsNullOrEmpty(rs0High))
            Console.WriteLine("RS0_HIGH状态:" + rs0High);

        // 5. 断开连接
        Console.WriteLine("\n===== 测试完成 =====");
        heater.Close();

        // 暂停窗口,防止一闪而过
        Console.WriteLine("\n按任意键关闭窗口...");
        Console.ReadKey();
    }
}

Main 逐行运行流程(最终执行链路)

  1. new SFP_EVB_Heater() → 创建对象 + 自动初始化IP/端口/超时
  2. heater.Open() → 发起TCP连接
  3. 依次调用 GetPower/GetCurrent/GetVoltage → 发指令、收数据、解析、打印
  4. 依次调用引脚查询方法 → 读取硬件状态
  5. heater.Close() → 断开网络
  6. Console.ReadKey() → 窗口停留,按按键关闭

补充:小白常用调用示例(直接写在Main里就能用)

  1. 设置1号槽电压3.2V
csharp 复制代码
bool ok = heater.SetVoltage(1, 3.2);
  1. 查询2号槽功率
csharp 复制代码
string p = heater.GetPower(2);
  1. 关闭1号槽TX引脚
csharp 复制代码
heater.SetTxDis(1,0);

核心总结(必记2点)

  1. TCP通讯固定套路(所有网口设备通用)
    创建Socket → 连接IP+端口 → 文字转字节发送 → 接收字节转回文字
  2. 代码分层思想
    • SFP_EVB_Heater 类:只管和硬件通讯、封装功能(底层能力)
    • Main 方法:只管调用功能、打印结果(上层测试逻辑)
    • 新增功能只需要仿照现有方法仿写即可。

下面逐行、逐语句、逐关键字 超详细讲解,包含语法含义、参数作用、执行结果、设计原因,重点对你举例的 string.Format 做拆解,全程贴合 VS2008 + .NET3.5 环境,小白也能完全看懂。

一、头部引用区

csharp 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
  1. using
    语法含义:引入外部类库 。C# 很多现成功能不是自己写的,而是系统提供,using 就是告诉编译器:我要使用下面这些工具库。
  2. using System;
    引入最基础核心库:控制台打印 Console、异常 Exception、基础数据类型(字符串、数字)都在这里。
  3. using System.Net;
    网络基础库:专门用来解析IP、组装 IP+端口 地址。
  4. using System.Net.Sockets;
    TCP通讯核心库Socket(网络管道)就在这个库,没有它无法和加热台网络通信。
  5. using System.Text;
    编码转换库:电脑发文字,网络只传二进制,这个库负责字符串 ↔ 字节数组互转。
  6. using System.Threading;
    线程库:本代码只用到 Thread.Sleep()(让程序暂停等待)。
  7. using System.Text.RegularExpressions;
    正则表达式库:用来从设备返回的文本里单独提取数字 (比如从 Power:42mW 取出 42)。

二、命名空间

csharp 复制代码
namespace VP_Communication.Communication.Heater
{
  1. namespace:命名空间,作用 = 虚拟文件夹
  2. VP_Communication.Communication.Heater:多级文件夹名称
    • 设计原因:车间项目代码多,把所有加热台相关代码统一归类,防止不同功能代码重名。
  3. {:代码范围开始,大括号内所有内容都属于这个"文件夹"。

三、主类定义

csharp 复制代码
    /// <summary>
    /// SFP EVB加热台 TCP 全功能控制类
    /// 适配 VS2008 + .NET 3.5
    /// 完整覆盖Excel指令表所有功能
    /// </summary>
    public class SFP_EVB_Heater
    {
  1. /// 注释:文档注释,只给人看,程序运行忽略,用来标注类的用途。
  2. public:访问修饰符 = 公开 ,外部代码(比如 Main 方法)可以访问这个类、创建对象。
  3. class:关键字,用来定义一个类(可以理解为功能模板/遥控器图纸)。
  4. SFP_EVB_Heater:类名,自定义名称,见名知意:加热台控制类。
  5. {:类的内容开始。

3.1 线程锁 & 网络管道变量

csharp 复制代码
        private readonly object _lock = new object();
        private Socket Client;

第一行 private readonly object _lock = new object();

  1. private:私有,仅当前类内部可以使用,外部代码访问不到。
  2. readonly:只读,变量一旦赋值,整个程序运行过程中不能再修改
  3. object:C# 所有数据的根类型,这里专门用来做「锁对象」。
  4. _lock:自定义变量名,行业习惯用下划线开头表示内部变量。
  5. new object():在内存中创建一个全新的对象。
  6. 整体作用:
    防止多个指令同时抢占网络通道,导致通讯错乱。老旧设备/多指令场景标配写法,直接保留即可。

第二行 private Socket Client;

  1. private:私有,类内部专用。
  2. Socket:网络管道类型,电脑和加热台之间的虚拟网线
  3. Client:变量名,代表当前这根网线。
  4. 含义:定义一根空网线,初始状态没有连接任何设备

3.2 设备配置属性

csharp 复制代码
        public string DefaultIP { get; set; }
        public int DefaultPort { get; set; }
        public int DefaultTimeout { get; set; }
  1. public:公开,外部可读可改。
  2. string:字符串类型,用来存放文本(IP地址是文本格式)。
  3. int:整数类型,存放纯数字(端口、超时都是数字)。
  4. DefaultIP:默认IP地址;DefaultPort:默认端口;DefaultTimeout:默认超时时间。
  5. { get; set; }
    • get:读取这个属性的值;
    • set:给这个属性赋值;
    • 组合含义:可读、可写属性
  6. 设计原因:
    VS2008/.NET3.5 不支持 public string DefaultIP = "xxx" 这种直接赋值写法,所以先定义属性,统一在构造函数里赋值。

3.3 连接状态 只读属性

csharp 复制代码
        public bool IsOpen
        {
            get
            {
                return Client != null && Client.Connected;
            }
        }
  1. bool:布尔类型,只有两个值:true(真)false(假)
  2. IsOpen:属性名,含义:是否已和设备建立连接
  3. { get { } }:只有 get、没有 set只读属性
    只能查询状态,代码不能强行修改连接状态。
  4. return:把后面计算的结果返回出去。
  5. Client != null:判断网络管道(网线)是否已经创建。
  6. &&:逻辑运算符 并且,左右两个条件必须同时成立。
  7. Client.Connected:Socket 自带属性,判断网线物理上是否连上设备
  8. 整句逻辑:
    网线已创建 并且 网线已接通设备 → 返回 true(已连接),否则 false(未连接)。

3.4 构造函数(对象初始化)

csharp 复制代码
        public SFP_EVB_Heater()
        {
            DefaultIP = "129.168.1.133";
            DefaultPort = 9000;
            DefaultTimeout = 5000;
            Client = null;
        }
  1. SFP_EVB_Heater()构造函数
    • 规则:函数名和类名完全一致没有返回值
    • 执行时机:代码执行 new SFP_EVB_Heater() 创建遥控器对象时,自动优先执行
  2. DefaultIP = "129.168.1.133":给IP属性赋值,加热台固定IP。
  3. DefaultPort = 9000:赋值通讯端口,设备固定端口。
  4. DefaultTimeout = 5000:超时时间,单位 毫秒 ,5000ms = 5秒。
    含义:5秒内连不上设备、收不到数据,直接判定通讯失败。
  5. Client = null:初始化网络管道为空,代表初始状态网线未创建、未连接。

3.5 Open 方法重载(适配VS2008,无默认参数)

csharp 复制代码
        #region 连接方法重载(适配VS2008,无默认参数)
        public bool Open()
        {
            return Open(DefaultIP, DefaultPort, DefaultTimeout);
        }

        public bool Open(string ipAddress)
        {
            return Open(ipAddress, DefaultPort, DefaultTimeout);
        }

        public bool Open(string ipAddress, int port)
        {
            return Open(ipAddress, port, DefaultTimeout);
        }
        #endregion
  1. #region 名称 / #endregion代码折叠标记
    仅影响 VS 编辑器显示(可以收起/展开代码),不改变程序运行逻辑
  2. 前置知识点:
    VS2008 不支持 Open(string ip="1.1.1.1") 这种「方法默认参数」,所以用 方法重载 实现灵活调用。
  3. public bool Open()
    • 公开方法,返回 bool 布尔值(连接成功/失败);
    • 无参数版本:调用时不填任何内容。
  4. return Open(DefaultIP, DefaultPort, DefaultTimeout);
    • return:把后面方法的执行结果,当作当前方法的结果返回;
    • 含义:调用无参 Open 时,自动使用默认IP、端口、超时 ,去调用完整版三参数 Open
  5. public bool Open(string ipAddress)
    只传入IP,端口、超时沿用默认值。
  6. public bool Open(string ipAddress, int port)
    传入IP+端口,超时沿用默认值。
  7. 设计目的:
    调用灵活,想用默认参数就不传,想临时改IP/端口就单独传,不用改源代码。

3.6 完整版 Open 核心连接方法

csharp 复制代码
        public bool Open(string ipAddress, int port, int timeOut)
        {
            if (ipAddress == null)
                ipAddress = DefaultIP;
            if (port <= 0)
                port = DefaultPort;
            if (timeOut <= 0)
                timeOut = DefaultTimeout;
  1. public bool Open(参数1,参数2,参数3):完整版连接方法,接收3个外部传入参数。
  2. if (ipAddress == null):判断如果传入的IP是空值。
  3. ipAddress = DefaultIP:空值就替换成默认IP,参数容错,防止代码报错。
  4. if (port <= 0):端口不能是0或负数,非法值替换为默认端口。
  5. if (timeOut <= 0):超时非法,替换为默认超时。
csharp 复制代码
            lock (_lock)
            {
  1. lock (_lock):上锁。
  2. 作用:同一时间只允许一段连接代码执行,防止多操作争抢网络。
csharp 复制代码
                if (IsOpen)
                {
                    return true;
                }
  1. if (IsOpen):判断当前已经连上设备
  2. return true:已经连接,直接返回成功,避免重复连接
csharp 复制代码
                try
                {
  1. try:异常捕获块。
  2. 含义:尝试执行大括号内的连接代码;一旦中间出错(网线断、设备关机、IP错误),自动跳转到 catch
csharp 复制代码
                    Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    Client.ReceiveTimeout = timeOut;
                    Client.SendTimeout = timeOut;
  1. Client = new Socket(...)创建一根TCP虚拟网线
  2. 三个固定参数(TCP标准写法,不用修改):
    • AddressFamily.InterNetwork:使用 IPv4 地址(我们现在用的地址格式);
    • SocketType.Stream:流式传输,TCP 专属特征;
    • ProtocolType.Tcp:指定使用 TCP 协议。
  3. Client.ReceiveTimeout = timeOut:设置接收数据超时时间
  4. Client.SendTimeout = timeOut:设置发送数据超时时间
csharp 复制代码
                    IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
  1. IPAddress.Parse(ipAddress):把文本格式的IP (如129.168.1.133)转换成程序能识别的IP对象。
  2. IPEndPoint:组合类型,作用是 把 IP + 端口 打包成一个完整的设备网络地址
  3. endPoint:自定义变量,存放打包后的设备地址。
csharp 复制代码
                    Client.Connect(endPoint);
  1. Client.Connect()核心联网动作,让虚拟网线和加热台设备建立TCP连接。
csharp 复制代码
                    Console.WriteLine("设备连接成功");
                    return true;
  1. 连接无异常,控制台打印提示文字。
  2. return true:方法返回 true,代表连接成功
csharp 复制代码
                }
                catch (Exception ex)
                {
  1. catch (Exception ex):捕获 try 块中所有类型的错误。
  2. ex:错误信息对象,里面存放具体报错内容。
csharp 复制代码
                    Console.WriteLine(string.Format("连接失败:{0}", ex.Message));
  1. ex.Message:取出错误的文字描述。
  2. string.Format("连接失败:{0}", ex.Message)字符串格式化
    • {0}:占位符,代表第一个参数的内容;
    • 执行结果:把 ex.Message 替换 {0},拼接成完整提示文本。
csharp 复制代码
                    if (Client != null)
                    {
                        Client.Close();
                        Client = null;
                    }
                    return false;
                }
            }
        }
  1. 如果网线已经创建,就执行 Client.Close() 关闭网线。
  2. Client = null:清空变量,释放内存资源。
  3. return false:返回 false,代表连接失败

3.7 Close 断开连接方法

csharp 复制代码
        public void Close()
        {
            lock (_lock)
            {
                if (IsOpen)
                {
                    Client.Close();
                    Client = null;
                    Console.WriteLine("连接已断开");
                }
            }
        }
  1. public void Close():公开方法,void = 无返回值,功能:断开TCP连接。
  2. lock (_lock):上锁,保证操作安全。
  3. if (IsOpen):只有当前处于连接状态,才执行断开逻辑。
  4. Client.Close():关闭网络管道。
  5. Client = null:清空网线对象,释放资源。

3.8 SendCommand 重载(指令收发)

csharp 复制代码
        #region 通用指令收发方法重载
        public string SendCommand(string cmd)
        {
            return SendCommand(cmd, 300);
        }
        #endregion
  1. public string Send(string cmd):只接收指令文本,无延迟参数。
  2. return SendCommand(cmd, 300):调用完整版方法,默认延迟300毫秒(给设备反应时间)。

完整版 SendCommand(TCP收发核心)

csharp 复制代码
        public string SendCommand(string cmd, int delay)
        {
            lock (_lock)
            {
                if (!IsOpen)
                {
                    return null;
                }
  1. 入口参数:cmd=要发送的指令文本;delay=等待延迟(毫秒)。
  2. !IsOpen! 取反 = 未连接设备
  3. return null:未连接直接返回空,不执行收发。
csharp 复制代码
                try
                {
                    byte[] wbuf = Encoding.UTF8.GetBytes(cmd + "\r\n");
  1. byte[]:字节数组,网络传输唯一标准格式
  2. Encoding.UTF8.GetBytes():把字符串指令转为二进制字节。
  3. cmd + "\r\n":拼接换行符。
    原因:设备协议强制要求,指令末尾必须加回车换行,设备才能识别指令。
csharp 复制代码
                    Client.Send(wbuf, SocketFlags.None);
  1. Client.Send():通过网络管道,把字节数据发送给加热台
  2. SocketFlags.None:固定默认参数,无特殊发送规则。
csharp 复制代码
                    Thread.Sleep(delay);
  1. 让程序暂停 delay 毫秒,等待设备处理指令、准备返回数据。
csharp 复制代码
                    byte[] rbuf = new byte[1024];
                    int count = Client.Receive(rbuf, SocketFlags.None);
  1. byte[] rbuf = new byte[1024]:开辟一块1024字节的内存,作为接收缓冲区,存放设备回传数据。
  2. Client.Receive():接收设备发回的字节数据。
  3. count:整数,代表实际收到了多少个字节
csharp 复制代码
                    if (count > 0)
                    {
                        string res = Encoding.UTF8.GetString(rbuf, 0, count).Trim();
                        Console.WriteLine(string.Format("发送指令:{0}\n设备返回:{1}", cmd, res));
                        return res;
  1. count > 0:判断收到了有效数据。
  2. Encoding.UTF8.GetString(rbuf, 0, count):把收到的字节 转回可读字符串
    • 0:从数组第0位开始读取;
    • count:一共读取收到的有效字节数量。
  3. .Trim():去掉字符串首尾的空格、换行。
  4. string.Format:格式化拼接字符串,打印收发日志。
  5. return res:把设备原始返回文本返回给调用方。
csharp 复制代码
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(string.Format("指令异常:{0}", ex.Message));
                }
                return null;
            }
        }
  1. 收发出错,打印异常信息。
  2. 最终返回 null,代表收发失败。

四、重点精讲:string.Format 语法(你指定的例句)

以这句为例:

csharp 复制代码
string cmd = string.Format("IO{0}:getPowerEN", slot);

1. 整体作用

按照模板字符串 + 传入变量,自动拼接生成最终设备指令

2. 逐段拆解

  1. string cmd
    • 定义一个字符串变量 cmd,专门用来存放拼接完成的设备指令。
  2. string.Format(模板字符串, 参数列表)
    • C# 字符串格式化方法,专门用来动态拼接文本
  3. 第一个参数:"IO{0}:getPowerEN"模板
    • {0}占位符,编号从 0 开始;
    • 含义:这个位置会被后面第1个变量替换。
  4. 第二个参数:slot填充变量
    • slot 是槽位号(数字 1、2、3...)。

3. 执行过程 & 最终结果

假设调用时 slot = 1

  1. 模板:IO{0}:getPowerEN
  2. {0} 替换为 1
  3. 最终生成字符串IO1:getPowerEN

4. 对应设备指令

生成的 IO1:getPowerEN 就是发给加热台的标准查询指令,和你Excel手册完全一致。

5. 拓展示例(举一反三)

  • string.Format("evb{0}:getpower?", 1) → 生成 evb1:getpower?
  • string.Format("IO{0}:setPowerEN {1}", 1, 0)
    两个占位符 {0} {1},分别替换 1 和 0 → 生成 IO1:setPowerEN 0

6. 为什么不直接写死字符串?

  • 如果槽位固定为1:直接写 "IO1:getPowerEN" 就行;
  • 但代码要支持 1号、2号、多槽位切换 ,所以用 string.Format 动态拼接,一套代码适配所有槽位。

五、逐个业务方法精讲(基于 string.Format 统一逻辑)

5.1 IIC_Set / IIC_Get

csharp 复制代码
public bool IIC_Set(int slot, string deviceAddr, string regAddr, string dataLength, string data)
{
    string cmd = string.Format("IIC{0}:set {1},{2},{3},{4}", slot, deviceAddr, regAddr, dataLength, data);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains("iic set ok");
}
  1. string.Format("IIC{0}:set {1},{2},{3},{4}", 五个参数)
    • {0} → slot 槽位
    • {1} → deviceAddr 器件地址
    • {2} → regAddr 寄存器地址
    • {3} → dataLength 数据长度
    • {4} → data 写入数据
  2. 拼接后生成标准 IIC 写入指令,调用 SendCommand 发送。
  3. res.Contains("iic set ok"):判断设备返回是否包含成功关键字,返回 true/false

5.2 GetPower 查询功率

csharp 复制代码
public string GetPower(int slot)
{
    string cmd = string.Format("evb{0}:getpower?", slot);
    string res = SendCommand(cmd);

    if (string.IsNullOrEmpty(res))
        return null;

    string numberOnly = Regex.Replace(res, @"[^0-9]", "");
    return !string.IsNullOrEmpty(numberOnly) ? numberOnly : null;
}
  1. 拼接指令 evb+槽位:getpower?
  2. Regex.Replace(res, @"[^0-9]", "")
    正则表达式:把所有不是数字的字符全部替换为空
    示例:Power : 42mW → 过滤后只剩下 42

5.3 GetCurrent / GetVoltage

逻辑和 GetPower 完全一致

  1. string.Format 拼接对应查询指令;
  2. 调用 SendCommand 收发;
  3. 正则过滤,只保留数字。

5.4 SetVoltage 设置电压

csharp 复制代码
public bool SetVoltage(int slot, double voltage)
{
    string cmd = string.Format("evb{0}:setvoltage {1}", slot, voltage);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains("set Voltage ok!");
}
  1. {0}=槽位,{1}=电压值;
  2. 示例:slot=1, voltage=3.3 → 生成 evb1:setvoltage 3.3
  3. 判断返回文本是否包含成功标识。

5.5 IO 引脚系列(Get/Set 通用逻辑)

GetPowerEN 为例:

csharp 复制代码
public string GetPowerEN(int slot)
{
    string cmd = string.Format("IO{0}:getPowerEN", slot);
    string res = SendCommand(cmd);

    if (string.IsNullOrEmpty(res) || !res.Contains("POWER_EN:"))
        return null;

    return res.Split(':')[1].Trim();
}
  1. string.Format 拼接指令;
  2. res.Split(':')字符串分割 ,以冒 : 为界限,把文本拆成数组。
    示例:POWER_EN:1 → 拆分后数组 ["POWER_EN", "1"]
  3. [1]:取数组第2个元素(C# 数组从0开始编号);
  4. .Trim():去掉前后空格,最终取出状态值 1

所有 GetTxDis / GetRs0High 等引脚查询,分割逻辑完全一样,仅指令名称不同。

5.6 SetDeviceIP 修改设备IP

csharp 复制代码
public bool SetDeviceIP(string newIp)
{
    string cmd = string.Format("setip {0}", newIp);
    string res = SendCommand(cmd);
    return !string.IsNullOrEmpty(res) && res.Contains(string.Format("setip:{0}", newIp));
}
  1. 拼接 setip + 新IP 指令;
  2. 判断设备是否返回设置成功的文本。

六、程序入口 Main 方法(整段逐句)

csharp 复制代码
    class Program
    {
        static void Main(string[] args)
        {
  1. class Program:程序入口类。
  2. static void Main(string[] args)C# 程序唯一启动入口
    程序双击运行后,第一行执行这里args 接收启动参数,本代码未使用。
csharp 复制代码
            SFP_EVB_Heater heater = new SFP_EVB_Heater();
  1. 定义变量 heater
  2. new SFP_EVB_Heater()创建加热台遥控器对象
  3. 创建瞬间,自动执行构造函数,初始化IP、端口、超时。
csharp 复制代码
            Console.WriteLine("===== 正在连接设备 =====");
            if (!heater.Open())
            {
                Console.WriteLine("按任意键退出...");
                Console.ReadKey();
                return;
            }
  1. 打印提示文字。
  2. !heater.Open():调用连接方法,连接失败则进入分支。
  3. Console.ReadKey():暂停窗口,等待按键,防止窗口一闪而过。
  4. return:退出 Main 方法,程序结束。
csharp 复制代码
            string power = heater.GetPower();
            string current = heater.GetCurrent();
            string voltage = heater.GetVoltage();

依次调用查询方法,接收返回的数值。

csharp 复制代码
            if (!string.IsNullOrEmpty(power))
                Console.WriteLine("当前功率:" + power + " mW");
  1. !string.IsNullOrEmpty(power):判断返回值不为空(读取成功);
  2. + 号:字符串拼接,把文本和数字拼在一起打印。
csharp 复制代码
            string powerEN = heater.GetPowerEN();
            string txDis = heater.GetTxDis();
            string rs0High = heater.GetRs0High();

调用引脚查询方法,读取硬件状态。

csharp 复制代码
            heater.Close();
            Console.WriteLine("\n按任意键关闭窗口...");
            Console.ReadKey();
        }
    }
  1. 调用 Close() 断开连接。
  2. 暂停窗口,查看运行日志。

七、核心总结(小白必记)

  1. string.Format 核心用法
    模板 + {编号} + 变量 → 动态拼接字符串,本代码用来生成设备标准指令
    {0} 对应第一个变量,{1} 对应第二个变量,依次类推。
  2. TCP 固定四步
    创建Socket → 连接IP+端口 → 文字转字节发送 → 接收字节转回文字。
  3. 代码分层
    • 类内部方法:负责底层通讯、指令拼接、数据解析;
    • Main 入口:负责调用功能、打印结果,只做业务测试。
  4. 重载方法作用
    适配老旧 VS2008,实现"传参/不传参都能调用同一个功能"。

下面按你10个问题,逐个用通俗语言 + 可运行示例讲解,结合你现有代码、VS2008 环境,保证能看懂、能上机测试。


1、_lock 程序锁:作用 + 可运行示例

一、基础理解

lock 直译:排队锁 / 单人通道

  • 同一时间,只允许一段代码被一个线程执行
  • 防止多段代码同时抢同一个资源(这里就是网络通道),造成数据错乱、程序卡死

你当前场景:

如果程序同时发2条指令给加热台,网络通道会"打架",lock 让指令排队依次发送

二、简易可运行示例(新建控制台项目,直接复制运行)

csharp 复制代码
using System;
using System.Threading;

namespace LockDemo
{
    class Program
    {
        // 定义锁对象(和你代码里的 _lock 一模一样)
        private static readonly object _lockObj = new object();

        // 模拟发送指令的方法
        static void SendCmd(string name)
        {
            // 上锁:进入排队通道
            lock (_lockObj)
            {
                Console.WriteLine("【" + name + "】开始执行");
                Thread.Sleep(1000); // 模拟操作耗时
                Console.WriteLine("【" + name + "】执行完毕\n");
            }
        }

        static void Main(string[] args)
        {
            // 模拟两个任务同时调用发送指令(多线程争抢)
            Thread t1 = new Thread(() => SendCmd("指令1"));
            Thread t2 = new Thread(() => SendCmd("指令2"));

            t1.Start();
            t2.Start();

            Console.ReadKey();
        }
    }
}
运行效果(加 lock):
复制代码
【指令1】开始执行
【指令1】执行完毕

【指令2】开始执行
【指令2】执行完毕

👉 两个指令排队执行,不会重叠。

lock (_lockObj) 注释掉再运行(无锁):
复制代码
【指令1】开始执行
【指令2】开始执行
【指令1】执行完毕

【指令2】执行完毕

👉 两个指令同时执行,争抢资源,对应你设备就会通讯乱码、超时。

总结

你代码里的 lock (_lock)

保证多条指令串行排队访问网络管道,是工业设备通讯的标准写法。


2、方法默认参数:是什么 + 用法 + VS2008 限制

1)什么是方法默认参数

调用方法时不传入某个参数,程序自动使用预先设置的默认值。

2)新版 C# 用法(VS2010+ 支持)

示例:

csharp 复制代码
// port=9000 就是默认参数,不填就自动用 9000
public bool Open(string ipAddress, int port = 9000)
{
    // 代码逻辑
}

// 调用两种写法
Open("129.168.1.133");      // 不填端口 → 自动用 9000
Open("129.168.1.133", 8080);// 手动指定端口

3)重点:你的 VS2008 + .NET3.5

不支持默认参数语法 ,所以项目里全部改用 方法重载 (多个同名方法)来实现相同效果,这也是你代码里一堆 Open() 重载的原因。

4、默认参数 和 重载 目标一致

都是为了:调用更灵活,少写重复代码


3、代码折叠标记 #region / #endregion 作用,不写会怎样

1)作用

编辑器美化标记对程序运行、逻辑、编译没有任何影响

  • 可以把一大段代码「折叠/收起」,界面更整洁
  • 用来给代码分区(比如"连接方法区""IO方法区"),方便查找

2)不写会怎么样

完全没事,程序正常编译、正常运行,只是代码平铺很长,不方便阅读。

3)补充规则

  • #region 分区名 必须 和 #endregion 成对出现
  • 少一个就会报编译错误(你之前遇到的就是这个问题)

4、string.Format("连接失败:{0}", ex.Message) 为什么不能用 {1}

1)规则

{数字}占位符 ,数字从 0 开始,按顺序对应后面的参数

  • {0} → 对应 Format 后面 第1个参数
  • {1} → 对应 第2个参数
  • {2} → 对应 第3个参数,以此类推

2)你这句拆解

csharp 复制代码
string.Format("连接失败:{0}", ex.Message)
  • 模板字符串:"连接失败:{0}"
  • 后面只有 1个参数ex.Message
  • 所以只能用 {0}

3)举例演示(多参数)

csharp 复制代码
// 2个参数 → 必须用 {0} {1}
string.Format("IP:{0} 端口:{1}", "129.168.1.133", 9000);

结果:IP:129.168.1.133 端口:9000

4)错误写法

csharp 复制代码
// 后面只有1个参数,模板写 {1} → 编译报错
string.Format("连接失败:{1}", ex.Message);

结论:占位符数字 = 参数的顺序编号(从0开始)


5、Encoding.UTF8.GetString(rbuf, 0, count).Trim() 逐段解释

整句作用:把设备发回来的二进制字节,转成正常文字,并清理多余空格

1)Encoding.UTF8.GetString(字节数组, 起始位置, 读取长度)

对应:GetString(rbuf, 0, count)

  • rbuf:接收数据的字节数组(存设备发回来的原始二进制)
  • 0从数组第0个位置开始读(从头读取)
  • countClient.Receive 返回的实际收到字节个数

为什么不直接写 rbuf 全长?

数组长度是固定1024,但设备可能只返回几十个字节,后面全是空数据,只读有效部分,避免乱码。

2).Trim() 方法

作用:删除字符串 头部、尾部 的空格、换行、制表符

示例:

原始字符串: Power : 42mW \r\n

执行 .Trim() 后:Power : 42mW

3)整句最终生成结果

把设备返回的二进制 → 干净、无多余空格的文本字符串。


6、IIC_Set(int slot, string deviceAddr, string regAddr, string dataLength, string data) 参数含义

结合你 Excel 设备指令手册,逐个对应:

csharp 复制代码
public bool IIC_Set(int slot, string deviceAddr, string regAddr, string dataLength, string data)
  1. int slot
    槽位号:对应设备 IIC1 / IIC2,取值 1、2、3......
  2. string deviceAddr
    器件地址:硬件IIC设备地址(手册示例 a0
  3. string regAddr
    寄存器地址:要操作的硬件寄存器地址(手册示例 0
  4. string dataLength
    数据长度:本次要发送的数据位数(手册示例 5
  5. string data
    要发送的十六进制数据,逗号分隔(手册示例 a1,9,1b,2c,3d

对应最终指令示例

调用:IIC_Set(2, "a0", "0", "5", "a1,9,1b,2c,3d")

内部拼接指令:IIC2:set a0,0,5,a1,9,1b,2c,3d

和你手册完全一致。


7、IsOpen 是设备自带的变量吗?

不是!完全是你代码自己定义的属性

拆解:

csharp 复制代码
public bool IsOpen
{
    get
    {
        return Client != null && Client.Connected;
    }
}
  1. IsOpen:C# 代码里自定义只读属性
  2. 逻辑:判断你代码中的 Socket Client(网络管道)是否已连接
  3. 和加热台硬件设备没有任何内置变量关联
    只是代码用来「自查网络状态」的工具。

8、lock (_lock)IsOpen 是同一个过程吗?

不是,是两个完全独立的逻辑,作用不同

  1. lock (_lock) 上锁

    作用:控制多段代码排队执行 ,防止并发争抢网络。

    范围:{} 内部所有代码都受排队限制。

  2. if (IsOpen) 判断连接状态

    作用:检查当前网线有没有连上设备,避免重复连接、空指令。

执行顺序(你 Open 方法里)

进入 Open 方法

→ 先 lock 上锁(开始排队)

→ 再 if(IsOpen) 判断是否已连接

→ 再执行创建 Socket、连接设备

两个功能搭配使用,各司其职。


9、Regex.Replace(res, @"[^0-9]", "")@^ 含义

整句作用:把字符串里所有非数字字符全部删掉,只保留 0~9

1)@ 符号(C# 字符串前缀)

叫做 逐字字符串

作用:让字符串里的 \ 不再被当成转义字符。

正则表达式大量用到 \,所以正则字符串前面习惯性加 @

示例对比:

  • 不加 @"[^\d]" 要写双斜杠 "[^\\d]" 很麻烦
  • @@"[^0-9" 直接写原生正则,更直观

2)^ 正则符号(放在 [] 内部)

[^字符] = 取反

  • [0-9]:匹配 0~9 所有数字
  • [^0-9]匹配 所有不是数字的字符

整句逻辑

Regex.Replace(内容, 匹配规则, 替换为空白)

= 找到所有非数字 → 替换成空字符 → 最后只剩数字

示例:

输入:Power : 42mW

输出:42


10、res.Contains("set Voltage ok!") 含义

1)方法解释

字符串.Contains("子字符串")

作用:判断前面的大字符串里,是否包含指定的一段文字

  • 包含 → 返回 true
  • 不包含 → 返回 false

2)结合你代码场景

csharp 复制代码
res.Contains("set Voltage ok!")
  • res:设备返回的整行文本
  • set Voltage ok!:设备约定的 设置成功标识

运行逻辑

设备正常返回:set Voltage ok! Voltage: 3360mV

Contains 判定为 true → 代表电压设置成功。

如果返回报错文本 → 判定为 false → 设置失败。


快速汇总(方便你记忆)

  1. lock:排队锁,防止多指令抢网络
  2. 默认参数:不传参用预设值,VS2008 不支持,改用重载
  3. #region:仅编辑器折叠,不影响运行
  4. {0}:占位符按参数顺序编号,从0开始
  5. GetString(0,count):读取有效字节;Trim():清首尾空格
  6. IIC 五个参数:槽位、器件地址、寄存器、数据长度、写入数据
  7. IsOpen:代码自定义属性,不是设备变量
  8. lock 控排队,IsOpen 查连接状态,两个逻辑
  9. @ 禁用转义;[^0-9] 匹配非数字
  10. Contains:判断字符串是否包含指定关键字(用来判断指令成败)
相关推荐
2301_780789662 小时前
零信任架构中,身份感知防火墙(IAFW)的部署要点与最佳实践
linux·运维·服务器·人工智能·tcp/ip·架构
TechWayfarer2 小时前
查IP归属地接入实战:保险理赔如何做动态风险监控与预警
网络·python·tcp/ip·安全·flask
IPdodo跨境网络3 小时前
TCP三次握手图解与Python Socket代码验证
tcp/ip
森G3 小时前
64、完善聊天室程序(TLV拓展)---------网络编程
网络·c++·tcp/ip
Yvonne爱编码8 小时前
JAVA EE初阶---DAY 2 计算机网络
java·开发语言·计算机网络·算法·java-ee·php
青瓦梦滋8 小时前
Linux:TCP协议的socket套接字
网络·网络协议·tcp/ip
KaMeidebaby9 小时前
卡梅德生物技术快报 | Fab 合成文库构建与抗体筛选实验流程及数据解析
人工智能·python·tcp/ip·算法·机器学习
tonydf9 小时前
DotNet项目接入Copilot SDK简单案例
后端·.net·github copilot