Как отключить сокет c
У меня возникли проблемы с повторным использованием сокета сервера в тестовом приложении, которое я сделал. По сути, у меня есть программа, которая реализует как на стороне клиента, так и на стороне сервера. Я запускаю два экземпляра этой программы в целях тестирования, один экземпляр начинает хост, а другой подключается. Это код прослушивания:
Это отлично работает в первый раз. Однако через некоторое время (когда моя маленькая игра заканчивается), я звоню Dispose() на server объект, реализованный так:
У меня также есть это в конструкторе объекта:
Я не получаю ошибку, когда нажимаю Listen Кнопка во второй раз. Клиентская сторона подключается просто отлично, однако моя серверная сторона не обнаруживает клиентское соединение второй раз, что в любом случае делает сервер бесполезным. Я предполагаю, что это соединяется со старым, давнишним сокетом, но я понятия не имею, почему это происходит, чтобы быть честным. Вот код подключения клиента:
Если я сделаю netstat -a в CMD я вижу, что порт, который я использую, все еще связан, и его состояние LISTENING даже после звонка Dispose() , Я прочитал, что это нормально, и есть тайм-аут для того порта, чтобы быть "свободным".
Есть ли способ, которым я могу принудительно отключить этот порт или установить очень короткий тайм-аут, пока он автоматически не освободится? Прямо сейчас, это только освободится, когда я выйду из программы. Может я что-то не так делаю на своем сервере? Если так, что это могло быть? Почему клиент подключается нормально, а серверная сторона не обнаруживает его во второй раз?
Я мог бы заставить сокет всегда слушать, а не утилизировать его и использовать отдельный сокет для обработки соединения с сервером, что, вероятно, исправит это, но я хочу, чтобы другие программы могли использовать порт между последовательными сеансами воспроизведения.
Я помню, как видел еще один вопрос, задававший этот вопрос, но там не было удовлетворительного ответа для моего случая.
2 ответа
Может быть несколько причин, по которым порт будет оставаться открытым, но я думаю, что вы сможете решить вашу проблему с помощью явного LingerOption на розетке:
Это в основном превращает отключение сокета в аварийное отключение вместо постепенного отключения. Если вы хотите, чтобы это было изящно, но просто не ждали так долго, используйте true в конструкторе и укажите небольшое, но ненулевое значение для времени ожидания.
Я только что заметил эту строку, которая, несомненно, является частью вашей проблемы:
Комментарий, который вы написали здесь, ну, неправильно. Ваш класс-обертка действительно не должен позволять connection быть написанным вообще. Но вы не можете просто заменить слушающий сокет клиентским сокетом - это два разных сокета!
Здесь произойдет следующее: (а) сокет прослушивания выходит из области видимости и поэтому никогда не будет явно закрыт / удален - это произойдет в случайное время, возможно, в неприятное время. И (б) сокет, который вы закрываете, является просто клиентским сокетом, он не закроет сокет прослушивания, и поэтому неудивительно, что у вас возникают проблемы с повторным подключением другого сокета прослушивания.
На самом деле, вам вообще никогда не нужно перепривязывать другой сокет прослушивания. Разъем для прослушивания остается живым все время. Фактическое соединение представлено только клиентским сокетом. Вы должны располагать сокетом прослушивания только после окончательного выключения сервера.
Есть сервер и клиентское приложение. При подключении клиента к серверу происходят следующие действия:
Проблема в том, что, по логике вещей, при вызове client->close() или client->disconnectFromHost() у клиентского приложения должен быть вызывал сигнал disconnected() , но вместо него вызывается сигнал error() , но по факту же никакой ошибки не произошло, я ведь просто отключил клиента от сервера.
Это я что то делаю не так? Или так и должно быть?
Судя по коду, который Вы привели, client является объектом сокета на серверной стороне. Так для кого всё-таки инициируется выполнение close() и disconnectFromHost() ? Ну а как тогда корректным способом отключить клиента от сервера со стороны сервера, чтобы у клиента не был вызван сигнал error() ?Метод QAbstractSocket::disconnectFromHost() должен вызываться только на стороне клиента, поскольку именно она вызывает перед этим QAbstractSocket::connectToHost() . Соответственно реакция на закрытие соединения приведёт к получению ожидаемого сигнала disconnected() .
Поскольку QAbstractSocket наследуется от класса QIODevice , то метод close() доступен также и для сокетов. Разница между close() и методом disconnectFromHost() состоит в том, что последний сначала отправит данные, что пока ещё не успели уйти в сеть, и лишь затем закроет соединение.
Закрытие соединения на серверной стороне - это в общем случае внештатная ситуация. Сервер предназначен, как пионер, всегда быть готовым к труду и, если к нему подключился клиент, быть готовым в максимально сжатые сроки выполнить предусмотренную операцию. Когда же именно сервер начинает решать, общаться ли с клиентом или нет, то логично предположить, что либо клиент лицом не вышел, либо случилось нечто из ряда вон выходящее. Соответственно, сокету на клиентской стороне необходимо понимать, что именно воля серверной стороны явилась определяющей при принятии решения о закрытии соединения.
Разумеется, что за оправданностью нарушения обычного поведения для серверной стороны в каждом конкретном случае отвечает автор кода, для которого это по некоторым причинам может быть вполне оправданно. Но суть в том, что фреймворк должен следовать правилам, приемлемым для стандартных ситуаций. Соответственно приравнивание случая разрыва соединения по инициативе серверной стороны к ошибке для клиента - это вполне себе нормально. Впрочем, насильно никто не заставляет считать флаг QAbstractSocket::RemoteHostClosedError в качестве неустранимой препоны. В собственном слоте-обработчике всегда есть возможность сделать исключение для тех "ошибок", которые по мнению автора кода в его частном случае таковыми не являются.
У меня возникли проблемы с повторным использованием сокета сервера в тестовом приложении, которое я сделал. В принципе, у меня есть программа, которая реализует как клиентскую, так и серверную сторону. Я запускаю два экземпляра этой программы для целей тестирования, один экземпляр запускает хост, а другой подключается. Это код прослушивания:
Это работает отлично в первый раз. Однако через некоторое время (когда заканчивается моя маленькая игра) я вызываю Dispose() в объект server , реализованный следующим образом:
У меня также есть это в конструкторе объекта:
Я не получаю ошибку при повторном нажатии кнопки Listen . Клиентская сторона подключается просто отлично, однако моя серверная сторона не обнаруживает клиентское соединение второй раз, что в любом случае делает сервер бесполезным. Я предполагаю, что он подключается к старой, затяжной розетке, но я понятия не имею, почему это происходит честно. Здесь код подключения клиента:
Если я делаю netstat -a в CMD, я вижу, что порт, который я использую, по-прежнему связан и его состояние LISTENING , даже после вызова Dispose() . Я читал, что это нормально, и что время ожидания для этого порта "несвязано".
Есть ли способ заставить этот порт отключить или установить очень короткий таймаут, пока он автоматически не будет отключен?. В настоящий момент он только отключается, когда я выхожу из программы. Может быть, я делаю что-то не так на моем сервере? Если да, то что это может быть? Почему клиент подключается нормально, но серверная сторона не обнаруживает его второй раз?
Я могу заставить сокет всегда слушать, а не удалять его, и использовать отдельный сокет для обработки соединения с сервером, который, вероятно, исправит его, но я хочу, чтобы другие программы могли использовать порт между последовательными сеансами воспроизведения.
Я помню, что еще один вопрос задавал этот вопрос, но не было удовлетворительного ответа на мой случай.
Может быть несколько причин, по которым порт останется открытым, но я думаю, что вы должны решить свою проблему, используя явный LingerOption в сокете:
Это в основном отключает выключение сокета в прерывистое завершение работы, а не изящное завершение работы. Если вы хотите, чтобы он был изящным, но просто не дождался дольше, используйте true в конструкторе и укажите небольшое, но отличное от нуля значение для таймаута.
Я только заметил эту строку, которая, несомненно, является частью вашей проблемы:
Комментарий, который вы здесь написали, это неправильно. Ваш класс-оболочка действительно не должен позволять писать connection вообще. Но вы не можете просто заменить прослушивающий сокет клиентским сокетом - это два разных сокета!
Что произойдет, так это то, что (а) гнездо для прослушивания выходит из сферы действия и поэтому никогда не становится явно закрытым/удаленным - это произойдет в случайное время, возможно, в неприятное время. И (б) сокет, который вы закрываете, является только клиентским сокетом, он не закрывает прослушивающий сокет, и поэтому неудивительно, что у вас возникли проблемы с повторным подключением другого прослушивающего сокета.
На самом деле вам действительно не нужно вообще переписывать другой слуховой сокет. Слуховой разъем остается живым все время. Фактическое соединение представлено только клиентским сокетом. Вам нужно будет только отключить прослушивающий сокет, когда вы окончательно завершите работу сервера.
[Введение в сетевое программирование] Использование Windows TCP Sockets в C ++
Примечание переводчика: эта статья была создана автором в 2006 году, а автора, найденного в документе Word, зовут Кэмерон Флинт. Для новичков, впервые знакомых с сетевым программированием, это очень хорошее вводное учебное пособие, эта статья очень ясна от теории к коду. Перед прочтением этой статьи практически не требуется никаких оснований, лучше всего, если вы немного знакомы с рабочим столом MFC или Win32. Подобные статьи в Китае трудно найти, поэтому я заменил комментарии в этой статье и код на китайский, а также создал проект VS. Информация об окне и подсказке в исходном коде просто обрабатывается. Скриншот выглядит следующим образом:
Введение
Прежде чем мы начнем, нам нужно включить winsock.h и связать libws2_32.a с проектом, прежде чем мы сможем использовать API, необходимые для TCP / IP. Если это невозможно, пожалуйста, используйте во время выполнения LoadLibrary() Загрузите ws2_32.dll или аналогичный метод
Весь код в этой статье написан и протестирован с использованием «Bloodshed Dev-C ++ 4.9.8.0», но в целом он должен работать с любым компилятором, требующим очень мало модификаций
Поток, порт, сокет
«Нить» - это идентификационное имя соединения между вашим компьютером и удаленным компьютером, а нить подключена только к сокету.
Чтобы подключить поток к каждому компьютеру, к нему должен быть подключен принимающий объект, который называется сокетом.
Сокет может открыть любой порт, порт имеет уникальный номер, используемый для различения других потоков, поскольку один и тот же компьютер может устанавливать несколько соединений одновременно
Если вы хотите использовать порт без назначенного сервиса, от 1000 до 6535 должно подойти
IP-адрес - это идентификатор, назначенный каждому компьютеру в сети, который можно просмотреть с помощью команды ipconfig в Windows.
Для выполнения этой задачи есть два API. Прежде чем устанавливать соединение, вам, скорее всего, потребуется преобразовать доменное имя в IP-адрес - для этого необходимо, чтобы компьютер был подключен к Интернету.
Порядок байтов
Поскольку компьютерные и сетевые протоколы Intel используют противоположный порядок байтов, мы должны преобразовать каждый порт и IP-адрес в порядок байтов сетевого протокола перед отправкой запроса, иначе это вызовет путаницу. Если вы не измените направление, порт 25 в конечном итоге не станет портом 25. Поэтому, когда мы общаемся с сервером, мы должны убедиться, что мы используем тот же язык, что и сервер
К счастью, Microsoft предоставляет некоторые API для изменения порядка байтов IP или порта
нота: «Хост» - это компьютер, который прослушивает и принимает соединение, «Сеть» - гость, подключенный к хосту.
Например, когда мы указываем порт, который мы хотим слушать или подключаться, мы должны использовать htons() Функция преобразует числа в сетевой порядок байтов. Конечно, если вы используете inet_addr() Функция преобразует IP-адрес строкового типа в IP-адрес, тогда полученный IP-адрес соответствует правильному порядку байтов в сети, поэтому нет необходимости использовать htonl() функция
Открыть Winsock
Первый шаг в использовании сокетов Windows - включить Winsock API. Существует две версии Winsock. Вторая версия - это последняя версия и версия, которую мы хотим указать.
Нам нужно только вызвать эти две функции, вызываемые при инициализации Winsock WSAStartup() Вызывается, когда задача выполнена WSACleanup() , Но не закрывайте Winsock до тех пор, пока задача не будет завершена, поскольку это приведет к отмене всех подключений к программе и прослушивающих портов.
Инициализировать сокет
Чтобы заполнить правильные параметры, переданные функции для открытия Socket, функция возвращает дескриптор сокета. Этот дескриптор очень удобен, мы можем использовать его для манипуляций с сокетами в любое время
Хорошая привычка - закрывать все открытые сокеты до выхода из программы. перечислить WSACleanup() Вызывает принудительное закрытие всех сокетов и соединений, но более элегантный метод - использовать closesocket() Чтобы закрыть указанный сокет, вам нужно только передать дескриптор сокета в этот API
При создании сокета вам нужно передать семейство адресов, тип сокета и тип протокола. Если вы не выполняете какую-либо специальную работу, обычно используйте AF_INET Передается в качестве семейства адресов по умолчанию, этот параметр определяет, как интерпретировать адрес
Фактически, существует не только один тип сокетов, три наиболее распространенных типа включают необработанные сокеты (Raw Sockets), потоковые сокеты (Stream Sockets) и дейтаграммы Socket (дейтаграммы Sockets). В этой статье используется потоковый сокет, потому что мы имеем дело с протоколом TCP, мы указываем SOCK_STREAM В виде socket() Второй параметр
Подключиться к удаленному хосту - как клиент
Далее попробуйте простую программу, которая может подключаться к удаленному компьютеру.
Нам нужно заполнить информацию об удаленном хосте, а затем передать указатель этой структуры на магическую функцию connect() Структура и API следующие. нота sin_zero Параметр не нужен, поэтому оставьте его пустым
Настоятельно рекомендуется вводить все примеры вручную
Получите соединение как сервер
После установления соединения с целевым хостом пришло время действовать как сервер. Чтобы все удаленные компьютеры могли подключаться к вам, нам нужно «прослушать» порт, чтобы дождаться запросов на подключение. Обычно используются следующие API:
Как сервер, пока вы слушаете этот порт, вы можете получать все запросы на этот порт. Например, если удаленный компьютер хочет пообщаться с вашим компьютером, он сначала спросит, может ли ваш сервер установить соединение. Чтобы установить соединение, ваш сервер должен accept() Запрос на подключение.
хотя listen() Эта функция - самый простой способ прослушивания порта и работы в качестве сервера, но она не самая идеальная. Мы обнаружим, что при выполнении программа будет зависать до тех пор, пока не будет установлено соединение. потому что listen() Является ли «блокирующей» функцией (одновременно может быть выполнена только одна задача, и она не вернется, пока не будет приостановлено соединение)
Это должно быть проблемой, но есть некоторые решения. Если вы знакомы с многопоточностью, мы можем поместить серверный код в отдельный поток, поток не замораживает всю программу при запуске, и родительская программа не будет мешать работе.
Но нет необходимости быть таким хлопотным, потому что вы можете заменить его «асинхронной» функцией Socket. listen() 。
Прежде чем вы планируете прослушивать порт, вы должны:
- Инициализация Winsock (обсуждалось ранее)
- Включите Socket, убедитесь, что вы возвращаете ненулевое значение (представляющее успех), это возвращаемое значение является дескриптором Socket
- в SOCKADDR_IN Запишите необходимые данные в структуру, включая семейство адресов, порт и IP-адрес
- использование bind() Функция привязывает сокет к IP (если sin_addr Параметр SOCKADDR_IN Используйте inet_addr ("0.0.0.0") или htonl(INADD_ANY) Можно привязать любой IP-адрес)
В это время, если все идет по плану, вы можете позвонить listen() Следите за тем, что вы хотите
listen() Первый параметр должен быть инициализированным дескриптором сокета. Конечно, порт, к которому подключен этот сокет, является портом, который мы намереваемся слушать. Используйте функции next и final, чтобы указать максимальное количество удаленных компьютеров, взаимодействующих с сервером. В общем, если вы не хотите использовать только несколько конкретных соединений, нам нужно только SOMAXCONN (SOcket MAX CONNection) передается в качестве параметра listen() , Если Socket запущен и работает нормально, все в порядке. При получении запроса на подключение listen() Вернусь. Если вы согласны установить соединение, позвоните accept()
Асинхронный сокет
использование listen() Такая функция блокировки настолько глупа, давайте посмотрим, как работает «асинхронный сокет»
Разве это не особенно сложно? Теперь все готово, мы должны быть в ListenOnPort() В функции listen() После добавления строки кода:
Если ваш сервер работает должным образом, вы должны увидеть «0.0.0.0: Порт №» в «Локальный адрес». Порт № - это номер порта, который вы прослушиваете, и в настоящее время он прослушивает. PS: если вы забыли использовать htons() Преобразовав номер порта, вы можете обнаружить, что новый порт открыт, но он полностью отличается от того, что вы ожидали.
Не волнуйтесь, каждый должен попробовать несколько раз, чтобы добиться успеха. Вы можете попробовать больше раз. (Конечно, если вы не пытались в течение нескольких недель, запишите этот урок и забудьте, кто его написал!)
На этом этапе все ваши серверы глухонемые, что, безусловно, не тот результат, которого вы хотите. Итак, посмотрите, как эффективно общаться с другими компьютерами. Как всегда, эти вызовы API решают проблему:
Когда мы отслеживаем активность, мы должны создать буфер для ее хранения, чтобы указатель на буфер был передан recv() , После того, как программа вернется, текст должен быть помещен в наш буфер, ожидая отображения. Исходный код выглядит следующим образом:
Теперь вы можете получать текст с удаленного компьютера или сервера, но сервер не ответил или возможность «отправлять» данные на удаленный компьютер. Это, вероятно, самый простой шаг в программировании Winsock, но если вы так же глупы, как я, каждый шаг должен быть обучен и использован правильно send() Способ заключается в следующем.
Для краткости приведенный выше фрагмент кода - это просто скелет, который может дать вам общее понимание send() как пользоваться. Чтобы просмотреть полный код, загрузите образец исходного кода, прилагаемый к этому руководству.
Расширенные инструкции: иногда просто send() с receive() Функция не может удовлетворить ваши потребности. Разные компьютеры имеют несколько подключений одновременно (мы называем listen() Когда мы прошли SOMAXCONN Этот параметр ограничивает максимальное количество подключений), и вам необходимо отправлять данные на указанный компьютер, а не на все компьютеры. Если вы умный призрак, вы можете найти send() с receive() Два других API: sendto() с receivefrom() (Если вы найдете его, наградите маленький красный цветок
Эти два API-интерфейса позволяют вам общаться с любым удаленным компьютером независимо от других подключений. Эти расширенные функции имеют дополнительный параметр для получения sockaddr_in Указатель типа структуры, вы можете использовать этот параметр, чтобы указать IP-адрес удаленного компьютера для связи. Если вы хотите создать полноценную программу чата или что-то подобное, это важный навык. Однако, помимо того, что я помогу вам понять основные понятия этих функций, я позволю вам решить эти проблемы самостоятельно. (Не думаете ли вы, что это раздражает, если вы говорите это из уст автора? Обычно у нас нет никаких следов… но если вы действительно решите это сделать, это не будет стоить вам слишком много времени.)
постскриптум
К настоящему времени вы должны хорошо понимать Windows Socket (или ненависть), но в любом случае, если вы хотите найти лучшее объяснение, посмотрите на исходный код, приведенный в этой статье. Практика позволяет узнать больше, чем теория
Кроме того, если вы скопируете код или скомпилируете чужой код, который вы найдете в Интернете, вы не сможете достичь того уровня понимания, который вы получите, вручную введя все примеры. Я знаю, что это больно, но в конечном итоге время, которое вы тратите, избавит вас от многих проблем
Я надеюсь, что вы что-то получили. Оставьте комментарий, чтобы сообщить мне, что вы думаете об этой статье
соглашение
Эта статья и сопровождающий ее исходный код и файлы следуют Атрибуция-поделился таким же образом 2.5 общийлицензия
Читайте также: