.NET - Socket server with async/await - c#

I'm writing simple socket server with C#. The idea is to do it as simple as possible since the communication won't be heavy. I've used some of the TAP/APM patterns on socket async calls so the code now looks like this:
public async Task StartListening()
{
try
{
var endpoint = new IPEndPoint(IPAddress.Loopback, Port);
using (Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
Socket.Bind(endpoint);
Socket.Listen(Limit);
while (!Shutdown)
{
// await till we get the connection - let the main thread/caller continue (I expect this loop to continue in separate thread after await)
var socket = await Socket.AcceptAsync().ConfigureAwait(false);
var state= new ClientStateObject(socket, id);
// do not await for receive - continue to listen for connections
Receive(socket, state);
}
}
}
catch (Exception ex)
{
// handle errors via callbacks
}
}
private async void Receive(Socket socket, ClientStateObject state)
{
try
{
while (!Shutdown)
{
var bytes = await socket.ReceiveAsync(state).ConfigureAwait(false);
var readResult = state.Read(bytes);
if (readResult == CloseConn)
{
// close connection
return;
}
if (readResult == Completed)
{
// process the message
state.Reset();
}
}
}
catch (Exception ex)
{
// handle errors via callbacks
}
}
This code seems to run fine in development builds, but sometimes behaves strange in release mode. I assume this might be related to race conditions or something that is related to threading and async/await. Does anyone see what might be the problem with the above code?
Just to be clear AcceptAsync/ReceiveAsync are wrappers around socket methods to return tasks.

Related

C# TcpListener and MySqlConnection stops accepting connections after a while

