问题
I am writing a TCP server in C#, and have run into a strange and potentially a security issue as well.
My basic arch for accepting new connections is as follows:
- A C# Socket listening on a port, using the
AcceptAsync
method to accept incoming connections. - Spinning off accepted connections using the
ThreadPool
for finishing the accept.
Everything works quite well, however everything grinds to a halt if someone telnets into the port.
Symptoms:
If I telnet into my server and do not send any data (i.e. do not hit any keys) the server will never finish accepting the connection.
My
SocketAsyncEventArgs.Completed
callback is never hit for the telnet connection.Even worse, all further connections are blocked/queued and never get accepted by my code. They are put into a
CLOSE_WAIT
state: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
Any advice would be appreciated.
StartAccept:
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());
}
}
}
}
Completed Callback for accepting connections:
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;
}
}
Finish Accept:
private void FinishAccept(object StateObject)
{
SocketAsyncEventArgs args = (SocketAsyncEventArgs)StateObject;
FinishAcceptInternal(args);
}
Here is the wireshark from connecting telnet but before sending data:
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
This should be the complete handshake to establish my connection, but the Completed
event is never raised.
回答1:
Answering my own question, as I found the underlying cause:
The error is this line:
if (AcceptArgs.Buffer == null ||
AcceptArgs.Buffer.Length < 1024)
{
AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
}
This is because if you set a buffer, AcceptAsync will block until it receives some data.
From MSDN:
The minimum buffer size required is 288 bytes. If a larger buffer size is specified, then the Socket will expect some extra data other than the address data received by the Winsock AcceptEx call and will wait until this extra data is received.
My corrected code:
// 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);
I'm not sure if this is the exact correct fix, setting a buffer of 288 bytes or smaller did not seem to correct the issue. Only setting the buffer to null caused the Completed
event to be raised when connecting without sending data.
回答2:
It looks like GetConnection is blocked somewhere. Usually if server is not under heavy load asynchronous operations may complete in synchronous manner. Also the fact that AcceptAsync returned false or callback method was called means that asynchronous operation is complete and your code should analyse the results.
Below is a simple asynchronous TCP server skeleton that accepts connections asynchronously.
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
}
来源:https://stackoverflow.com/questions/8887190/telnet-blocking-c-sharp-tcp-server