SetupG
SetupG

Reputation: 175

C# Multithreading Clients Duplicate Connection/Data Loss

I am having a weird problem where I am making a chat connection with TCPListeners and sockets. For some reason when a client "connects" to the server it will show they have connected twice. Also what is weird once all of the users have connected I will send out a message to all of them. They will respond back with it acknowledged and state that their chat has started.

Things I am noticing with how i have it setup:

  1. It appears according to the log that the user "connects" twice the second "connect occurs once it hits the white(true) loop.
  2. When they send over the acknowledgement back to the server not all of the data is getting sent over. If I do a thread sleep on the client it does appear to start working but it is still inconsistent.

Here is the code:

Server:

private TcpListener tcpListener;
private Thread listen;
private TcpListener tcpUser1, tcpUser2,tcpUser3;
NetworkStream User1Stream,User2Stream,User3Stream;
public event NetworkMessageStringDelegate MessageFromUser;
TcpClient client;

public void start(string ip){

    IpHostEntry host = dns.GetHostEntry(dns.GetHostName());
    IpAddress[] ip = host.AddressList;
    serverStatus = "Server started with IP of: " + ip;
    Thread.Sleep(1);

    tcpUser1 = new TcpListener(IpAddress.Any, 4001);
    listen = new Thread(new ThreadStart(() => ListenUser1(tcpUser1)));
    listen.Start();
    Thread.Sleep(1);

    tcpUser2 = new TcpListener(IpAddress.Any, 4002);
    listen = new Thread(new ThreadStart(() => ListenUser2(tcpUser2)));
    listen.Start();
    Thread.Sleep(1);

    tcpUser3 = new TcpListener(IpAddress.Any, 4003);
    listen = new Thread(new ThreadStart(() => ListenUser3(tcpUser3)));
    listen.Start();
    Thread.Sleep(1);

}

public void ListenUser3(TcpListener tmp){

    tcpListener = (TcpListener)tmp;
    Socket = "Listening for User3";
    tcpUser3.Start();

    Thread.Sleep(2);

    while(true){
        user3 = this.tcpListener.AcceptTcpClient();
        Thread user3Thread = new Thread(new ParmeterizedThreadStart(receiveUser3Data));
        user3Thread.Start(user3);
    }
}

//Mostly from MS documenation
private void receiveUser3Data(object client){
    client = (TcpClient)client;
    User3Stream = client.getStream();
    Socket = "Connected to User: " + client.Client.RemoteEndPoint.toString();
    byte[] message = new byte[4096];
    int total;

    //This is the line it will display the socket message Twice. "Connected to User...."
    while(true){
        total = 0;
        try{
            do{
                total = User3Stream.Read(message,0,4096);
            }
            while(user3.DataAvailable);
        }
        catch()
        {
            Socket = "Error State";
        }
    }
    byte[] infoPacket = new byte[total];
    Array.ConstrainedCopy(message,0,infoPacket,total);
    if(MessageFromUser3 != null){
        MessageFromUser?.Invoke(packet);
    }
}

Client:

public void ConfigureUser3(){

    try{
        socket = new Network.TCPIPClient();
        socket.ReceiveMessage() = new Newowrk.TCPIPClient.NetworkMessageStringDelgate(MessageFromserver);
        socket.SendMessage() = new Newowrk.TCPIPClient.NetworkMessageStringDelgate(sendMessage);
        userThread = new Thread(() => socket.Start("0.0.0.0),4054));
        userThread.Start();
    }
    catch(Exception ex){

    }
}

//This is where if I sleep it will send but it is still inconsident
private void SendMEssageToSever(object tmpVal){
    object[] sendMessage = tmpVal as object[];
    string tmpSendValue  = tmpVal[0].ToString();
    byte sendValue = Coonvert.ToByte(tmpVal[1]);
    packetData[0] = 0;
    packetData[1] = sendValue;
    packetData[2] = Convert.ToByte(tmpSendValue);

    socket.sendMessage = packetData;
}

private voide sendMessage(byte[] userMessage){
    try{
        if(socket){
            outputMessage.Enqueue(userMessage);
            while(outputMessage.Count > 0){
                Byte[] sendMessage = outputMessage.Dequeue();
                string message = ascII.GetString(sendMessage);
                if(socket.Connected){
                    lock(socket){
                        socket.Send(sendMessage,sendMessage.length,0);
                    }
                }
            }
        }
    }
    catch(Exception ex)
}

This code is essentially repeated for all users that are connected to the server.

Upvotes: 0

Views: 622

Answers (1)

Oguz Ozgul
Oguz Ozgul

Reputation: 7187

The TcpListener has asynchronous methods like BeginAcceptTcpClient.

TcpClient.GetStream() (which is a NetworkStream) also has asynchronous methods like BeginRead.

I suggest you change your server code to use these and to store the user state in a class and pass this class to and fro between Begin* and End* methods.

You can support N number of users then, and don't have to repeat code for each user. You also don't have to have 3 different listeners for 3 connections. Have just one listener and accept clients over this one. The rest is two-way communication via TcpClient.GetStream()

Here is a minimal server example which listens on port 9988 (for only LoopBack, which means the local machine). You can of course change this.

There is no client example here. Only the server. Just copy/paste the code into your program.cs file in a console application.

I hope the comments are sufficient to explain the code.

I hope also, that this helps.

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

class Program
{
    /// <summary>
    /// Contains the state for a client connection
    /// </summary>
    public class ClientState
    {
        public const int ReceiveBufferSize = 8192;
        // The buffer to receive in
        internal byte[] receiveBuffer = new byte[ReceiveBufferSize];

        // The TcpClient instance representing the remote end (connected client)
        public TcpClient TcpClient { get; set; }

        public byte[] GetReceiveBuffer()
        {
            return receiveBuffer;
        }
    }

    // This method is invoked when data is received from a client
    static void OnReceive(IAsyncResult asyncResult)
    {
        // The state parameter passed to the BeginRead method
        // is provided here in the asyncResult.AsyncState property
        ClientState clientState = asyncResult.AsyncState as ClientState;

        int numberOfBytesReceived = clientState.TcpClient.GetStream().EndRead(asyncResult);

        if (numberOfBytesReceived == 0)
        {
            // This means that the transmission is over
            Console.WriteLine("Client disconnect: {0}", clientState.TcpClient.Client.RemoteEndPoint);
            return;
        }

        // Now the receiveBuffer is filled with <numberOfBytesReceived> bytes received from the client.
        // Do whatever is needed here.
        Console.WriteLine("Received {0} bytes from {1}", numberOfBytesReceived, clientState.TcpClient.Client.RemoteEndPoint);
        // We are also sending some information back:
        StreamWriter streamWriter = new StreamWriter(clientState.TcpClient.GetStream());
        streamWriter.WriteLine("The server has received {0} bytes from you! Keep up the good job!", numberOfBytesReceived);
        streamWriter.Flush();

        // Begin read again
        clientState.TcpClient.GetStream().BeginRead(clientState.GetReceiveBuffer(), 0, ClientState.ReceiveBufferSize, OnReceive, clientState);
    }

    // This method is invoked when a new client connects
    static void OnConnect(IAsyncResult asyncResult)
    {
        // The state parameter passed to the BeginAcceptTcpClient method
        // is provided here in the asyncResult.AsyncState property
        TcpListener tcpListener = asyncResult.AsyncState as TcpListener;

        // Accept the TcpClient:
        TcpClient newClient = tcpListener.EndAcceptTcpClient(asyncResult);

        // Immediately begin accept a new tcp client.
        // We do not want to cause any latency for new connection requests
        tcpListener.BeginAcceptTcpClient(OnConnect, tcpListener);

        // Create the client state to store information aboutn the client connection
        ClientState clientState = new ClientState()
        {
            TcpClient = newClient
        };

        Console.WriteLine("A new client has connected. IP Address: {0}", newClient.Client.RemoteEndPoint);

        // Start receiving data from the client
        // Please note that we are passing the buffer (byte[]) of the client state
        // We are also passing the clientState instance as the state parameter
        // this state parameter is retrieved using asyncResult.AsyncState in the asynchronous callback (OnReceive)
        newClient.GetStream().BeginRead(clientState.GetReceiveBuffer(), 0, ClientState.ReceiveBufferSize, OnReceive, clientState);

        // Nothing else to do.
        // The rest of the communication process will be handled by OnReceive()
    }

    static void Main()
    {
        // Start a tcp listener
        TcpListener tcpListener = new TcpListener(IPAddress.Loopback, 9988);
        tcpListener.Start();

        // Begin accept a new tcp client, pass the listener as the state
        // The state is retrieved using asyncResult.AsyncState in the asynchronous callback (OnConnect)
        tcpListener.BeginAcceptTcpClient(OnConnect, tcpListener);

        // That's it. We don't need anything else here, except wait and see.
        Console.WriteLine("Server is listening on port 9988. Press enter to stop.");
        Console.ReadLine();
    }
}

Upvotes: 1

Related Questions