Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

C# multiuser TCP server

Trying to create an async tcp server.
Previously I could accomplish this with Begin/End pattern pretty good and handle multiple clients like this:

class Server
{
    IPEndPoint ipep;
    TcpListener listener;
    bool Running;
    List<Client> clients;

    public Server(string host)
    {
        IPAddress ip = IPAddress.Parse(host);
        ipep = new(ip, 21);
        Running = false;
        clients = new();
    }

    public void Start()
    {
        listener = new(ipep);
        listener.Start();
        Running = true;
        while (Running)
            listener.BeginAcceptTcpClient(Accept, null);
    }

    public void Accept(IAsyncResult ar)
    {
        Client client = new(listener.EndAcceptTcpClient(ar));
        clients.Add(client);
        client.WaitCommands();
    }
}

class Client
{
    TcpClient client;
    NetworkStream stream;

    public Client(TcpClient client)
    {
        this.client = client;
        stream = client.GetStream();
    }

    public void WaitCommands()
    {
        stream.BeginRead(/*some buffer stuff*/, Receive, null);
    }

    public void Receive(IAsyncResult ar)
    {
        stream.EndRead(ar);
        stream.BeginRead(/*again buffer stuff*/, Receive, null);
    }
}

and there are a lot of examples of this in the Internet. But MSDN seems to recommend using async methods instead, so I wanna convert to it. Here’s what I have:

class Server
{
    IPEndPoint ipep;
    TcpListener listener;
    bool Running;
    List<Client> clients;

    public Server(string host)
    {
        IPAddress ip = IPAddress.Parse(host);
        ipep = new(ip, 21);
        Running = false;
        clients = new();
    }

    public async Task Start()
    {
        listener = new(ipep);
        listener.Start();
        Running = true;
        while (Running)
        {
            Client c = await listener.AcceptTcpClientAsync();
            clients.Add(c);
            await c.WaitCommands();
        }
    }
}

class Client
{
    TcpClient client;
    NetworkStream stream;

    public Client(TcpClient client)
    {
        this.client = client;
        stream = client.GetStream();
    }

    public async Task WaitCommands()
    {
        while (true)
        {
            await stream.ReadAsync(/*buffer stuff*/);
        }
    }
}

and obviously await c.WaitCommands(); blocks other clients, since app is stuck in while (true) loop and never reaches await Accept again. I found some that _ = Task.Run(async () => await client.WaitCommands()); does the trick. But as I understood that takes threads from threadpool, unlike Begin/End approach (or am I wrong? that’s the question too).

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

So the questions are

  1. How to start reading from client and accept another ones in the second example?
  2. Am I going the right way? Or what approach should be used for most user-count scale (i.e. maybe should I go some threads per client instead)?

>Solution :

Something like this:

using System.Net;
using System.Net.Sockets;

var svr = new Server("127.0.0.1");
await svr.Run();
Console.WriteLine("Goodby, World!");

class Server
{
    IPEndPoint ipep;
    TcpListener listener;
    bool Running;
    List<Client> clients;
    private CancellationTokenSource cts;

    public Server(string host)
    {
        IPAddress ip = IPAddress.Parse(host);
        ipep = new(ip, 1121);
        Running = false;
        clients = new();

        this.cts = new CancellationTokenSource();
    }

    public void Stop()
    {
        Running = false;
        cts.Cancel();
    }
    public async Task Run()
    {
        listener = new(ipep);
        listener.Start();
        Running = true;
        while (Running)
        {
            var c = await listener.AcceptTcpClientAsync(cts.Token);
            var client = new Client(c);
            clients.Add(client);
            var clientTask = client.Run(); //don't await
            clientTask.ContinueWith(t => clients.Remove(client));
        }
    }
}

class Client
{
    TcpClient client;
    NetworkStream stream;

    public Client(TcpClient client)
    {
        this.client = client;
        stream = client.GetStream();
        
    }

    public async Task Run()
    {
        var r = new StreamReader(stream);
        var w = new StreamWriter(stream);
        while (true)
        {
            await w.WriteLineAsync("You are standing in an open field west of a white house, with a boarded front door. There is a small mailbox here.");
            await w.WriteAsync(">");
            await w.FlushAsync();

            var l = await r.ReadLineAsync();
            await w.WriteLineAsync("Invalid command " + l);
        }
    }
}

Which you can test with

c:\> telnet localhost 1121
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading