【发布时间】:2018-06-05 05:05:33
【问题描述】:
我正在编写一个侦听 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.
}
}
}
编辑: 使用建议的更改更新了代码。我还发现,只有在我的一个客户端线程中引发异常时,才会出现这种流氓线程问题。如果一切正常,当我停止服务时就没有恶意线程。
【问题讨论】:
-
TcpListener 上没有 Dispose 函数。
-
我想推荐两件事:不要为不同的目的重复使用名称:服务器和任务是我想到的。另一个问题是,我建议保留对任务的引用,这两种情况都发生。这也可能对您的问题有所帮助。
-
我看到 Task 有 Dispose,所以我保留了对 Task 的引用并将 task.Dispose 放在我的 StopServer 函数中。但是我收到以下错误:只有处于完成状态(RanToCompletion、Faulted 或 Canceled)的任务才能被释放。
标签: c# windows service task-parallel-library tcplistener