C# Многопоточность клиентов Дублирующее соединение / Потеря данных - PullRequest
0 голосов
/ 22 марта 2020

У меня странная проблема, когда я устанавливаю соединение в чате с TCPListeners и сокетами. По какой-то причине, когда клиент «подключается» к серверу, он покажет, что он подключился дважды. Также, что странно, когда все пользователи подключились, я отправлю всем им сообщение. Они ответят с подтверждением и сообщат, что их чат запущен.

Вещи, которые я заметил, как у меня настроен чат:

  1. Похоже, что в журнале пользователя "подключается" дважды за секунду "происходит подключение, когда оно достигает белого (true) l oop.
  2. Когда они отправляют подтверждение обратно на сервер, не все данные передаются. Если я сделать поток в спящем режиме на клиенте, он, кажется, начинает работать, но все еще не согласован.

Вот код:

Сервер:

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);
    }
}

Клиент:

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)
}

Этот код практически повторяется для всех пользователей, подключенных к серверу.

1 Ответ

1 голос
/ 22 марта 2020

TcpListener имеет асинхронные методы, такие как BeginAcceptTcpClient.

TcpClient.GetStream () (который является NetworkStream) также имеет асинхронные методы, такие как BeginRead.

Я предлагаю вам изменить серверный код, чтобы использовать их и хранить пользовательское состояние в классе и передавать этот класс туда и обратно между Begin* и End* методами.

Тогда вы можете поддерживать N пользователей, но не приходится повторять код для каждого пользователя. Вам также не нужно иметь 3 разных слушателя для 3 соединений. Имейте только одного слушателя и принимайте клиентов по этому. Остальное - двусторонняя связь через TcpClient.GetStream()

Вот минимальный пример сервера, который прослушивает порт 9988 (только для LoopBack, что означает локальный компьютер). Конечно, вы можете изменить это.

Здесь нет примера клиента. Только сервер. Просто скопируйте / вставьте код в ваш файл program.cs в консольном приложении.

Я надеюсь, что комментариев достаточно для объяснения кода.

Я также надеюсь, что это поможет.

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();
    }
}
...