Что такое код socket_set_timeout


Socket.timeout при повторном recv()

Python 3.5 Windows

Есть некая железка на микроконтроллере , работающая по TCP/IP , на ней используется LWIP.
Принцип ее работы прост — получает команду (256 байт ) — отдает ответ (256 байт)
В этом режиме все хорошо, и никаких проблем и вопросов.
Но есть часть команд, по которым железка отдает текущие данные , то есть сначала выстреливает заголовок (256 байт) , потом миллисекундная пауза и она начинает отдавать текущие данные по мере поступления, предопределенной ранее в команде длины, весь процесс может занимать до нескольких минут. Пока железка в режиме отдачи текущих данных — на другие команды не реагирует. В Дельфях и C# никаких проблем , получаю событие WSocket1DataAvailable в нем делаю Len := WSocket1.Receive(. ) имею текущий буфер и его длину, могу по мере поступления данных их отображать.

В Питоне же могу получить только первый recv() — получаю всегда заголовок, вне зависимости от того что в параметрах recv() 256 байт, все последующие recv или recv_into вылетают с socket.timeout.

26.07.2020, 15:00

Падает скрипт при повторном использовании функций
Все очень просто, у меня есть 3 функции которые я вызываю подряд, а потом снова. Мне нужно просто.

Windows socket server python + socket client js
Здравствуйте пытаюсь решить такую задачу, на веб странице реализовал soket клиент на js, и socket.

Assembler linux. Socket recv and socket send invalid arguments
Пишу шеллкод. Он ожидает подключения с 2222 порта, сам подключается на 1111 порт, выделяет память.

SOCKET: send(), recv()
Начал разбираться с сокетами и в самом начале застрял на элементарном, с функциями send и recv, в.

recv (socket api)
при первом в хождении в цикл рекв возвращает как положено количество принятых байт,но потом.

stream_socket_enable_crypto (); Отключает stream_set_timeout

Я создал сервер xmpp, сначала используя не TLS, который работает, как я хочу, без ошибок. Аутентификация дыры работает, и я могу общаться без проблем.

Прежде чем начать чтение из сокета, я сделал:

Это позволит мне делать вещи в то время, когда нет новых данных с сервера.

Теперь я реализовал starttls.
я сделал

который получает TLS для меня. Это не сработало, потому что истекло время ожидания чтения и произошел сбой.

Я изменил выше stream_socket_enable_crypto в

Чтобы установить тайм-аут немного выше, чтобы у клиента было 30 секунд, чтобы принять сертификат (если он сам подписан) и выполнить все действия tls.
После того, как это закончено, мне действительно нужно установить тайм-аут обратно на 1000 микросекунд.
TLS-Negotation работал после этого изменения, но я потерял настройки тайм-аута.

Это моя проблема. stream_set_timeout позвонить после stream_socket_enable_crypto не работает.

У меня есть запись в журнале

Сообщение ПОСЛЕ ПРОЦЕССА СООБЩЕНИЕ ОЧЕРЕДЬ печатается но ПОСЛЕ РАЗРЯДА не распечатывается и не имеет значения, как долго я жду.

У кого-нибудь есть клей, как я могу установить тайм-аут чтения потока обратно в 1000 микросекунд?

Для вашей информации:
Решение, где я должен установить для stream_set_blocking значение false, в настоящее время НЕ в розыске.
Пока я хочу только блокирующее решение.
Если ДЕЙСТВИТЕЛЬНО не существует другого решения относительно использования неблокирования, то, конечно, я хочу знать ��

Решение

У меня такая же проблема. stream_select помог мне решить проблему:

Socket with recv-timeout: что не так с этим кодом?

Я пытаюсь реализовать сокет с тайм-аутом recv 1 Second:

Без ‘setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, & tv, sizeof (tv);’ вызовы для приема и возврата, но recv блокирует.

С помощью ‘setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, & tv, sizeof (tv); «вызов accept вызывает ошибку» Ресурс временно недоступен «.

Может кто-нибудь, пожалуйста, скажите мне, что не так с этим подходом?

В каком гнезде вы хотите включить один-второй тайм-аут? Одно принимающее соединение, или тот, который установлен accept()?

Я бы предположил, что последний — попробуйте установить тайм-аут приема на clientfd ПОСЛЕ возврата accept. Вы также можете добраться до того места, где вам нужно использовать select, но вам не нужно.

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

Здесь фрагмент с использованием select :

Вам понадобится еще одна закрывающая скобка на каждой из этих двух строк.

Как настроить тайм-аут подключения сокета

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

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

10 ответов:

Я нашел это. Проще, чем принятый ответ, и работает с .NET v2

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

Я не программирую на C#, но в C мы решаем ту же проблему, делая сокет неблокирующим, а затем помещая fd в цикл select/poll со значением тайм-аута, равным количеству времени, которое мы готовы ждать успешного подключения.

нашел этой для Visual C++ и объяснение там также склоняется к механизму выбора / опроса, который я объяснил ранее.

по моему опыту, вы не можете изменить значения времени ожидания подключения в розетку. Изменения это для всех (путем настройки параметров ОС).

Я решил проблему с помощью сокетов.Метод ConnectAsync вместо сокета.метод Connect. После вызова сокета.ConnectAsync (SocketAsyncEventArgs), запустите таймер (timer_connection), если время истекло, проверьте, подключено ли соединение сокета(if (m_clientSocket.Подключен)), если нет, всплывает ошибка тайм-аута.

проверьте это на MSDN. Не похоже, что вы можете сделать это с помощью реализованных свойств в классе сокета.

Плакат на MSDN на самом деле решил свою проблему С помощью резьбы. У него был основной поток, который вызывал другие потоки, которые запускали код соединения в течение нескольких секунд, а затем проверяли свойство Connected сокета:

Я создал другой метод, который на самом деле подключил розетку . было главное нить сна в течение 2 секунд, а затем проверьте способ подключения (который выполняется в отдельном потоке), если розетка была подключена хорошо в противном случае исключение «тайм-аут» и вот и все. Еще раз спасибо за отвечает.

Что вы пытаетесь сделать, и почему он не может ждать 15-30 секунд, прежде чем тайм-аут?

У меня была такая же проблема при подключении к сокету ,и я придумал следующее решение, оно отлично работает для меня. —

Я работал с Unity и имел некоторые проблемы с BeginConnect и другими асинхронными методами из сокета.

есть что-то, чем я не понимаю, но примеры кода, пока не работает для меня.

поэтому я написал этот кусок кода, чтобы заставить его работать. Я тестирую его в сети adhoc с android и ПК, а также в локальном на моем компьютере. Надеюсь, это поможет.

и там очень простой сторожевой пес на C#, чтобы заставить его работать:

Это похоже на ответ FlappySock, но я добавил к нему обратный вызов, потому что мне не понравился макет и как возвращалось логическое значение. В комментариях к этому ответу от Ника Миллера:

Определение blocking-режима TCP-сокета под Windows

Те, кто работает с TCP-сокетами, знают что сокет может работать в блокирующем или неблокирующем (nonblocking) режиме. Windows-сокеты, после создания, находятся в блокирующем режиме, но их можно перевести в неблокирующий функцией ioctlsocket().

При работе над одним проектом у меня появилась задача определить, в каком режиме работает сокет, предоставленный мне DLL-функцией. Т.е. кроме самого сокета, у меня никакой информации не было и приходили они в непредсказуемом режиме.

