Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
ГЛАВА 7 Основы Winsock 179 Листинг 7-1. (продолжение) else if (ret == SOCKET.ERROR) { printf("recv() failed: Xd\n", WSAGetLastErrorO); break; } szBuff[ret] = '\0'; printf("RECV: 'Xs'\n", szBuff); // // Возврат данных клиенту, если задан соответствующий параметр // if (IbRecvOnly) { nLeft = ret; i ldx = 0; -,nfh I II Проверка, что все данные записаныдар // " ' while(nLeft > 0) { ( ret = send(sock, &szBuff[idx], nLeft, 0); t if (ret == 0) break; else if (ret == SOCKET.ERROR) { printf("send() failed: Xd\n", WSAGetLastErrorO); « break; } nLeft -= ret; idx += ret; return 0; // Функция: main 4 * • к ШГ 'I ' i w II Описание: // Главный поток выполнения. Инициализирует Winsock, анализирует // параметры командной строки, создает и прослушивает сокет, / привязывает его к локальному адресу и ждет подключений клиентов jnt maindnt argc, char .*argv) WSADATA SOCKET wsd; sListen, см. след. стр. hit 180 Ч А С Т Ь II Интерфейс прикладного программирования Winsock Листинг 7-1. (продолжение) sClient; int iAddrSize; HANDLE hThread; DWORD dwThreadld; struct sockaddr_in local, client; ValidateArgs(argc, argv); if (WSAStartup(MAKEW0RD(2,2), &wsd) != 0) < printf("Failed to load Winsock!\n"); return 1; } // Создание сокета прослушивания // sListen = socket(AF_INET, SOCK.STREAM, IPPROTO.IP); if (sListen == SOCKET,ERROR) { printf("socket() failed: J£d\n", WSAGetLastErrorO); return 1; } // Выбор локального интерфейса и привязка к нему , // if (blnterface) // 7 •7 / • ' >Ж local.sin_addr.s_addr = inet_addr(szAddress); if (local.sin_addr.s_addr == INADDR_NONE) usage(); else local.sin_addr.s_addr = htonl(INADDR_ANY); 7, local.sin_family = AF.INET; l o c a l s i n t htons(iPor local.sin_port = htons(iPort); if (bind(sListen, (struct sockaddr *)&local, sizeof(local)) == SOCKET.ERROR) { printf("bind() failed: Xd\n", WSAGetLastErrorO); return 1; listen(sListen, 8); » // Ожидание клиентов в бесконечном цикле. // Создание потока в случае обнаружения и передача ему описателя. // while (1) { * IAddrSize = sizeof(client); 8 '>' sClient = accept(sListen, (struct sockaddr *)&client, iiAddrSize); Г Л А В А 7 Основы Winsock 181 Листинг 7-1. {продолжение) if (sClient == INVALID_SOCKET) { pnntf("accept() failed: Xd\n", WSAGetLastErrorO); break; } printf("Accepted client: Xs:Xd\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)sClient, 0, idwThreadld); if (hThread == NULL) { pnntf("CreateThread() failed: Xd\n", GetLastError()); break; > CloseHandle(hThread); } closesocket(sListen); WSACleanupO; return 0; } Клиентская часть данного примера (листинг 7-2) значительно проще. Клиент создает сокет, разрешает переданное приложению имя сервера и соединяется с сервером. Как только соединение установлено, серверу от- правляется несколько сообщений. После каждой отправки клиент ожидает эхо-ответа сервера. Все полученные по сокету данные выводятся на экран. Эхо-взаимодействие клиента и сервера не вполне отражает потоковую сущность TCP — для клиента каждая операция чтения сопровождается опе- рацией записи, а для сервера наоборот. Таким образом, каждый вызов сер- вером функции чтения почти всегда возвращает все сообщение, отправлен- ное клиентом. Почти, но не всегда — если размер сообщения клиента пре- вышает максимальную единицу передачи для TCP, то оно разбивается на от- дельные пакеты и получателю потребуется несколько раз вызвать функцию приема данных. Для лучшей иллюстрации потоковой передачи запустите клиент и сервер с параметром -о. Тогда клиент будет только отправлать дан- ные, а сервер — только принимать: server -p:5150 -о client -р:5150 -s:IP -n:10 -о Корее всего, вы увидите, что клиент совершает десять отправок, а сер- Р получает все десять сообщений за один или два вызова recv. Листинг 7-2. Эхо-клиент //Имя модуля; Client.с см. след. стр. I 1 82 ЧАСТЬ II Интерфейс прикладного программирования Winsock Листинг 7-2. (продолжение) II // Описание: // Это простой эхо-клиент. Соединяется с сервером TCP, // отправлает данные и принимает их с сервера обратно. // // Параметры компиляции: // cl-oClientClient.cws2_32.1ib // // Параметры командной строки: // client[-p:x] [-s:IP] [-n:x] [-о] // -р:х Удаленный порт отправки // -s:IP IP-адрес сервера или имя узла // -п:х Количество попыток отправки сообщения // „> -о Только отправка сообщений, без приема // «include «include «include i * «define DEFAULT_COUNT 20 «define DEFAULT_PORT 5150 «define DEFAULT_BUFFER 2048 «define DEFAULT_MESSAGE "This is a test of the emergency broadcasting system" char szServer[128], //Сервер соединения szMessage[1024]; //Сообщение, отправляемое серверу int iPort = DEFAULT_PORT; //Порт сервера соединения DWORD dwCount = DEFAULT_COUNT; //Число отправлок сообщения BOOL bSendOnly = FALSE; //Только отправка сообщений, без приема // Функция: usage // // Описание: // Выводит сведения о параметрах командной строки и выходит // void usage() { т printf("usage: c l i e n t [-p:x] [-s:IP] [-n:x] [-o]\n\n"); ?гч p r i n t f ( " -p:x Remote port to send t o \ n " ) ; p r i n t f ( " -s:IP Server's IP address or host name\n"); p r i n t f ( " -n:x Number of times to send message\n"); p r i n t f ( " -o Send messages only; don't receive\n"); ExitProcess(i); // Функция: ValidateArgs // H'tuot чпг,",л<$й > • «АЛ lent ГЛАВА 7 Основы Winsock 183 Листинг 7-2. {продолжение) ц описание: Анализ* // некоторые глобальные флаги для указания выполняемых действий // Анализирует параметры командной строки и задает v 0 i d ValidateArgsdnt argc, char **argv) i n t il 1 84 ЧАСТЬ II Интерфейс прикладного программирования Wmsock Листинг 7-2. (продолжение) ^штжиЛоо^ &А SOCKET sClient; _ :©«)• char szBuffer[DEFAULT_BUFFER]; оиднвмо i n t ret, myt -*пЬ*% i; i '«Tno • struct sockaddr_in server; struct hostent «host = NULL; ;l // Анализ командной строки и загрузка Winsock {•+! ;сцр8 > i ;г » ValidateArgs(argc, argv); ' '*) || ('-' • if (WSAStartup(MAKEW0RD(2,2), &wsd) 1=0)-* } { }&t|mv«^iew«i0j) rto five, printf("Failed to load Winsock libraryWjnt^wiu } return 1; \\ -seas» strcpy(szMessage, DEFAULT_MESSAGE); 1ч1й // // Создание сокета и попытка подключения к серверу г **а // sClient = socket(AF_INET, SOCK_STREAM, IPPROTO.TCP); if (sClient == INVALID_SOCKET) printf("socket() failed: Xd\n", WSAGetLastErrorO); return 1; } server.sin_family = AF_INET; server.sin_port = htons(iPort); server.sin_addr.s_addr = inet_addr(szServer); // // Если адрес сервера не соответствует форме // "aaa.bbb.ccc.ddd", значит это имя узла, и его следует разрешить // if (server.sin_addr.s_addr == INADDR_NONE) { host = gethostbyname(szServer); if (host == NULL) < printf("Unable to resolve server: Xs\n", szServer); return 1; } CopyMemory(&server.sin_addr, host->h_addr_list[0], host->h_length); } if (connect(sClient, (struct sockaddr *)4server, sizeof(server)) == S0CKET_ERR0R) { printf("connect() failed: Xd\n", WSAGetLastErrorO); return 1; ГЛАВА 7 Основы Winsock 185 Листинг 7-2. (продолжение) II Отправка и прием данных for(i = 0; i < dwCount; ret = send(sClient, szMessage, strlen(szMessage), 0); if (ret == 0) break; else if (ret == SOCKET.ERROR) { printf("send() failed: Xd\n", WSAGetLastErrorO); break; 'i' } printf("Send Xd bytes\n", ret); ,.t if (IbSendOnly) - { ret = recv(sClient, szBuffer, DEFAULT_BUFFER, 0); if (ret == 0) // Корректное завершение сеанса '' ' break; else if (ret == SOCKET_ERROR) '. », < printf("recv() failed: Xd\n", WSAGetLastErrorO); break; •• ' , '' szBuffer[ret] = '\0'; printf("RECV [Xd bytes]: 'Xs'\n", ret, szBuffer); closesocket(sClient); -v ., WSACleanupO; return 0; } Протоколы, не требующие соединения финцип действия таких протоколов иной, так как в них используются дру- гие методы отправки и приема данных. Обсудим сначала получателя (или сервер), потому что не требующий соединения приемник мало отличается о т серверов, требующих соединения. Приемник Процесс получения данных на сокете, не требующем соединения, прост. Начала создают сокет функцией socket или WSASocket. Затем выполняют привязку сокета к интерфейсу, на котором будут принимать данные, функ- Чией bind (как и в случае протоколов, ориентированных на сеансы). Разни- Ч а в том, что нельзя вызвать listen или accept: вместо этого нужно просто 186 ЧАСТЬ II Интерфейс прикладного программирования Winsock ожидать приема входящих данных. Поскольку в этом случае соединения нет, принимающий сокет может получать дейтаграммы от любой машины в сети. Простейшая функция приема — recvform-. int recvfrom( SOCKETS, char FAR* buf, int len, int flags. ' struct sockaddr FAR* from, int FAR* fro m len )•• t < 0 « . Первые четыре параметра такие же, как и для функции recv, включают до- пустимые значения для flags-. MSGJDOB и MSG_PEEK. Параметр from — струк- тура SOCKADDR для данного протокола слушающего сокета, на размер струк- туры адреса ссылается fromlen. После возврата вызова структура SOCKADDR будет содержать адрес рабочей станции, которая отправляет данные. В Winsock 2 применяется другая версия recvform — WSARecvForm-. int WSARecvFrom( J SOCKET s, tr, LPWSABUF lpBuffers, - DWORD dwBufferCount, >Ы •»" LPDWORD lpNumberOfBytesRecvd, LPDWORD l p F l a g s , ?*' s t r u c t sockaddr FAR * lpFrom, LPINT l p F r o m l e n , / ' f - LPWSAOVERLAPPED l p O v e r l a p p e d , LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE 'fit • Разница между версиями — в использовании структуры WSABUF для полу- чения данных. Вы можете предоставить один или несколько буферов WSABUF, указав их количество в divBufferCount — в этом случае возможен комплекс- ный ввод-вывод. Суммарное количество считанных байт передается в ipNum- berOfBytesRecvd. При вызове функции WSARecvFrom, lpFlags может принимать следующие значения: 0 (при отсутствии параметров), MSGJDOB, MSG_PEEK нпа MSGJPARTIAL. Данные флаги можно комбинировать логической опера- цией ИЛИ. Если при вызове функции задан флаг MSGJPARTIAL, поставщик пе- решлет данные даже в случае приема лишь части сообщения. По возвраще- нии флаг задается в MSGJPARTIAL, только если сообщение принято частич- но. По возвращении WSARecvFrom присвоит параметру lpFrom (указатель на структуру SOCKADDR) адрес компьютера-отправителя. Опять же ipFromLen указывает на размер структуры SACKADDR, однако в данной функции он явля- ется указателем на DWORD. Два последних параметра — lpOverlapped и lpCom- pletionROUTINE, используются для перекрытого ввода-вывода (см. главу 8). Другой способ приема (отправки) данных в сокетах, не требующих со- единения, — установление соединения (хоть это и звучит странно). После создания сокета можно вызвать connect или WSAConnect, присвоив парамет- Г Л А В А 7 Основы Winsock 187 ру SOCKADDR аДр^с удаленного компьютера, с которым необходимо связать- ся. Фактически никакого соединения не происходит. Адрес сокета, передан- ный в функцию соединения, ассоциируется с сокетом, чтобы было можно использовать функции recv и WSARecv вместо recvfrom или WSARecvFrom (по- скольку источник данных известен). Если приложению нужно одновремен- но связываться лишь с одной конечной точкой, задействуйте возможность подключить сокет дейтаграмм. Отправитель Есть два способа отправки данных через сокет, не требующий соединения. Первый и самый простой — создать сокет и вызвать функцию sendto или WSASendTo. Рассмотрим сначала функцию sendto: int sendto( SOCKET s, const char FAR • buf, m t len, int flags, const struct sockaddr FAR * to, mt tolen ); Параметры этой функции такие же, как и у recvfrom, за исключением buf— буфера данных для отправки, и len — показывающего сколько байт от- правлять. Параметр to — указатель на структуру SOCKADDR с адресом при- нимающей рабочей станции. Также можно использовать функцию WSASendTo из Winsock 2: 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 dwBufferCount задает ко- личество структур. Для комплексного ввода-вывода можно отправить не- сколько структур WSABUF. Перед выходом WSASendTo присваивает четверто- му параметру — lpNumberOfBytesSent, количество реально отправленных Получателю байт. Параметр lpTo — структура SOCKADDR для данного прото- кола с адресом приемника. Параметр iToLen — длина структуры SOCKADDR. Два последних параметра — lpOverlapped и lpCompletionROUTINE, применя- ются для перекрытого ввода-вывода (см. также главу 8). 188 ЧАСТЬ И Интерфейс прикладного программирования Winsock Как и при получении данных, сокет, не требующий соединения, можно подключать к адресу конечной точки и отправлять данные функциями send и WSASend. После создания этой привязки использовать для обмена данными функции sendto или WSASendTo с другим адресом нельзя — будет выдана ошиб- ка WSAEISCONN. Отменить привязку сокета можно, лишь вызвав функцию dosesocket с описателем этого сокета, после чего следует создать новый сокет. Протоколы, ориентированные на передачу сообщений Большинство протоколов, требующих соединения, — потоковые, а не тре- бующих соединения — ориентированы на передачу сообщений. Поэтому при отправке и приеме данных нужно учесть ряд факторов. Во-первых, по- скольку ориентированные на передачу сообщений протоколы сохраняют границы сообщений, данные, поставленные в очередь отправки, блокируют- ся до завершения выполнения функции отправки. Если отправка не может быть завершена, при асинхронном или неблокирующем режиме ввода-вы- вода функция отправки вернет ошибку WSAEWOULDBLOCK. Это означает, что базовая система не смогла обработать данные и нужно попытаться вызвать функцию отправки повторно (подробней — в главе 8). Главное — в ориен- тированных на сообщения протоколах запись происходит только в резуль- тате самостоятельного действия. С другой стороны, при вызове функции приема нужно предоставить вме- стительный буфер, иначе функция выдаст ошибку WSAEMSGSIZE-. буфер за- полнен, и оставшиеся данные отбрасываются. Исключением являются про- токолы, поддерживающие обмен фрагментарными сообщениями, например AppleTalk PAP. Если была принята лишь часть сообщения, до возврата функ- ция WSARecvEx присваивает параметру flag значение MSGJPARTIAL. Для передачи дейтаграмм на основе протоколов, поддерживающих фраг- ментарные сообщения, задействуйте одну из функций WSARecv. При вызове recv нельзя отследить полноту чтения сообщения (эта задача программис- та). После очередного вызова recv будет получена следующая часть дейтаг- рамы. Из-за данного ограничения удобнее использовать функцию WSARe- cvEx, позволяющую задавать и считывать флаг MSGJPARTIAL, который указы- вает на полноту чтения сообщения. Функции Winsock 2 WSARecv и WSARe- cvFrom также поддерживают работу с данным флагом. В заключение рассмотрим, что происходит, когда сокет UDP явно привя- зан к локальному IP-интерфейсу. При использовании UDP-сокетов привяз- ка к сетевому интерфейсу не выполняется. Вы создаете ассоциацию, посред- ством которой IP-интерфейс становится исходным IP-адресом отправляе- мых UDP-дейтаграмм. Физический интерфейс для передачи дейтаграмм фак- тически задает таблица маршрутизации. Если вместо bind вы вызываете sendto или WSASendTo или сначала устанавливаете соединение, сетевой стек автоматически выбирает наилучший локальный IP-адрес из таблицы марш- рутизации. Таким образом, если сначала была выполнена привязка, исход- ный IP-адрес может быть неверным и не соответствовать интерфейсу, с ко- торого фактически была отправлена дейтаграмма. Основы Win*|Bk 189 Освобождение ресурсов сокета Поскольку соединение не устанавливается, его формального разрыва или корректного закрытия не требуется. После прекращения отправки или по- лучения данных отправителем или получателем просто вызывается функция closesocket с описателем требуемого сокета, в результате чего освобождают- ся все выделенные ему ресурсы. Пример Теперь рассмотрим реальный код, выполняющий отправку и прием данных по протоколу. В листинге 7-3 приведен код приемника. Листинг 7-3. Приемник, не требующий установления соединения // Имя модуля: Receiver.с // // Описание: // В данном примере выполняется получение UDP-дейтаграмм при помощи привязки к // конкретному интерфейсу и номеру порта, затем вызовы recvfromO блокируются. // // Параметры компиляции: // cl -о Receiver Receiver.с ws2_32.1ib // // Параметры командной строки: , + // sender [-p:int] [-i:IP][-n:x] [-b:x] // - p : i n t Локальный порт // *i ' - i : I P Локальный IP-адрес, на котором будут прослушиваться соединения // -п:х Количество попыток отправки сообщения // '" f> ' -b: Размер буфера отправки // «include «include «include «define DEFAULT.PORT 5150 «define DEFAULT_COUNT 25 «define DEFAULT_BUFFER_LENGTH 4096 i n t iPort = DEFAULT_PORT; // Порт, на котором будет идти прием DWORD dwCount = DEFAULT_COUNT, // Количество читаемых сообщений dwLength = DEFAULT_BUFFER_LENGTH; // Длина приемного буфера BOOL blnterface = FALSE; // Использование альтернативного интерфейса char szlnterface[32]; // Интерфейс, с которого читаются дейтаграмы // Функция: usage // // Описание: ' I Выводит сведения о параметрах командной строки и выходит. см. след. стр. 1 90 ЧАСТЬ И Интерфейс прикладного программирования Winsock Листинг 7-3. (продолжение) II void usage() { printf("usage: sender [-p:int] [-i:IP][-n:x] [-b:x]\n\n"); printf(" -p:int Local port\n"); printf(" -i:IP Local IP address to listen on\n"); printf(" -n:x Number of times to send message\n"); printf(" -b:x Size of buffer to send\n\n"); ExitProcess(i); // Функция: ValidateArgs // // Описание: // Анализирует параметры командной строки и задает // некоторые глобальные флаги для указания выполняемых действий // void ValidateArgs(int argc, char **argv) { int i; for(i = 1; i < argc; i++) { if ((argv[i][O] == '-') || (argv[i][O] == •/•)) { switch (tolower(argv[i][1])) { case 'p': // Локальный порт lW if (strlen(argv[i]) > 3) ' iPort = atoi(&argv[i][3]); -'" break; case 'n': // Количество попыток приема сообщения »« if (strlen(argv[i]) > 3) dwCount = atol(&argv[i][3]); break; case 'b': // Размер буфера if (strlen(argv[i]) > 3) dwLength = atol(&argv[i][3]); break; case "i 1 : // Интерфейс для приема дейтаграмм if (strlen(argv[i]) > 3) { blnterface = TRUE; strcpy(szlnterface, &argv[i][3]); > break; 'default: usage(); |