Режимы сокетов

Как мы уже упомянали, 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, вы должны проверять все коды возвратов и быть готовыми к неудачному вызову в любое время. Многие программисты совершают большую ошибку все время вызывая функцию пока она не вернет удачный код возврата. К примеру постоянный вызов recv в цикле в ожидании прочтения 100 байт данных ничем не лучше чем вызов recv в блокирующем режиме с параметром MSG_PEEK. Winsock модели ввода - вывода могут помоч приложению, определьть когда сокет готов к чтению, либо передаче данных.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: