NetworkStream грязный во время TCP-связи клиент/сервер

У меня есть и клиент, и сервер, общающиеся через TCP. Клиент использует NetworkStream для отправки информации на сервер, который считывает ее, затем процесс продолжается до тех пор, пока пользователь не захочет выйти и закрыть соединение. Проблема в том, что NetworkStream загрязнен предыдущей записью. Итак, давайте предположим, что клиент отправляет строку «aa» в первый раз и «b» во второй раз. При втором чтении сервер получит «ba». Чего здесь не хватает? Разве NetworkStream не должен потребляться во время чтения сервера? Вот соответствующий код...

Сервер

        while (true)
        {
            try
            {
                NetworkStream clientStream = tcpClient.GetStream();
                bytesRead = clientStream.Read(messageBytes, 0, messageBytes.Length);
            }
            catch (Exception ex)
            {
                LogToConsole(clientEndPoint, String.Format("[ERROR] Exception: {0}", ex.Message));
                break;
            }

            if (bytesRead == 0)
            {
                LogToConsole(clientEndPoint, "Client has disconnected");
                break;
            }

            messageCounter++;
            string message = Encoding.ASCII.GetString(messageBytes);
            message = message.Substring(0, message.IndexOf('\0'));
            LogToConsole(clientEndPoint, String.Format(" >> [{0}] Message received: {1}", messageCounter, message));
        }

КЛИЕНТ

            string infoToSend = null;
            do
            {
                Console.Write(" >> Info to send: ");
                infoToSend = Console.ReadLine();

                if (!String.IsNullOrEmpty(infoToSend))
                {
                    NetworkStream serverStream = client.GetStream();
                    byte[] buffer = Encoding.ASCII.GetBytes(infoToSend);
                    serverStream.Write(buffer, 0, buffer.Length);
                    serverStream.Flush();
                }
            } while (!String.IsNullOrEmpty(infoToSend));

РЕШЕНИЕ

Как заметил Дуглас, буфер (messageBytes) был грязным из-за предыдущего чтения. Я получил этот код для сервера (я разместил весь код, так как он может быть полезен для кого-то еще):

namespace Gateway
{
    class Program
    {
        static void Main(string[] args)
        {
            int requestCount = 0;

            TcpListener serverSocket = new TcpListener(IPAddress.Any, 8888);
            serverSocket.Start();
            LogToConsole("Server Started. Waiting for clients ...");

            while ((true))
            {
                try
                {
                    TcpClient client = serverSocket.AcceptTcpClient();
                    requestCount++;
                    LogToConsole(String.Format("Connection from {0} accepted. Request #{1}", client.Client.RemoteEndPoint.ToString(), requestCount));

                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientConnection));
                    clientThread.IsBackground = true;
                    clientThread.Start(client);
                    LogToConsole(String.Format("Thread #{0} created to handle connection from {1}", clientThread.ManagedThreadId, client.Client.RemoteEndPoint.ToString()));
                    LogToConsole("Waiting for next client ...");
                }
                catch (Exception ex)
                {
                    LogToConsole(ex.ToString());
                    break;
                }
            }
        }

        static void HandleClientConnection(object client) 
        {
            TcpClient tcpClient = (TcpClient)client;

            byte[] messageBytes = new byte[1024];
            int bytesRead;
            int messageCounter = 0;

            string clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString();

            while (true)
            {
                try
                {
                    NetworkStream clientStream = tcpClient.GetStream();
                    bytesRead = clientStream.Read(messageBytes, 0, messageBytes.Length);
                }
                catch (Exception ex)
                {
                    LogToConsole(clientEndPoint, String.Format("[ERROR] Exception: {0}", ex.Message));
                    break;
                }

                if (bytesRead == 0)
                {
                    LogToConsole(clientEndPoint, "Client has disconnected");
                    break;
                }

                messageCounter++;
                string message = Encoding.ASCII.GetString(messageBytes, 0, bytesRead);
                LogToConsole(clientEndPoint, String.Format(" >> [{0}] Message received: {1}", messageCounter, message));
            }

            LogToConsole(clientEndPoint, "Closed connection to client");
            tcpClient.Close();
        }

        static void LogToConsole(string clientEndPoint, string message)
        {
            int threadId = Thread.CurrentThread.ManagedThreadId;
            string time = DateTime.Now.ToString("HH:mm:ss");
            Console.WriteLine("{0} [{1}, {2}] {3}", time, threadId, clientEndPoint, message);
        }

        static void LogToConsole(string message)
        {
            int threadId = Thread.CurrentThread.ManagedThreadId;
            string time = DateTime.Now.ToString("HH:mm:ss");
            Console.WriteLine("{0} [{1}] {2}", time, threadId, message);
        }
    }
}

person gsb    schedule 15.01.2012    source источник


Ответы (1)


string message = Encoding.ASCII.GetString(messageBytes);

Приведенный выше вызов каждый раз декодирует весь буфер, несмотря на то, что каждое сообщение будет записываться только в его первые n байты (где n — длина сообщения). Ваше первое сообщение «аа» записывается в первые два байта буфера. Ваше второе сообщение, «b», записывается только в первый байт, перезаписывая первый символ «a», но оставляя второй «a» нетронутым. Вот почему буфер содержит «ba» после вашего второго сообщения.

Вы можете тривиально решить это, изменив вызов выше на:

string message = Encoding.ASCII.GetString(messageBytes, 0, bytesRead);

Однако ваш код по-прежнему подвержен другой проблеме: NetworkStream.Read считывает только те данные, которые доступны в данный момент. Если клиент все еще передает, он может вернуть частичное сообщение. Таким образом, ваш сервер может прочитать два сообщения как «a» и «ab».

В вашем случае, поскольку вы, кажется, передаете только однострочные текстовые сообщения, вы можете обернуть NetworkStream в StreamReader на сервере и в StreamWriter на клиенте. Затем просто вызовите StreamReader.ReadLine на сервере и StreamWriter.WriteLine на клиенте. ReadLine будет продолжать чтение, пока не встретит новую строку, и вернет null, когда будет достигнут конец потока.

Сервер:

using (NetworkStream clientStream = tcpClient.GetStream())
using (StreamReader reader = new StreamReader(clientStream))
{
    while (true)
    {
        message = reader.ReadLine();

        if (message == null)
        {
            LogToConsole(clientEndPoint, "Client has disconnected");
            break;
        }

        messageCounter++;
        LogToConsole(clientEndPoint, String.Format(" >> [{0}] Message received: {1}", messageCounter, message));
    }
}

Клиент:

using (NetworkStream serverStream = client.GetStream())
using (StreamWriter writer = new StreamWriter(serverStream))
{
    do
    {
        Console.Write(" >> Info to send: ");
        infoToSend = Console.ReadLine();

        if (!String.IsNullOrEmpty(infoToSend))
            writer.WriteLine(infoToSend);

    } while (!String.IsNullOrEmpty(infoToSend));
}

Это решение не будет работать, если ваш клиент может отправлять новые строки внутри ваших сообщений.

person Douglas    schedule 15.01.2012
comment
Ага. Хорошо, что ты это заметил. Буфер был действительно грязным. Я опубликую то, что я реализовал в конце концов, в исходном вопросе. Спасибо! - person gsb; 15.01.2012