Под *nix blocking-режим без проблем определяется вызовом функции fcntl(), но под WinSock2 ничего подобного не обнаружилось, и на форумах ничего кроме «Windows does not offer any way to query whether a socket is currently set to blocking or non-blocking» никто не ответил.

Но способ определения все-таки существует:

Функция принимает номер сокета и возвращает 1 если сокет в nonblocking mode, 0 — blocking, -1 если произошла ошибка определения и -2 если сокет после определения режима остался с маленьким таймаутом.

Вкратце последовательность действий такова:

1. Сохраняем значение таймаута сокета (по умолчанию там 0 — «ждать вечно»).
2. Устанавливаем таймаут в 1 милисекунду.
3. Читаем из сокета 0 (ноль) байт Out of Band данных. Тут нужно пояснение: Если передаются OOB-данные, то функция может врать, но я никогда не сталкивался с OOB с тех пор, как Windows NT4 валилась в синий экран при попытке принять такое (WinNuke).
4. Получаем ошибку, которая произошла в результате чтения.
5. Восстанавливаем старое значение таймаута сокета.
6. Смотрим что за ошибка чтения у нам была: если WSAEWOULDBLOCK — то сокет находится в nonblocking mode, что и требовалось определить.

К недостаткам данного метода, кроме уже упомянутого MSG_OOB, можно отнести задержку определения в 1 миллисекунду и ненулевую вероятность испортить таймаут чтения сокета (хотя нагрузочный тест ни разу такого поведения не выявил).

Что такое код socket_set_timeout

Для реализации местоположения пользователя на сайте под управлением HostCMS мы обычно используем класс Core_Geoip , который для определения города обращается к сервису ipgeobase.ru. В начале января этот сервис был недоступен или перегружен, и у клиента наблюдалась проблема с зависанием сайта, которое было вызвано как раз обращением к этому сервису. В таком поведении была некоторая странность, ведь внутри класса на выполнение запроса устанавливается таймаут запроса, который в случае долгого ответа/неответа должен прерывать запрос, но похоже, что он не сработал, почему же?

В классе Core_Geoip есть такой код, устанавливающий таймаут запроса в 5 секунд:

В качестве драйвера для HTTP-запросов на сайте используется реализация на сокетах через класс Core_Http_Socket , идем внутрь него, внимательно смотрим на код и видим, что внутри устанаваливается таймаут создания сокета:

. и таймаут потока:

Далее идем в документацию функций stream_set_timeout и fgets и читаем, что для определения таймаута потока необходимо анализировать возвращаемый результат либо stream_get_meta_data , либо fgets , чего не в коде драйвера Core_Http_Socket не выполняется:

Получаем, что класс Core_Http_Socket принимет параметр timeout и даже пытается его использовать, но делает это некорректно, поэтому выполнение HTTP-запросов через данный класс является небезопасным.

После сообщения в тех. поддержку был получен ответ, что решение этой проблемы появится в обновлении 6.7.8, мы рекомендуем не использовать драйвер сокетов для выполнения HTTP-запросов до установки соответствующего обновления, а использовать вместо него реализацию поверх библиотеки curl, которая также есть в системе.

Как правильно использовать python socket.settimeout()

Насколько я знаю, когда вы вызываете socket.settimeout(value) , и вы устанавливаете значение с плавающей точкой больше 0,0, этот сокет будет поднимать scocket.timeout при вызове, например, socket.recv должен ждать дольше указанного значения.

Но представьте себе, что мне нужно получить большой объем данных, и мне нужно позвонить recv() несколько раз, то как это влияет на это?

Учитывая следующий код:

Третья строка кода устанавливает тайм-аут сокета до 20 секунд. Сбрасывает ли этот тайм-аут каждую итерацию? Будет ли время ожидания поднято, только если одна из этих итераций займет более 20 секунд?

A) Как я могу перекодировать его так, чтобы он вызывал исключение, если он принимает более 20 секунд для получения всех ожидаемых данных?

B) Если я не установил таймаут в None после того, как мы прочитаем все данные, может ли что-нибудь плохое произойти? (связь сохраняется, и в будущем может быть запрошено больше данных).

Создан 19 дек. 15 2015-12-19 13:03:12 Jorky10

2 ответа

Тайм-аут применяется к одному вызову операции чтения/записи сокетов. Таким образом, следующий вызов будет снова 20 секунд.

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

B) Любой код, который использует сокет с тайм-аут и не готов обрабатывать socket.timeout исключение, скорее всего, не в состоянии. Это более надежно запомнить значение тайм-аута сокета, прежде чем начать работу, и восстановить его, когда вы сделали:

Таким образом, ваша функция не будет влиять на функционирование кода, зовущий его, независимо от того, что они делают с тайм-аутом сокета.

Создан 19 дек. 15 2015-12-19 13:23:06 Lav

Тайм-аут применяется к каждому вызову recv().

A) просто используйте свой существующий тайм-аут и вызовите recv (to_receive) — I.e. Попытайтесь получить все данные в одном вызове recv — на самом деле я не понимаю, почему вы не должны использовать это по умолчанию, как это работает

B) Ничего плохого не может произойти, но любой другой код, который использует этот socket должен знать о тайм-ауте обработки.

На ваш существующий код не следует, чтобы вызов recv() был recv (max (4096, to_receive-received)) — таким образом вы не будете непреднамеренно потреблять любые данные, которые следует за байтами to_receive.

Создан 19 дек. 15 2015-12-19 13:15:45 barny

Программирование сокетов в Linux

Автор: Александр Шаргин

Опубликовано: 16.05.2001
Исправлено: 04.02.2006
Версия текста: 1.1

Введение

Socket API был впервые реализован в операционной системе Berkley UNIX. Сейчас этот программный интерфейс доступен практически в любой модификации Unix, в том числе в Linux. Хотя все реализации чем-то отличаются друг от друга, основной набор функций в них совпадает. Изначально сокеты использовались в программах на C/C++, но в настоящее время средства для работы с ними предоставляют многие языки (Perl, Java и др.).

Сокеты предоставляют весьма мощный и гибкий механизм межпроцессного взаимодействия (IPC). Они могут использоваться для организации взаимодействия программ на одном компьютере, по локальной сети или через Internet, что позволяет вам создавать распределённые приложения различной сложности. Кроме того, с их помощью можно организовать взаимодействие с программами, работающими под управлением других операционных систем. Например, под Windows существует интерфейс Window Sockets, спроектированный на основе socket API. Ниже мы увидим, насколько легко можно адаптировать существующую Unix-программу для работы под Windows.

Илон Маск рекомендует:  Что такое код getdc

Сокеты поддерживают многие стандартные сетевые протоколы (конкретный их список зависит от реализации) и предоставляют унифицированный интерфейс для работы с ними. Наиболее часто сокеты используются для работы в IP-сетях. В этом случае их можно использовать для взаимодействия приложений не только по специально разработанным, но и по стандартным протоколам — HTTP, FTP, Telnet и т. д. Например, вы можете написать собственный Web-броузер или Web-сервер, способный обслуживать одновременно множество клиентов.

