|
|
|
Статья
Данная версия документа является черновиком, и по большей части является переводом с небольшими поправками и добавлениями. Выкладывается с целью услышать критику, замечания и пожелания, которые помогут в доработке и вывода в свет конечного документа. Здесь пойдет речь о использовании моделей ввода - вывода в программировании Winsock приложений. Winsock предостовляет возможность управления режимами и модели ввода - вывода сокета, для того чтоб определить как операции ввода - вывода будут обработанны. Режим сокета в сущности определяет поведение вызванных Winsock функций. Модель ввода - вывода в свою очередь определяет как приложение будет обрабатывать операции ввода - вывода для определённого сокета. Winsock предостовляет два режима для сокетов: блокирующий и неблокирующий, а также несколько интересных моделей ввода - вывода которые помогают приложениям в управлении оерациями ввода - вывода нескольких сокетов одновременно асинхронным способом: блокирование, select, WSAAsyncSelect, WSAEventSelect, перекрытый ввод - вывод (overlapped I/O), и порт завершения (completion port). Все Windows платформы предоставляют блокирующий и неблокирующий режим работы для сокетов. И всеже не все модели ввода - вывода доступны на всех платформах. Следующая таблица показывает доступность моделей на разных Windows платформах.
Таблица 1. Доступные Модели ввода - вывода сокетов | Платформа | Блокирующий | Неблокирующий Select | WSAAsync Select | WSAEvent Select | Over-lapped | Completion Port | Windows CE | Да | Да | Нет | Нет | Нет | Нет | Windows 95(Winsock 1) | Да | Да | Да | Нет | Нет | Нет | Windows 95(Winsock 2) | Да | Да | Да | Да | Да | Нет | Windows 98 | Да | Да | Да | Да | Да | Нет | Windows МЕ | Да | Да | Да | Да | Да | Нет | Windows NT | Да | Да | Да | Да | Да | Да | Windows 2000 | Да | Да | Да | Да | Да | Да | Windows XP | Да | Да | Да | Да | Да | Да |
1. Режимы сокетовКак мы уже упомянали, Windows сокеты могут выполнять операции ввода - вывода в двух режимах: блокирующий и неблокирующий. В блокирующем режиме вызовы Winsock функций которые выполняют операции ввода - вывода, таких как send и recv - ждут пока операция завершится прежде чем отдать управление приложению. В неблокирующем режиме Winsock функции отдают управление приложению сразу. Приложения которые работают на Windows CE и Windows 95 (Winsock 1) платформах, которые потдерживают только некоторые модели ввода - вывода, вынуждены выполнять оперделенные дейвсвия с блокирующими и неблокирующими сокетами для коректной отработки разных ситуаций. 1.1. Блокирующий режимБлокирующие сокеты создают некоторые неудобства, потомуч то вызов любой из Winsock API функций блокируют на некоторое время. Большинство Winsock приложений следуют модели "производитель - потребитель" в которой приложение считывает либо записывает определенное количество байт и выполняет их обработку. Следующий отрывок кода иллюстрирует эту модель: SOCKET sock; char buffer[256]; int done = 0, err; while(!done) { // прием данных err = recv(sock, buffer, sizeof (buffer)); if (err == SOCKET_ERROR) { // отработка ошибки приема printf("recv failed with error %dn", WSAGetLastError()); return; } // обработка данных ProcessReceivedData(buffer); }
Проблема в приведенном коде состоит в том, что функция recv может никогда не отдать управление приложению если на данный сокет не прийдут какието данные. Некоторые прграммисты проверяют наличие ожидающих данных на сокете вызовом тогоже recv с флагом MSG_PEEK либо ioctlsocket с FIONREAD опцией. Проверка наличия ожидающих данных на сокете без ихнего приема считается в прогаммировании плохим тоном, это надо избегать любой ценной чтением данных из системного буфера. Для избежания этого метода, мы должны не дать приложению полностью застыть из-за отсутсвия ожидающих данных без вызова проверки наличия таковых. Решением данной проблемы может быть разделить приложение на два потока: читаюший и обрабатывающий данные, оба разделяя общий буфер данных. Доступ к которому осуществляется синхронизирующим обьектом как событие(event) или мьютекс(mutex). Задача читающего потока состоит в считвыании поступающих данных из сети на сокет в общий буфер. Когда читающий поток считал минимальное необходимое количество данных преднозначенных для обрабатывающего потока, он переключает событие в отсигналенное состояние, таким образом давая знать обрабатывающему потоку о наличии в общем буфере данных для обработки. Обрабатывающий поток в свою очередь забирает из буфера данные и обрабатывает их. Следующий кусок кода показывает реализацию данного метода, реализуя две функции: одна обеспечивая чтение данных из сети (ReadingThread), другая обработку данных(ProcessingThread): #define MAX_BUFFER_SIZE 4096 // Инициализация critical section // и события с автосбросом перед инициализацией потоков CRITICAL_SECTION data; HANDLE hEvent; SOCKET sock; CHAR buffer[MAX_BUFFER_SIZE]; // создание читающего сокета // читающий поток void ReadingThread(void) { int nTotal = 0, nRead = 0, nLeft = 0, nBytes = 0; while (true) { nTotal = 0; nLeft = NUM_BYTES_REQUIRED; while (nTotal < NUM_BYTES_REQUIRED) { EnterCriticalSection(&data); nRead = recv(sock, &(buffer[MAX_BUFFER_SIZE - nBytes]), nLeft, 0); if (nRead == -1) { printf("errorn"); ExitThread(); } nTotal += nRead; nLeft -= nRead; nBytes += nRead; LeaveCriticalSection(&data); } SetEvent(hEvent); } } // обрабатывающий поток void ProcessingThread(void) { while (true) { // ждем данных WaitForSingleObject(hEvent); EnterCriticalSection(&data); DoSomeComputationOnData(buffer); // удаляем из буфера обработанные данные nBytes -= NUM_BYTES_REQUIRED; LeaveCriticalSection(&data); } }
Основная трудность в програмировании блокирующих сокетов состоит в подержке передачи - приёма данных для более одного сокета. Используя предыдущую реализацию, приложение должно быть изменено для того чтоб иметь по одной паре читающего и обрабатывающего потока на каждый сокет. Это добавляет некоторую рутинную работу для програмиста и осложнение кода. Единственный недостаток состоит в том, что приложение плохо маштабируется при большом количестве сокетов. 1.2. Неблокирующий режимАльтернативой блокирующим сокетам является неблокирующие. Неблокирующие сокеты являются более перспективными, но ихнее приемущество над блокирующими не велико. Следующий пример показывет как создать сокет и переключить его в неблокирующий режим: SOCKET sock; unsigned long nb = 1; int err; sock = socket(AF_INET, SOCK_STREAM, 0); err = ioctlsocket(sock, FIONBIO, (unsigned long *) &nb); if (err == SOCKET_ERROR) { //ошибка при переключении сокета в неблокирующий режим }
После переключения сокета в неблокирующий режим, вызовы Winsock API связанные с приемом, передачей данных либо управлением соединений будут сразу возвращять управление прилоежению, не ожидая завершения текущей операции. В большинстве случаев данные вызовы возвращают ошибку типа WSAEWOULDBLOCK, что означает, что операция не имела времени закончится в период вызова функции. К примеру функция recv вернет WSAEWOULDBLOCK если нет ожидающих данных в системном буфере для данного сокета. Часто нужны дополнительные вызовы функции пока она не вернет сообшение об удачном завершение операции. Следующая таблица описывает значение WSAEWOULDBLOCK при вызове разных Winsock API функций: Описание WSAEWOULDBLOCK ошибки для неблокирующих сокетов | Имя функции | Описание | WSAAccept и accept | Нет запросов на установление связи, вызовите опять для провеки наличия запросов. | closesocket | В большинстве случаев, это означает что setsockopt была вызвана с опцией SO_LINGER отличной от нуля тайм аут был установлен. | WSAConnect и connect | Установка связи начата. вызовите снова, чтобы проверить завершение операции. | WSARecv, recv, WSARecvFrom, и recvfrom | Данные не были получены. Проверьте снова позже. | WSASend, send, WSASendTo, и sendto | Нет места в системном буфере отсылаемых данных. Пробуйте снова позже. |
Потому как большинство неблокирующих вызовов функции терпят неудачу с ошибкой WSAEWOULDBLOCK, вы должны проверять все коды возвратов и быть готовыми к неудачному вызову в любое время. Многие программисты совершают большую ошибку все время вызывая функцию пока она не вернет удачный код возврата. К примеру постоянный вызов recv в цикле в ожидании прочтения 100 байт данных ничем не лучше чем вызов recv в блокирующем режиме с параметром MSG_PEEK. Winsock модели ввода - вывода могут помоч приложению, определьть когда сокет готов к чтению, либо передаче данных. Каждый из режимов - блокирующий и неблокирующий - имеют свои недостатки и преимущества. Блокирующие сокеты более легки в использовании с концептуальной точки зрения, но есть затруднения в управлении большого количества соединений, либо когда передаются данные разных обьемов и в разные периоды времени. С другой стороны неблокирующие сокеты более сложны, так как существует необходимость в написание более сложного кода для управления возможностью приема кодов возврата типа WSAEWOULDBLOCK при каждом вызове Winsock API функций. Сокетные модели ввода - вывода помогают приложению справится с управлением передачей данных на одном или более соединений одновременно асинхронным способом.
Автор: ixania
|
|
|