Когда я смотрел на setsockopt от MSDN ссылка на сайт. я наткнулся на параметр SO_RCVTIMEO, это описание «Устанавливает время ожидания в миллисекундах для блокировки входящих вызовов.«Я думал, что операция прослушивания сокета управляется событиями, что означает, что когда ядро истощает фрейм с карты NIC, оно уведомляет мой программный сокет, так что же такое блокировка?
Функции recv и WSARecv блокируются. Они не управляются событиями (по крайней мере, не на уровне вызова). Даже когда блокировка имеет тайм-аут (как установлено с SO_RECTIMEO
опция), они не управляются событиями в том, что касается вашего кода. В этом случае они являются просто псевдоблокирующими (возможно неблокирующими в зависимости от того, насколько коротким является время ожидания).
Когда вы вызываете WSARecv, он будет ждать, пока данные не будут готовы для чтения. Пока данные не готовы для чтения, они просто ждут. Вот почему это считается блокированием.
Вы правы в том, что по своей сути сетевое взаимодействие обусловлено событиями. Под капотом компьютеры по своей природе управляются событиями. Так работает аппаратная часть. Аппаратные прерывания по сути являются событиями. Вы правы, что на низком уровне происходит то, что ваша сетевая карта сообщает ОС, что она готова к чтению. На этом уровне это действительно событие.
Проблема в том, что WSARecv ожидает этого события.
Вот, надеюсь, ясная аналогия. Представьте, что вы по какой-то причине не можете покинуть свой дом. Теперь представьте, что ваш друг F живет по соседству. Кроме того, предположим, что ваш второй друг G находится в вашем доме.
Теперь представьте, что вы даете G лист бумаги с вопросом на него и просите его отнести его к F.
Как только вопрос будет отправлен, представьте, что вы отправляете G, чтобы получить ответ F. Это похоже на вызов recv. G будет ждать, пока F не запишет свой ответ, а затем принесет его вам. G не сразу оборачивается и возвращается, если F еще не написал это.
Отсюда и разрыв. G действительно знает о «F написал!» события, но вы не. Вы не смотрите прямо на лист бумаги.
Установка таймаута означает, что вы говорите G подождать не более некоторого времени, прежде чем сдаться и вернуться. В этой ситуации G все еще ждет записи F, но если F не пишет в x
миллисекунды, G оборачивается и возвращается с пустыми руками.
По сути, псевдокод recv выглядит примерно так:
1) is data available?
1a) Yes: read it and return
1b) No: GOTO 2
2) Wait until an event is received
2a) GOTO 1
Я знаю, что это было ужасно запутанное объяснение, но моя главная мысль такова: recv взаимодействует с событиями, а не с вашим кодом. recv блокируется, пока одно из этих событий не будет получено. Если время ожидания установлено, оно блокируется до тех пор, пока не будет получено одно из этих событий, или же тайм-аут достигнут.
Сокеты НЕ управляются событиями по умолчанию. Вы должны написать дополнительный код, чтобы включить это. Вместо этого изначально создается сокет в режиме блокировки. Это означает, что вызов send()
, recv()
, или же accept()
по умолчанию блокирует вызывающий поток до тех пор, пока запрошенная операция не будет завершена.
За recv()
это означает, что вызывающий поток блокируется до тех пор, пока не будет доступен хотя бы 1 байт для чтения из буфера приема сокета или пока не произойдет ошибка сокета, в зависимости от того, что произойдет раньше. SO_RCVTIMEO
позволяет установить таймаут на блокировку чтения так recv()
выходит с WSAETIMEDOUT
ошибка, если никакие входящие данные не становятся доступными до истечения времени ожидания.
Другой способ реализовать тайм-аут — установить сокет в неблокирующий режим вместо ioctlsocket(FIONBIO)
а затем позвоните select()
с таймаутом, затем позвоните recv()
или же accept()
только если select()
сообщает, что сокет находится в читаемом состоянии, и send()
только если select()
сообщает, что сокет находится в состоянии записи. Но для этого требуется больше кода для управления случаями, когда сокет переходит в состояние блокировки, что приводит к сбою операций с WSAEWOULDBLOCK
ошибки.