I have an async socket server written in C#, running on a Lightsail server running Amazon Linux. It consists of a TcpListener that accepts connections, starts up a new thread to listen when someone connects, initiates an SSL connection, and then acts as a server for an online game.
This server works fine for about a day, until suddenly all networking stops working on the server. The crash takes anywhere from 22 hours to one week to occur. The symptoms are as follows:
Anyone already connected to the server will suddenly stop receiving/sending data. I can see in the logs that my inactivity checking code will eventually kick them for not sending heartbeat packets.
The server will also be unable to connect to its MySQL database (which is running on the same system, so it's unable to connect to localhost? I can still access it through PHPMyAdmin during this time).
It is, however, still able to write both to files and to console, as my logger is still able to write to both.
The code looks like everyone else's (I did try the changes suggested for this question, but it still crashed after ~24 hours). None of the errors get logged, so it looks like it never encounters an exception. No exceptions precede the crash, which is why I've been having problems figuring this one out.
For completeness, here is my main loop:
public void ListenLoop()
{
TcpListener listener = new TcpListener(IPAddress.Any, 26000);
listener.Start();
while (true)
{
try
{
if (listener.Pending())
{
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptConnection), listener);
Logger.Write(Logger.Level.INFO, "continuing the main loop");
}
// Yield so we're not stuck in a busy-loop
Thread.Sleep(5);
}
catch (Exception e)
{
Logger.Write(Logger.Level.ERROR, $"Error while waiting for listeners: {e.Message}\n{e.StackTrace}");
}
}
}
and here are the accept parts:
/// <summary>
/// Finish an async callback but spawn a new thread to handle it if necessary
/// </summary>
/// <param name="ar"></param>
private void AcceptConnection(IAsyncResult ar)
{
if (ar.CompletedSynchronously)
{
// Force the accept logic to run async, to keep our listening
// thread free.
Action accept = () => AcceptCallback(ar);
accept.BeginInvoke(accept.EndInvoke, null);
} else
{
AcceptCallback(ar);
}
}
private void AcceptCallback(IAsyncResult ar)
{
try
{
TcpListener listener = (TcpListener) ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
// If the SSL connection takes longer than 5s we have a problem, and should stop
client.Client.ReceiveTimeout = 5000;
// Attempt to get the IP address of the client we're connecting to
IPEndPoint ipep = (IPEndPoint)client.Client.RemoteEndPoint;
string ip = ipep.Address.ToString();
Logger.Write(Logger.Level.INFO, $"Connection begun to {ip}");
// Authenticate and begin communicating with the client
SslStream stream = new SslStream(client.GetStream(), false);
try
{
stream.AuthenticateAsServer(
serverCertificate,
enabledSslProtocols: System.Security.Authentication.SslProtocols.Tls12,
clientCertificateRequired: false,
checkCertificateRevocation: true
);
stream.ReadTimeout = 3600000;
stream.WriteTimeout = 3600000;
NetworkPlayer player = new NetworkPlayer();
player.Name = ip;
player.Connection.Stream = stream;
player.Connection.Connected = true;
player.Connection.Client = client;
stream.BeginRead(player.Connection.Buffer, 0, 1024, new AsyncCallback(ReadCallback), player);
}
catch (Exception e)
{
Logger.Write(Logger.Level.ERROR, $"Error while starting the connection to {ip}: {e.Message}");
// The following code just calls stream.Close(); and client.Close(); but sends exceptions to my logger.
CloseConnectionSafely(client, stream);
}
}
catch (Exception e)
{
Logger.Write(Logger.Level.ERROR, $"Error while starting a connection to an unknown user: {e.Message}");
}
}
I'm guessing that your primary issue is that you are not disposing the stream and therefore you are getting socket exhaustion.
Apart from that I would advise you to move to fully async code using Task.
public async Task ListenLoop(CancellationToken cancel) // use a cancellation token to shutdown the loop
{
using (var TcpListener listener = new TcpListener(IPAddress.Any, 26000))
{
listener.Start();
while (!cancel.IsCancellationRequested)
{
try
{
var client = await listener.AcceptTcpClientAsync(cancel);
Task.Run(async () => await AcceptConnection(client, cancel));
Logger.Write(Logger.Level.INFO, "continuing the main loop");
// no need to yield due to async
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Logger.Write(Logger.Level.ERROR, $"Error while waiting for listeners: {e.Message}\n{e.StackTrace}");
}
}
listener.Stop();
}
}
private async Task AcceptConnection(TcpClient client, CancellationToken cancel)
{
try
{
using (client)
{
// If the SSL connection takes longer than 5s we have a problem, and should stop
client.Client.ReceiveTimeout = 5000;
await AcceptConnectionImpl(client, cancel);
}
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Logger.Write(Logger.Level.ERROR, $"Error while starting a connection to an unknown user: {e.Message}");
}
}
private async Task AcceptConnectionImpl(TcpClient client, CancellationToken cancel)
{
// Attempt to get the IP address of the client we're connecting to
IPEndPoint ipep = client.Client.RemoteEndPoint;
Logger.Write(Logger.Level.INFO, $"Connection begun to {ipep.Address}");
// Authenticate and begin communicating with the client
using (SslStream stream = new SslStream(client.GetStream(), false))
{
try
{
await stream.AuthenticateAsServerAsync(
serverCertificate,
enabledSslProtocols: System.Security.Authentication.SslProtocols.Tls12,
clientCertificateRequired: false,
checkCertificateRevocation: true
);
stream.ReadTimeout = 3600000;
stream.WriteTimeout = 3600000;
NetworkPlayer player = new NetworkPlayer();
player.Name = ip;
player.Connection.Stream = stream;
player.Connection.Connected = true;
player.Connection.Client = client;
player.Cancellation = cancel;
await player.YourReadLoopAsync();
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Logger.Write(Logger.Level.ERROR, $"Error while starting the connection to {ip}: {e.Message}");
// The following code just calls stream.Close(); and client.Close(); but sends exceptions to my logger.
CloseConnectionSafely(client, stream);
}
}
}
The function YourReadLoopAsync should read data from the stream using ReadAsync, or using classes like StreamReader which also has async functions.
You don't need to use CancellationToken, but it does make it easier to deal with shutting everything down cleanly. Make sure to catch OperationCanceledException on every try.
See also this link for further tips.
The solution I found after consulting some people more familiar with C# than me is that I was running into Thread Pool Exhaustion. Essentially, I had a bunch of other async tasks (not shown in the code in the question, as they didn't look like they could cause what I was seeing) that were stuck executing some extremely-long-IOs (talking to users that had either disconnected improperly or were behind very high latency), which prevented the async AcceptCallback in my post from being picked up by the Thread Pool. This had a myriad of other side-effects which I outlined in the question:
Creating a new connection to a MySQL database involves an async task behind-the-scenes, which was being starved out due to exhaustion.
Completing the EndAcceptTcpClient required my async task to run, which requires an available thread.
Tasks which did not involve the async keyword, such as Timer() bound tasks (like my logger I/O) were unaffected and could still run.
My solution involved reducing the number of synchronization steps elsewhere in my program, and restructuring any tasks that could take a long time to execute so that they didn't block threads. Thank you to everyone who looked/commented.

