Программирование в сетях Windows
.pdf1 78 |
ЧАСТЬ II |
|
Интерфейс прикладного программирования Winsock |
||
Листинг 7-1. |
(продолжение) |
|
|
||
for(i = 1; 1 < argc; |
|
|
|||
if |
((argv[i][0] = = • - • ) || |
(argv[i][O] == '/•)) |
|||
{ |
|
|
|
|
|
|
switch |
(tolower(argv[i][1])) |
|
||
|
{ |
|
|
|
|
|
case |
'p': |
|
|
|
|
|
|
iPort = atoi(&argv[i][3]); |
||
|
|
|
break; |
|
|
|
case 'i': |
|
|
||
|
|
|
blnterface = TRUE; |
|
|
|
|
|
if (strlen(argv[i]) |
> |
3) |
|
|
|
strcpy(szAddress, |
&argv[i][3]); |
|
|
|
|
break; |
|
|
|
case |
'o': |
|
|
|
|
|
|
bRecvOnly = TRUE; |
|
|
|
|
|
break; |
|
|
default;
usage();
break;
// Функция: |
ClientThread |
// |
•- * |
II Описание:
//Вызывается в качестве потока, управляет данным клиентским
//соединением. Входным параметром является описатель сокета,
//возвращаемый функцией accept(). Функция получает данные
//от клиента и отправлает их обратно.
//
DWORD WINAPI ClientThread(LPVOID lpParam)
SOCKET |
sock=(SOCKET)lpParam; |
char |
szBuff[DEFAULT_BUFFER]; |
int |
ret, |
|
nLeft, |
|
idx; |
>' |
whiled) |
те |
. |
N, |
Лонднак |
|
|
' |
{ |
|
|
|
г |
г.- v- |
|
|
// |
Блокирующий |
вызов |
recv() |
|
|
|
|
// |
|
|
|
|
|
-л» sat) |
|
ret = recv(sock, szBuff, DEFAULT.BUFFER, 0); |
|
|||||
|
if |
(ret == 0) |
|
// |
Корректное завершение |
^ |
|
|
|
break; |
|
|
|
|
|
|
|
|
ГЛАВА 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;
// Функция: main4 |
* • к |
ШГ |
'I |
' i w |
|
II Описание: |
|
|
//Главный поток выполнения. Инициализирует Winsock, анализирует
//параметры командной строки, создает и прослушивает сокет,
/привязывает его к локальному адресу и ждет подключений клиентов
jnt maindnt argc, char .*argv)
WSADATA wsd;
SOCKET 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)
//local.sin_addr.s_addr = inet_addr(szAddress); if (local.sin_addr.s_addr == INADDR_NONE)
7 |
usage(); |
|
|
|
|
•7 else |
htonl(INADDR_ANY); |
|
/ • ' |
local.sin_addr.s_addr = |
|
7, local.sin_family = AF.INET; |
|
|
|
localc .asinl_iporn t = htons(iPort)r |
; |
>Ж 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 |
<winsock2.h> |
|
|
|
|
||
«include |
<stdio.h> |
|
|
|
|
||
«include |
<stdlib.h> |
|
|
|
|
||
|
|
|
|
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: |
client |
[-p:x] [-s:IP] [-n:x] [-o]\n\n"); ?гч |
p r i n t f ( " |
-p:x |
Remote port to send to\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)
int |
il |
<rt* |
f o r ( i = 1; |
i < argc; i++) |
ЩфтМ |
{ |
|
|
if ((argv[i][0] == '-') |
|| (argv[i][O] == '/')) |
|
{ |
|
|
switch (tolower(argv[i][1])) |
||
{ |
|
|
|
case ' p ' : |
// Удаленный порт |
$ |
if ( s t r l e n ( a r g v [ i ] ) > 3) |
|
,-ftt |
iPort = |
a t o i ( & a r g v [ i ] [ 3 ] ) ; |
?break;
|
case |
' s ' : |
// |
Сервер |
|
|
|
|
.. - |
if ( s t r l e n ( a r g v [ i ] ) > 3) |
|
|
|||
|
( |
strcpy(szServer, &argv[i][3]); |
|
||||
|
|
break; |
|
|
|
|
|
|
case |
'n': |
|
// |
Число отправок сообщения |
||
|
|
if (strlen(argv[i]) > 3) |
|
|
|||
|
|
dwCount = atol(&argv[i][3]); |
|
||||
|
p- |
break; |
|
|
|
|
|
} |
case 'о': |
|
|
// Только |
отправка |
сообщений, без приема |
|
|
|
bSendOnly = TRUE; |
|
|
|
||
|
|
break; |
|
|
vn . |
|
• |
|
default: |
|
|
|
|
|
|
|
|
usage(); |
|
#*qo| те^втугеетооо |
»н |
||
|
иц< |
break; |
s |
N |
,»ав¥ *>«« |
ore |
|
(3«0й Я'ШАЙ! »• ibbti
//Функция: main
//Описание:
I |
Главный поток выполнения. Инициализирует Winsock, анализирует параметры |
||
• |
командной |
строки, создает сокет, соединяется с сервером, |
|
/ |
отправлает |
и |
принимает данные. |
j |
UM argc, |
char ..argv) |
w s d ;
см.след.стр.
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)rtofive, |
|||
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, |
t |
|
|
int |
len, |
|
|
int |
flags. |
' |
< 0 |
struct sockaddr FAR* from, |
|
int FAR* fromlen |
« . |
|
)••
Первые четыре параметра такие же, как и для функции 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 |
lpFromlen, |
|
/ ' f - |
'fit• |
LPWSAOVERLAPPED |
lpOverlapped, |
|
|
|
LPWSAOVERLAPPED_COMPLETION_ROUTINE |
lpCompletionROUTINE |
Разница между версиями — в использовании структуры WSABUF для получения данных. Вы можете предоставить один или несколько буферов WSABUF, указав их количество в divBufferCount — в этом случае возможен комплексный ввод-вывод. Суммарное количество считанных байт передается в ipNumberOfBytesRecvd. При вызове функции WSARecvFrom, lpFlags может принимать следующие значения: 0 (при отсутствии параметров), MSGJDOB, MSG_PEEK нпа MSGJPARTIAL. Данные флаги можно комбинировать логической операцией ИЛИ. Если при вызове функции задан флагMSGJPARTIAL, поставщик перешлет данные даже в случае приема лишь части сообщения. По возвращении флаг задается в MSGJPARTIAL, только если сообщение принято частично. По возвращении WSARecvFrom присвоит параметру lpFrom (указатель на структуру SOCKADDR) адрес компьютера-отправителя. Опять же ipFromLen указывает на размер структуры SACKADDR, однако в данной функции он является указателем на DWORD. Два последних параметра — lpOverlapped и lpCompletionROUTINE, используются для перекрытого ввода-вывода (см. главу 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).