Как стать автором
Обновить

Комментарии 50

Сокет в С++ – это структура данных (не класс) типа SOCKET.

Тут все таки следует уточнить, что не в С++ сокет, а в конкретной библиотеке. С++ ничего не знает про сокеты. В том же boost::asio это класс.

Да, правильное замечание! Спасибо!

Выбор C++/C для изучения сокетов, как мне кажется, не самый оптимальный.
Слишком много отвлекающих технических сложностей. Плюс реализация сокетов в Винде сделана немного через жопу.


Лучше начать изучение с высокоуровневых кроссплатформенных обёрток в C#, Java, Python. А уже потом, при необходимости, опускаться на уровень API операционной системы.


Просто посмотрите, насколько кратко и выразительно выглядит код в Python:
https://habr.com/ru/post/149077/

НЛО прилетело и опубликовало эту надпись здесь

Вроде бы аккуратное изложение, и вдруг такое:

В итоговом коде я не использую проверку на точное получение отосланной информации, т.к. при единичной (не циклической) отсылке небольшого пакета информации накладные расходы на проверку его получения и отправку ответа будут выше, чем выгоды от такой проверки. Иными словами – такие пакеты теряются редко, а проверять их целостность и факт доставки очень долго.

Во-первых, потери пакетов здесь ни при чем. Их целостность и гарантию доставки обеспечивает ОС, мы уже выбрали это, когда создали потоковый сокет. Во-вторых, не обеспечивать фрейминг поверх потокового протокола --- это грубая ошибка, не надо такому учить. Не совсем про сокеты случай, но неделю назад у меня студент при передаче десятка байт(!) через локальный(!) named pipe словил проблемы из-за этого. Наконец, накладные расходы на проверку результата --- это ни о чем по сравнению с ценой системного вызова.

Чтобы избежать этих сложностей, проще начать с UDP и дейтаграммных сокетов, чтобы освоить API, а потом уже думать о буферах и протоколах.

Спасибо за вдумчивый комментарий. Отвечу Вам так:

"...целостность и гарантию доставки обеспечивает ОС..."

Я бы даже по-другому сказал - обеспечивает не только и не сколько ОС, сколько сам протокол TCP/IP, который как раз для этого и был придуман, улучшив и расширив функционал UDP. Давайте будем откровенны: полагаться на ОС и транспорт в таком щепетильном вопросе - дело опасное. Тем более, что в критических случаях, когда потеря даже одного байта в процессе передачи, приводит к полной нефункциональности пересылаемого сообщения (например, передача по сети файлов *.exe), проверку целостности пакетов делать необходимо. И я честно написал, что не сделал этого, т.к. риски в конкретно этом приложении тут минимальны, но покажу как это сделать в сл. статьях, где буду развивать тему. Для первого знакомства с темой мне кажется представленной информации достаточно.

Извините, но вы смешиваете гарантии TCP и прикладной протокол.

TCP гарантирует, что все байты, которые одна сторона отдает в send(), другая сторона получит из recv(), причем в том же порядке. В случае порчи, потери, переупорядочивания пакетов при передаче данные будут оправлены повторно, причем программа об этом не узнает, это все сделает TCP-стек ОС. Если ничего не помогает, соединение отвалится. Именно так ОС гарантирует доставку и в этом смысле мы на нее полагаемся.

TCP --- потоковый протокол: приложение работает с потоком байт, а не с пакетами. Если клиент сделал send() на 10 байт, на сервере вызов recv() может выдать 10 байт, а может выдать 8 байт и на следующий вызов --- 2 байта. Если приложению требуется из этого потока выделять сообщения, нужно включить в прикладной протокол средства для этого (framing): длину перед сообщением, метку конца и т. п. Код обязан быть готов к тому, что recv() может вернуть меньше, чем запрошено, а send() может взять меньше, чем указано; при необходимости вызывать их несколько раз, разбирать фрейминг. Не делать этого я назвал грубой ошибкой. Это баг. Программа может сработать неправильно не потому, что в сети что-то случилось, а потому, что некорректно пользуется функциями. Учебный характер программы усугубляет это.

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

P.S. TCP не "улучшает и расширяет" UDP, это протоколы для разных задач. TCP/IP --- это стек протоколов, UDP тоже работает поверх IP.

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

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

Фрейминг нужен для выделения сообщений из потока, только после этого можно говорить о проверке их целостности, например, добавляя к ним контрольную сумму. Если мы говорим о сообщениях (пишем-то чат), работать с TCP без фрейминга некорректно. Конкретно для вашей программы: клиент посылает "xxx", а на сервере recv() возвращает сначала "x", потом "xx" --- и все, не работает команда выхода. Или клиент посылает сначала "tax", потом "xxl" (двумя send), а на сервере recv() вернет "ta", потом "xxxl" --- и это будет воспринято как команда выхода, которую не посылали. В сети ничего не терялось и не искажалось, просто TCP-стек имеет право так отработать, а программа это не учитывает. Риск этого никак не зависит от алгоритма.

Ух, какой сильный замес из posix и win32 API.

Как минимум в коде сервера следующие боольшие ошибки:

  • смешивание одного и второго, уж определитесь, либо микрософт, либо posix

  • зачем закрывать серверный сокет вместе с клиентским?

  • при акцепте нужно бы породить или тред или встать на select

  • за fgets должна быть отдельная камера пыток

В общем, для новичков рекомендую классические примеры send-recv хотя бы отсюда https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2

Ну или отсюда - https://rsdn.org/article/unix/sockets.xml

смешивание одного и второго, уж определитесь, либо микрософт, либо posix

Зачем, если можно учиться писать универсальный код, кроме пары мест?

при акцепте нужно бы породить или тред или встать на select

Автор же пишет, что оставил это на будущее.

за fgets должна быть отдельная камера пыток

Спутали с gets()? Хотя, конечно, при живом-то std::getline() просто не нужно.

Юзер @kozlyuk ответил за меня на все Ваши комментарии, за что ему спасибо :) От себя добавлю следующее:

1) "сильный замес из posix и win32 API" - никто и не говорил, что мы пишем программу исключительно с использованием WinAPI. Такой подход был бы крайне искусственной конструкцией, если не сказать более грубо. Задача была иной, а именно: показать каким образом написать и запустить простейшее клиент-сервер приложение под Windows и человеческим языком объяснить, что вообще за функции для этого используются с их детальным описанием.

2) "зачем закрывать серверный сокет вместе с клиентским?" - вопрос не в полной мере понимаю. Речь о месте кода в серверной части или в частях и клиента, и сервера?

3) "за fgets должна быть отдельная камера пыток" - опять же, не вполне понимаю, чем Вам не нравится данная функция? Как совершенно точно заметил @kozlyuk, реальная проблема возникает с другой функцией - gets(), которая дырявая и глубоко устаревшая. Функция же fgets() лишена минусов gets() (главная проблема этой функции - возможное переполнение буфера ввода и крах программы), при этом работает быстро и эффективно, решая нужную задачу для написанной в этой статье программы. Возможно Вы имели ввиду, что функция fgets() - это наследие С, однако, повторюсь, здесь она прекрасно работает и не усложняет при этом чтение программы. В целом наверное можно согласиться с тем, что "прогрессивнее" использовать более современную getline(), однако и мой вариант - это явно не ошибка.

Вообще говоря, когда Вы пишете фразу "грубая ошибка", то это вызывает непонимание. Грубая ошибка - это такой род ошибок, которые приводят либо к полной нефункциональности программы (неприменимо, т.к. программа работает), либо к ситуациям критических отказов и уязвимостей программы. Ни то, ни другое тут не применимо. Поэтому давайте все будем аккуратнее в выражениях.

 никто и не говорил, что мы пишем программу исключительно с использованием WinAPI

вы сами написали это в первом же предложении

Это приложение будет использовать Win32API

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

Хорошо, тогда вопрос, что же было использовано из WinAPI кроме минимально требуемых WSAStartup/WSACleanup, о которых написано в любой статье по сокетам?

Тот же полный код простых клиента и сервера расположен в официальной документации Microsoft.

ZeroMemory спокойно меняется на вызов memset.

подавляющее большинство написано под *nix-системы...Это приложение будет использовать Win32API

В итоге весь сетевой код таки на posix и ни каких WSASocket() вместо socket(), WSASend() вместо send() и так далее я не увидел.

Кстати жаль, потому что при таком подходе кроме select() ни чего продемонстрировать невозможно, а ведь WinAPI предоставляет много возможностей, начиная от простейших WSACreateEvent() и заканчивая IOCP.

фрагмент кода в примере от Microsoft по ссылке выше:
service.sin_addr.s_addr = inet_addr("127.0.0.1");

вообще не заработает

Заработает, для этого в WinSock2.h определён специальный макрос

#define s_addr  S_un.S_addr

@Tujh, спасибо за комментарий!

1) "В итоге весь сетевой код таки на posix "

Универсальность никогда не бывает лишней. Если серьезно, то я расширю именно WinAPI применение в следующих статьях, которые анонсировал в этой. Другое дело - уверенно утверждать, что такое использование существенно расширит функционал программы я бы не стал. А вот в универсальности точно будут потери. Более развернуто отвечал на подобный комментарий выше.

2) "Заработает, для этого в WinSock2.h определён специальный макрос #define s_addr S_un.S_addr"

Да, Вы правы - данный код всё-таки заработает (не считая того, что вызов функции inet_addr() недопустим в современных компиляторах, но этот момент можно пролечить спец. директивой). Странным образом при тесте этой строчки перед написанием статьи она вызывала ошибку компиляции. Возможно проблема была в моей системе. В целом же мне кажется недопустимым, что на сайте с документацией для строго типизированного языка программирования используется присваивание разных по типу сущностей в явном виде через подобные "скрытые" в библиотеке макросы. Приведенная конструкция с сайта Microsoft, это очень плохой стиль программирования, который скорее ухудшает читаемость кода, нежели помогает в нём разобраться.

 Другое дело - уверенно утверждать, что такое использование существенно расширит функционал программы я бы не стал

Я уже привёл пример, если ограничиваться в примерах только вызовом select(), то да, но чуть более серьёзное применении уже потребует или универсальности или функционала. Но и даже в таком варианте универсальности не получится. По умолчанию в WinAPI вызов select() ограничен 64 сокетами

The default value of FD_SETSIZE is 64, which can be modified by defining FD_SETSIZE to another value before including Winsock2.h

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select

В Linux этот лимит установлен в 1024 и не может быть изменён в принципе

WARNING: select() can monitor only file descriptors numbers that are less than FD_SETSIZE (1024)—an unreasonably low limit for many modern applications—and this limitation will not change. All modern applications should instead use poll or epoll, which do not suffer this limitation.

https://man7.org/linux/man-pages/man2/select.2.htm

вызов функции inet_addr() недопустим в современных компиляторах

К компилятору это не имеет ни какое отношение.

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

А вот сейчас сами себе противоречите.

  1. Это присваивание абсолютно легально с точки зрения posix реализации, которую вы называете - универсальной. То есть пример должен быть компилируемым в любом случае.

  2. Так как реализация Microsoft не полностью совместима с posix им приходится делать код компилируемым с применением дополнительных средств, в данном случае - макроса, что может быть вводит в заблуждение порой, но совершенно нормально для переносимого кода.

  3. Если следовать вашей логике, что "недопустимо так писать в официальной документации", то и INVALID_SOCKET является недопустимым. В posix только любое отрицательное значение сокета (тип данных - int) является недопустимым, а в WinAPI это специальный тип данных - SOCKET и значение INVALID_SOCKET == 0 противоречит идее универсальности, так как ноль - валидное значение для posix (не отрицательное).

Небольшой совет, вы стараетесь обсуждать в статье вещи, в которых не разобрались, в этом и корень проблемы. Или "копайте глубже" или не будьте таким категоричным :)