Output of one Task input to other Task

In Winforms, I have following: ProcessClientAsync adds an element to a ConcurrentDictionary. How can I ensure that the forloop runs after the Task.Run(). I tried to remove ConfigureAwait but it freezes the UI.
public async Task Listen(int port)
{
try
{
IPAddress serverAddress = IPAddress.Parse("127.0.0.1"); // localhost
_listener = new TcpListener(serverAddress, port);
_listener.Start();
while (true)
{
TcpClient tcpClient = await _listener.AcceptTcpClientAsync();
await Task.Run(() => ProcessTcpClientAsync(tcpClient).ConfigureAwait(false));
_statusText.StatusUpdate = "number of users are " + _mapClient.GetUsers().Count;
}
}
catch (SocketException ex)
{
string message = string.Format("Error listening on port {0}. Make sure IIS or another application is not running and consuming your port.", port);
throw new Exception(message, ex);
}
}
private async Task<string> ProcessTcpClientAsync(TcpClient tcpClient)
{
string key = string.Empty;
WebSocket webSocket = null;
try
{
if (_isDisposed)
return string.Empty;
// this worker thread stays alive until either of the following happens:
// Client sends a close conection request OR
// An unhandled exception is thrown OR
// The server is disposed
// get a secure or insecure stream
NetworkStream stream = tcpClient.GetStream();
WebSocketHttpContext context = await _webSocketServerFactory.ReadHttpHeaderFromStreamAsync(stream);
if (context.IsWebSocketRequest)
{
key = GetKeyFromContext(context);
// _statusText.StatusUpdate = "Connection from origin.";
webSocket = await _webSocketServerFactory.AcceptWebSocketAsync(context);
//_statusText.StatusUpdate = "Connection accepted.";
await RespondToWebSocketRequestAsync(tcpClient, key, webSocket);
}
else
{
//_statusText.StatusUpdate = "Http header contains no web socket upgrade request. Ignoring...";
}
}
catch (Exception ex)
{
}
finally
{
try
{
await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closed in server by the client", CancellationToken.None);
tcpClient.Client.Close();
tcpClient.Close();
}
catch (Exception ex)
{
}
}
return key;
}
You could await the Task.Run, but make sure to make the parent method async
await Task.Run(() => ProcessClientAsync(client).ConfigureAwait(false));
This will wait for the async task to complete, and then execute the rest of the code. I would suggest learning a bit more about async/await.
To block the ProcessClientAsync call you can do the following:
Task.Run(() => ProcessClientAsync(client)).Wait();
If you want to access the result of ProcessClientAsync:
Task<TResult> task = null;
Task.Run(() => task = ProcessClientAsync(client)).Wait();
// task.Result contains the result
Even if this works, it's recommended to await tasks rather than blocking with wait.
Quiz, what is the type of the variable x below?
var x = Task.Run(() => Task.Delay(1000).ConfigureAwait(false));
The type is not Task. It is Task<ConfiguredTaskAwaitable>. That internal ConfigureAwait(false) call is not only meaningless, but it also created a
unexpected situation where the return value must now be awaited twice:
await await x;
Don't do this. If you have to use ConfigureAwait(false) for some reason, then you are expected to await the result immediately. Don't pass ConfiguredTaskAwaitable structs around. My advice is to search your code for more instances of this anti-pattern, and eliminate them.
If you don't want to mark the parent method as async, then you could use the following instead:
Task.Wait(Task.Run(() => ProcessClientAsync(client)));
This method has several overloads that allow for configurability of cancellation and timeouts as well:
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.wait?view=netframework-4.8
Also, if your code actually says while (true) and has no break conditions, then it will never exit.