Как видим, сокеты — весьма мощное и удобное средство для сетевого программирования. В этой статье я покажу, как ими пользоваться. Начав с понятия сокета и самых основных функций для работы с ним, мы постепенно перейдём к обсуждению более сложных тем. В частности, мы рассмотрим использование низкоуровневых сокетов, различные способы организации параллельного обслуживания клиентов, использование стандартных протоколов Internet и взаимодействие с программами, работающими под управлением операционной системы Microsoft Windows.

ПРИМЕЧАНИЕ
Большая часть материала, изложенного в статье, применимо ко всему семейству ОС Unix. Тем не менее, все приводимые далее факты и демонстрационные программы проверялись только под Linux, поэтому название этой ОС и вынесено в заголовок статьи.

Основы socket API


Понятие сокета

Сокет (socket) — это конечная точка сетевых коммуникаций. Он является чем-то вроде «портала», через которое можно отправлять байты во внешний мир. Приложение просто пишет данные в сокет; их дальнейшая буферизация, отправка и транспортировка осуществляется используемым стеком протоколов и сетевой аппаратурой. Чтение данных из сокета происходит аналогичным образом.

В программе сокет идентифицируется дескриптором — это просто переменная типа int . Программа получает дескриптор от операционной системы при создании сокета, а затем передаёт его сервисам socket API для указания сокета, над которым необходимо выполнить то или иное действие.

Атрибуты сокета

С каждым сокет связываются три атрибута: домен , тип и протокол . Эти атрибуты задаются при создании сокета и остаются неизменными на протяжении всего времени его существования. Для создания сокета используется функция socket , имеющая следующий прототип.

Домен определяет пространство адресов, в котором располагается сокет, и множество протоколов, которые используются для передачи данных. Чаще других используются домены Unix и Internet, задаваемые константами AF_UNIX и AF_INET соответственно (префикс AF означает «address family» — «семейство адресов»). При задании AF_UNIX для передачи данных используется файловая система ввода/вывода Unix. В этом случае сокеты используются для межпроцессного взаимодействия на одном компьютере и не годятся для работы по сети. Константа AF_INET соответствует Internet-домену. Сокеты, размещённые в этом домене, могут использоваться для работы в любой IP-сети. Существуют и другие домены ( AF_IPX для протоколов Novell, AF_INET6 для новой модификации протокола IP — IPv6 и т. д.), но в этой статье мы не будем их рассматривать.

Тип сокета определяет способ передачи данных по сети. Чаще других применяются:

  • SOCK_STREAM . Передача потока данных с предварительной установкой соединения. Обеспечивается надёжный канал передачи данных, при котором фрагменты отправленного блока не теряются, не переупорядочиваются и не дублируются. Поскольку этот тип сокетов является самым распространённым, до конца раздела мы будем говорить только о нём. Остальным типам будут посвящены отдельные разделы.
  • SOCK_DGRAM . Передача данных в виде отдельных сообщений (датаграмм). Предварительная установка соединения не требуется. Обмен данными происходит быстрее, но является ненадёжным: сообщения могут теряться в пути, дублироваться и переупорядочиваться. Допускается передача сообщения нескольким получателям (multicasting) и широковещательная передача (broadcasting).
  • SOCK_RAW . Этот тип присваивается низкоуровневым (т. н. «сырым») сокетам. Их отличие от обычных сокетов состоит в том, что с их помощью программа может взять на себя формирование некоторых заголовков, добавляемых к сообщению.

Обратите внимание, что не все домены допускают задание произвольного типа сокета. Например, совместно с доменом Unix используется только тип SOCK_STREAM . С другой стороны, для Internet-домена можно задавать любой из перечисленных типов. В этом случае для реализации SOCK_STREAM используется протокол TCP, для реализации SOCK_DGRAM — протокол UDP, а тип SOCK_RAW используется для низкоуровневой работы с протоколами IP, ICMP и т. д.

Наконец, последний атрибут определяет протокол, используемый для передачи данных. Как мы только что видели, часто протокол однозначно определяется по домену и типу сокета. В этом случае в качестве третьего параметра функции socket можно передать 0, что соответствует протоколу по умолчанию. Тем не менее, иногда (например, при работе с низкоуровневыми сокетами) требуется задать протокол явно. Числовые идентификаторы протоколов зависят от выбранного домена; их можно найти в документации.

Адреса

Прежде чем передавать данные через сокет, его необходимо связать с адресом в выбранном домене (эту процедуру называют именованием сокета). Иногда связывание осуществляется неявно (внутри функций connect и accept ), но выполнять его необходимо во всех случаях. Вид адреса зависит от выбранного вами домена. В Unix-домене это текстовая строка — имя файла, через который происходит обмен данными. В Internet-домене адрес задаётся комбинацией IP-адреса и 16-битного номера порта. IP-адрес определяет хост в сети, а порт — конкретный сокет на этом хосте. Протоколы TCP и UDP используют различные пространства портов.

Для явного связывания сокета с некоторым адресом используется функция bind . Её прототип имеет вид:

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

Поле sa_family содержит идентификатор домена, тот же, что и первый параметр функции socket . В зависимости от значения этого поля по-разному интерпретируется содержимое массива sa_data . Разумеется, работать с этим массивом напрямую не очень удобно, поэтому вы можете использовать вместо sockaddr одну из альтернативных структур вида sockaddr_XX (XX — суффикс, обозначающий домен: «un» — Unix, «in» — Internet и т. д.). При передаче в функцию bind указатель на эту структуру приводится к указателю на sockaddr . Рассмотрим для примера структуру sockaddr_in .

Здесь поле sin_family соответствует полю sa_family в sockaddr , в sin_port записывается номер порта, а в sin_addr — IP-адрес хоста. Поле sin_addr само является структурой, которая имеет вид:

Зачем понадобилось заключать всего одно поле в структуру? Дело в том, что раньше in_addr представляла собой объединение (union), содержащее гораздо большее число полей. Сейчас, когда в ней осталось всего одно поле, она продолжает использоваться для обратной совместимости.

И ещё одно важное замечание. Существует два порядка хранения байтов в слове и двойном слове. Один из них называется порядком хоста (host byte order), другой — сетевым порядком (network byte order) хранения байтов. При указании IP-адреса и номера порта необходимо преобразовать число из порядка хоста в сетевой. Для этого используются функции htons (Host TO Network Short) и htonl (Host TO Network Long). Обратное преобразование выполняют функции ntohs и ntohl .

ПРИМЕЧАНИЕ
На некоторых машинах (к PC это не относится) порядок хоста и сетевой порядок хранения байтов совпадают. Тем не менее, функции преобразования лучше применять и там, поскольку это улучшит переносимость программы. Это никак не скажется на производительности, так как препроцессор сам уберёт все «лишние» вызовы этих функций, оставив их только там, где преобразование действительно необходимо.

Установка соединения (сервер)

Установка соединения на стороне сервера состоит из четырёх этапов, ни один из которых не может быть опущен. Сначала сокет создаётся и привязывается к локальному адресу. Если компьютер имеет несколько сетевых интерфейсов с различными IP-адресами, вы можете принимать соединения только с одного из них, передав его адрес функции bind . Если же вы готовы соединяться с клиентами через любой интерфейс, задайте в качестве адреса константу INADDR_ANY . Что касается номера порта, вы можете задать конкретный номер или 0 (в этом случае система сама выберет произвольный неиспользуемый в данный момент номер порта).

