I'm trying to understand the backlog parameter of TcpListener
class but I struggle on how to achieve maximum number of pending connections at same time so I can test it.
I have a sample async server and client code. MSDN says that the backlog is the maximum length of the pending connections queue. I made the server listen for connections all the time and the client is connecting 30 times. What I expect is after the 20th request to throw a SocketException
in the client because the backlog is set to 20. Why doesn't it block it?
My second misunderstanding is do I really need to put my logic of the accepted connection in a new thread assuming there is a slow operation which takes around 10 seconds e.g. sending a file over the TCP? Currently, I put my logic in a new Thread
, I know it's not the best solution and instead I should use a ThreadPool
but the question is principal. I tested it by changing the client side's loop to 1000 iterations and if my logic is not in a new thread, the connections were getting blocked after the 200th connection probably because Thread.Sleep slows the main thread each time by 10 seconds and the main thread is responsible for all the accept callbacks. So basically, I explain it myself as the following: if I want to use the same concept, I have to put my AcceptCallback logic in a new thread like I did or I have to do something like the accepted answer here: TcpListener is queuing connections faster than I can clear them. Am I right?
Server code:
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Server
{
class Program
{
private static readonly ManualResetEvent _mre = new ManualResetEvent(false);
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 80);
try
{
listener.Start(20);
while (true)
{
_mre.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);
_mre.WaitOne();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static void AcceptCallback(IAsyncResult ar)
{
_mre.Set();
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
IPAddress ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
Console.WriteLine($"{ip} has connected!");
// Actually I changed it to ThreadPool
//new Thread(() =>
//{
// Console.WriteLine("Sleeping 10 seconds...");
// Thread.Sleep(10000);
// Console.WriteLine("Done");
//}).Start();
ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
{
Console.WriteLine("Sleeping 10 seconds...");
Thread.Sleep(10000);
Console.WriteLine("Done");
}));
// Close connection
client.Close();
}
}
}
Client code:
using System;
using System.Net.Sockets;
namespace Client
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
Console.WriteLine($"Connecting {i}");
using (TcpClient client = new TcpClient()) // because once we are done, we have to close the connection with close.Close() and in this way it will be executed automatically by the using statement
{
try
{
client.Connect("localhost", 80);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.ReadKey();
}
}
}
Edit: Since my second question might be a little bit confusing, I will post my code which includes sent messages and the question is should I leave it like that or put the NetworkStream
in a new thread?
Server:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Server
{
class Program
{
private static readonly ManualResetEvent _mre = new ManualResetEvent(false);
static void Main(string[] args)
{
// MSDN example: https://learn.microsoft.com/en-us/dotnet/framework/network-programming/asynchronous-server-socket-example
// A better solution is posted here: https://stackoverflow.com/questions/2745401/tcplistener-is-queuing-connections-faster-than-i-can-clear-them
TcpListener listener = new TcpListener(IPAddress.Any, 80);
try
{
// Backlog limit is 200 for Windows 10 consumer edition
listener.Start(5);
while (true)
{
// Set event to nonsignaled state
_mre.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);
// Wait before a connection is made before continuing
_mre.WaitOne();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue
_mre.Set();
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
IPAddress ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
Console.WriteLine($"{ip} has connected!");
using (NetworkStream ns = client.GetStream())
{
byte[] bytes = Encoding.Unicode.GetBytes("test");
ns.Write(bytes, 0, bytes.Length);
}
// Use this only with backlog 20 in order to test
Thread.Sleep(5000);
// Close connection
client.Close();
Console.WriteLine("Connection closed.");
}
}
}
Client:
using System;
using System.Net.Sockets;
using System.Text;
namespace Client
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 33; i++)
{
Console.WriteLine($"Connecting {i}");
using (TcpClient client = new TcpClient()) // once we are done, the using statement will do client.Close()
{
try
{
client.Connect("localhost", 80);
using (NetworkStream ns = client.GetStream())
{
byte[] bytes = new byte[100];
int readBytes = ns.Read(bytes, 0, bytes.Length);
string result = Encoding.Unicode.GetString(bytes, 0, readBytes);
Console.WriteLine(result);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.ReadKey();
}
}
}