How to avoid blocking a .NET thread when waiting for requests

I have a server application that is listening on a System.Net.Sockets.Socket. It blocks on the Socket.Accept(), tying up the thread. What is a good way to relinquish control of the thread, yet yield the result of the computation to the calling client?
The server code, somewhat cleaned up, looks like this:
void ManeLupe(Socket socket)
{
for (;;)
{
Socket client = null;
NetworkStream stm = null;
try
{
client = socket.Accept(); // Blocks thread
stm = new NetworkStream(client);
var response = ProcessRequest(stm); // this could take a while
WriteResponse(response, stm);
}
catch (Exception ex)
{
LogError(client, ex);
}
finally
{
if (stm != null) stm.Dispose();
if (client != null) client.Dispose();
}
}
}
My constraint, currently, is that the code has to run on .NET framework 3.5, so I can't take advantage of the new-fangled Task<> and async goodness. Frankly I'm rather new to asynchronous programming in general, which I'm suspecting is going to be the answer to this query.
Socket provides Asynchronous variant of Accept called BeginAccept, which is what you're after.
BeginAccept is the APM implementation of Accept, It provides a pair of methods for each operation. In this case you'll be using BeginAccept and EndAccept.
As noted in comments by #CoryNelson you might consider using AcceptAsync as well. It is upto you to choose which one.
You can use AcceptAsync and register to the callback to do your processing after the accept is completed. Simple example below
void ManeLupe(Socket socket)
{
var args = new SocketAsyncEventArgs();
args.Completed += Accept_Completed;
socket.AcceptAsync(args);
}
void Accept_Completed(object sender, SocketAsyncEventArgs e)
{
//do your processing here
}

abort a thread or just let it to be cleaned by the computer?

I have the following code, I want to abort a thread if it is unfinished in 2 secs.
You can see from the first code that i create a new myThread evertyime in the while loop and do not abort it. Well, I dont want to it to be like this, but if i take the myThread outside the loop and use the abort() function as the second code does. there will be error in aborting.
while (true)
{
try
{
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//reset up socket
myThread = new System.Threading.Thread(new System.Threading.ThreadStart(socket_connect));
myThread.Start();
if (!myThread.Join(2000))
{
throw new SocketException(SocketError.AccessDenied);
}
}
catch (Exception ex)
{
m_socket.Close();
}
}
}
private static void socket_connect()
{
m_socket.Connect(remoteEndPoint);//Connect to remote device
}
I was trying the following code at first, however it give threadabortexceptions.
myThread = new System.Threading.Thread(new System.Threading.ThreadStart(socket_connect));
while (true)
{ try
{
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//reset up socket
myThread.Start();
if (!myThread.Join(2000))
{
myThread.Abort();
throw new SocketException(SocketError.AccessDenied);
}
}
catch (Exception ex)
{
m_socket.Close();
}
}
}
private static void socket_connect()
{
m_socket.Connect(remoteEndPoint);//Connect to remote device
}
I know abort() is not a good idea so I turn to let the threads stay and let C#(.Net? I dont know who actually does that) dealing with the garbage collection. And can anyone tell if it is a good idea since this program will be run on a board which does not have lots of memory for holding bunches of threads. And can someone tell me how exactly the garbage collection is done in C#? for example the threads.
Another thing need to be mention is that i dont have the Task class, or the socket.beginconnect() method, I guess its because i'm building a program that is going to run on a small board, not a pc. The board is a netduido plus and I'm building my project on a netduino plus platform.
I have the following code, I want to abort a thread if it is unfinished in 2 secs.
Reading into the code, it looks like you actually want to attempt to connect a socket to a resource within two seconds. If more than two seconds elapses, you want to move on.
I'm mostly reproducing the code in this answer, I believe this is approximately what you should be doing to achieve your goal, rather than spinning up a thread and aborting it:
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
// Connect using a timeout (2 seconds)
IAsyncResult result = socket.BeginConnect( sIP, iPort, null, null );
bool success = result.AsyncWaitHandle.WaitOne( 2000, true );
if ( !success )
{
// NOTE, MUST CLOSE THE SOCKET
socket.Close();
throw new ApplicationException("Failed to connect server.");
}
// Success
//...
[edit: massive copypasta fail when untabbing/spacing]
Oh, please use the Task library, it's way easier to deal with these situations:
(LINQPad-friendly blob)
void Main()
{
var canceller = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => DoStuff(canceller.Token), canceller.Token);
if(!task.Wait(2000, canceller.Token))
{
canceller.Cancel();
task.Wait(2);
}
sw.Elapsed.Dump();
}
private Stopwatch sw;
private void DoStuff(CancellationToken token)
{
try
{
sw = Stopwatch.StartNew();
while(!token.IsCancellationRequested)
{
}
}
// no catch - rethrown exceptions must be checked on Task
finally
{
sw.Stop();
}
}
Alternatively, you can use some "exit flag" condition - a bool that both your thread-starter and your thread-runner can see/alter, and use that in your while condition.