Если сокет равен INVALID_SOCKET, то я бы настоятельно не рекомендовал его закрывать. Это некое служебное число за пределами массива сокетов. Как отработает его закрытие в разных реализациях я предпочитаю не думать.

https://www.opennet.ru/man.shtml?topic=socket&category=2&russian=0

@brn, спасибо за комментарий. Принимается!

Еще одно пожелание как к программистам, так и к создателям ОС при работе с мультикастами: выдерживайте, пожалуйста, паузы при многочисленных подписках и отписках от групп, хотя бы по 10-20мс. Иначе коммутаторы сходят с ума. Если прибить процесс (или он сам упал по какой-то причине), который работает со многими группами - сама Windows штормом шлёт кучу IGMP Leave

Другими словами все эти примеры просто не будут работать под Windows.


Почему вы так решили? Unix-системы используют posix-реализацию и она работает в Windows (с небольшими изменениями- например, в select в windows первый параметр 0, а в Unix максимальный номер сокета). Впрочем, я догадываюсь, что вас смущает событийная модель в windows (только вы её не используете :) )? Но вы можете просто создать отдельный поток и в нём реализовать все эти posix-сокеты. А если вас смущает их блокировка, то их всегда можно сделать неблокирующими. А так — честно, не увидел, что там у вас такого в коде специфичного для Windows? Инициализация? Она примитивна и описана очень много где (да хотя бы в «С++ глазами хаккера»). А событийную модель вы не используете. Строго говоря, вы накатали простейшую обработку сокетов, но ведь таких обучалок в инете навалом. Зачем же нужна ещё одна такого же уровня? Я не знаю. Честно, со стороны выглядит словно вы прочитав вступление к книжке по сокетам бросились рассказывать миру об этом откровении. Я так видел, как понявший суть *.h и *.c файлов помчался на радиокоте нести свет в массы, рассказывая, как их применять (а никто и не в курсе :) ). И это удивляет, словно мы докатились до ситуации, когда вещи, понятные лет 20 назад, теперь стали откровением. Удивительно…
А вот если вы с IEE1394 и связью с видеокамерами разберётесь… Вот это будет реально интересной вещью. Про такое статей и книг и не найти практически. Вот тут статья была бы нужна, несмотря на то, что шина устарела.

Обратите ещё внимание на «сырые сокеты» (RAW). Они позволяют отличненько лабать снифферы. :)

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

@da-nie, спасибо за комментарий.

Он, правда, немного удивляет. Вот пишешь в начале статьи дисклеймер: "Она для начинающих", и тут прилетает такой вот комментарий )) Вы наверное забыли те времена, когда были студентом и имели ограниченный временной ресурс на поиск нужной неперегруженной информации. У этой статьи была очень чёткая цель - ввести в тему сокетов и показать работающую реализацию под Windows. Человеку, который ищет в интернете, как программировать сокеты, выпадает несколько тысяч ссылок с кодом в чистой posix-нотации. У него ничего не работает под Винду, он судорожно начинает искать информацию на сайте с документацией от Microsoft, но и там не всё гладко. Наконец, ему никто не расскажет, что отдельные описания есть в спец. литературе вроде "С++ глазами хакера" (она ещё есть в продаже-то?). Мне всегда казалось, что Хабр - это то место, где уютно сосуществуют люди, которые только начали свой путь в IT, и опытные специалисты. И это сосуществование должно быть мирным. Давайте так и будет.

В следующих статьях я разовью тему сокетов для Windows, и надеюсь Вас не разочарую :))

Собственно, поэтому у меня и был совет: вместо C++ использовать другой язык.

Вот пишешь в начале статьи дисклеймер: «Она для начинающих»


Потому что таких статей валом. Это как статья «помигаем светодиодом на ардуино». При этом видно, что автор только недавно открыл для себя сокеты. :)

Вы наверное забыли те времена, когда были студентом и имели ограниченный временной ресурс на поиск нужной неперегруженной информации.


Я был студентом в 2000-2006 годах (и моя специальность с IT не связана). У меня инет был через модем с нефиговой почасовой оплатой на 56600 бод и поиск много времени не занимал (я книжки покупал — в них было много чего полезного). :) Времени же у меня вполне хватало на написание всяких 3D типа DooM, не говоря уж о всём остальном. :)

