Не удается остановить асинхронный TCP-сервер для Windows

У меня есть служба Windows сервера TCP, разработанная в.net 4.0 с асинхронными серверными сокетами.

Он работает, но примерно в 90% случаев я просто не могу его остановить: после нажатия кнопки останова в консоли Windows Service он зависает и останавливается примерно через минуту, но его процесс продолжается, и связь по протоколу TCP продолжается. Он зависает в _listener.Close(). Единственное, что я могу сделать, чтобы закрыть сообщение, - перезагрузить Windows. Может быть что-то с закрывающимися сокетами. Я попытался выяснить, но я просто не могу найти корень проблемы.

Клиентская сторона не находится под моим контролем, они составляют около 100 гаджетов, отправляющих данные через TCP.

Вот мой код (обновлен).

Большое спасибо, любые предложения высоко ценятся!

    public class DeviceTCPServer
{
    public readonly IPAddress IPAddress;
    public readonly int Port;
    public readonly int InputBufferSize;
    public readonly ConcurrentDictionary<Guid, StateObject> Connections;

    public event EventHandler OnStarted;
    public event EventHandler OnStopped;
    public event ServerEventHandler OnConnected;
    public event ServerEventHandler OnDisconnected;
    public event RecievedEventHandler OnRecieved;
    public event DroppedEventHandler OnDropped;
    public event ExceptionEventHandler OnException;
    public event ServerLogEventHandler ServerLog;

    private volatile bool _iAmListening;
    private Socket _listener;
    private Thread _listenerThread;
    private readonly ManualResetEvent _allDone = new ManualResetEvent(false);

    public bool Listening
    {
        get { return _iAmListening; }
    }

    public DeviceTCPServer(IPAddress ipAddress,
                         int port,
                         int inputBufferSize)
    {
        IPAddress = ipAddress;
        Port = port;
        InputBufferSize = inputBufferSize;

        Connections = new ConcurrentDictionary<Guid, StateObject>();
    }

    public void ThreadedStart()
    {
        _listenerThread = new Thread(Start)
        {
            CurrentUICulture = Thread.CurrentThread.CurrentUICulture,
            IsBackground = true
        };

        _listenerThread.Start();
    }

