- •Адресация
- •Повторители, мосты, коммутаторы и маршрутизаторы.
- •Эталонная сетевая модель osi. Межуровневые взаимодействия.
- •Физический уровень osi
- •Канальный уровень osi.
- •Сетевой уровень osi
- •Транспортный уровень osi.
- •Прикладной уровень osi.
- •Сеансовый уровень osi.
- •Представительский уровень osi.
- •Ethernet
- •TokenRing
- •FrameRelay
- •Адресация tcp/ip
- •Протокол arp
- •Протокол ip
- •Протокол icmp
- •Протокол udp
- •Протокол tcp
- •Протокол spx
- •NetWareCoreProtocol (ncp)
- •Протокол NetWare Core Packet Burst (ncpb)
- •Протокол sap
- •NetBeui
- •NetBios
- •Протокол smb
- •Основные определения winsock.
- •Сетевые протоколы, поддерживаемые Win32
- •Сокеты Windows
- •Winsock и модель osi
- •Создание сокета для tcp/ip
- •Создание сокетов и адресации для NetBios
- •Серверные api-функции
- •Api-функции клиента
- •Передача данных
- •Завершение сеанса
- •Передатчик для протоколов, не требующих соединения
- •Комплексный ввод-вывод
- •Режимы сокетов
- •Модель Select (Выбор)
- •WsaaSyncSelect
- •WsaEventSelect
- •Модель перекрытого ввода-вывода
- •Модель портов завершения
Передатчик для протоколов, не требующих соединения
Есть два способа отправки данных через сокет, не требующий соединения. Первый и самый простой — создать сокет и вызвать функцию sendto или WSASendTo. Целесообразно рассмотреть сначала функцию sendto:
// Code 3.30
int sendto(
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr FAR * to,
int tolen
);
Параметры этой функции такие же, как и у recvfrom, за исключением buf— буфера данных для отправки, и len — показывающего, сколько байт отправлять. Параметр to — указатель на структуру SOCKADDR с адресом принимающей рабочей станции.
Также можно использовать функцию WSASendTo из Winsock 2:
// Code 3.31
int WSASendTo(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
const struct sockaddr FAR * lpTo,
int iToLen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION ROUTINE lpCompletionROUTINE
);
Снова функция WSASendTo аналогична своей предшественнице. Она принимает указатель на одну или несколько структур WSABUF с данными для отправки получателю в виде параметра lpBuffers, a divBufferCount задает количество структур. Для комплексного ввода-вывода можно отправить несколько структур WSABUF. Перед выходом WSASendTo присваивает четвертому параметру — lpNumberOfBytesSent, количество реально отправленных получателю байт. Параметр lpTo — структура SOCKADDR для данного протокола с адресом приемника. Параметр iToLen — длина структуры SOCKADDR. Два последних параметра — lpOverlapped и lpCompletionROUTINE, применяются для перекрытого ввода-вывода (см. также следующую главу).
Как и при получении данных, сокет, не требующий соединения, можно подключать к адресу конечной точки и отправлять данные функциями send и WSASend. После создания этой привязки использовать для обмена данными функции sendto или WSASendTo с другим адресом нельзя — будет выдана ошибка WSAEISCONN. Отменить привязку сокета можно, лишь вызвав функцию closesocket с описателем этого сокета, после чего следует создать новый сокет.
Комплексный ввод-вывод
Режимы сокетов
Как мы уже упомянали, 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 функций. Сокетные модели ввода - вывода помогают приложению справится с управлением передачей данных на одном или более соединений одновременно асинхронным способом.
Модели ввода-вывода сокетов и их сравнение
Блокирующий ввод-вывод
Модель Select
Модель ввода-вывода WSAAsyncSelect.
Модель ввода-вывода WSAEventSelect.
Модель перекрытого ввода-вывода.
Модель портов завершения.
У каждой из моделей свои достоинства и недостатки, и все модели сложнее в программировании, чем простой ввод-вывод с блокировкой и несколькими потоками Рассмотрим возможные решения для разработки клиентских и серверных приложений
Клиент
При разработке клиентского приложения, управляющего одним или не-сколькими сокетами, мы рекомендуем использовать модель перекрытого ввода-вывода или модель WSAEventSelect для увеличения производительности Впрочем, при разработке приложения Windows, работающего с сообщениями окна, модель WSAAsyncSelect может быть лучше, гак как сама опирается на модель сообщений Windows, а ваше приложение уже содержит механизм обработки сообщений
Сервер
При проектировании сервера, обрабатывающего несколько сокетов одно-временно, рекомендуем модель перекрытого ввода-вывода Впрочем, если вы планируете, что сервер будет одновременно обрабатывать большое количе-ство запросов ввода-вывода, попробуйте использовать порты завершения
Резюме
Мы рассмотрели все модели ввода-вывода, доступные в Winsock Они позво-ляют приложению использовать ввод-вывод Winsock в соответствии со своими нуждами от простого ввода-вывода с блокировкой до быстрого ввода-вывода через порт завершения