Человеку, который ищет в интернете, как программировать сокеты, выпадает несколько тысяч ссылок с кодом в чистой posix-нотации.


Вверху ссылки.

Вот, если конкретнее.

Просто надо указывать про Windows и статьи будут про неё.

Мне всегда казалось, что Хабр — это то место, где уютно сосуществуют люди, которые только начали свой путь в IT, и опытные специалисты.


А по-моему, это рекламная платформа разных контор. :) Вон их рейтинг, справа в верхнем углу. :) Спрашивают и рассказывают же на киберфоруме и подобных. Там и формат ответов удобнее.

C:/Windows/System32

Что у вас с разделителями путей? Должно быть \.

MAKEWORD(2,2)

Это не функция, это макрокоманда в заголовочных файлах, вы не найдёте её в виндовых DLL.

Но как же явно указать адрес и порт для привязки сокета?

Чтобы не возиться с приведениями к нужным типам, подсчётам 14 байт, каким‐то htons, используйте функцию GetAddrInfoW (или неюникодную оболочку над ней getaddrinfo), которая сама заполнит sockaddr, sin_addr и прочие sockaddr_in. К тому же GetAddrInfoW можно использовать и для заполнения данных для функции connect.

После вызова данной функции (listen) исполнение программы приостанавливается до тех пор, пока не будет соединения с Клиентом, либо пока не будет возвращена ошибка прослушивания порта.

Нет. listen не блокирует поток, иначе вы не доберётесь до accept.

Привязка сокета к конкретному процессу (bind) не требуется, т.к. сокет будет привязан к серверному Адресу и Порту через вызов функции connect()

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

Вообще функции recv и send подходят только для учебных программ вроде телнета. Ни для GUI, ни для больших приложений это не походит, потому что они блокируют поток. Если у вас что‐то посложнее laba06.cpp, то также не мучайте select. Сразу используйте асинхронные сокеты и перекрывающиеся операции ввода‐вывода (OVERLAPPED), WSAReceive и WSASend, для сервера — порт завершения ввода‐вывода.

Вообще функции recv и send подходят только для учебных программ вроде телнета. Ни для GUI, ни для больших приложений это не походит, потому что они блокируют поток.


Так ведь
unsigned long nb=1;
ioctlsocket(socket_server,FIONBIO,&nb);

И больше никаких блокировок.

Неблокирующий режим — это ещё не асинхронный режим.

В неблокирующем режиме вам придётся проверять данные либо по таймеру, либо делая Sleep() между вызовами. Это хуже, чем создание отдельного потока. 1. Если данных нет, программа всё равно будет нагружать процессор проверками, разряжать батарею. И чем меньше период — тем выше нагрузка. 2. Данные обрабатываются не сразу при поступлении, программа не сможет отвечать на данные быстро. Например, это важно для торговых ботов.

Поэтому лучше сразу использовать асинхронные сокеты и перекрывающиеся операции.

Зачем таймер? Поток же засыпает на вызове мультиплексора или функции ожидания.

И опять пришли к блокировке потока и зависанию GUI?

Я пока вижу только два способа не блокировать GUI:

  • асинхронный сокет на оконных сообщениях через WSAAsyncSelect;

  • OVERLAPPED‐операции ввода‐вывода + MsgWaitForMultipleObjectsEx.

Есть ли ещё какие‐нибудь способы без дополнительных потоков и таймеров не блокировать GUI?

Приличные люди всю обработку выносят в отдельные потоки. :)
В неблокирующем режиме вам придётся проверять данные либо по таймеру, либо делая Sleep() между вызовами


Вас спасёт select/poll/epool.

Данные обрабатываются не сразу при поступлении, программа не сможет отвечать на данные быстро.


См. выше. Сможет и будет отлично работать. Я так рабочие столы и потоковое видео с камер передавал в одном проекте. К тому же в Unix нет никаких WSA-функций.

К тому же в Unix нет никаких WSA-функций.

Вот только автор пишет

Это приложение будет использовать Win32API

Это приложение будет использовать Win32API


«К тому же в Unix нет никаких WSA-функций.» — это как бы намёк, что другие ОС обходятся и без WSA. :)
Вас спасёт select/poll/epool.

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


См. выше. Сможет и будет отлично работать.

Вы не сможете без танцев с бубнами мультиплексировать операции по сокетам с событиями GUI в одном потоке. Так что да, все операции с сокетами — в отдельный поток + веселуха с межпотоковой синхронизацией.

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


Вот именно, что системонезависимый. В Windows это тоже будет работать (в отличие от WSA, которые ТОЛЬКО в Windows и есть).

Вы не сможете без танцев с бубнами мультиплексировать операции по сокетам с событиями GUI в одном потоке.


С чего бы это вдруг? ;)

+ веселуха с межпотоковой синхронизацией.


Это вообще, обычно, никаких проблем не вызывает.
Вот именно, что системонезависимый. В Windows это тоже будет работать (в отличие от WSA, которые ТОЛЬКО в Windows и есть).

С каких пор в Windows завезли epoll, во FreeBSD — IOCP, а в Linux — kqueue?
Или предлагаете по-старинке через select работать?


С чего бы это вдруг? ;)

Вы вообще представляете, как выглядит очередь сообщений в GUI-приложений и как к ней прикрутить сокеты?


Это вообще, обычно, никаких проблем не вызывает.

Пользователь в GUI-приложении нажал на кнопку отмены, нужно остановить операцию по сокету в другом потоке, висящим на select/epoll_wait. Что предложите?

Или предлагаете по-старинке через select работать?


Почему бы и нет? Вам же нужна независимость от ОС?
А не через select (и не через WSA) вы покроете почти все UNIX-системы, но не покроете Windows. С WSA же вы получите всё ровно наоборот — только Windows и всё. Впрочем, тот же Pool есть как WSAPool и вы можете просто сделать макрос замены. При этом код менять не потребуется.

Вы вообще представляете, как выглядит очередь сообщений в GUI-приложений и как к ней прикрутить сокеты?


Единственное, чего я не представляю, так это выдуманные на пустом месте проблемы. :)
Что вам помешает передать требуемые работы с сокетами события потоку? Прямо из GUI, да. А потоку их можно хоть в очередь ставить, хоть сразу исполнять — это уже его дело.

Пользователь в GUI-приложении нажал на кнопку отмены, нужно остановить операцию по сокету в другом потоке, висящим на select/epoll_wait. Что предложите?


Вы в потоке крутитесь в цикле while(true) с внутренним select:
timeval timeout;
while(true)
{
  ...
  timeout.tv_sec=0;
  timeout.tv_usec=timeout_us;
  long ret=select(0,&Readen,0,&Exeption,&timeout);

  ...

}

Что помешает вам проверить сразу после select какое-либо событие (его можете хоть как флаг сделать, защитив мютексом или критической секцией)?

Кстати, а вы не смотрели, с какой частотой Windows переключает потоки? ;) Думаете, очередь сообщений (вместе с GUI) работает быстрее? Ну-ну.
Почему бы и нет? Вам же нужна независимость от ОС?

Как уже написали в соседнем комментарии, если номер дескриптора больше, чем FD_SETSIZE (который захардкожен в 1024), то вы не сможете поместить его в select. Хорошая переносимость, правда?


Вы в потоке крутитесь в цикле while(true) с внутренним select

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


Кстати, а вы не смотрели, с какой частотой Windows переключает потоки? ;) Думаете, очередь сообщений (вместе с GUI) работает быстрее? Ну-ну.

Как-то раньше игрался: простой пинг-понг событиями между потоками показал что-то вроде 20-30к переключений в секунду. А вот сколько сообщений можно пропустить через очередь сообщений, я не замерял.


Ну а так-то да, логично всю обработку делать в отдельном потоке и не насиловать GUI-поток.

Как уже написали в соседнем комментарии, если номер дескриптора больше, чем FD_SETSIZE (который захардкожен в 1024), то вы не сможете поместить его в select. Хорошая переносимость, правда?


