Читайте также:
|
|
При блокировке сокета необходима осторожность, так как этом режиме любой вызов функции Winsock именно блокирует сокет на некоторое время. Большинство приложений Winsock следуют модели «поставщик — потребитель», в которой программа считывает или записывает определенное количество байт и затем выполняет с ними какие-либо операции.
SOCKET sock;
char buff[256];
int done = 0;
while(!done) {
nBytes = recv(sock, buff, 65);
if (nBytes == SOCKET_ERROR) {
printf("recv failed with error %d\n", WSAGetLastError());
return;
}
DoComputationOnData(buff);
}
Проблема в том, что функция recv может не завершиться никогда, так как для этого нужно считать какие-либо данные из буфера системы. В такой ситуации некоторые программисты могут соблазниться «подглядыванием» данных (чтение без удаления из буфера), используя флаг MSG_PEEK в recv или вызывая ioctlsocket с параметром FIONREAD. Подобный стиль программирования заслуживает резкой критики. Издержки, связанные с «подглядыванием», велики, так как необходимо сделать один или более системных вызовов для определения числа доступных байт, после чего все равно приходится вызывать recv для удаления данных из буфера.
Чтобы этого избежать, следует предотвратить замораживание приложения из-за недостатка данных (из-за сетевых проблем или проблем клиента) без постоянного «подглядывания» в системные сетевые буферы. Один из методов — разделить приложения на считывающий и вычисляющий потоки, совместно использующие общий буфер данных. Доступ к буферу регулируется синхронизирующим объектом, таким как событие или мьютекс. Задача считывающего потока — постоянно читать данные из сети и помещать их в общий буфер. Считав минимально необходимое количество данных, этот поток инициирует сигнальное событие, уведомляющее вычисляющий поток, что можно начинать вычисления. Затем вычисляющий поток удаляет часть данных из буфера и производит с ними необходимые операции. В следующем листинге реализованы две функции: для приема данных (ReadThread) и их обработки (ProcessThread).
// Перед созданием двух потоков,
// инициализируется общий буфер (data)
// и создается сигнальное событие (hEvent)
CRITICAL_SECTION data;
HANDLE hEvent;
TCHAR buff[MAX_BUFFER_SIZE];
int nbytes;
// Считывающий поток void ReadThread(void) {
int nTotal = 0, nRead = 0, nLeft = 0 nBytes = 0;
while (!done)
{
nTotal = 0;
nLeft = NUM_BYTES_REQUIRED;
while (nTotal!= NUM_BYTES_REQUIRED)
{
EnterCriticalSection(&data);
nRead = recv(sock, &(buff[MAX_BUFFER_SIZE - nBytes]), nLeft);
if (nRead == -1) {
printf("error\n"); ExitThread();
nTotal += nRead;
nLeft -= nRead;
nBytes += nRead;
LeaveCriticalSection(&data);
}
SetEvent(hEvent); } }
// Вычисляющий поток void ProcessThread(void)
WaitForSingleObject(hEvent);
EnterCriticalSection(&data);
DoSomeComputationOnData(buff);
// Удаление обработанных данных из буфера
// и сдвиг оставшихся в начало массива
nBytes -= NUM BYTES REQUIRED;
}
LeaveCriticalSection(&data);
}
Недостаток блокировки сокетов — приложению трудно поддерживать связь по нескольким сокетам одновременно. Приведенную схему можно изменить, чтобы считывающий и вычисляющий потоки создавались отдельно для каждого сокета. Для этого придется попотеть, зато вы получите более эффективное решение. Но учтите: данный вариант не предусматривает масштабирования, если вам придется работать с большим количеством сокетов.
Дата добавления: 2014-12-19; просмотров: 74 | Поможем написать вашу работу | Нарушение авторских прав |