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(),此时流已经被释放,会触发额外异常
修复步骤
- 删除DoOpc方法最后一行的
TcpConn()调用,避免重复执行TCP监听逻辑 - 调整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); }
- 调整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,不要直接杀死整个进程 } }
- 调整服务运行账户:在服务管理中,将服务的运行账户改为有OPC访问权限的本地用户或者管理员账户,避免COM组件访问被拒绝。
- 部署时确认将exe对应的
.config文件一起复制到部署目录,保证配置读取正常。
内容的提问来源于stack exchange,提问作者Brunes
相关产品推荐
相关产品推荐

