nie_trzeba Ответов: 1

Как я могу привязать клиентский сокет?


- Привет!
Мне нужно сделать TCP-клиент, который будет отправлять сообщение на сервер с определенного порта (например, 50000). Я пытался вызвать метод Bind перед отправкой, и он работает, но только в первый раз. Сообщение отправляется с порта, который я выбрал раньше, но когда я пытаюсь вызвать его второй или более раз, появляется исключение: "обычно разрешают использовать только один адрес". Когда я перезапускаю свою форму, она повторяется еще раз: сначала - Ок, затем - исключение. Я читал, что такое исключение происходит, потому что порт, с которого я отправляю сообщение, не успевает освободиться сам, а ему требуется больше времени. Вот код моего клиента:

private void button2_Click_1(object sender, EventArgs e)
{
    string local_host = System.Net.Dns.GetHostName();
    string local_ip_address = Dns.GetHostByName(local_host).AddressList[0].ToString();
    IPEndPoint send_point = new IPEndPoint(IPAddress.Parse(local_ip_address), 50000);

    Socket send_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    IPEndPoint connected_point = new IPEndPoint(IPAddress.Parse(textBoxIPListen.Text), 
                                                Convert.ToInt32(textBoxPortListen.Text));
    try
    {
        send_socket.Bind(send_point);
        send_socket.Connect(connected_point);
        byte[] data = Encoding.Unicode.GetBytes("TestTest");
        send_socket.Send(data);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    finally
    {
        send_socket.Close();
        ((IDisposable)send_socket).Dispose();
    }
}


Что я уже пробовал:

У меня была рекомендация закрыть сокет с помощью Закрывать() метод, использование в конце концов Располагать(), Выключение(), Разъединять() и send_socket.SetSocketOption (SocketOptionLevel.Сокет, SocketOptionName.ReuseAddress, правда) но, к сожалению, ничего не работает. Можно ли когда-нибудь привязывать клиентский сокет каждый раз перед отправкой ? Если да, то что мне делать, не могли бы вы мне помочь?

1 Ответов

Рейтинг:
6

Jochen Arndt

Простое решение:
Не привязывайте клиентский сокет к определенному порту. Обычно в этом нет необходимости.

Проблема, которую вы видите, возникает из-за того, что сокет не закрывается внутренне при вызове Close метод и соединение не было закрыто изящно, как описано в потоке SO c - при привязке клиентского TCP-сокета к определенному локальному Порту с помощью Winsock SO_REUSEADDR не имеет никакого эффекта-переполнение стека[^]:

Цитата:
Ошибка, о которой Вы упомянули, обычно возникает, когда последнее соединение с тем же хостом:порт не имел изящного завершения работы (FIN/ACK FIN/ACK). В этих случаях сокет остается в состоянии TIME_WAIT в течение определенного периода времени (зависит от операционной системы, но регулируется).
Нить содержит также возможные решения. Таким образом, вы можете попытаться установить Разъем.LingerState Имущества (Системы.Нет.Розетки)[^].


nie_trzeba

Я бы с удовольствием не привязывал клиента к определенному порту, но мой босс хотел, чтобы это сообщение всегда отправлялось с одного конкретного порта :/
Я прочитал и попытался настроить опцию сокетов с помощью Разъем.LingerState() как вы мне советуете, и поместите его в любом месте моего кода, но, к сожалению, это не помогает. Я Разъем.LingerState.LingerTime = 0 но в любом случае это мне не помогло. Серьезно, я не знаю, что еще с этим делать.

Jochen Arndt

Жаль, что это не сработало. Он должен быть установлен сразу после создания сокета (используя true и timeout zero).

Другим решением может быть использование молотка:
Прекратите использование winsocks с помощью вызова API WSACleanup () с помощью PInvoke. Это могло бы освободить сокет, но было бы уродливым решением.

Вы также можете спросить своего босса, почему это должен быть фиксированный порт. Я думаю, что он не может дать вам разумного ответа. Но он-босс.

nie_trzeba

Действительно странно, потому что так много вещей, которые помогают другим, не помогли в моем случае. Может, просто руки растут не с той стороны ? :)

