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

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

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

1 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).