На следующем шаге создаётся очередь запросов на соединение. При этом сокет переводится в режим ожидания запросов со стороны клиентов. Всё это выполняет функция listen .

Первый параметр — дескриптор сокета, а второй задаёт размер очереди запросов. Каждый раз, когда очередной клиент пытается соединиться с сервером, его запрос ставится в очередь, так как сервер может быть занят обработкой других запросов. Если очередь заполнена, все последующие запросы будут игнорироваться. Когда сервер готов обслужить очередной запрос, он использует функцию accept .

Функция accept создаёт для общения с клиентом новый сокет и возвращает его дескриптор. Параметр sockfd задаёт слушающий сокет. После вызова он остаётся в слушающем состоянии и может принимать другие соединения. В структуру, на которую ссылается addr , записывается адрес сокета клиента, который установил соединение с сервером. В переменную, адресуемую указателем addrlen , изначально записывается размер структуры; функция accept записывает туда длину, которая реально была использована. Если вас не интересует адрес клиента, вы можете просто передать NULL в качестве второго и третьего параметров.

Обратите внимание, что полученный от accept новый сокет связан с тем же самым адресом, что и слушающий сокет. Сначала это может показаться странным. Но дело в том, что адрес TCP-сокета не обязан быть уникальным в Internet-домене. Уникальными должны быть только соединения , для идентификации которых используются два адреса сокетов, между которыми происходит обмен данными.

Установка соединения (клиент)

На стороне клиента для установления соединения используется функция connect , которая имеет следующий прототип.

Здесь sockfd — сокет, который будет использоваться для обмена данными с сервером, serv_addr содержит указатель на структуру с адресом сервера, а addrlen — длину этой структуры. Обычно сокет не требуется предварительно привязывать к локальному адресу, так как функция connect сделает это за вас, подобрав подходящий свободный порт. Вы можете принудительно назначить клиентскому сокету некоторый номер порта, используя bind перед вызовом connect . Делать это следует в случае, когда сервер соединяется с только с клиентами, использующими определённый порт (примерами таких серверов являются rlogind и rshd). В остальных случаях проще и надёжнее предоставить системе выбрать порт за вас.

Обмен данными

После того как соединение установлено, можно начинать обмен данными. Для этого используются функции send и recv . В Unix для работы с сокетами можно использовать также файловые функции read и write , но они обладают меньшими возможностями, а кроме того не будут работать на других платформах (например, под Windows), поэтому я не рекомендую ими пользоваться.

Функция send используется для отправки данных и имеет следующий прототип.

Здесь sockfd — это, как всегда, дескриптор сокета, через который мы отправляем данные, msg — указатель на буфер с данными, len — длина буфера в байтах, а flags — набор битовых флагов, управляющих работой функции (если флаги не используются, передайте функции 0). Вот некоторые из них (полный список можно найти в документации):

  • MSG_OOB . Предписывает отправить данные как срочные (out of band data, OOB). Концепция срочных данных позволяет иметь два параллельных канала данных в одном соединении. Иногда это бывает удобно. Например, Telnet использует срочные данные для передачи команд типа Ctrl+C. В настоящее время использовать их не рекомендуется из-за проблем с совместимостью (существует два разных стандарта их использования, описанные в RFC793 и RFC1122). Безопаснее просто создать для срочных данных отдельное соединение.
  • MSG_DONTROUTE . Запрещает маршрутизацию пакетов. Нижележащие транспортные слои могут проигнорировать этот флаг.

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

Использование sendall ничем не отличается от использования send , но она отправляет весь буфер с данными целиком.

Для чтения данных из сокета используется функция recv .

В целом её использование аналогично send . Она точно так же принимает дескриптор сокета, указатель на буфер и набор флагов. Флаг MSG_OOB используется для приёма срочных данных, а MSG_PEEK позволяет «подсмотреть» данные, полученные от удалённого хоста, не удаляя их из системного буфера (это означает, что при следующем обращении к recv вы получите те же самые данные). Полный список флагов можно найти в документации. По аналогии с send функция recv возвращает количество прочитанных байтов, которое может быть меньше размера буфера. Вы без труда сможете написать собственную функцию recvall , заполняющую буфер целиком. Существует ещё один особый случай, при котором recv возвращает 0. Это означает, что соединение было разорвано.

Закрытие сокета

Закончив обмен данными, закройте сокет с помощью функции close . Это приведёт к разрыву соединения.

Вы также можете запретить передачу данных в каком-то одном направлении, используя shutdown .

Параметр how может принимать одно из следующих значений:

  • 0 — запретить чтение из сокета
  • 1 — запретить запись в сокет
  • 2 — запретить и то и другое

Хотя после вызова shutdown с параметром how , равным 2, вы больше не сможете использовать сокет для обмена данными, вам всё равно потребуется вызвать close , чтобы освободить связанные с ним системные ресурсы.

Обработка ошибок

До сих пор я ни слова не сказал об ошибках, которые могут происходить (и часто происходят) в процессе работы с сокетами. Так вот: если что-то пошло не так, все рассмотренные нами функции возвращают -1, записывая в глобальную переменную errno код ошибки. Соответственно, вы можете проанализировать значение этой переменной и предпринять действия по восстановлению нормальной работы программы, не прерывая её выполнения. А можете просто выдать диагностическое сообщение (для этого удобно использовать функцию perror ), а затем завершить программу с помощью exit . Именно так я буду поступать в демонстрационных примерах.

Отладка программ

Начинающие программисты часто спрашивают, как можно отлаживать сетевую программу, если под рукой нет сети. Оказывается, можно обойтись и без неё. Достаточно запустить клиента и сервера на одной машине, а затем использовать для соединения адрес интерфейса внутренней петли (loopback interface). В программе ему соответствует константа INADDR_LOOPBACK (не забудьте применять к ней функцию htonl !). Пакеты, направляемые по этому адресу, в сеть не попадают. Вместо этого они передаются стеку протоколов TCP/IP как только что принятые. Таким образом моделируется наличие виртуальной сети, в которой вы можете отлаживать ваши сетевые приложения.

Для простоты я буду использовать в демонстрационных примерах интерфейс внутренней петли.

Эхо-клиент и эхо-сервер

Теперь, когда мы изучили основные функции для работы с сокетами, самое время посмотреть, как они используются на практике. Для этого я написал две небольшие демонстрационные программы. Эхо-клиент посылает сообщение «Hello there!» и выводит на экран ответ сервера. Его код приведён в листинге 1. Эхо-сервер читает всё, что передаёт ему клиент, а затем просто отправляет полученные данные обратно. Его код содержится в листинге 2.

Листинг 1. Эхо-клиент.

Листинг 2. Эхо-сервер.

Обмен датаграммами

Как уже говорилось, датаграммы используются в программах довольно редко. В большинстве случаев надёжность передачи критична для приложения, и вместо изобретения собственного надёжного протокола поверх UDP программисты предпочитают использовать TCP. Тем не менее, иногда датаграммы оказываются полезны. Например, их удобно использовать при транслировании звука или видео по сети в реальном времени, особенно при широковещательном транслировании.

Поскольку для обмена датаграммами не нужно устанавливать соединение, использовать их гораздо проще. Создав сокет с помощью socket и bind , вы можете тут же использовать его для отправки или получения данных. Для этого вам понадобятся функции sendto и recvfrom .

