Асинхронный сокет, принудительное отключение и повторное использование - PullRequest
0 голосов
/ 02 сентября 2010

Я пишу приложение, которое будет выступать в качестве прослушивателя TCP для одного клиента.Клиент является Java-апплетом и будет периодически подключаться к слушателю, отправлять некоторые данные и затем ждать ответа.

Код для класса TcpServer, приведенный ниже, был в значительной степени взят из примера, предоставленного более осведомленным потоком стековучастник в ответ на другой вопрос.

Все шло отлично, пока я не нашел в тестировании то, что не упоминалось ни в одном из документов интерфейса, которые у меня есть.После того, как сервер ответил клиенту, он должен затем отключить клиент и снова начать прослушивание.

Моей первой мыслью было вызвать Disconnect () из SendData (), но это привело к вызову ReceiveCompleted () изгде-то и неприятное исключение в отношении сокета, уже находящегося в распоряжении.

Легко ли выполнить это требование с имеющимся у меня дизайном кода, и возникнут ли у меня проблемы с повторным использованием сокета для последующих подключений?

sealed class TcpServer : IDisposable
    {
        #region Fields

        private const int SocketBufferSize = 1024;

        private readonly TcpListener tcpListener;

        private Socket connectedSocket;
        private bool disposed = false;

        #endregion Fields

        #region Constructors

        public TcpServer(int port)
        {
            tcpListener = new TcpListener(IPAddress.Any, port);
            tcpListener.Start();
            tcpListener.BeginAcceptSocket(EndAcceptSocket, tcpListener);
        }

        ~TcpServer()
        {
            Dispose(false);
        }

        #endregion Constructors

        #region Events

        public event EventHandler<DataReceivedEventArgs> DataReceived;

        public event EventHandler<IPEndPointEventArgs> SocketConnected;

        public event EventHandler<IPEndPointEventArgs> SocketDisconnected;

        #endregion Events

        #region Methods

        public void Dispose()
        {
            Dispose(true);
        }

        public void SendData(byte[] data)
        {
            if (connectedSocket == null)
            {
                return;
            }
            connectedSocket.Send(data);
        }

        private void BeginReceiveAsync(Socket sock, SocketAsyncEventArgs e)
        {
            if (!sock.ReceiveAsync(e))
            {
                ReceiveCompleted(sock, e);
            }
        }

        private void Connected(Socket socket)
        {
            var endPoint = (IPEndPoint)socket.RemoteEndPoint;

            connectedSocket = socket;

            OnSocketConnected(endPoint);
        }

        private void Disconnect(Socket socket)
        {
            var endPoint = (IPEndPoint)socket.RemoteEndPoint;

            socket.Close();

            connectedSocket = null;

            OnSocketDisconnected(endPoint);

            tcpListener.BeginAcceptSocket(EndAcceptSocket, tcpListener);
        }

        private void Dispose(bool disposing)
        {
            if (this.disposed == false)
            {
                if (disposing)
                {
                    try
                    {
                        if (tcpListener != null)
                        {
                            this.disposed = true;
                            tcpListener.Stop();
                        }
                    }
                    catch (Exception ex)
                    {
                        TraceLog.Error("TcpServer: tcpListener.Stop(): {0}", ex.Message);
                    }

                    try
                    {
                        if (connectedSocket != null)
                        {
                            connectedSocket.Close();
                            connectedSocket = null;
                        }
                    }
                    catch (SocketException ex)
                    {
                        TraceLog.Error("TcpServer: connectedSocket.Close(): {0}", ex);
                    }
                }
                this.disposed = true;
            }
        }

        private void EndAcceptSocket(IAsyncResult asyncResult)
        {
            var listener = (TcpListener)asyncResult.AsyncState;

            if (disposed)
            {
                return;
            }

            try
            {
                Socket sock = listener.EndAcceptSocket(asyncResult);
                Connected(sock);

                var e = new SocketAsyncEventArgs();
                e.Completed += ReceiveCompleted;
                e.SetBuffer(new byte[SocketBufferSize], 0, SocketBufferSize);
                BeginReceiveAsync(sock, e);
            }
            catch (SocketException ex)
            {
                TraceLog.Error("TcpServer.EndAcceptSocket: {0}", ex.Message);
            }
            catch (Exception ex)
            {
                TraceLog.Error("TcpServer.EndAcceptSocket: {0}", ex.Message);
            }
        }

        private void OnDataReceived(byte[] data, IPEndPoint ipEndPoint)
        {
            if (DataReceived != null)
            {
                DataReceived(this, new DataReceivedEventArgs(data, ipEndPoint));
            }
        }

        private void OnSocketConnected(IPEndPoint ipEndPoint)
        {
            if (SocketConnected != null)
            {
                SocketConnected(this, new IPEndPointEventArgs(ipEndPoint));
            }
        }

        private void OnSocketDisconnected(IPEndPoint ipEndPoint)
        {
            if (SocketDisconnected != null)
            {
                SocketDisconnected(this, new IPEndPointEventArgs(ipEndPoint));
            }
        }

        private void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            var sock = (Socket)sender;

            if (!sock.Connected)
            {
                Disconnect(sock);
            }

            try
            {
                int size = e.BytesTransferred;
                if (size == 0)
                {
                    Disconnect(sock);
                }
                else
                {
                    var buf = new byte[size];
                    Array.Copy(e.Buffer, buf, size);
                    ReceiveData(buf, (IPEndPoint)sock.RemoteEndPoint);
                    BeginReceiveAsync(sock, e);
                }
            }
            catch (SocketException ex)
            {
                TraceLog.Error("TcpServer: ReceiveCompleted: {0}", ex.Message);
            }
            catch (Exception ex)
            {
                TraceLog.Error("TcpServer: ReceiveCompleted: {0}", ex.Message);
            }
        }

        private void ReceiveData(byte[] data, IPEndPoint endPoint)
        {
            OnDataReceived(data, endPoint);
        }

        #endregion Methods
    }

1 Ответ

1 голос
/ 02 сентября 2010

Всякий раз, когда я пишу код, охватывающий System.Net.Sockets.Socket, я постоянно добавляю предложения try / catch для SocketException и ObjectDisposedException. В большинстве случаев ObjectDisposedException можно просто игнорировать, поскольку в 99% случаев это означает, что клиент просто отключился.

По крайней мере, я так понимаю, как работает Socket API в .NET. Попробуйте добавить некоторые обработчики исключений здесь и там, и посмотрите, как это происходит. В любом случае, ваш метод Disconnect не должен делать больше, чем что-то вроде этого:

    public void Disconnect()
    {
        try
        {
            connectedSocket.Shutdown(SocketShutdown.Both);
        }
        catch (Exception)
        {
            // Ignore the exception. The client probably already disconnected.
        }

        connectedSocket.Dispose(); // This is safe; a double dispose will simply be ignored.
    }

Я надеюсь, что это проливает свет на проблему ...

...