Я никогда не слышал об этом, поэтому погуглил об этом и попытался сделать точно так же, как по этой ссылке https://www.experts-exchange.com/questions/21014265/PInvoke-winsock-WSAStartup-and-WSACleanup-in-c.html но, к сожалению, это тоже не помогло.

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

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

Jochen Arndt

Я ожидал такой "причины".

Это не то, как работает обычный TCP. Сервер должен использовать фиксированный номер порта, на котором он прослушивает. Это должно быть исправлено, потому что клиент должен это знать. Но нет никаких причин использовать фиксированный номер порта на клиенте.

Брандмауэры используют "проверку состояния". Это означает, что они отслеживают номера портов (и другую информацию) исходящих исходных пакетов. Если есть пакеты, поступающие в порт, который отслеживается (принадлежит к связи, которая была инициирована ранее), им разрешается пройти.

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

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

nie_trzeba

Большое вам спасибо за ваш ответ! Я постараюсь описать и доказать это своему боссу. Спасибо вам за вашу помощь !

nie_trzeba

Извините еще раз, я хочу задать еще один вопрос, если мы не свяжем наш клиентский сокет, то как сервер узнает, куда отправить ответное сообщение ?

Jochen Arndt

Метод connect () выполняет неявную привязку () к любому адресу/порту, если функция bind () не вызывалась ранее. То есть ОС выберет интерфейс в соответствии с IP-адресом назначения с помощью таблицы маршрутизации и выберет высокий номер порта.

Сервер сохраняет IP-номер клиента при приеме входящего соединения. Номера портов являются частью заголовков TCP. Например, см. https://en.wikipedia.org/wiki/Transmission_Control_Protocol.

Но вы не должны беспокоиться об этом. Все это обрабатывается операционной системой и API сокета.

nie_trzeba

Большое спасибо! И еще раз, пожалуйста (обещаю!), я постараюсь связывать() метод снова по протоколу UDP, там есть Отправить() метод, который отправляет сообщение без Подключить(), поэтому я попытался Связывать() это к указанному порту только один раз, в начале, а затем просто вызовите Отправить() метод, и я не знаю, но похоже, что он работает! Я имею в виду, что после каждой отправки я Райт на консоли LocalEndPoint, и он показывает мне тот, который я указываю при привязке. Как вы думаете, моя программа сейчас работает нормально и действительно ли она отправляет сообщения с одного порта ? Вот код:

        
static void UDPMessage()
        {
            
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("10.11.2.147"), Convert.ToInt32("17000"));
            Socket s = new Socket(endPoint.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);

            IPEndPoint myPoint = new IPEndPoint(IPAddress.Parse("10.11.2.147"), Convert.ToInt32("61000"));

            try
            {
                byte[] msg = Encoding.Unicode.GetBytes("hello");

                s.Bind(myPoint);
                s.SendTo(msg, SocketFlags.None, endPoint);
                Console.WriteLine(s.LocalEndPoint.ToString());
                s.SendTo(msg, SocketFlags.None, endPoint);
                Console.WriteLine(s.LocalEndPoint.ToString());
                s.SendTo(msg, SocketFlags.None, endPoint);
                Console.WriteLine(s.LocalEndPoint.ToString());
                s.SendTo(msg, SocketFlags.None, endPoint);
                Console.WriteLine(s.LocalEndPoint.ToString());
            }
            catch
            {

            }
            finally
            {
                Console.ReadLine();
            }
        }

Jochen Arndt

UDP-это не TCP. Это совершенно другое. Он не использует соединения или какие-либо квитирования и восстановления ошибок. Это включает в себя то, что сокет действительно закрыт при вызове метода Close ().

С UDP использование фиксированного исходного порта имеет смысл. В противном случае номер порта должен быть отправлен в качестве полезной нагрузки на сервер, чтобы он знал, на какой порт должен быть отправлен ответ. Реализация такого подхода-это определение вашего собственного вида протокола.

Но прежде чем использовать UDP вы должны подумать о недостатках:
- Пакеты могут потеряться (с TCP они обижаются)
- Пакеты могут поступать не по порядку (обрабатываются TCP)
- Требуется определение протокола для 2-сторонней связи

nie_trzeba

О, хорошо, я понял! Большое спасибо за все Ваши советы! Я постараюсь подробнее изучить этот протокол. Спасибо!