Главная страница

Программирование в сетях Windows. Э. Джонс, Д. Оланд


Скачать 2.88 Mb.
НазваниеЭ. Джонс, Д. Оланд
АнкорПрограммирование в сетях Windows.pdf
Дата12.10.2017
Размер2.88 Mb.
Формат файлаpdf
Имя файлаПрограммирование в сетях Windows.pdf
ТипКнига
#9346
страница19 из 50
1   ...   15   16   17   18   19   20   21   22   ...   50

ГЛАВА 7 Основы Winsock
191
Листинг 7-3. (продолжение)
break;
II
Ц функция: main
//
// Описание:
// Главный поток выполнения. Инициализирует Winsock, обрабатывает аргументы
// командной строки, создает сокет, привязывает его к локальному интерфейсу и
// порту, читает дейтаграммы.
i n t main(int argc, char **argv)
WSADATA
SOCKET
char int
DWORD
SOCKADDR_IN
wsd;
s;
*recvbuf = NULL;
ret,
il dwSenderSize;
sender,
local;
* S/bt 'Mlit"\
// Анализ аргументов и загрузка Winsock
//
ValidateArgs(argc, argv);
if (WSAStartup(MAKEW0RD(2,2), &wsd) !=0)
{
printf("WSAStartup failed! \n");
return 1;
Jit<
// Создание сокета и его привязка к локальному интерфейсу и порту
//
s = socket(AF_INET, SOCK.DGRAM, 0); ' sif (s == INVALID_SOCKET)
{
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
1   ...   15   16   17   18   19   20   21   22   ...   50


написать администратору сайта