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

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


Скачать 2.88 Mb.
НазваниеЭ. Джонс, Д. Оланд
АнкорПрограммирование в сетях Windows.pdf
Дата12.10.2017
Размер2.88 Mb.
Формат файлаpdf
Имя файлаПрограммирование в сетях Windows.pdf
ТипКнига
#9346
страница18 из 50
1   ...   14   15   16   17   18   19   20   21   ...   50
ГЛАВА 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;
// Функция: main
4
* •
к
ШГ
'I
' i w
II Описание:
// Главный поток выполнения. Инициализирует Winsock, анализирует
// параметры командной строки, создает и прослушивает сокет,
/ привязывает его к локальному адресу и ждет подключений клиентов jnt maindnt argc, char .*argv)
WSADATA
SOCKET
wsd;
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)
//
7
•7
/ • '

local.sin_addr.s_addr = inet_addr(szAddress);
if (local.sin_addr.s_addr == INADDR_NONE)
usage();
else
local.sin_addr.s_addr = htonl(INADDR_ANY);
7, local.sin_family = AF.INET;
l o c a l s i n t htons(iPor
local.sin_port = htons(iPort);
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
«include
«include
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: c l i e n t [-p:x] [-s:IP] [-n:x] [-o]\n\n"); ?гч p r i n t f ( " -p:x Remote port to send t o \ 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)
i n t il 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, & a r g v [ i ] [ 3 ] ) ;
break;
case 'n': // Число отправок сообщения if (strlen(argv[i]) > 3)
dwCount = atol(&argv[i][3]);
p- break;
} case 'о': // Только отправка сообщений, без приема bSendOnly = TRUE;
break; v n . •
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) rto five,
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,
int len,
int flags. '
struct sockaddr FAR* from,
int FAR* fro m
len
)••
t
< 0
« .
Первые четыре параметра такие же, как и для функции 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 l p F r o m l e n , / ' f -
LPWSAOVERLAPPED l p O v e r l a p p e d ,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
'fit •
Разница между версиями — в использовании структуры WSABUF для полу- чения данных. Вы можете предоставить один или несколько буферов WSABUF,
указав их количество в divBufferCount — в этом случае возможен комплекс- ный ввод-вывод. Суммарное количество считанных байт передается в ipNum-
berOfBytesRecvd. При вызове функции WSARecvFrom, lpFlags может принимать следующие значения: 0 (при отсутствии параметров), MSGJDOB, MSG_PEEK
нпа MSGJPARTIAL. Данные флаги можно комбинировать логической опера- цией ИЛИ. Если при вызове функции задан флаг MSGJPARTIAL, поставщик пе- решлет данные даже в случае приема лишь части сообщения. По возвраще- нии флаг задается в MSGJPARTIAL, только если сообщение принято частич- но. По возвращении WSARecvFrom присвоит параметру lpFrom (указатель на структуру SOCKADDR) адрес компьютера-отправителя. Опять же ipFromLen
указывает на размер структуры SACKADDR, однако в данной функции он явля- ется указателем на DWORD. Два последних параметра — lpOverlapped и lpCom-
pletionROUTINE, используются для перекрытого ввода-вывода (см. главу 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).

188
ЧАСТЬ И Интерфейс прикладного программирования Winsock
Как и при получении данных, сокет, не требующий соединения, можно подключать к адресу конечной точки и отправлять данные функциями send
и WSASend. После создания этой привязки использовать для обмена данными функции sendto или WSASendTo с другим адресом нельзя — будет выдана ошиб- ка WSAEISCONN. Отменить привязку сокета можно, лишь вызвав функцию
dosesocket с описателем этого сокета, после чего следует создать новый сокет.
Протоколы, ориентированные на передачу сообщений
Большинство протоколов, требующих соединения, — потоковые, а не тре- бующих соединения — ориентированы на передачу сообщений. Поэтому при отправке и приеме данных нужно учесть ряд факторов. Во-первых, по- скольку ориентированные на передачу сообщений протоколы сохраняют границы сообщений, данные, поставленные в очередь отправки, блокируют- ся до завершения выполнения функции отправки. Если отправка не может быть завершена, при асинхронном или неблокирующем режиме ввода-вы- вода функция отправки вернет ошибку WSAEWOULDBLOCK. Это означает, что базовая система не смогла обработать данные и нужно попытаться вызвать функцию отправки повторно (подробней — в главе 8). Главное — в ориен- тированных на сообщения протоколах запись происходит только в резуль- тате самостоятельного действия.
С другой стороны, при вызове функции приема нужно предоставить вме- стительный буфер, иначе функция выдаст ошибку WSAEMSGSIZE-. буфер за- полнен, и оставшиеся данные отбрасываются. Исключением являются про- токолы, поддерживающие обмен фрагментарными сообщениями, например
AppleTalk PAP. Если была принята лишь часть сообщения, до возврата функ- ция WSARecvEx присваивает параметру flag значение MSGJPARTIAL.
Для передачи дейтаграмм на основе протоколов, поддерживающих фраг- ментарные сообщения, задействуйте одну из функций WSARecv. При вызове
recv нельзя отследить полноту чтения сообщения (эта задача программис- та). После очередного вызова recv будет получена следующая часть дейтаг- рамы. Из-за данного ограничения удобнее использовать функцию WSARe-
cvEx, позволяющую задавать и считывать флаг MSGJPARTIAL, который указы- вает на полноту чтения сообщения. Функции Winsock 2 WSARecv и WSARe-
cvFrom также поддерживают работу с данным флагом.
В заключение рассмотрим, что происходит, когда сокет 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 : I P Локальный IP-адрес, на котором будут прослушиваться соединения
// -п:х Количество попыток отправки сообщения
// '"
f>
' -b: Размер буфера отправки
//
«include
«include
«include
«define DEFAULT.PORT 5150
«define DEFAULT_COUNT 25
«define DEFAULT_BUFFER_LENGTH 4096
i n t 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 "i
1
: // Интерфейс для приема дейтаграмм
if (strlen(argv[i]) > 3)
{
blnterface = TRUE;
strcpy(szlnterface, &argv[i][3]);
>
break;
'default:
usage();

1   ...   14   15   16   17   18   19   20   21   ...   50


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