Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование в сетях Windows

.pdf
Скачиваний:
538
Добавлен:
11.03.2015
Размер:
3.02 Mб
Скачать

188

ЧАСТЬ И Интерфейс прикладного программирования Winsock

Как и при получении данных, сокет, не требующий соединения, можно подключать к адресу конечной точки и отправлять данные функциями send и WSASend. После создания этой привязки использовать для обмена данными функции sendto или WSASendTo с другим адресом нельзя — будет выдана ошибка WSAEISCONN. Отменить привязку сокета можно, лишь вызвав функцию dosesocket с описателем этого сокета, после чего следует создать новый сокет.

Протоколы, ориентированные на передачу сообщений

Большинство протоколов, требующих соединения, — потоковые, а не требующих соединения — ориентированы на передачу сообщений. Поэтому при отправке и приеме данных нужно учесть ряд факторов. Во-первых, поскольку ориентированные на передачу сообщений протоколы сохраняют границы сообщений, данные, поставленные в очередь отправки, блокируются до завершения выполнения функции отправки. Если отправка не может быть завершена, при асинхронном или неблокирующем режиме ввода-вы- водафункцияотправкивернетошибкуWSAEWOULDBLOCK.Этоозначает,что базовая система не смогла обработать данные и нужно попытаться вызвать функцию отправки повторно (подробней — в главе 8). Главное — в ориентированных на сообщения протоколах запись происходит только в результате самостоятельного действия.

С другой стороны, при вызове функции приема нужно предоставить вместительный буфер, иначе функция выдаст ошибку WSAEMSGSIZE-. буфер заполнен, и оставшиеся данные отбрасываются. Исключением являются протоколы, поддерживающие обмен фрагментарными сообщениями, например AppleTalk PAP. Если была принята лишь часть сообщения, до возврата функцияWSARecvExприсваиваетпараметруflagзначениеMSGJPARTIAL.

Для передачи дейтаграмм на основе протоколов, поддерживающих фрагментарные сообщения, задействуйте одну из функций WSARecv. При вызове recv нельзя отследить полноту чтения сообщения (эта задача программиста). После очередного вызова recv будет получена следующая часть дейтаграмы. Из-за данного ограничения удобнее использовать функцию WSARecvEx, позволяющую задавать и считывать флагMSGJPARTIAL, который указывает на полноту чтения сообщения. Функции Winsock 2 WSARecv и WSARecvFrom также поддерживают работу с данным флагом.

В заключение рассмотрим, что происходит, когда сокет 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:IP

Локальный

IP-адрес,

на

котором будут прослушиваться

соединения

//

 

 

-п:х

 

Количество попыток отправки

сообщения

 

//

'"

f>' -b:

 

Размер буфера отправки

 

 

 

//

 

 

 

 

 

 

 

 

 

 

 

«include

<winsock2.h>

 

 

 

 

 

 

«include

<stdio.h>

 

 

 

 

 

 

 

«include

<stdlib.h>

 

 

 

 

 

 

 

«define DEFAULT.PORT

5150

 

 

 

 

 

«define DEFAULT_COUNT

25

 

 

 

 

 

«define DEFAULT_BUFFER_LENGTH

4096

 

 

 

 

 

int

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

"i1 :

// Интерфейс для приема дейтаграмм

if (strlen(argv[i]) > 3)

{

blnterface = TRUE; strcpy(szlnterface, &argv[i][3]);

>

break;

'default:

usage();

ГЛАВА 7 Основы Winsock

191

Листинг 7-3. (продолжение) break;

II

Ц функция:

main

 

 

 

 

 

 

//

 

 

 

 

 

 

 

 

//

Описание:

 

 

 

 

 

 

 

//

Главный поток

выполнения.

Инициализирует Winsock,

обрабатывает

аргументы

//

командной строки,

создает

сокет,

привязывает его

к локальному

интерфейсу и

//

порту,

читает

дейтаграммы.

 

 

 

 

i n t

main(int

argc,

char

**argv)

 

 

 

 

 

WSADATA

 

wsd;

 

 

 

 

 

 

SOCKET

 

s;

 

 

 

 

 

 

char

*recvbuf = NULL;

 

 

 

 

int

 

ret,

 

 

 

 

 

 

 

 

il

 

 

 

 

 

 

DWORD

 

dwSenderSize;

*

S/bt 'Mlit"\

 

 

 

SOCKADDR_IN

sender,

 

 

 

 

 

local;

 

 

 

 

 

// Анализ аргументов и загрузка Winsock

 

 

 

//

 

 

 

 

 

 

 

 

ValidateArgs(argc, argv);

 

 

 

 

 

if (WSAStartup(MAKEW0RD(2,2),

&wsd)

!=0)

 

 

 

{

 

 

 

 

 

 

 

 

printf("WSAStartup failed! \n");

 

Jit<

 

 

return 1;

 

 

 

 

 

 

// Создание сокета и его привязка к локальному интерфейсу и порту

//

s = socket(AF_INET, SOCK.DGRAM, 0); ' s<Jv. if (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

<winsock2.h>

 

 

 

«include

<stdio.h>

 

 

 

«include

<stdlib.h>

 

 

 

«define

DEFAULT_PORT

 

 

5150

«define

DEFAULT.COUNT

 

 

25

«define

DEFAULT_CHAR

 

 

'ap

«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 94

ЧАСТЬ 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

 

Recipient'Remots IP address or host name\n");

 

-p:int

port\n");

printfC

-r:IP

Connect to remote IP first\n");

printfC

-c

Number of times to send message\n");

printfC

-n:x

Size of buffer to send\n");

printfC

-db:c

Character to f i l l buffer with\n\n");

 

x

 

ExitProcess(i);

 

 

// Функция: ValidateArgs

 

,,

//

 

//Описание:

//Анализирует параметры командной строки и задает

// некоторые глобальные флаги для указания выполняемых действий

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;

ее

вад поаммЭ

 

•bit),

 

 

if <(argv[i][0] == '-'

(argv[i][0]

 

 

 

switch (tolower(argv[i][1]))

 

 

 

<

 

 

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 = atol(&argv[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)

 

 

WSADATA

 

wsd;

 

 

•.bS'is'r

 

SOCKET

•'

s;

 

 

 

char

 

*sendbuf

= NULL;

 

 

int

 

ret,

 

 

 

 

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);

см.след.стр.

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)

struct hostent *host=NllLL,

host = gethostbyname(szRecipient), i f (host)

CopyMemory(&recipient.sm_addr, host->h_addr_list[0], host->h_length);

else

,printf("gethostbyname() failed: 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