Telnet阻止C#TCP服务器
本文关键字:服务器 C#TCP 阻止 Telnet | 更新日期: 2023-09-27 18:20:24
我正在用C#编写一个TCP服务器,遇到了一个奇怪的、潜在的安全问题。
我接受新连接的基本架构如下:
- 监听端口的C#套接字,使用
AcceptAsync
方法接受传入连接 - 使用
ThreadPool
旋出已接受的连接以完成接受
一切都很好,但如果有人远程登录到端口,一切都会停止。
症状:
-
如果我telnet到我的服务器,并且不发送任何数据(即,不点击任何键),服务器将永远无法完成接受连接。
-
对于telnet连接,我的
SocketAsyncEventArgs.Completed
回调从未命中。 -
更糟糕的是,所有进一步的连接都被阻塞/排队,并且永远不会被我的代码接受。它们被置于
CLOSE_WAIT
状态:TCP 127.0.0.1:8221 chance:53960 CLOSE_WAIT
TCP 127.0.0.1:8221 chance:53962 CLOSE_WAIT
TCP 127.0.0.1:8221 chance:53964 CLOSE_WAIT
如有任何建议,我们将不胜感激。
开始接受:
private void StartAccept(SocketAsyncEventArgs AcceptArgs)
{
CurrentAcceptArgs = AcceptArgs;
AcceptArgs.AcceptSocket = null;
if (AcceptArgs.Buffer == null ||
AcceptArgs.Buffer.Length < 1024)
{
AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
}
if (MainSocket != null)
{
lock (MainSocket)
{
// If this is false, we have an accept waiting right now, otherwise it will complete aynsc
if (MainSocket.AcceptAsync(AcceptArgs) == false)
{
ThreadPool.QueueUserWorkItem(FinishAccept, AcceptArgs);
StartAccept(GetConnection());
}
}
}
}
已完成接受连接的回调:
protected override void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
PWClientRemote RemoteClient = e.UserToken as PWClientRemote;
// Determine which type of operation just completed and call the associated handler.
switch (e.LastOperation)
{
case SocketAsyncOperation.Accept:
StartAccept(GetConnection());
ThreadPool.QueueUserWorkItem(FinishAccept, e);
break;
default:
base.OnIOCompleted(sender, e);
break;
}
}
最终验收:
private void FinishAccept(object StateObject)
{
SocketAsyncEventArgs args = (SocketAsyncEventArgs)StateObject;
FinishAcceptInternal(args);
}
以下是连接telnet但在发送数据之前的wireshark:
No. Time Source Destination Protocol Length Info
1 0.000000 192.168.1.146 192.168.1.109 TCP 66 59766 > 8221 [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
2 0.000076 192.168.1.109 192.168.1.146 TCP 66 8221 > 59766 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
3 0.000389 192.168.1.146 192.168.1.109 TCP 60 59766 > 8221 [ACK] Seq=1 Ack=1 Win=65536 Len=0
这应该是建立连接的完整握手,但从未引发Completed
事件。
回答我自己的问题,因为我发现了根本原因:
错误是这一行:
if (AcceptArgs.Buffer == null ||
AcceptArgs.Buffer.Length < 1024)
{
AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
}
这是因为如果设置了缓冲区,AcceptAsync将阻塞,直到它接收到一些数据。
来自MSDN:
所需的最小缓冲区大小为288字节。如果指定了更大的缓冲区大小,那么Socket将期待一些额外的数据,而不是Winsock AcceptEx调用接收到的地址数据,并将等待,直到接收到这些额外的数据。
我更正的代码:
// We set a null buffer here.
// If we set a valid buffer, the accept will expect data
// and will hang unless it gets it.
AcceptArgs.SetBuffer(null, 0, 0);
我不确定这是否是正确的解决方案,设置288字节或更小的缓冲区似乎并不能解决这个问题。仅将缓冲区设置为null会导致在不发送数据的情况下进行连接时引发Completed
事件。
看起来GetConnection在某个地方被阻止了。通常,若服务器不在重负载下,异步操作可以以同步方式完成。此外,AcceptAsync返回false或调用了回调方法,这意味着异步操作已经完成,您的代码应该分析结果。
下面是一个简单的异步TCP服务器框架,它接受异步连接。
void StartServer()
{
Socket serverSocket = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(addr, port));
s.Listen(5000);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptCompleted);
args.UserToken = serverSocket;
if ( !serverSocket.AcceptAsync(args) )
AcceptCompleted(this, args);
}
void AcceptCompleted(object obj, SocketAsyncEventArgs args)
{
Socket client = args.AcceptSocket;
if (args.SocketError != SocketError.Success)
return;
StartClientOperations(args.AcceptSocket);
args.AcceptSocket = null;
Socket s = (Socket)args.UserToken;
if (!s.AcceptAsync(args))
AcceptCompleted(this, args);
}
void StartClientOperations(Socket newClient)
{
//start other asynchronous operations here with the client socket
}