Функция sendto очень похожа на send . Два дополнительных параметра to и tolen используются для указания адреса получателя. Для задания адреса используется структура sockaddr , как и в случае с функцией connect . Функция recvfrom работает аналогично recv . Получив очередное сообщение, она записывает его адрес в структуру, на которую ссылается from , а записанное количество байт — в переменную, адресуемую указателем fromlen . Как мы знаем, аналогичным образом работает функция accept .

Некоторую путаницу вносят присоединённые датаграммные сокеты (connected datagram sockets). Дело в том, что для сокета с типом SOCK_DGRAM тоже можно вызвать функцию connect , а затем использовать send и recv для обмена данными. Нужно понимать, что никакого соединения при этом не устанавливается. Операционная система просто запоминает адрес, который вы передали функции connect , а затем использует его при отправке данных. Обратите внимание, что присоединённый сокет может получать данные только от сокета, с которым он соединён.

Для иллюстрации процесса обмена датаграммами я написал две небольшие программы — sender (листинг 3) и receiver (листинг 4). Первая отправляет сообщения «Hello there!» и «Bye bye!», а вторая получает их и печатает на экране. Программа sender демонстрирует применение как обычного, так и присоединённого сокета, а receiver использует обычный.

Листинг 3. Программа sender.

Листинг 4. Программа receiver.

Использование низкоуровневых сокетов

Низкоуровневые сокеты открывают перед вами новые горизонты. Они предоставляют программисту полный контроль над содержимым пакетов, которые отправляются в путешествие по сети. С другой стороны, они сложнее в использовании и обладают плохой переносимостью. Вот почему использовать их следует только в случае необходимости. Например, без них не обойтись при разработке системных утилит типа ping и traceroute.

Первым делом выясним, чем низкоуровневые сокеты отличаются от обычных. Работая с обычными сокетами, вы передаёте системе «чистые» данные, а она сама заботится о добавлении к ним необходимых заголовков (а иногда ещё и концевиков). Например, когда вы посылаете сообщение через UDP-сокет, к нему добавляется сначала UDP-заголовок, потом IP-заголовок, а в самом конце — заголовок аппаратного протокола, который используется в вашей локальной сети (например, Ethernet). В результате получается кадр, показанный на рисунке 1.

Низкоуровневые сокеты позволяют вам включать в буфер с данными заголовки некоторых протоколов. Например, вы можете включить в ваше сообщение TCP- или UDP-заголовок, предоставив системе сформировать для вас IP-заголовок, а можете вообще сформировать все заголовки самостоятельно. Разумеется, при этом вам придётся изучить работу соответствующих протоколов и строго соблюсти формат их заголовков, иначе программа работать не будет.

При работе с низкоуровневыми сокетами вам придётся указывать в третьем параметре функции socket тот протокол, к заголовкам которого вы хотите получить доступ. Константы для основных протоколов Internet объявлены в файле netinet/in.h . Они имеют вид IPPROTO_XXX , где XXX-название протокола: IPPROTO_TCP , IPPROTO_UDP , IPPROTO_RAW (в последнем случае вы получите возможность поработать с «сырым» IP и формировать IP-заголовки вручную).

Все числовые данные в заголовках должны записываться в сетевом формате. Поэтому не забывайте использовать функции htons и htonl .

Чтобы проиллюстрировать всё это примером, я переписал программу sender из предыдущего раздела с использованием низкоуровневых UDP-сокетов. При этом мне пришлось вручную формировать UDP-заголовок отправляемого сообщения. Я выбрал для примера UDP, потому что у этого протокола заголовок выглядит совсем просто (рисунок 2).

Код примера приведён в листинге 5. Хочу обратить ваше внимание на несколько моментов. Во-первых, я не стал задавать номер порта в структуре sockaddr_in . Поскольку этот номер содержится в UDP-заголовке, от поля sin_port уже ничего не зависит. Во-вторых, я записал в качестве контрольной суммы ноль, чтобы не утомлять вас её вычислением. Протокол UDP является ненадёжным по своей природе, поэтому он допускает подобную вольность. Но другие протоколы (например, IP) могут и не допускать. Наконец, обратите внимание, что все данные UDP-заголовка форматируются с использованием htons .

Листинг 5. Программа sender с использованием низкоуровневых сокетов.

Функции для работы с адресами и DNS

В этом разделе мы обсудим несколько функций, без которых можно написать учебный пример, но без которых вряд ли обойдётся реальная программа. Поскольку для идентификации хостов в Internet широко используются доменные имена, мы должны изучить механизм преобразования их в IP-адреса. Кроме того мы изучим несколько удобных вспомогательных функций.

IP-адреса принято записывать в виде четырёх чисел, разделённых точками. Для преобразования адреса, записанного в таком формате, в число и наоборот используется семейство функций inet_addr , inet_aton и inet_ntoa .

Функция inet_addr часто используется в программах. Она принимает строку и возвращает адрес (уже с сетевым порядком следования байтов). Проблема с этой функцией состоит в том, что значение -1, возвращаемое ею в случае ошибки, является в то же время корректным адресом 255.255.255.255 (широковещательный адрес). Вот почему сейчас рекомендуется использовать более новую функцию inet_aton (Ascii TO Network). Для обратного преобразования используется функция inet_ntoa (Network TO Ascii). Обе эти функции работают с адресами в сетевом формате. Обратите внимание, что в случае ошибки они возвращают 0, а не -1.

Для преобразования доменного имени в IP-адрес используется функция gethostbyname .

Эта функция получает имя хоста и возвращает указатель на структуру с его описанием. Рассмотрим эту структуру более подробно.

  • h_name . Имя хоста.
  • h_aliases . Массив строк, содержащих псевдонимы хоста. Завершается значением NULL.
  • h_addrtype . Тип адреса. Для Internet-домена — AF_INET .
  • h_length . Длина адреса в байтах.
  • h_addr_list . Массив, содержащий адреса всех сетевых интерфейсов хоста. Завершается нулём. Обратите внимание, что байты каждого адреса хранятся с сетевым порядке, поэтому htonl вызывать не нужно.

Как видим, gethostbyname возвращает достаточно полную информацию. Если нас интересует адрес хоста, мы можем выбрать его из массива h_addr_list . Часто берут самый первый адрес (как мы видели выше, для ссылки на него определён специальный макрос h_addr ). Для определения имени хоста по адресу используется функция gethostbyaddr . Вместо строки она получает адрес (в виде sockaddr ) и возвращает указатель на ту же самую структуру hostent . Используя эти две функции, нужно помнить, что они сообщают об ошибке не так, как остальные: вместо указателя возвращается NULL , а расширенный код ошибки записывается в глобальную переменную h_errno (а не errno ). Соответственно, для вывода диагностического сообщения следует использовать herror вместо perror .

Следует иметь в виду, что функции gethostbyname и gethostbyaddr возвращают указатель на статическую область памяти. Это означает, что каждое новое обращение к одной из этих функций приведёт к перезаписи данных, полученных при преыдущем обращении.

В заключение рассмотрим ещё одно семейство полезных функций — gethostname , getsockname и getpeername .

Функция gethostname используется для получения имени локального хоста. Далее его можно преобразовать в адрес при помощи gethostbyname . Это даёт нам способ в любой момент программно получить адрес машины, на которой выполняется наша программа, что может быть полезным во многих случаях.

