Как очистить буфер TCP-клиента?

Я использовал несколько примеров для настройки полнодуплексного TCP-клиента C#, подключенного к серверу. Основная концепция заключается в том, что и клиент, и сервер отправляют/получают сообщения (команды и события). Поэтому я разработал класс FullDuplexSocket, который предоставляет метод Send для отправки сообщений на сервер и обработчик событий для получения сообщений с сервера. Все работает, за исключением того, что я не могу очистить буфер сообщений, полученных с сервера. Каждый раз, когда сервер отправляет новое сообщение, буфер в моем сокете содержит все старые сообщения (уже прочитанные) и новые сообщения. Я мог бы разделить сообщения по известному разделителю (/r/n) и отслеживать их, но это, вероятно, будет источником проблем с памятью при длительном обмене данными. [Редактировать: код обновлен до решения, которое больше не имеет проблем с буфером и работает правильно].

У кого-нибудь есть предложения. Всего переписать??? Код ниже в надежде, что он поможет другим.

Вот класс FullDuplexSocket:

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

namespace Utilities
{
    public class StateObject{
        public Socket workSocket = null;
        public const int BUFFER_SIZE = 1024;
        public byte[] buffer = new byte[BUFFER_SIZE];
    }

    public class FullDuplexSocket : IDisposable
    {
        public event NewMessageHandler OnMessageReceived;
        public delegate void NewMessageHandler(string Message);
        public event DisconnectHandler OnDisconnect;
        public delegate void DisconnectHandler(string Reason);

        private Socket _socket;
        private bool _useASCII = true;
        private string _remoteServerIp = "";
        private int _port = 0;

        /// <summary>
        /// Constructer of a full duplex client socket.   The consumer should immedately define 
        /// and event handler for the OnMessageReceived event after construction has completed.
        /// </summary>
        /// <param name="RemoteServerIp">The remote Ip address of the server.</param>
        /// <param name="Port">The port that will used to transfer/receive messages to/from the remote IP.</param>
        /// <param name="UseASCII">The character type to encode/decode messages.  Defaulted to use ASCII, but setting the value to false will encode/decode messages in UNICODE.</param>
        public FullDuplexSocket(string RemoteServerIp, int Port, bool UseASCII = true)
        {
            _useASCII = UseASCII;
            _remoteServerIp = RemoteServerIp;
            _port = Port;

            try //to create the socket and connect
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _socket.Connect(RemoteServerIp, _port);
            }
            catch (Exception e)
            {
                throw new Exception("Unable to connect to the remote Ip.", e);
            }

            try //to listen to the socket
            {
                StateObject stateObject = new StateObject();
                stateObject.workSocket = _socket;

                _socket.BeginReceive
                    (
                        stateObject.buffer, //Buffer to load in our state object
                        0, //Start at the first position in the byte array
                        StateObject.BUFFER_SIZE, //only load up to the max per read
                        0, //Set socket flags here if necessary 
                        new AsyncCallback(ReadFromSocket), //Who to call when data arrives
                        stateObject //state object to use when data arrives
                    );
            }
            catch (Exception e)
            {
                throw new Exception("Unable to start listening to the socket.", e);
            }
        }


