Программирование в сетях Windows
.pdf188 |
ЧАСТЬ И Интерфейс прикладного программирования 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 |
|
,, |
|
// |
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; |
ее |
вад поаммЭ |
|
||
•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