Как я могу отсоединить сокет в C #? - PullRequest
3 голосов
/ 12 мая 2010

У меня проблемы с повторным использованием сокета сервера в тестовом приложении, которое я создал. По сути, у меня есть программа, которая реализует как на стороне клиента, так и на стороне сервера. Я запускаю два экземпляра этой программы для целей тестирования: один экземпляр начинает хост, а другой подключается. Это код прослушивания:

private void Listen_Click(object sender, EventArgs e)
{
    try
    {
        server = new ConnectionWrapper();
        HideControls();
        alreadyReset = false;

        int port = int.Parse(PortHostEdit.Text);
        IPEndPoint iep = new IPEndPoint(IPAddress.Any, port);

        server.connection.Bind(iep); // bellow explanations refer to this line in particular
        server.connection.Listen(1);
        server.connection.BeginAccept(new AsyncCallback(OnClientConnected), null);
        GameStatus.Text = "Waiting for connections on port " + port.ToString();
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}
private void OnClientConnected(IAsyncResult iar)
{
    try
    {
        me = Player.XPlayer;
        myTurn = true;
        server.connection = server.connection.EndAccept(iar); // I will only have one client, so I don't care for the original listening socket.
        GameStatus.Text = server.connection.RemoteEndPoint.ToString() + " connected";
        StartServerReceive();
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}

Это отлично работает в первый раз. Однако через некоторое время (когда моя маленькая игра заканчивается), я вызываю Dispose() для объекта server, реализованного так:

public void Dispose()
{
    connection.Close(); // connection is the actual socket
    commandBuff.Clear(); // this is just a StringBuilder
}

У меня также есть это в конструкторе объектов:

public ConnectionWrapper()
{
    commandBuff = new StringBuilder();
    connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}

Я не получаю ошибку, когда нажимаю кнопку Listen во второй раз. Клиентская сторона подключается просто отлично, однако моя серверная сторона не обнаруживает клиентское соединение второй раз, что в любом случае делает сервер бесполезным. Я предполагаю, что он подключается к старому, давнишнему разъему, но я понятия не имею, почему это происходит, если честно. Вот код подключения клиента:

private void Connect_Click(object sender, EventArgs e)
{
    try
    {
        client = new ConnectionWrapper();
        HideControls();
        alreadyReset = false;

        IPAddress ip = IPAddress.Parse(IPEdit.Text);
        int port = int.Parse(PortConnEdit.Text);
        IPEndPoint ipe = new IPEndPoint(ip, port);
        client.connection.BeginConnect(ipe, new AsyncCallback(OnConnectedToServer), null); 
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}

Если я сделаю netstat -a в CMD, я увижу, что используемый порт все еще связан, и его состояние равно LISTENING, даже после вызова Dispose(). Я прочитал, что это нормально, и есть тайм-аут для того порта, чтобы быть "свободным".

Есть ли способ, которым я могу принудительно отключить этот порт или установить очень короткий таймаут, пока он автоматически не освободится? Прямо сейчас, он освобождается только при выходе из программы. Может я что-то не так делаю на своем сервере? Если так, что это могло быть? Почему клиент подключается нормально, но серверная сторона не обнаруживает его во второй раз?

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

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

Ответы [ 2 ]

7 голосов
/ 12 мая 2010

Может быть несколько причин, по которым порт будет оставаться открытым, но я думаю, что вы сможете решить вашу проблему, используя явный LingerOption в сокете:

LingerOption lo = new LingerOption(false, 0);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lo);

Это в основном превращает отключение сокета в аварийное отключение вместо постепенного отключения. Если вы хотите, чтобы он был изящным, но просто не ждал так долго, используйте true в конструкторе и укажите небольшое, но ненулевое значение для тайм-аута.

Я только что заметил эту строку, которая, несомненно, является частью вашей проблемы:

server.connection = server.connection.EndAccept(iar); // I will only have one client, so I don't care for the original listening socket.

Комментарий, который вы написали здесь, ну, неправильно. Ваш класс-обертка действительно не должен позволять писать connection вообще. Но вы не можете просто заменить прослушивающий сокет клиентским сокетом - это два разных сокета!

Что здесь произойдет, так это то, что (a) сокет прослушивания выходит из области видимости и, следовательно, никогда не будет явно закрыт / удален - это произойдет в случайное время, возможно, в неприятное время. И (б) сокет, который вы закрываете, является просто клиентским сокетом, он не закроет сокет прослушивания, и поэтому неудивительно, что у вас возникают проблемы с повторным подключением другого сокета прослушивания.

То, что вы на самом деле видите, это не тайм-аут сокета, это время, которое требуется сборщику мусора, чтобы понять, что сокет прослушивания мертв, и освободить / завершить его. Чтобы это исправить, вам нужно перестать перезаписывать сокет прослушивания; Dispose метод вашего класса-оболочки должен располагать оригинальным слушающим сокетом, а клиентский сокет должен отслеживаться отдельно и утилизироваться всякий раз, когда вы на самом деле с ним покончили.

На самом деле вам вообще никогда не нужно перепривязывать другой сокет прослушивания. Разъем для прослушивания остается живым все время. Фактическое соединение представлено только клиентским сокетом. Вы должны располагать сокетом прослушивания только после окончательного выключения сервера.

3 голосов
/ 12 мая 2010

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

socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(true);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...