Stopping a TcpListener after calling BeginAcceptTcpClient

I have this code...
internal static void Start()
{
TcpListener listenerSocket = new TcpListener(IPAddress.Any, 32599);
listenerSocket.Start();
listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}
Then my call back function looks like this...
private static void AcceptClient(IAsyncResult asyncResult)
{
MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
ThreadPool.QueueUserWorkItem((object state) => handler.Process());
listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}
Now, I call BeginAcceptTcpClient, then some time later I want to stop the server. To do this I have been calling TcpListener.Stop(), or TcpListener.Server.Close(). Both of these however execute my AcceptClient function. This then throws an exception when I call EndAcceptTcpClient. What is the best practice way around this? I could just put a flag in to stop the execution of AcceptClient once I have called stop, but I wonder if I am missing something.
Update 1
Currently I have patched it by changing the code to look like this.
private static void AcceptClient(IAsyncResult asyncResult)
{
if (!shutdown)
{
MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
ThreadPool.QueueUserWorkItem((object state) => handler.Process());
listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}
}
private static bool shutdown = false;
internal static void Stop()
{
shutdown = true;
listenerSocket.Stop();
}
Update 2
I changed it to impliment the answer from Spencer Ruport.
private static void AcceptClient(IAsyncResult asyncResult)
{
if (listenerSocket.Server.IsBound)
{
MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
ThreadPool.QueueUserWorkItem((object state) => handler.Process());
listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}
}
I just ran into this issue myself, and I believe your current solution is incomplete/incorrect. There is no guarantee of atomicity between the check for IsBound and the subsequent call to EndAcceptTcpClient(). You can still get an exception if the listener is Stop()'d between those two statements. You didn't say what exception you're getting but I assume it's the same one I'm getting, ObjectDisposedException (complaining that the underlying socket has already been disposed).
You should be able to check this by simulating the thread scheduling:
Set a breakpoint on the line after the IsBound check in your callback
Freeze the thread that hits the breakpoint (Threads window -> right click, "Freeze")
Run/trigger the code that calls TcpListener.Stop()
Break in and step through the EndAcceptTcpClient() call. You should see the ObjectDisposedException.
IMO the ideal solution would be for Microsoft to throw a different exception from EndAcceptTcpClient in this case, e.g. ListenCanceledException or something like that.
As it is, we have to infer what's happening from the ObjectDisposedException. Just catch the exception and behave accordingly. In my code I silently eat the exception, since I have code elsewhere that's doing the real shutdown work (i.e. the code that called TcpListener.Stop() in the first place). You should already have exception handling in that area anyway, since you can get various SocketExceptions. This is just tacking another catch handler onto that try block.
I admit I'm uncomfortable with this approach since in principle the catch could be a false positive, with a genuine "bad" object access in there. But on the other hand there aren't too many object accesses in the EndAcceptTcpClient() call that could otherwise trigger this exception. I hope.
Here's my code. This is early/prototype stuff, ignore the Console calls.
private void OnAccept(IAsyncResult iar)
{
TcpListener l = (TcpListener) iar.AsyncState;
TcpClient c;
try
{
c = l.EndAcceptTcpClient(iar);
// keep listening
l.BeginAcceptTcpClient(new AsyncCallback(OnAccept), l);
}
catch (SocketException ex)
{
Console.WriteLine("Error accepting TCP connection: {0}", ex.Message);
// unrecoverable
_doneEvent.Set();
return;
}
catch (ObjectDisposedException)
{
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
Console.WriteLine("Listen canceled.");
return;
}
// meanwhile...
SslStream s = new SslStream(c.GetStream());
Console.WriteLine("Authenticating...");
s.BeginAuthenticateAsServer(_cert, new AsyncCallback(OnAuthenticate), s);
}
No you're not missing anything. You can check the IsBound property of the Socket object. At least for TCP connections, while the socket is listening this will be set to true and after you call close it's value will be false. Though, your own implementation can work just as well.
try this one. it works fine for me without catching exceptions.
private void OnAccept(IAsyncResult pAsyncResult)
{
TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
if(listener.Server == null)
{
//stop method was called
return;
}
...
}
i think that all tree things are needed and that the restart of BeginAcceptTcpClient should be placed outside the tryctach of EndAcceptTcpClient.
private void AcceptTcpClientCallback(IAsyncResult ar)
{
var listener = (TcpListener)ar.AsyncState;
//Sometimes the socket is null and somethimes the socket was set
if (listener.Server == null || !listener.Server.IsBound)
return;
TcpClient client = null;
try
{
client = listener.EndAcceptTcpClient(ar);
}
catch (SocketException ex)
{
//the client is corrupt
OnError(ex);
}
catch (ObjectDisposedException)
{
//Listener canceled
return;
}
//Get the next Client
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), listener);
if (client == null)
return; //Abort if there was an error with the client
MyConnection connection = null;
try
{
//Client-Protocoll init
connection = Connect(client.GetStream());
}
catch (Exception ex)
{
//The client is corrupt/invalid
OnError(ex);
client.Close();
}
}
This is a simple example how to start listening, how to process requests asynchronously, and how to stop listening.
Full example here.
public class TcpServer
{
#region Public.
// Create new instance of TcpServer.
public TcpServer(string ip, int port)
{
_listener = new TcpListener(IPAddress.Parse(ip), port);
}
// Starts receiving incoming requests.
public void Start()
{
_listener.Start();
_ct = _cts.Token;
_listener.BeginAcceptTcpClient(ProcessRequest, _listener);
}
// Stops receiving incoming requests.
public void Stop()
{
// If listening has been cancelled, simply go out from method.
if(_ct.IsCancellationRequested)
{
return;
}
// Cancels listening.
_cts.Cancel();
// Waits a little, to guarantee
// that all operation receive information about cancellation.
Thread.Sleep(100);
_listener.Stop();
}
#endregion
#region Private.
// Process single request.
private void ProcessRequest(IAsyncResult ar)
{
//Stop if operation was cancelled.
if(_ct.IsCancellationRequested)
{
return;
}
var listener = ar.AsyncState as TcpListener;
if(listener == null)
{
return;
}
// Check cancellation again. Stop if operation was cancelled.
if(_ct.IsCancellationRequested)
{
return;
}
// Starts waiting for the next request.
listener.BeginAcceptTcpClient(ProcessRequest, listener);
// Gets client and starts processing received request.
using(TcpClient client = listener.EndAcceptTcpClient(ar))
{
var rp = new RequestProcessor();
rp.Proccess(client);
}
}
#endregion
#region Fields.
private CancellationToken _ct;
private CancellationTokenSource _cts = new CancellationTokenSource();
private TcpListener _listener;
#endregion
}

Categories