        /// <summary>
        /// This will read the bytes from the socket, convert the bytes to a string and fire the OnMessageReceived event.
        /// If the socket is forcibly closed, the OnDisconnect event will be fired.   This happens when the other side of
        /// the socket connection on the remote Ip is no longer available.
        /// </summary>
        /// <param name="asyncResult"></param>
        public void ReadFromSocket(IAsyncResult asyncResult)
        {
            StateObject stateObject = (StateObject)asyncResult.AsyncState; //pull out the state object
            int bytesReceived = 0;

            try //to receive the message.
            {
                bytesReceived = stateObject.workSocket.EndReceive(asyncResult); 
            }
            catch (Exception e)  //Exception will occur if connection was forcibly closed.
            {
                RaiseOnDisconnect(e.Message);
                return;
            }

            if (bytesReceived > 0)
            {
                RaiseOnMessageReceived
                    (
                        _useASCII ?
                            Encoding.ASCII.GetString(stateObject.buffer, 0, bytesReceived) :
                            Encoding.Unicode.GetString(stateObject.buffer, 0, bytesReceived)
                    );

                stateObject.workSocket.BeginReceive
                    (
                        stateObject.buffer, //Buffer to load in our state object
                        0, //Start at the first position in the byte array
                        StateObject.BUFFER_SIZE, //only load up to the max per read
                        0, //Set socket flags here if necessary 
                        new AsyncCallback(ReadFromSocket), //Who to call when data arrives
                        stateObject //state object to use when data arrives
                    );

            }
            else
            {
                stateObject.workSocket.Close();
                RaiseOnDisconnect("Socket closed normally.");
                return;
            }
        }
        /// <summary>
        /// Broadcast a message to the IP/Port.  Consumer should handle any exceptions thrown by the socket.
        /// </summary>
        /// <param name="Message">The message to be sent will be encoded using the character set defined during construction.</param>
        public void Send(string Message)
        {
            //all messages are terminated with /r/n
            Message += Environment.NewLine;

            byte[] bytesToSend = _useASCII ?
                Encoding.ASCII.GetBytes(Message) :
                Encoding.Unicode.GetBytes(Message);

            int bytesSent = _socket.Send(bytesToSend);

        }

        /// <summary>
        /// Clean up the socket.
        /// </summary>
        void IDisposable.Dispose()
        {
            try
            {
                _socket.Close();
                RaiseOnDisconnect("Socket closed via Dispose method.");
            }
            catch { }
            try
            {
                _socket.Dispose();
            }
            catch { }
        }


        /// <summary>
        /// This method will gracefully raise any delegated events if they exist.
        /// </summary>
        /// <param name="Message"></param>
        private void RaiseOnMessageReceived(string Message)
        {
            try //to raise delegates
            {
                OnMessageReceived(Message);
            }
            catch { } //when none exist ignore the Object Reference Error
        }

        /// <summary>
        /// This method will gracefully raise any delegated events if they exist.
        /// </summary>
        /// <param name="Message"></param>
        private void RaiseOnDisconnect(string Message)
        {
            try //to raise delegates
            {
                OnDisconnect(Message);
            }
            catch { } //when none exist ignore the Object Reference Error
        }

    }
}

Потребитель класса просто сделал бы следующее:

using System;

namespace Utilities
{
    public class SocketConsumer
    {
        private FullDuplexSocket _fds;

        public Consumer()
        {
            _fds = new FullDuplexSocket("192.168.1.103", 4555);

            _fds.OnMessageReceived += fds_OnMessageReceived;

            _fds.Send("Hello World!");
        }

        void fds_OnMessageReceived(string Message)
        {
            Console.WriteLine("Message: {0}", Message);
        }
    }
}

Любая помощь будет здорово. Спасибо!


person Greg Grater    schedule 18.10.2013    source источник


Ответы (1)


Вы вызываете OnMessageReceived, даже если буфер не заполнен (в случае bytesRead < count). Рассмотрите возможность переключения на await для асинхронной части приложения. Это избавляет от отвратительной рекурсии обратного вызова.

person usr    schedule 18.10.2013
comment
Спасибо за ответ. Проходя код, кажется, что счетчик установлен на 1029990765 и всегда больше, чем bytesRead. Непонятно, как здесь использовать await, может быть, поможет пример? Может у меня логика обратная? - person Greg Grater; 19.10.2013
comment
Не уверен, что там происходит, но почему count равен ~ 1 миллиарду? Это ошибка. Добавьте утверждение времени выполнения, чтобы поймать самый ранний случай, когда возникает это условие.; await fill не устранит проблему сразу - хотя это значительно упростит код (вообще никаких обратных вызовов), поэтому ошибки станут очевидными. Я также не решаюсь тратить больше времени на эту мешанину спагетти из обратных вызовов. Это не ваша вина - это просто природа кода, управляемого обратным вызовом. - person usr; 19.10.2013
comment
После нескольких итераций я отредактировал код в вопросе до работоспособного решения. Буфер очищается после получения каждого сообщения от удаленного сервера и все в порядке. Я не смог определить метод использования Async/await для решения этой проблемы, поэтому уродливый обратный вызов все еще существует, но, похоже, правильно используется с учетом реализации класса Socket в .NET. Любые другие комментарии/альтернативные решения приветствуются. ...Спасибо нам... - person Greg Grater; 23.10.2013