Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
ГЛАВА 7 Основы Winsock{ printf("socket() failed; Xd\n", WSAGetLastErrorO); return 1; } local.sin_family = AF_INET; local.sin_port = htons((short)iPort); if (blnterface) local.sin_addr.s_addr = inet_addr(szlnterface); else local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (SOCKAODR O&local, sizeof(local)) == SOCKET_ERROR) см. след. стр. 1 92 ЧАСТЬ II Интерфейс прикладного программирования Winsock Листинг 7-3. (продолжение) { printf("bind() failed: Xd\n", WSAGetLastErrorO); return 1; > // Выделение буфера приема // recvbuf = GlobalAlloc(GMEH_FIXED, dwLength); if (!recvbuf) { У printf("GlobalAlloc() failed: Xd\n", GetLastErrorO); return 1; } // Чтение дейтаграмм // for(i = 0; l < dwCount; i++) < dwSenderSize = sizeof(sender); ret = recvfrom(s, recvbuf, dwLength, 0, (SOCKADDR *)&sender, idwSenderSize); if (ret == S0CKET_ERR0R) < printf("recvfrom() failed; Xd\n", WSAGetLastErrorO); break; * } else if (ret == 0) break; else { recvbuf[ret] = '\0'; printf("[Xs] sent me: 'Xs'\n", inet_ntoa(sender.sin_addr), recvbuf); closesocket(s); GlobalFree(recvbuf); WSACleanupO; return 0; > Прием дейтаграмм прост. Сначала необходимо создать сокет, затем при- вязать его к локальному интерфейсу. Для привязки к интерфейсу по умолча- нию определите его IP-адрес функцией getsockname. В качестве параметра ей передается сокет, а она возвращает структуру SOCKADDRJN, которая указы- вает связанный с сокетом интерфейс. Затем для чтения входящих данных остается только выполнить вызовы recvfrom. Заметьте, что мы используем recvfrom, потому что нас не интересуют фрагментарные сообщения, так как протокол UDP не поддерживает их передачу. Фактически, стек TCP/IP пыта- ется собрать большое сообщение из полученных фрагментов. Если один или Г Л А В А 7 Основы Winsock 193 несколько фрагментов отсутствуют или нарушен порядок их следования, стек отбрасывает все сообщение. В листинге 7-4 приведен код отправителя, не требующего соединения. В этом примере используются несколько дополнительных параметров. Обяза- тельные параметры — IP-адрес и порт удаленного получателя. Параметр -с заставляет первоначально вызывать connect, что по умолчанию не происхо- дит. Снова все очень просто: сначала создается сокет, если присутствует па- раметр -с — выдается connect с адресом удаленного получателя и номером порта. Затем выполняются вызовы send. Если соединение не нужно, данные просто отправляются получателю после создания сокета функцией sendto. Листинг 7-4. Отправитель, не требующий установления соединения // Имя модуля: Sender.с // // Описание: // Данный пример выполняет отправку UDP-дейтаграмм указанному получателю. // Если задан параметр -с, сначала вызывается connect() для сопоставления // IP-адреса получателя с описателем сокета, чтобы можно было использовать // функцию send() вместо sendto(). // // Параметры компиляции: // cl -о Sender Sender.с ws2_32.1ib // // Параметры командной строки: // sender [-p:int] [-r:IP] [-с] [-n:x] [-b:x] [-d:c] // -p:int Удаленный порт // t , f -r:IP IP-адрес получателя или имя узла // '^ " -с Предварительно соединиться с удаленным узлом // -п:х Количество попыток отправки сообщения // -Ь:х Размер буфера отправки // -d:c Символ для заполнения буфера // <••* «include «include «include «define DEFAULT_PORT 5150 «define DEFAULT.COUNT 25 «define DEFAULT_CHAR 'a p «define DEFAULT_BUFFER_LENGTH 64 BOOL bConnect = FALSE; // Предварительное соединение int iPort = DEFAULT_PORT; // Порт для отправки данных char cChar = DEFAULT_CHAR; // Символ для заполнения буфера DWORD dwCount = DEFAULT_COUNT, // Количество сообщений для отправки dwLength = DEFAULT_BUFFER_LENGTH; // Длина буфера отправки char szRecipient[128]; // IP-адрес или имя хоста получателя i ff < (ii ti см. след. стр. 1 9 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock Листинг 7-4. (продолжение) II II Функция: usage // Описание: // Выводит сведения о параметрах командной строки и выходит. void usage() printf("usage: sender [-p:int] [-r:IP] " "[-c] [-n:x] [-b:x] [-d:c]\n\n"); printfC -p:int Remote port\n"); -r:IP -c -n:x -b:x printfC printfC printfC printfC printfC ExitProcess(i); -d:c Recipient's IP address or host name\n"); Connect to remote IP first\n"); Number of times to send message\n"); Size of buffer to send\n"); Character to f i l l buffer with\n\n"); ,, // Функция: ValidateArgs // f» // Описание: // Анализирует параметры командной строки и задает // некоторые глобальные флаги для указания выполняемых действий v o i d V a l i d a t e A r g s ( i n t a r g c , c h a r * » a r g v ) i n t ' %• for(i = 1; i < argc; if <(argv[i][0] == '-' ее вад поаммЭ •bit), (argv[i][0] 1 switch (tolower(argv[i][1])) < case 'p': // Удаленный порт ^ if (strlen(argv[i]) > 3) $ , iPort = atoi(&argv[i][3]); break; case 'r': // IP-адрес получателя if (strlen(argv[i]) > 3) strcpy(szRecipient, &argv[i][3]); break; case 'с': // Подключение к IP адресу получателя bConnect = TRUE; break; case 'n': // Количество попыток отправки сообщения if (strlen(argv[i]) > 3) Г Л А В А 7 Основы Winsock 195 Листинг 7-4. (продолжение) dwCount = atol(&argv[i][3]); break; 1 case ' b ' : // Размер буфера if ( s t r l e n ( a r g v [ i ] ) > 3) dwLength = a t o l ( & a r g v [ i ] [ 3 ] ) ; break; case ' d ' : // Символ для заполнения буфера , cChar = a r g v [ i ] [ 3 ] ; I break; , is<» default: ( usage(); '• й break; } у i, * i • Ш } \ } -• //Функция: main *Ш< II II Описание: // Главный поток выполнения. Инициализирует Winsock, обрабатывает аргументы // командной строки, создает сокет, при необходимости подключается по удаленному // IP-адресу, затем отправляет дейтаграммы получателю. // int main(int argc, char **argv) •.bS'is'r SOCKADDR_IN recipient; // Анализ аргументов и загрузка Winsock // ValidateArgs(argc, argv); if (WSAStartup(MAKEW0RD(2, 2), &wsd) != 0) < printf("WSAStartup failed!\n"); return 1; > // Создание сокета // s = socket(AF_INET, SOCK.DGRAM, 0); if (s == INVALID_SOCKET) { printf("socket() failed; Xd\n", WSAGetLastErrorO); см. след. стр. WSADATA SOCKET char int wsd; •' s; *sendbuf = ret, NULL; 1 9Ь ЧАСТЬ II Интерфейс прикладного программирования Winsock Листинг 7-4. (продолжение') return 1; // Разрешение IP-адреса или имени узла получателя recipient sin_fainily = AF_INET; recipient sin_port = htons((short)iPort); if ((recipient.sin_addr.s_addr = inet_addr(szRecipient)) == INADDR_NONE) s t r u c t hostent *host=NllLL, host = gethostbyname(szRecipient), i f (host) CopyMemory(&recipient.sm_addr, host->h_addr_list[0], host->h_length); else , printf("gethostbyname() f a i l e d : Xd\n", WSAGetLastErrorO); WSACleanupO; return 1; // Выделение буфера отправки sendbuf = GlobalAlloc(GMEM_FIXED, dwLength); ,. if (isendbuf) { printf("GlobalAllocO failed: Xd\n", GetLastErrorO); return 1; memset(sendbuf, cChar, dwLength); // Если задан параметр -с, выполняется "^^дрючение" к отправителю // и отправка данных функцией send(). т $щ M e t t , ь , II if (bConnect) if (connect(s, (SOCKADDR *)&recipient, sizeof(recipient)) == SOCKET.ERROR) printf("connect() failed Xd\n", WSAGetLastErrorO); GlobalFree(sendbuf), WSACleanupO, return 1; for(i = 0; l < dwCount; i++) ret = send(s, sendbuf, dwLength, 0); щ < Ч^\ ч ^ м if (ret == S0CKET_ERR0R) Г Л А В А 7 Основы Winsock 197 Листинг 7-4. (продолжение) printf("send() failed: Xd\n", WSAGetLastErrorO); break; } else if (ret == 0) break; // Функция send() отработала успешно! else { // Иначе используется функция sendtoQ // for(i = 0, i < dwCount; i++) { ret = sendto(s, sendbuf, dwLength, 0, (SOCKADDR *)&recipient, sizeof(recipient)); if (ret == SOCKET_ERROR) { printf("sendto() failed; Xd\n", WSAGetLastErrorO); break; } else if (ret == 0) break; // Функция sendtoO отработала успешно! closesocket(s); GlobalFree(sendbuf); WSACleanupO, return 0; Дополнительные функции API Рассмотрим API-функции Winsock, которые пригодятся вам при создании сетевых приложений Функция getpeername Эта функция возвращает информацию об адресе сокета партнера на под- ключенном сокете ir) t getpeername( SOCKET s, struct sockaddr FAR* name, mt FAR. namelen 1 УЬ ЧАСТЬ II Интерфейс прикладного программирования Winsock Первый параметр — сокет для соединения, два последних — указатель на структуру SOCKADDR базового протокола и ее длина. Для сокетов дейтаграмм данная функция возвращает адрес, переданный вызову соединения (за ис- ключением адресов, переданных в вызов sendto или WSASendTd). Функция getsockname Эта функция противоположна getpeernctme и возвращает адресную инфор- мацию для локального интерфейса определенного сокета: int getsockname( SOCKET s, struct sockaddr FAR* name, int FAR* namelen Используются те же параметры, что и для getpeername, однако возвраща- ется информация о локальном адресе. В случае TCP адрес совпадает с соке- том сервера, слушающим на заданном порте и IP-интерфейсе. Функция WSADuplicateSocket Данная функция применяется для создания структуры WSAPROTOCOLJNFO, которую можно передать другому процессу, что позволит ему открыть опи- сатель того же базового сокета и оперировать данным ресурсом. Заметьте: такая необходимость возникает только между процессами. Потоки в одном и том же процессе могут свободно передавать описатели сокета. Функция определена так: i n t WSADuplicateSocket( у ь". ••n\'{i\ SOCKET s, DWORD dwProcessId, ,{"$Mb LPWSAPR0T0C0L_INF0 lpProtocolInfo ); Первый параметр — копируемый описатель сокета. Второй — dwProcessId, код процесса, коорый будет использовать скопированный сокет. Третий па- раметр — lpProtocolInfo, указатель на структуру WSAPROTOCOLJNFO, которая содержит информацию, необходимую целевому процессу для открытия ко- пии описателя. Некоторые виды межпроцессного взаимодействия должны происходить таким образом, чтобы текущий процесс мог передать структу- ру WSAPROTOCOLJNFO целевому, который затем использует ее для создания описателя сокета (при помощи функции WSASockef). Описатели в обоих сокетах можно использовать для ввода-вывода неза- висимо, однако Winsock не обеспечивает контроля за доступом, поэтому программисту необходимо предусмотреть некие способы синхронизации. Все сведения о состоянии любого сокета хранятся в одном месте для всех его описателей, так как копируются описатели сокета, а не сам сокет. Например, любой параметр сокета, заданный функцией setsockopt для одного из описа- телей, затем можно увидеть, вызвав функцию getsockopt для любого другого его описателя. Если процесс вызывает closesocket для копии сокета, описатель Г Л А В А 7 Основы Winsock nay в данном процессе освобождается, однако сокет останется открытым, пока функция closesocket не будет вызвана для последнего его описателя. Кроме того, учтите общие особенности сокетов при уведомлении с ис- пользованием WSAAsyncSelect и WSAEventSelect. Эти функции применяются для асинхронного ввода-вывода (см. главу 8). При их вызове с любым из общих описателей отменяется регистрация любого предыдущего события для со- кета, независимо от того, какой описатель применялся для регистрации. Поэтому, например, через общий сокет нельзя доставить события FDREAD процессу А и события FDJWRITE процессу В. Если необходимо передавать уведомления о событиях по обоим описателям, измените конструкцию при- ложения, использовав потоки вместо процессов. Функция TransmitFile Это расширение Microsoft позволяет быстро передавать данные из файла. Высокая эффективность обусловлена тем, что вся передача данных происхо- дит в режиме ядра. Если приложение считывает блок данных из файла, а за- тем вызывает send или WSASend, происходят многократные переключения между режимами ядра и пользовательским. При вызове TransmitFile весь про- цесс чтения и отправки выполняется в режиме ядра. Функция определена таю BOOL TransmitFile( SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, LPOVERLAPPED lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwFlags ); Параметр hSocket — подключенный сокет, по которому будет передан файл. Параметр hFile — описатель открытого файла. Параметр nNumberOj- BytesToWrite задает количество записываемых из файла байт. Если он равен О, файл отправляется целиком. Параметр nNumberOfBytesPerSend задает раз- мер отправляемых блоков данных для операций записи. Например, если он равен 2048, TransmitFile передаст файл порциями по 2 кб; если 0 — исполь- зуется стандартный размер отправки. Параметр lpOverlapped определяет структуру OVERLAPPED, применяемую в перекрытом вводе-выводе (см. так- же главу 8). Следующий параметр — lpTransmitBuffers, представляет собой структуру TRANSMIT'JFILEBUFFERS, содержащую данные, которые нужно отправить до и после передачи файла: typedef struct _TRANSMIT_FILE_BUFFERS { PVOID Head; DWORD HeadLength; PVOID Tail; DWORD TailLength; > TR ANSMIT_FILE_BUFFERS- 2 0 0 ЧАСТЬ И Интерфейс прикладного программирования Winsock Поле Head — указатель на данные, которые отправляются перед переда- чей файла Поле HeadLength задает количество заранее передаваемых дан- ных Поле Tail ссылается на данные, отправляемые после передачи файла В TailLength указано количество передаваемых затем байт Последний параметр — dwFlags, управляет режимами работы TransmitFtle Вот описание используемых в нем флагов Ш TF_DISCONNECT — инициирует закрытие сокета после передачи данных Ш TF_KEUSE_SOCKET — позволяет повторно использовать в функции Ас- ceptEx описатель сокета в качестве клиентского сокета К TFUSEDEFAULT WORKER и TF USE SYSTEM THREAD - указывают, что передача должна идти в контексте стандартного системного процес- са Этот флаги полезны при передаче больших файлов • TF_USE_KERNEL_APC — указывает, что передача должна выполняться ядром при помощи асинхронных вызовов процедур (Asynchronous Pro- cedure Call, APC) Это существенно увеличивает производительность, если для считывания файла в кэш требуется лишь одна операция чтения • TFWRITEBEHIND — указывает, что TransmitFile может завершиться, не получив подтверждений о приеме данных от удаленной системы Для платформы Windows СЕ Вся информация из предыдущих разделов в равной степени относится к Windows СЕ Исключение — функции, специфичные для Winsock 2, посколь- ку Windows СЕ опирается на спецификацию Winsock 1 1 например WSA-раз- новидностей функций В Windows СЕ доступны только следующие WSA- функции WSAStartup, WSACleanup, WSAGetLastError и WSAIoctl Мы уже обсуж- дали первые три из них, а о последней поговорим в главе 9 Windows СЕ поддерживает протокол TCP/IP, следовательно, у вас есть до-j ступ как к TCP, так и к UDP Помимо TCP/IP поддерживаются инфракрасны^ сокеты Протокол IrDA поддерживает только потоковые соединения П использовании обоих протоколов выполняют все обычные API-вызовы Win4 sock I 1 для подготовки сокетов и передачи данных Необходимо учитывать ошибку в Windows СЕ 2 0, связанную с дейтаграммными UDP-сокетами вы- зов функций send или sendto влечет утечки памяти ядра Эта ошибка исправле- на в Windows СЕ 2 1, но из-за того, что ядро записано в ПЗУ, в Windows СЕ 2 0 невозможно устранить данную проблему при помощи распространяемых программных обновлений Единственное решение — отказаться от исполь- зования дейтаграмм в Windows СЕ 2 0 Windows СЕ не поддерживает консольные приложения и использует толь- ко кодировку UNICODE, поэтому примеры, представленные в данной главе, предназначены для Windows 95,98, NT и 2000 Мы приводим их, чтобы дать вам возможность изучить основные концепции Winsock без утомительного рассмотрения программного кода Почти всегда необходим пользовательс- кий интерфейс, если только вы не пишете службу для Windows СЕ — тогда потребуется создать множество дополнительных функций для обработчиков Г Л А В А 7 Основы Winsock 201 событий окон и других элементов пользовательского интерфейса, разбор которых помешает вам понять главные аспекты применения Winsock Также существует дилемма использовать функции Winsock, поддержива- ющие UNICODE, или нет Выбор кодировки лежит на программисте Winsock все равно, что вы передаете функциям лишь бы это был действительный буфер (конечно, нужно привести буфер к соответсвующему типу, чтобы не появлялись предупреждения при компиляции) Если вы приведете строку UNICODE к char ', не забудьте соответственно изменить параметр длины, задающий количество отправляемых байт Для правильного отображения любых отправленных или принятых данных в Windows СЕ необходимо убе- диться, что они в кодировке UNICODE Это нужно и для любых других фун- кций Win32, требующих строк в кодировке UNICODE В общем, создание приложений Winsock в Windows СЕ более трудоемко Для компиляции и запуска приведенных примеров в Windows СЕ потре- буется незначительно изменить код Заголовочным файлом будет Winsock h, в отличие от Winsock2 h Функция WSAStartup должна загружать версию Win- sock 1 1, потому что она текущяя в Windows СЕ Эта ОС не поддерживает кон- сольных приложений, поэтому необходимо использовать функцию WtnMam вместо тат Не требуется включать окно в приложение, просто не исполь- зуйте функции консольного текстового ввода-вывода, напримерргш/f |