    private void Start()
    {
        try
        {
            var localEP = new IPEndPoint(IPAddress, Port);
            _listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            _listener.Bind(localEP);
            _listener.Listen(10000);

            if (OnStarted != null)
                OnStarted(this, new EventArgs());

            _iAmListening = true;

            var listenerWithCultureInfo = new Tuple<Socket, CultureInfo>(_listener,
                                                                         Thread.CurrentThread.CurrentUICulture);

            while (_iAmListening)
            {
                _allDone.Reset();
                _listener.BeginAccept(AcceptCallback, listenerWithCultureInfo);
                _allDone.WaitOne();
            }
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, "Start"));
        }
    }

    public void StopListening()
    {
        try
        {
            _iAmListening = false;
            _allDone.Set();
        }
        catch(Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, "StopListening"));
        }
    }

    public void Stop()
    {
        try
        {
            _listener.Close(0);
            CloseAllConnections();
            _listenerThread.Abort();
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, "Stop"));
        }
    }

    private void AcceptCallback(IAsyncResult ar)
    {
        var arTuple = (Tuple<Socket, CultureInfo>)ar.AsyncState;
        var state = new StateObject(arTuple.Item2, InputBufferSize);

        try
        {
            Connections.AddOrUpdate(state.Guid,
                                    state,
                                    (k, v) => v);

            Thread.CurrentThread.CurrentUICulture = state.CurrentUICulture;

            var listener = arTuple.Item1;
            var handler = listener.EndAccept(ar);

            _allDone.Set();
            if (!_iAmListening)
                return;

            state.WorkSocket = handler;
            handler.BeginReceive(state.Buffer, 0, state.InputBufferSize, 0,
                                 RecieveCallBack, state);

            if (OnConnected != null)
                OnConnected(this, new ServerEventArgs(state));
        }
        catch(ObjectDisposedException)
        {
            _allDone.Set();
            return;
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, state, "AcceptCallback"));
        }
    }

    public void RecieveCallBack(IAsyncResult ar)
    {
        var state = (StateObject)ar.AsyncState;

        try
        {
            Thread.CurrentThread.CurrentUICulture = state.CurrentUICulture;

            var handler = state.WorkSocket;
            var read = handler.EndReceive(ar);

            var pBinayDataPocketCodecStore = new BinayDataPocketCodecStore();

            if (read > 0)
            {
                state.LastDataReceive = DateTime.Now;

                var data = new byte[read];
                Array.Copy(state.Buffer, 0, data, 0, read);
                state.AddBytesToInputDataCollector(data);

                //check, if pocket is complete
                var allData = state.InputDataCollector.ToArray();
                var codecInitRes = pBinayDataPocketCodecStore.Check(allData);

                if (codecInitRes.Generic.Complete)
                {
                    if (!codecInitRes.Generic.Drop)
                    {
                        if (OnRecieved != null)
                            OnRecieved(this, new RecievedEventArgs(state, allData));
                    }
                    else
                    {
                        if (OnDropped != null)
                            OnDropped(this, new DroppedEventArgs(state, codecInitRes.Generic));

                        //get new data
                        state.ResetInputDataCollector();

                        handler.BeginReceive(state.Buffer, 0, state.InputBufferSize, 0,
                                             RecieveCallBack, state);
                    }
                }
                else
                {
                    //get more data
                    handler.BeginReceive(state.Buffer, 0, state.InputBufferSize, 0,
                                         RecieveCallBack, state);
                }
            }
            else
            {
                if ((handler.Connected == false) || (handler.Available == 0))
                {
                    Close(state);
                }
            }
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, state, "RecieveCallBack"));
        }
    }

    public void Send(StateObject state, byte[] data)
    {
        try
        {
            var handler = state.WorkSocket;

            handler.BeginSend(data, 0, data.Length, 0,
                              SendCallback, state);
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, state, "Send"));
        }
    }

    private void SendCallback(IAsyncResult ar)
    {
        var state = (StateObject)ar.AsyncState;

        try
        {
            Thread.CurrentThread.CurrentUICulture = state.CurrentUICulture;
            var handler = state.WorkSocket;

            handler.EndSend(ar);
            handler.BeginReceive(state.Buffer, 0, state.InputBufferSize, 0,
                                 RecieveCallBack, state);
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, state, "SendCallback"));
        }
    }

    public void Close(StateObject state)
    {
        try
        {
            if (state == null)
                return;

            var handler = state.WorkSocket;

            if (handler == null)
                return;

            if (!handler.Connected)
                return;

            if (handler.Available > 0)
            {
                var data = new byte[handler.Available];
                handler.Receive(data);
            }

            handler.Shutdown(SocketShutdown.Both);
            handler.Close(0);

            if (OnDisconnected != null)
                OnDisconnected(this, new ServerEventArgs(state));

            StateObject removed;
            var removeResult = Connections.TryRemove(state.Guid, out removed);
        }
        catch (Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, "Close"));
        }
    }

    private void CloseAllConnections()
    {
        try
        {
            var connections = Connections.Select(c => c.Value);

            foreach(var connection in connections)
            {
                Close(connection);
            }
        }
        catch(Exception exc)
        {
            Debug.Assert(false, exc.Message);
            if (OnException != null)
                OnException(this, new ExceptionEventArgs(exc, "CloseAllConnections"));
        }
    }

    public override string ToString()
    {
        return string.Format("{0}:{1}", IPAddress, Port);
    }
}

Ответы

Ответ 1

Итак, наконец, проблема решена, никаких проблем с моей службой Windows не было!

Это было вызвано обновлением Microsoft Windows:

Microsoft KB4338815, в результате чего closesocket вечно вешался на процессорах Intel Xeon: https://forum.filezilla-project.org/viewtopic.php?t=49308

Спасибо всем вашим усилиям, чтобы попытаться помочь мне!

Ответ 2

Поскольку это услуга, обычное правило "ждать не-фоновых потоков для выхода" не применяется, и ваша задача - убить рабочих (которые могут включать в себя ожидающие операции async). Сейчас; на данный момент вы убиваете слушателя, но это только предотвращает присоединение новых сокетов. В идеале вы должны отслеживать своих клиентов, чтобы вы могли также убить все клиентские сокеты. Когда вы это сделаете, все будет хорошо. Не забудьте синхронизировать доступ к любой коллекции клиентов или использовать поточный тип коллекции.

Ответ 3

Я думаю, вы должны использовать метод Socket.Shutdown перед закрытием сокета. Как указано в документации:

При использовании ориентированного на соединение Socket всегда вызывайте метод Shutdown перед закрытием Socket. Это гарантирует, что все данные будут отправлены и получены на подключенном сокете до его закрытия.

Более того, возможно, я ошибаюсь, но я не вижу никаких вызовов метода StopListening(). С тех пор поле _iAmListening не установлено в false, и обратный вызов (AcceptCallback (IAsyncResult ar)) все еще отвечает на входящие вызовы.