首页 > 解决方案 > 服务停止后后台线程仍在侦听 TCP 端口

问题描述

我正在编写一个侦听 TCP 端口的 C# 服务(.NET 4.0)。我在后台线程(使用任务并行库)上启动 TcpListener,因此该服务不会对 Windows 无响应。每当客户端连接时,我也会使用 TPL,因为每个客户端都会做一些数据库工作,我不想阻止其他客户端。

我在 Windows Server 2012 R2 Standard 上使用 InstallUtil.exe 安装和卸载该服务。每当我停止服务并卸载它时,使用 netstat -abo 我可以看到 [System] 进程仍在监听该端口。它有一个 PID,但是我在任务管理器或任务列表中看不到具有此 PID 的进程,也不能用 taskkill 杀死它。它只是说找不到进程,但是当我运行 netstat -abo 时它总是在那里。如果我尝试使用相同的端口再次启动服务,则会收到套接字异常:

Only one usage of each socket address (protocol/network address/port) is normally permitted
Stacktrace:
at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Bind(EndPoint localEP)
at System.Net.Sockets.TcpListener.Start(Int32 backlog)

我猜我的后台线程在我停止服务后仍然存在,但我不知道现在如何杀死它们,以及如何防止这种情况发生。以下是我希望的相关代码(已删除异常处理和日志记录以便于阅读):

public partial class MyService : ServiceBase
        {
            private static TCPServer server = null;

            protected override void OnStart(string[] args)
            {
                server = new TCPServer();
            }

            protected override void OnStop()
            {
                if (server != null)
                {
                    server.StopServer();
                }
            }
        }

    class TCPServer
        {
            public static TcpListener listener = null;
            private static Task listenerTask = null;
            private static List<Task> clientTasks = new List<Task>();

            public TCPServer()
            {
                    listenerTask = new Task(() => StartServer());
                    listenerTask.Start();
            }

            public void StopServer()
            {
                foreach(Task task in clientTasks)
                {
                    task.Dispose();
                }

                    listenerTask.Dispose();

                    if (listener != null)
                    {
                        listener.Stop();
                        listener = null;
                    }
            }

            private void StartServer()
            {
                    Int32 port = 51987;
                    IPAddress localAddr = GetLocalIP();

                    listener = new TcpListener(localAddr, port);
                    listener.Start();

                    while (listener != null)
                    {
                        if (listener.Pending())
                        {
                            TcpClient client = listener.AcceptTcpClient();

                            Task task = new Task((obj) => ProcessClient(obj), client);
                            task.Start();
                            clientTasks.Add(task);
                        }
                    }
            }

            private void ProcessClient(object obj)
            {
                    using (TcpClient client = obj as TcpClient)
                    {

                    Byte[] bytes = new Byte[2048];
                    String data = null;

                    NetworkStream stream = client.GetStream();
                    int i;

                    while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                    {
                        data = Encoding.ASCII.GetString(bytes, 0, i);
                    }

                    // do some stuff with data
                    // If an exception is thrown here, the rogue thread issue happens when I stop the service. 
                    // Otherwise, everything is good - I stop the service and no rogue thread, I can reuse the listener port. 
                    }
            }
        }

编辑: 用建议的更改更新了代码。我还发现,只有在我的一个客户端线程中引发异常时,才会出现这种流氓线程问题。如果一切正常,当我停止服务时就没有恶意线程。

标签: c#windowsservicetask-parallel-librarytcplistener

解决方案


在这种情况下,“停止”方法意味着停止当前连接并开始监听新连接,因此请确保您的客户端已正确关闭,以免创建新连接。

Stop 方法还关闭底层 Socket 并为 TcpListener 创建一个新的 Socket。如果在调用 Stop 方法之前在基础 Socket 上设置了任何属性,这些属性将不会传递到新的 Socket。

https://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.stop(v=vs.110).aspx#

此外,请确保您using与客户端一起使用

using(TcpClient client = obj as TcpClient){//DO SOMETHING}

推荐阅读