Поведение TcpListener.AcceptSocket (): застревает в одном приложении после завершения, но не в другом? - PullRequest
1 голос
/ 04 октября 2011

У меня есть два приложения TCP-сервера, которые основаны на одном и том же коде, но по какой-то причине ведут себя по-разному, и я готов вырваться, пытаясь понять, почему. Шаблон кода выглядит следующим образом:

public class TcpServer
{
    public static void Start( bool bService )
    {
        ..
        oTcpListnr= new TcpListener( ip, iOutPort );
        aTcpClient= new ArrayList( );
        bListen=    true;
        oTcpListnr.Start( );
        thOutComm=  new Thread( new ThreadStart( AcceptTcpConn ) );
        thOutComm.Name= "App-i.AcceptTcpConn";
        thOutComm.Start( );
        ..
    }
    public static void      Stop( )
    {
        bListen=    false;

        if(  thOutComm != null  )
        {
            thOutComm.Join( iTimeout );
            thOutComm=  null;
        }
        if(  oTimer != null  )
        {
            oTimer.Change( Timeout.Infinite, Timeout.Infinite );
            oTimer.Dispose( );
        }
    }
    public static void      AcceptTcpConn( )
    {
        TcpState    oState;
        Socket      oSocket=    null;

        while(  bListen  )
        {
            try
            {
        //      if(  oTcpListnr.Pending( )  )
                {
                    oSocket=    oTcpListnr.AcceptSocket( );
                    oState=     new TcpState( oSocket );

                    if(  oSocket.Connected  )
                    {
                        Utils.PrnLine( "adding tcp: {0}", oSocket.RemoteEndPoint.ToString( ) );
                        Monitor.Enter( aTcpClient );
                        aTcpClient.Add( oState );
                        Monitor.Exit( aTcpClient );

                        oSocket.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.DontFragment, true );
                        oSocket.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.DontLinger, true );

            //  /       oSocket.BeginReceive( oState.bData, 0, oState.bData.Length, SocketFlags.None,   //  no need to read
            //  /                               new AsyncCallback( AsyncTcpComm ), oState );            //  for output only
                    }
                    else
                    {
                        Utils.PrnLine( "removing tcp: {0}", oSocket.RemoteEndPoint.ToString( ) );
                        Monitor.Enter( aTcpClient );
                        aTcpClient.Remove( oState );
                        Monitor.Exit( aTcpClient );
                    }
                }
        //      Thread.Sleep( iTcpWake );
            }
            #region catch
            catch( Exception x )
            {
                bool    b=  true;
                SocketException se= x as SocketException;
                if(  se != null  )
                {
                    if(  se.SocketErrorCode == SocketError.Interrupted  )
                    {
                        b=  false;
                        if(  oSocket != null  )
                            Utils.PrnLine( "TcpConn:\tclosing tcp: {0} ({1})", oSocket.RemoteEndPoint.ToString( ), se.SocketErrorCode );
                    }
                }
                if(  b  )
                {
                    Utils.HandleEx( x );
                }
            }
            #endregion
        }
    }
}

Для краткости я опустил обработку исключений в методах Start / Stop. Изменения в поведении происходят во время завершения программы: одно приложение закрывается почти сразу, а другое застревает в вызове oTcpListnr.AcceptSocket (). Я знаю, что это блокирующий вызов, но в таком случае почему он не представляет проблемы для 1-го приложения?

Использование этого класса не может быть проще, например. для инструмента командной строки:

class   Program
{
    public static void  Main( string[] args )
    {
        TcpServer.Start( false );
        Console.Read( );
        Console.WriteLine( "\r\nStopping.." );
        TcpServer.Stop( );
        Console.WriteLine( "\r\nStopped.  Press any key to exit.." );
        Console.Read( );
    }
}

Независимо от того, подключены клиенты или нет, не имеет значения, второе приложение всегда зависает.

Я нашел потенциальное решение (закомментированные строки), проверив TcpListener.Pending () перед вызовом .AcceptSocket (), но это сразу же влияет на загрузку ЦП, поэтому необходимо включить что-то вроде Thread.Sleep (.). В целом, хотя я бы предпочел избегать такого подхода, если это возможно, из-за дополнительного времени ожидания соединения и загрузки ЦП (хотя бы небольшого).

Тем не менее, главный вопрос: что может заставить один и тот же точный код выполняться по-разному? Оба приложения скомпилированы в .NET 4 Client Profile, x86 (32-разрядная версия), без особых оптимизаций. Заранее спасибо за хорошие идеи!

1 Ответ

1 голос
/ 07 октября 2011

Наконец нашел основную причину: я пропустил пару важных строк [скрытых в #region] в методе Stop (), который запускает бросок мяча.Вот как это должно выглядеть:

public static void      Stop( )
{
        bListen=    false;

        if(  thOutComm != null  )
        {
            try
            {
                oTcpListnr.Stop( );
            }
            catch( Exception x )
            {
                Utils.HandleEx( x );
            }
            thOutComm.Join( iTimeout );
            thOutComm=  null;
        }
        if(  oTimer != null  )
        {
            oTimer.Change( Timeout.Infinite, Timeout.Infinite );
            oTimer.Dispose( );
        }
    }

Вызов TcpListener.Stop () запускает цикл ожидания внутри .AcceptSocket () с операцией блокировки была прервана вызовом исключения WSACancelBlockingCall, котороезатем "обычно игнорируется" (проверьте SocketError.Interrupted) кодом, который у меня был изначально.

...