服务停止后后台线程仍在侦听 TCP 端口

Background thread still listening on TCP port after service stopped

我正在编写一个侦听 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. 
                    }
            }
        }

编辑: 使用建议的更改更新了代码。我还发现,这个流氓线程问题似乎只有在我的一个客户端线程中抛出异常时才会发生。如果一切 运行 没问题,当我停止服务时没有流氓线程。

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

The Stop method also closes the underlying Socket and creates a new Socket for the TcpListener. If you set any properties on the underlying Socket prior to calling the Stop method, those properties will not carry over to the new 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}