Функция getpeername позволяет в любой момент узнать адрес сокета на «другом конце» соединения. Она получает дескриптор сокета, соединённого с удалённым хостом, и записывает адрес этого хоста в структуру, на которую указывает addr . Фактическое количество записанных байт помещается по адресу addrlen (не забудьте записать туда размер структуры addr до вызова getpeername ). Полученный адрес при необходимости можно преобразовать в строку, используя inet_ntoa или gethostbyaddr . Функция getsockname по назначению обратна getpeername и позволяет определить адрес сокета на «нашем конце» соединения.

Параллельное обслуживание клиентов

Следующий важный вопрос, который нам предстоит обсудить, — это параллельное обслуживание клиентов. Эта проблема становится актуальной, когда сервер должен обслуживать большое количество запросов. Конечно, на машине с одним процессором настоящей параллельности достичь не удастся. Но даже на одной машине можно добиться существенного выигрыша в производительности. Допустим, сервер отправил какие-то данные клиенту и ждёт подтверждения. Пока оно путешествует по сети, сервер вполне мог бы заняться другими клиентами. Для реализации такого алгоритма обслуживания существует множество способов, но чаще всего применяются два из них.

Способ 1

Этот способ подразумевает создание дочернего процесса для обслуживания каждого нового клиента. При этом родительский процесс занимается только прослушиванием порта и приёмом соединений. Чтобы добиться такого поведения, сразу после accept сервер вызывает функцию fork для создания дочернего процесса (я предполагаю, что вам знакома функция fork ; если нет, обратитесь к документации). Далее анализируется значение, которое вернула эта функция. В родительском процессе оно содержит идентификатор дочернего, а в дочернем процессе равно нулю. Используя этот признак, мы переходим к очередному вызову accept в родительском процессе, а дочерний процесс обслуживает клиента и завершается ( _exit ).

С использованием этой методики наш эхо-сервер перепишется, как показано в листинге 6.

Листинг 6. Эхо-сервер (версия 2, fork)

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

Способ 2

Второй способ основан на использовании неблокирующих сокетов (nonblocking sockets) и функции select . Сначала разберёмся, что такое неблокирующие сокеты. Сокеты, которые мы до сих пор использовали, являлись блокирующими (blocking). Это название означает, что на время выполнения операции с таким сокетом ваша программа блокируется. Например, если вы вызвали recv , а данных на вашем конце соединения нет, то в ожидании их прихода ваша программа «засыпает». Аналогичная ситуация наблюдается, когда вы вызываете accept , а очередь запросов на соединение пуста. Это поведение можно изменить, используя функцию fcntl .

Эта несложная операция превращает сокет в неблокирующий. Вызов любой функции с таким сокетом будет возвращать управление немедленно. Причём если затребованная операция не была выполнена до конца, функция вернёт -1 и запишет в errno значение EWOULDBLOCK . Чтобы дождаться завершения операции, мы можем опрашивать все наши сокеты в цикле, пока какая-то функция не вернёт значение, отличное от EWOULDBLOCK . Как только это произойдёт, мы можем запустить на выполнение следующую операцию с этим сокетом и вернуться к нашему опрашивающему циклу. Такая тактика (называемая в англоязычной литературе polling) работоспособна, но очень неэффективна, поскольку процессорное время тратится впустую на многократные (и безрезультатные) опросы.

Чтобы исправить ситуацию, используют функцию select . Эта функция позволяет отслеживать состояние нескольких файловых дескрипторов (а в Unix к ним относятся и сокеты) одновременно.

Функция select работает с тремя множествами дескрипторов, каждое из которых имеет тип fd_set . В множество readfds записываются дескрипторы сокетов, из которых нам требуется читать данные (слушающие сокеты добавляются в это же множество). Множество writefds должно содержать дескрипторы сокетов, в которые мы собираемся писать, а exceptfds — дескрипторы сокетов, которые нужно контролировать на возникновение ошибки. Если какое-то множество вас не интересуют, вы можете передать вместо указателя на него NULL . Что касается других параметров, в n нужно записать максимальное значение дескриптора по всем множествам плюс единица, а в timeout — величину таймаута. Структура timeval имеет следующий формат.

Поле «микросекунды» смотрится впечатляюще. Но на практике вам не добиться такой точности измерения времени при использовании select . Реальная точность окажется в районе 100 миллисекунд.

Теперь займёмся множествами дескрипторов. Для работы с ними предусмотрены функции FD_XXX , показанные выше; их использование полностью скрывает от нас детали внутреннего устройства fd_set . Рассмотрим их назначение.

  • FD_ZERO(fd_set *set) — очищает множество set
  • FD_SET(int fd, fd_set *set) — добавляет дескриптор fd в множество set
  • FD_CLR(int fd, fd_set *set) — удаляет дескриптор fd из множества set
  • FD_ISSET(int fd, fd_set *set) — проверяет, содержится ли дескриптор fd в множестве set

Если хотя бы один сокет готов к выполнению заданной операции, select возвращает ненулевое значение, а все дескрипторы, которые привели к «срабатыванию» функции, записываются в соответствующие множества. Это позволяет нам проанализировать содержащиеся в множествах дескрипторы и выполнить над ними необходимые действия. Если сработал таймаут, select возвращает ноль, а в случае ошибки -1. Расширенный код записывается в errno .

Программы, использующие неблокирующие сокеты вместе с select , получаются весьма запутанными. Если в случае с fork мы строим логику программы, как будто клиент всего один, здесь программа вынуждена отслеживать дескрипторы всех клиентов и работать с ними параллельно. Чтобы проиллюстрировать эту методику, я в очередной раз переписал эхо-сервер с использованием select . Новая версия приведена в листинге 7. Обратите внимание, что эта программа, в отличие от всех остальных, написана на C++ (а не на C). Я воспользовался классом set из библиотеки STL языка C++, чтобы облегчить работу с набором дескрипторов и сделать её более понятной.

Листинг 7. Эхо-сервер (версия 3, неблокирующие сокеты и select).

Работа по стандартным протоколам

Как я уже говорил, сокеты могут использоваться при написании приложений, работающих по протоколам прикладного уровня Internet (HTTP, FTP, SMTP и т. д.). При этом взаимодействие клиента и сервера происходит по той же самой схеме, что и взаимодействие эхо-клиента и эхо-сервера в нашем примере. Разница в том, что данные, которыми обмениваются клиент и сервер, интерпретируются в соответствии с предписаниями соответствующего протокола.

Например, веб-сервер может работать по следующему алгоритму.

  1. Создаём слушающий сокет и привязываем его к 80-му порту (стандартный порт для HTTP-сервера).
  2. Принимаем очередной запрос на соединение.
  3. Читаем HTTP-запрос от клиента (он имеет стандартный формат и описан в RFC2616).
  4. Обрабатываем запрос и отправляем клиенту ответ, который также имеет стандартный формат.
  5. Разрываем соединение.

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

  1. Соединяемся с сервером по заданному адресу.
  2. Отправляем ему HTTP-запрос.
  3. Получаем и обрабатываем ответ сервера (например, форматируем и выводим на экран полученную HTML-страницу).
  4. Разрываем соединение.

Как видим, в работе по стандартным протоколам нет ничего сложного или принципиально нового.

