How to use TCP client/listener in multithreaded c#

2020-07-14 05:28发布

问题:

I have written this code for my server:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Net;
    using System.Threading;
    using System.Net.Sockets;
    using System.IO;


namespace ConsoleApplication1
{
    class Program
    {
        private static bool terminate;

        public static bool Terminate
        {
            get { return terminate; }
        }

        private static int clientNumber = 0;
        private static TcpListener tcpListener;

        static void Main(string[] args)
        {
            StartServer();
            Console.Read();
        }

        private static void StartServer()
        {
            try
            {
                Console.WriteLine("Server starting...");
                tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);
                terminate = false;
                tcpListener.Start();
                tcpListener.BeginAcceptTcpClient(ConnectionHandler, null);
                Console.ReadLine();

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally 
            {
                Console.WriteLine("Server stopping...");
                terminate = true;
                if (tcpListener != null)
                {
                    tcpListener.Stop();
                }
            }
        }

        private static void ConnectionHandler(IAsyncResult result)
        {
            TcpClient client = null;

            try
            {
                client = tcpListener.EndAcceptTcpClient(result);
            }
            catch (Exception)
            {
                return;
            }

            tcpListener.BeginAcceptTcpClient(ConnectionHandler, null);

            if (client!=null)
            {
                Interlocked.Increment(ref clientNumber);
                string clientName = clientNumber.ToString();
                new ClientHandler(client, clientName);
            }
        }
    }
}


   class ClientHandler
    {
        private TcpClient client;
        private string ID;

        internal ClientHandler(TcpClient client, string ID)
        {
            this.client = client;
            this.ID = ID;
            Thread thread = new Thread(ProcessConnection);
            thread.IsBackground = true;
            thread.Start();
        }

        private void ProcessConnection()
        {
            using (client)
            {
                 using (BinaryReader reader=new BinaryReader(client.GetStream()))
                 {
                     if (reader.ReadString()==Responses.RequestConnect)
                     {
                          using (BinaryWriter writer=new BinaryWriter(client.GetStream()))
                          {
                              writer.Write(Responses.AcknowledgeOK);
                              Console.WriteLine("Client: "+ID);
                              string message = string.Empty;
                              while (message!=Responses.Disconnect)
                              {
                                  try
                                  {
                                      message = reader.ReadString();
                                  }
                                  catch
                                  {
                                      continue;
                                  }
                                  if (message==Responses.RequestData)
                                  {
                                      writer.Write("Data Command Received");
                                  }
                                  else if (message==Responses.Disconnect)
                                  {
                                      Console.WriteLine("Client disconnected: "+ID);
                                  }
                                  else
                                  {
                                      Console.WriteLine("Unknown Command");
                                  }
                              }
                          }
                     }
                     else
                     {
                         Console.WriteLine("Unable to connect client: "+ID);
                     }
                 }
            }
        }
    }

   class Responses
    {
        public const string AcknowledgeOK = "OK";
        public const string AcknowledgeCancel = "Cancel";
        public const string Disconnect = "Bye";
        public const string RequestConnect = "Hello";
        public const string RequestData = "Data";
    }

this code listen for client requests in a multi threaded way. I am unable to understand how can i distinguish between different clients connected to my this server and which client is disconnecting and requesting for different commands.

my client code is:

private static void clietnRequest(string message,ref string response)
{
    using (TcpClient client = new TcpClient())
    {
        if (!client.Connected)
        {
            client.Connect(IPAddress.Parse("127.0.0.1"), 8000);
            using (NetworkStream networkStream = client.GetStream())
            {
                using (BinaryWriter writer = new BinaryWriter(networkStream))
                {
                    writer.Write(Responses.RequestConnect);
                    using (BinaryReader reader = new BinaryReader(networkStream))
                    {
                        if (reader.ReadString() == Responses.AcknowledgeOK)
                        {
                            response = Responses.AcknowledgeOK;

                        }
                    }
                }
            }
        }
    }
}

this piece of code connects the client to server, but i am unable to send anymore messages. I want in my app if client is connected then he can send commands to server. instead of doing this it every time act as a new client to server. I am missing some thing here, Kindly guide me in right direction. I am totally new to c# networking programming. Kindly help me improve my code. Tcp Listener and Tcp Client is valid for this scenario or do I need to use Sockets?

回答1:

You are closing the connection every time client side after you send a message, if you want to do that there is nothing wrong with it but you will need to send some form of identification to the server so it can tell that this is not a new connection but a old connection connecting in for a second time. This is EXACTLY what the HTTP protocol is doing and that "identification" are internet cookies.

That first option is fine if you transmit data very infrequently but if you are doing it more often you need to keep the connection open.

Basicly you need to take the act of connecting and disconnecting out of the client request function and pass the open connection in as a argument.

private void MainCode()
{
    using (TcpClient client = new TcpClient())
    {
        client.Connect(IPAddress.Parse("127.0.0.1"), 8000);

        while(variableThatRepresentsRunning)
        {
            //(Snip logic that gererates the message)

            clietnRequest(message, ref response, client);

            //(Snip more logic)
        }
    }
}

private static void clietnRequest(string message,ref string response, TcpClient client)
{
    if (client.Connected)
    {
        using (NetworkStream networkStream = client.GetStream())
        {
            using (BinaryWriter writer = new BinaryWriter(networkStream))
            {
                writer.Write(Responses.RequestConnect);
                using (BinaryReader reader = new BinaryReader(networkStream))
                {
                    if (reader.ReadString() == Responses.AcknowledgeOK)
                    {
                        response = Responses.AcknowledgeOK;

                    }
                }
            }
        }
    }
    else
    {
        //Show some error because the client was not connected
    }
}

By doing it this way the client object server side represents the client, you will have one instance of it per connected client and will remain associated with that client till he disconnects. If you want to track all of the connected clients you will need to insert them all in to some collection like a List<TcpClient> (either use a Concurrent Collection or use locking because you are multi-threaded) and then you will have a list of all clients (you will need to have the clients clean up after themselves so they remove themselves from the list after a disconnection).