2016-02-08 1 views
1

Я работаю с некоторым старым кодом TCP-сервера, который работает с сокетами напрямую, как это было написано в .NET 2.0 и ранее. Сервер имеет функцию «остановить» и «начать» прием клиентских подключений.Метод .NET TcpListener Stop не останавливает прослушиватель, когда есть дочерний процесс

Чтобы устранить проблему, я запускаю сервер в консольном режиме как пользователь admin. Кроме того, я устранил сокет принимает нить из уравнения и весь код делает это что-то вроде:

tcpListener = new TcpListener(IPAddress.Any, this.Port); 
tcpListener.Start(); 

и

tcpListener.Stop(); 

Это вызывается из различных методов. Я отлаживал код, и я уверен, что код выполняется только один раз. Однако проблема заключается в том, что вызов Stop фактически не освобождает адрес сокета, и последующий вызов Start, следовательно, с ошибкой «Обычно разрешено только одно использование каждого адреса сокета (протокола/сетевого адреса/порта)». Я также могу подтвердить из ProcessExplorer, что сервер все еще прослушивает порт сервера.

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

Непонятно мне, почему звонок Stop не освобождает адрес сокета?

Update: После еще расследования выясняется, что есть какой-то странный эффект запуска дочернего процесса в TcpListener. Я сделал «голая кость» образец код, который иллюстрирует этот вопрос:

using System; 
using System.Diagnostics; 
using System.Net; 
using System.Net.Sockets; 
using System.Reflection; 
using System.Threading; 

namespace TcpListenerStartStop 
{ 
    class MyTcpListener 
    { 
     public static void Main(string[] args) 
     { 
      Int32 port = 13000; 

      if (args.Length > 0) // indicates child process 
      { 
       Thread.Sleep(4000); // as a child do nothing and wait for a few seconds 
      } 
      else // parent will play with the TcpListener 
      { 
       //LaunchChildProcess(); // launch child here and listener restart is fine?!? 

       var tcpListener = new TcpListener(IPAddress.Any, port); 
       tcpListener.Start(); 

       Console.WriteLine("Starting test in 2 seconds..."); 
       Thread.Sleep(2000); 

       LaunchChildProcess(); // launch child here and listener restart is not fine?!? 

       tcpListener.Stop(); 
       Console.WriteLine("Stopped."); 
       Thread.Sleep(1000); 

       tcpListener.Start(); 
       Console.WriteLine("Started"); 
      } 

      Console.WriteLine("All is good, no exceptions :)"); 
     } 

     private static void LaunchChildProcess() 
     { 
      Process process = new Process(); 
      var processStartInfo = new ProcessStartInfo 
      { 
       CreateNoWindow = true, 
       FileName = Assembly.GetExecutingAssembly().Location, 
       UseShellExecute = false, // comment out this line out listener restart is fine?!? 
       Arguments = "child" 
      }; 
      process.StartInfo = processStartInfo; 

      process.Start(); 
     } 
    } 
} 

Как видно из кода, если я запускаю дочерний процесс перед созданием слушателя все нормально, если я сделаю это после перезагрузки слушателя выходит из строя. Это как-то связано с опцией UseShellExecute = false для дочернего процесса.

Не уверен, что это ошибка .NET или какое-то особое поведение, о котором я не знал?

+0

[Из документации] (https://msdn.microsoft.com/en-us/library/system. net.sockets.tcplistener.stop (v = vs.110) .aspx # Anchor_2): «* Примечания к вызывающим: метод Stop также закрывает базовый Socket и создает новый Socket для TcpListener. Если вы установите какие-либо свойства на лежащий в основе Socket до вызова метода Stop, эти свойства не переносятся на новый Socket.* ", из этого кажется, что вызов' Stop() 'действительно освобождает сокет, но он сразу начинает новый. EDIT: неважно, это несвязанный сокет. –

ответ

2

Настоящей причиной такого поведения является то, что дескриптор сокета TcpListener наследуется дочерним процессом вместе со многими другими ручками. Некоторые обсуждения по этой теме можно найти here и here.

Одним из очевидных решений является запуск дочернего процесса до инициализации TcpListener.

Другим решением является наличие UseShellExecute = true, чтобы избежать наследования этого дескриптора сокета.

Идеальное решение было бы установить параметр дескриптора сокета, чтобы предотвратить наследование дочерним процессом. Можно это сделать в .NET с помощью P/Invoke на гнездо ручки TcpListener:

[DllImport("kernel32.dll", SetLastError = true)] 
    static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags); 
    private const uint HANDLE_FLAG_INHERIT = 1; 

    private static void MakeNotInheritable(TcpListener tcpListener) 
    { 
     var handle = tcpListener.Server.Handle; 
     SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0); 
    } 
+1

Отличный вопрос и отличный ответ! –