Прорыв за пределы платформы

В мире Internet взаимодействие программ, работающих на разных платформах, встречается сплошь и рядом. Так, практически ежесекундно очередной Internet Explorer подсоединяется к веб-серверу Apache, а очередной Netscape Navigator совершенно спокойно подключается к IIS. Вот почему весьма полезно писать программы так, чтобы их можно было без труда переносить на другие платформы. В этом разделе мы посмотрим, как переносить Linux-программы, использующие сокеты, на платформу Windows.

Список основных отличий socket API и Winsock API выглядит примерно так.

  • В Windows набор заголовочных файлов существенно уменьшен. Собственно говоря, вам нужно включить всего один файл winsock.h (или winsock2.h, если вы хотите использовать расширенные возможности Winsock 2).
  • В Windows библиотеку Winsock необходимо явно проинициализировать до обращения к любым другим функциям из неё. Это делается с помощью функции WSAStartup . Кроме того, существует функция WSACleanup , которую следует вызывать по завершении работы с сокетами.
  • Как мы знаем, в Linux дескрипторы сокетов имеют тип int . В Windows сокеты не являются файловыми дескрипторами, поэтому для них введён свой тип SOCKET . Хотя этот тип и объявлен как u_int , полагаться на это в программе не следует.
  • В Windows для работы с сокетами не используются функции файлового ввода/вывода ( read и write ). Вместо close используется closesocket .
  • В Windows глобальная переменная errno не используется. Вместо этого код последней ошибки сохраняется системой для каждого потока отдельно. Чтобы его получить, используется функция WSAGetLastError .
  • В Windows введены дополнительные константы, которые следует применять вместо конкретных чисел. Так, значения, возвращаемые функциями Winsock, следует сравнивать с константами INVALID_SOCKET или SOCKET_ERROR , а не с -1.

Если переписать наш эхо-клиент с учётом приведённых особенностей Winsock API, а затем скомпилировать его под Windows (например, с помощью Visual C++), он вполне сможет взаимодействовать с эхо-сервером, работающим под Linux. Таким образом, сокеты позволяют решить проблему кроссплатформенного взаимодействия двух приложений.

К сожалению, различия socket API и Winsock не ограничиваются приведённым списком. При портировании более сложных, «продвинутых» программ начинают возникать более принципиальные проблемы. Например, под Windows существуют ограничения в поддержке низкоуровневых сокетов (они впервые появились в спецификации Winsock 2, а возможность напрямую манипулировать IP-заголовками доступна только под Windows 2000). Кроме того, проблемы могут возникнуть с функциями, не имеющими прямого отношения к socket API. Так, в Windows нет прямого аналога функции fork , и для организации параллельного обслуживания клиентов придётся прибегнуть к другим средствам.

Заключение

В этой статье мы рассмотрели целый ряд важных аспектов программирования сокетов. Тем самым мы заложили прочную основу для дальнейших исследований в этой области. Разумеется, большое количество деталей осталось за рамками нашей беседы. Но теперь вы сможете самостоятельно почерпнуть недостающую информацию из man-страниц Linux и из собственного практического опыта. Желаю удачи.

Что такое код socket_set_timeout

В последней статье мы написали простейшее Winsock-приложение, получающее данные с удалённого Web-сервера. Исходный код программы прост и прозрачен, и с виду никаких «подводных» камней в этой программе нет. Однако, это далеко не так. Ведь в предыдущих статьях нашей целью было понять только принцип работы и организации простейшего Winsock приложения, работающего с TCP сокетами.

Теперь же рассмотрим пройденный материал более подробно, так как мы уже немного «подросли» и можем перейти к решению более серьёзных проблем. Итак, вот некоторые особенности пройденного материала, на которые я не делал упор в начале. Мне даже приходили письма от некоторых читателей, которые указывали мне на эти «упущения». Спасибо за ваши отзывы! Значит, материал читают!

1. В примере программы из статьи 2, есть такой фрагмент кода:

На первый взгляд вроде бы всё нормально. Но это не так! В вызове функции recv присутствует параметр MAX_PACKET_SIZE, который определяет длину буфера приёма данных. То есть, Winsock протоколы (TCP/UDP) могут «отправить» пакет размером и 65535 байт. Однако, реальный размер IP пакета, может быть меньше передаваемых данных для конкретного типа сети ( величина MTU — размер наибольшего допустимого кадра в локальной сети или глобальном канале) и поэтому данные фрагментируются (кто хочет — смотрит описание TCP/IP протокола, мы не будем пока углубляться в описание «железного» уровня сети). При этом, фрагментированные данные могут идти с задержками. Какое отношение имеет данная особенность к нашей программе? Всё очень просто. Если мы запросим у удалённого Web-сервера не такой маленький кусочек HTML-кода, как в нашем примере, а немного побольше, то мы получим только первую порцию данных от сервера. Остальная часть данных будет, скорее всего, утеряна. Выход из данного положения очень прост. Он основан на знании принципа работы HTTP и TCP/IP протоколов, плюс некоторых особенностей Winsock. В нашем случае Web-сервер, передав последнюю порцию данных, закроет соединение. А функция recv в таком случае, после получения последнего фрагмента данных, возвращает ноль. Вывод напрашивается сам: мы должны читать входящие данные до тех пор, пока функция recv не вернет ноль.

Вот новый вариант кода:

При такой организации считывания данных, потерь не будет.

Полный исходный код программы можно взять здесь: ws3_1.zip

2. При рассмотрении функций send/recv мы упустили интерпретацию поля flags, заполняя его нулём, что значило отсутствие каких-либо флагов при вызове send/recv. Рассмотрим, какие значения может принимать поле flags.

MSG_DONTROUTE — указывает на то, что в отправляемое сообщение, не включатся информация о маршрутизации. Однако Winsock service provider может игнорировать этот флаг при доставке сообщения. Используется для отладки. Адрес назначения — локальный. То есть данные могут быть доставлены только на машины, соединенные напрямую.

MSG_OOB — Сообщение является OOB данными. (Out Of Band) То есть, такое сообщение передаётся вне потока. Это значит, что при отправке сообщения, транспортный протокол не ждёт полного заполнения буфера, а отсылает сообщение немедленно. Данный флаг можно использовать при передаче приоритетных данных. При использовании MSG_OOB, Winsock-приложения поддерживающие связь, должны заранее «договориться» об использовании этого флага.

MSG_PEEK — Данные копируются в принимающий буфер, но из очереди сообщений не изымаются. Функция возвращает количество принятых на данный момент байт данных.

MSG_OOB — Сообщение является OOB данными. (Out Of Band) То есть, такое сообщение передаётся вне потока. Это значит, что при отправке такого сообщения, транспортный протокол не ждёт полного заполнения TCP-буфера, а отсылает сообщение немедленно. Данный флаг можно использовать при передаче приоритетных данных. При использовании MSG_OOB, Winsock-приложения поддерживающие связь, должны заранее «договориться» о использовании этого флага.

Теперь мы знаем, какие значения может принимать переменная flags. Как, зачем, и главное нужно ли использовать эти флаги, мы рассмотрим в следующих статьях. В процессе написания новых программ.Пока все. Не будем забивать себе головы лишней для нас информацией.