Вы с этим сталкивались? ;) Нет, серьёзно, вы когда-нибудь ожидали 1024 сокета? Фантазировать, конечно, не запретишь, но всё же, как вам тогда 65535 портов хватает? Мне вот раз в 10 больше надо. :) Ну а почему бы и нет? ;)

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


Кто это сказал, что это плохая практика?

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


Не совсем.
Вы с этим сталкивались?

Нет, потому что я использую epoll и IOCP.


Кто это сказал, что это плохая практика?

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


Не совсем.

А причём тут таймер? ОС не ждёт следующего кванта времени, чтобы стартовать выполнение потока.

Нет, потому что я использую epoll и IOCP.


Речь о количестве сокетов, а не о ограничениях. Если у вас набирается столько сокетов на один поток, то пора пересмотреть архитектуру приложения.

А если маленький, тогда поток будет постоянно просыпаться по таймауту select, что не есть хорошо.


У вас в любом случае потоки будут потреблять процессор. Не этот, так другой. Так что всё это не более, чем собственноручно введённые ограничения. Добавление в очередь асинхронных событий с сокетами тоже не святой дух делает.

А причём тут таймер? ОС не ждёт следующего кванта времени, чтобы стартовать выполнение потока.


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

Таки не поток, а процесс. Тот же nginx на пару порядков больше соединений может держать, кстати.


У вас в любом случае потоки будут потреблять процессор.

Нет. В нормальном случае поток будет спать и просыпаться только при выполнении условия просыпания по дескриптору.


Разбор очереди сообщений привязан к этому таймеру.

Ну если у вас всё на sleep-ах сделано, тогда да.

Таки не поток, а процесс. Тот же nginx на пару порядков больше соединений может держать, кстати.


Ну может и может, вы лично 1024 использовали?

Нет. В нормальном случае поток будет спать и просыпаться только при выполнении условия просыпания по дескриптору.


Вы невнимательно читаете. Процессор в любом случае будет кем-то потребляться, хоть IDLE, но будет. И беречь в этом случае поток обработки смысла нет — ничего не выиграете.

Ну если у вас всё на sleep-ах сделано, тогда да.


Оно и без них будет идти неравномерно. То бежать, то приостанавливаться.
Процессор в любом случае будет кем-то потребляться, хоть IDLE, но будет.

Энергосбережение? Нет, не слышали.

Энергосбережение? Нет, не слышали.


Да ладно! :O
То есть, когда сайт сбербанка грузится хрен знает сколько или те же avito, youtube и ozon не стесняются кушать память и процессор в моём firefox, то это нормально, тут ни о каком энергосбережении речи как-то не заходит. :)
Да и ОС с остальным ПО всё жиреют и жиреют. Процессор греется от натуги, вытаскивая все эти слои абстракций и прослоек. Но «это другое». :)

Да, и это просто отвратительно. У меня есть и другие примеры, когда криво написанный сетевой софт отжирает в фоне процессорное время, даже если он не используется.


Например, в nxserver сделан epoll_wait с таймаутом 100 мс. Как следствие, он жрёт процессор. За 5 суток сожрал 7 минут процессорного времени. Мелочь, а неприятно.


С x2goserver ещё хуже: скрипт на perl, который раз в 2 секунды запускает несколько других процессов для очистки зависших сессий. Жрёт ещё больше. Частично пофиксил проблему, пропатчив скрипт и увеличив интервал до 120 секунд.


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

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


А вы проведите такой эксперимент — напишите с ожиданием и на асинхронке. Думаю, результат разницы времени автономной работы ноутбука будет на уровне статистической погрешности (я про nxserver).

Есть хорошая книга - Джонс Энтони, Оланд Джим. "Программирование в сетях Microsoft Windows". Единственная по Winsock 2 , хоть ей лет 20, но в сетевом программировании мало что меняется. Еще есть несколько хороших по первой версии винсок (и где-то временам Windows 95).

Неплохо бы раскрыть также и тему IPv6.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации