You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#使用Topshelf创建Windows服务无法同时运行OPC通信与TCP监听方法

问题解决方案

核心错误原因

你代码中最直接的问题出在DoOpc方法的最后一行,你额外调用了TcpConn(),这会导致两个问题:

  • OPC线程执行完初始化逻辑后,直接进入TcpConn的死循环,占用OPC线程资源
  • 如果你的TcpServer是静态变量,重复调用TcpServer.Start()会抛出端口占用异常,触发System.Environment.Exit(6)直接杀死整个服务进程

其他会导致服务运行异常的问题

1. 服务启动逻辑未阻塞

Topshelf的服务Start方法执行完成后,如果没有阻塞逻辑,服务会直接判定为启动完成并退出,你启动的两个线程也会被直接销毁。

2. 权限与配置问题

  • Windows服务默认以Local System账户运行,该账户默认没有访问OPC COM组件的权限,也可能没有权限绑定指定的TCP端口
  • 服务运行时读取的是执行目录下的配置文件,若部署时未将app.config和服务exe放在同一目录,会出现读取配置失败的问题

3. TcpConn逻辑缺陷

  • StopTcp的判断位置在AcceptTcpClient之后,程序会长期阻塞在AcceptTcpClient调用上,即使修改StopTcp的值也无法及时响应停止信号
  • finally块中的System.Environment.Exit(6)会在TCP逻辑出现任何异常时直接杀死整个服务进程,没有重试机会
  • 关闭客户端时先调用client.Close()再调用client.GetStream().Close(),此时流已经被释放,会触发额外异常

修复步骤

  1. 删除DoOpc方法最后一行的TcpConn()调用,避免重复执行TCP监听逻辑
  2. 调整Topshelf服务启动逻辑,在Start方法中添加阻塞逻辑,避免服务直接退出:
private ManualResetEvent _stopEvent = new ManualResetEvent(false);
public void Start()
{
    try
    {
        // 将线程设为后台线程,避免阻塞进程退出
        T1 = new Thread(DoOpc) { IsBackground = true };
        T1.Start();

        T2 = new Thread(TcpConn) { IsBackground = true };
        T2.Start();
    }
    catch (Exception e)
    {
        Program.LogEntry("Error", e.ToString(), "Fatal");
        throw; // 启动失败直接抛出异常,让Topshelf标记服务启动失败
    }
    // 阻塞Start方法,直到收到停止信号
    _stopEvent.WaitOne();
}

public void Stop()
{
    StopTcp = true;
    // 唤醒Start方法的阻塞
    _stopEvent.Set();
    // 等待线程退出
    T1.Join(3000);
    T2.Join(3000);
}
  1. 调整TcpConn的逻辑,优化停止逻辑和异常处理:
public static void TcpConn()
{
    try
    {
        Program.LogEntry("TCP Comm", "Thread start...");

        Int32 port = Int32.Parse(ConfigurationManager.AppSettings["TCP_port"]);
        // 建议默认绑定0.0.0.0,允许所有网卡的访问请求
        IPAddress localAddr = IPAddress.Parse(ConfigurationManager.AppSettings["TCP_IP"] ?? "0.0.0.0");
        TcpServer = new TcpListener(localAddr, port);
        TcpServer.Start();

        const int bytesize = 2048 * 2048;
        Byte[] bytes = new Byte[bytesize];

        while (!StopTcp)
        {
            // 用Pending判断有没有待处理的连接,避免长期阻塞
            if (!TcpServer.Pending())
            {
                Thread.Sleep(100);
                continue;
            }
            TcpClient client = TcpServer.AcceptTcpClient();
            IPAddress TcpIPClient = ((IPEndPoint)(client.Client.RemoteEndPoint)).Address;
            Program.LogEntry("TCP Comm", "Connected! IP: " + TcpIPClient);

            using (NetworkStream stream = client.GetStream())
            {
                int i;
                while (!StopTcp && (i = stream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    string data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                    Program.LogEntry("TCP Comm", "Received: " + data);
                    string output = ProccessData(data);
                    byte[] msg = System.Text.Encoding.ASCII.GetBytes(output);
                    Program.LogEntry("TCP Comm", "Output: " + output);
                    stream.Write(msg, 0, msg.Length);
                }
            }
            client.Close();
        }
        Program.LogEntry("Service status", "Stopping TCP server...");
        TcpServer.Stop();
    }
    catch (SocketException e)
    {
        Program.LogEntry("TCP Comm", "SocketException: " + e.ToString(), "Fatal");
    }
    finally
    {
        Program.LogEntry("TCP Comm", "Bye Bye", "Warning");
        if (TcpServer != null) TcpServer.Stop();
        // 移除Environment.Exit,不要直接杀死整个进程
    }
}
  1. 调整服务运行账户:在服务管理中,将服务的运行账户改为有OPC访问权限的本地用户或者管理员账户,避免COM组件访问被拒绝。
  2. 部署时确认将exe对应的.config文件一起复制到部署目录,保证配置读取正常。

内容的提问来源于stack exchange,提问作者Brunes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 11:15:07