Если вы выполняли программу из статьи 2 пошагово, то могли заметить, что некоторые Winsock-функции ожидают завершения выполняемых ими операций. Особенно надолго «подвисает» функция recv. Другими словами, выход из функции не происходит до момента завершения текущей операции. Эта особенность не очень хорошо подходит для программ, которые кроме получения/отправки данных должны выполнять еще множество других действий (отслеживание состояния системы меню, вывод информации, опрос других устройств ввода/вывода) Избежать этого можно многими способами. Можно обойтись средствами мультизадачности, и процедуру обмена данными «повесить» на отдельную ветвь. А можно решить проблему средствами Winsock. В любом случае, выбор конкретного метода остаётся за программистом. Наша же задача, разобраться с механизмом блокировки TCP-сокетов в Winsock. Итак, я хочу предложить вам два метода устранения проблемы блокировки:

Функция ioctlsocket позволяет менять/получать режим ввода/вывода конкретного сокета.

Для перевода сокета в не блокируемое состояние (nonblocking mode) используется команда FIONBIO. Argp должно указывать на ненулевое значение.

Кроме команды FIONBIO существуют команды FIONREAD и SIOCATMARK. Если коротко, то FIONREAD позволяет получить количество байт информации, поступившей в буфер на данный момент операции чтения, а SIOCATMARK — используется при работе с OOB данными. На данный момент нас интересует только команда FIONBIO. Остальные команды будем рассматривать более подробно по мере надобности в следующих статьях.

Итак, вернёмся к нашему примеру. После вызова ioctlsocket сокет s стал не блокируемым, то есть, Winsock-функции для этого сокета не дожидаются окончания операций ввода/вывода, что в свою очередь не вызывает нежелательных пауз в работе программы. Однако, не всё так просто. Например, возврат из функции recv может произойти до момента получения данных. Как определить, что текущая операция ввода/вывода полностью завершена? Способ есть. Имя ему — WSAEWOULDBLOCK. Что это такое? WSAEWOULDBLOCK — это код ошибки, которую возвращают Winsock-функции для nonblocked сокета, если текущая операция не завершена. То есть, если функция revc вернула этот код ошибки, значит, данные еще не готовы для чтения, и операцию придется повторить позже. В таком случае, ваша программа может выполнять другие действия, попутно проверяя, не завершена ли текущая операция ввода/вывода.

Полный исходный код программы можно взять здесь: ws3_2.zip

Функция slect позволяет определить текущее состояние одного или более сокетов. То есть, из какого-то входящего множества сокетов, она формирует выходящее множество сокетов, готовых к операциям чтения/записи/.

Каждый из параметров readfds, writefds, exceptfds, timeout есть необязательным, и может быть проигнорирован (установлен в NULL). В случае readfds/writefds/exceptfds == NULL проверка на опущенные типы сокетов просто не будет производиться. В случае timeout ==NULL, функция select вызовет блокировку (до первого готового к вводу/выводу сокета). Функция возвращает общее количество сокетов (во всех заданных множествах readfds/writefds/exceptfds), готовых к операциям ввода/вывода.

Параметры readfds/writefds/exceptfds — указатели на тип fd_set (представляющий собою множество сокетов). Для работы с этим типом данных объявлены такие макросы:

С помощью функции select и этого набора макросов, мы можем проверять конечное множество сокетов на готовность к считыванию/отсылке данных, выполнения connect, на предмет входящих соединений, наличия OOB сообщений и т.п. На данном этапе нас интересует проверка сокета на возможность считывания данных, поэтому пока ограничимся самым простым вызовом select. Для этого нам необходимо поместить наш сокет в множество на которое будет указывать readfds (в примере это read_s), задать timeout и выполнить select.

fd_set read_s; // Множество
timeval time_out; // Таймаут

FD_ZERO (&read_s); // Обнуляем мнодество
FD_SET (s, &read_s); // Заносим в него наш сокет
time_out.tv_sec = 0;time_out.tv_usec = 500000; //Таймаут 0.5 секунды.
if (SOCKET_ERROR == (res = select (0, &read_s, NULL, NULL, &time_out) ) ) return -1;

if ((res!=0) && (FD_ISSET (s, &read_s)) ) // Использую FD_ISSET только для примера! :)
<
// Получаю данные
>
// .

Полный исходный код программы можно взять здесь: ws3_3.zip

Итак, мы рассмотрели некоторые нюансы работы с TCP-сокетами и простейшие способы решения проблемы блокировки. Конечно, каждый пример носит условный характер, но ведь это же всё-таки примеры!

В следующих статьях мы приступим к написанию приложения-сервера, принимающего входящие соединения, и продолжим изучать Winsock-функции, которые пригодятся нам при написании более сложных сетевых приложений.

В следующем выпуске читайте:

  • Приём входящих соединений
  • Учим новые функции.

Что такое код socket_set_timeout

.NET has native implementation for socket communication. The namespace System.Net.Sockets contains most of the objects required for socket communication.

You can set the Timeout value via SendTimeout and ReceiveTimeout properties of System.Net.Sockets.TcpClient class.
Code sample:

Dim clientSocket As System.Net.Sockets.TcpClient= New System.Net.Sockets.TcpClient

clientSocket.Connect( «hostname» , «port» )

TcpClient.SendTimeout Property (System.Net.Sockets)
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.sendtimeout.aspx
TcpClient.ReceiveTimeout Property (System.Net.Sockets)
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.receivetimeout.aspx
TcpClient.Connect Method (IPAddress, Int32) (System.Net.Sockets)
http://msdn.microsoft.com/en-us/library/fkbhht5w.aspx

Best regards,
Martin Xie

Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.

  • Marked as answer by Martin Xie — MSFT Friday, October 23, 2009 9:48 AM

All replies

Mods — Please do not mark my posts as answered. It is extremely rude. You have no idea if you’ve answered my question. I WILL come back and mark the ones that are answers, as answers.

Is your threads but you dont have total control over your threads.

kaymaf I hope this helps, if that is what you want, just mark it as answer so that we can move on

I’m trying to use a socket to connect a TCP stream. The socket connects no problem if the endpoint has a valid IP associated to it. If I give it an invalid one, the Connect method will sit and block (I want it to) for 10-20 seconds before telling me the remote host timed out. Is there a timeout property I can set so I don’t have to wait that long for the connect method? Or is there another way of doing it that does implement a timeout?

Thanks. Mods — Please do not mark my posts as answered. It is extremely rude. You have no idea if you’ve answered my question. I WILL come back and mark the ones that are answers, as answers.

.NET has native implementation for socket communication. The namespace System.Net.Sockets contains most of the objects required for socket communication.

You can set the Timeout value via SendTimeout and ReceiveTimeout properties of System.Net.Sockets.TcpClient class.
Code sample:

Dim clientSocket As System.Net.Sockets.TcpClient= New System.Net.Sockets.TcpClient

clientSocket.Connect( «hostname» , «port» )

TcpClient.SendTimeout Property (System.Net.Sockets)
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.sendtimeout.aspx
TcpClient.ReceiveTimeout Property (System.Net.Sockets)
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.receivetimeout.aspx
TcpClient.Connect Method (IPAddress, Int32) (System.Net.Sockets)
http://msdn.microsoft.com/en-us/library/fkbhht5w.aspx

Best regards,
Martin Xie

Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.

Илон Маск рекомендует:  Какой хостинг выбрать
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL