| Форум| Гостевая| Ссылки| Программы| Исходные тексты| Наши партнеры|
   
| Главная| Рассылки| Услуги| Библиотека| Новости| Авторам| Программистам| Студентам|
delphi c++ assembler
 

Самоучитель игры на WINSOCK

Крис Касперски
kpnc@aport.ru


   В этом номере мы заканчиваем рассказ об использовании сокетов в Windows. Напомним, что в первой части статьи речь шла о самых основных вещах: разновидностях сокетов, особенностях клиентских и серверных приложений, средствах Windows для работы с сокетами.

   Адрес раз, адрес два… 

   При общении с сокетами наибольшая путаница наблюдается с адресами . Здесь не помешает внести немного ясности. Прежде всего – есть структура sockaddr, которая определяется так:

struct sockaddr
{
u_short sa_family; // семейство протоколов (как правило AF_INET)
char sa_data[14]; // IP-адрес узла и порт
};

   Однако, теперь она считается устаревшей, и в Winsock 2.x на смену ей пришла структура sockaddr_in, определенная так:

struct sockaddr_in
{
short sin_family; // семейство протоколов (как правило AF_INET)
u_short sin_port; // порт 
struct in_addr sin_addr; // IP – адрес
char sin_zero[8]; // хвост
};

   В общем - ничего не изменилось (стоило ли огород городить?), замена беззнакового короткого целого на знаковое для представления семейства протоколов ничего не дает. Зато теперь адрес узла представлен в виде трех полей – sin_port (номера порта), sin_addr (IP-адреса узла) и "хвоста" из восьми нулевых байтов, который остался от четырнадцатисимвольного массива sa_data. Для чего он нужен? Дело в том, что структура sockaddr не привязана именно к TCP/IP, и может работать и с другими сетевыми протоколами. Адреса же некоторых сетей требуют для своего представления гораздо больше четырех байт – вот и приходится брать с запасом.

   Структура in_addr определяется следующим образом:

struct in_addr {
union {
struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b; // IP-адрес
struct { u_short s_w1, s_w2; } S_un_w; // IP-адрес
u_long S_addr; // IP-алрес
} S_un;
}

   Как видно, она состоит из одного IP-адреса, записанного в трех "ипостасях" – четырехбайтовой последовательности (S_un_b), пары двухбайтовых слов (S_un_W) и одного длинного целого (S_addr) – выбирай на вкус… Но не все так просто! Во многих программах, технических руководствах и даже демонстрационных примерах, прилагающихся к Winsock SDK, встречается обращение к "таинственному" члену структуры s_addr, который в SDK явно не описан! Например, вот строка из файла "Simples.h": local.sin_addr.s_addr = (!interface)?INADDR_ANY:inet_addr(interface);

   Это что такое?! Заглянув в файл "winsock2.h" можно обнаружить следующее: "#define s_addr S_un.S_addr". Оказывается - это эквивалент s_addr, т.е. IP-адрес, записанный в виде длинного целого!

   На практике можно с одинаковым успехом пользоваться как "устаревшей" sockaddr, так и "новомодной" sockaddr_in. Однако, поскольку прототипы остальных функций не изменились, при использовании sockaddr_in придется постоянно выполнять явные преобразования; например, так: 

sockaddr_in dest_addr; 
connect (mysocket, (struct sockaddr*) &dest_addr, sizeof(dest_addr);

   Для преобразования IP-адреса, записанного в виде символьной последовательности (наподобие "127.0.0.1") в четырехбайтовую числовую последовательность предназначена функция 

unsigned long inet_addr (const char FAR * cp )

   Она принимает указатель на символьную строку и, в случае успешной операции, преобразует ее в четырехбайтовый IP адрес (или –1, если это невозможно). Возвращаемый результат можно присвоить элементу структуры sockaddr_in: 

struct sockaddr_in dest_addr;
dest_addr.sin_addr.S_addr=inet_addr("195.161.42.222");

   Непосредственно передать доменное имя узла функции inet_addr нельзя. Но существует функция 
struct hostent FAR * gethostbyname (const char FAR * name);

- которая обращается к DNS и возвращает ответ в структуре hostent (0, если DNS сервер не смог определить IP-адрес).

   Структура hostent выглядит следующим образом:

struct hostent
{
char FAR * h_name; // официальное имя узла
char FAR * FAR* h_aliases; // альтернативные имена узла (массив строк) 
short h_addrtype; // тип адреса 
short h_length; // длина адреса (как правило AF_INET)
char FAR * FAR * h_addr_list; // список указателей на IP-адреса
// ноль – конец списка
};

   Как и в случае с in_addr, во множестве программ и прилагаемых к Winsock SDK примерах активно используется недокументированное поле структуры h_addr. Например, вот строка из файла "simplec.c": memcpy(&(server.sin_addr),hp->h_addr,hp->h_length);

   Заглянув в "winsock2.h", можно найти, что оно обозначает: 

#define h_addr h_addr_list[0]

   А вот это уже интересно! Дело в том, что с некоторыми доменными именами связано сразу несколько IP-адресов. В случае неработоспособности одного узла, клиент может попробовать подключиться к другому, или просто выбрать узел с наибольшей скоростью обмена. Но в приведенном примере клиент использует только первый IP-адрес в списке – и игнорирует все остальные! Конечно, это не смертельно, но не использовать такую возможность обидно.

   Функция gethostbyname ожидает на входе только доменные имена, но не цифровые IP-адреса. Однако, считается "хорошим тоном" предоставлять клиенту возможность задавать адреса в любой форме. Очевидное решение – проанализировать переданную клиентом строку; если это IP-адрес, то передать его функции inet_addr, иначе – gethostbyaddr, полагая, что это доменное имя. Отличить IP-адреса от доменных имен не всегда просто; забавно, но многие программисты используют нехитрый трюк: если первый символ строки – цифра, это IP-адрес, иначе – имя домена; однако это работает не всегда – доменные имена могут начинаться с цифры, например, "666.ru", могут они и заканчиваться цифрой, например, к узлу "666.ru" члены cубдомена "666" могут так и обращаться – "666". Самое смешное, что (теоретически) могут существовать имена доменов, синтаксически неотличимые от IP-адресов! Поэтому, на мой взгляд, лучше всего действовать так: всегда передаем введенную пользователем строку функции inet_addr; а если она возвращает ошибку, то вызываем gethostbyaddr.

   Для решения обратной задачи – определения доменного имени по IP-адресу – предусмотрена функция 
struct HOSTENT FAR * gethostbyaddr (const char FAR * addr, int len, int type)

- во всем аналогичная gethostbyname, за тем исключением, что ее аргументом является не указатель на строку, содержащую имя, а указатель на четырехбайтовый IP-адрес. Еще два аргумента задают его длину и тип (соответственно, 4 и AF_INET).
Определение имени узла по его адресу бывает полезным для серверов, желающих "в лицо" знать своих клиентов.

   Для преобразования IP-адреса, записанного в сетевом формате в символьную строку, предусмотрена функция 
char FAR * inet_ntoa (struct in_addr), которая принимает на вход структуру in_addr, а возвращает указатель на строку, если преобразование выполнено успешно, и ноль в противном случае.

   Сетевой порядок байтов

   Среди производителей процессоров нет единого мнения насчет порядка следования младших и старших байтов. Так, например, у микропроцессоров Intel младшие байты располагаются по меньшим адресам, а у Motorola 68000 – наоборот. Естественно, это вызывает проблемы при межсетевом взаимодействии; поэтому был введен специальный сетевой порядок байтов, предписывающий старший байт передавать первым (не так, как у Intel).

   Для преобразований чисел из сетевого формата в формат локального хоста и наоборот предусмотрено четыре функции; первые две манипулируют короткими целыми (16-битными словами), а последние две – длинными (32-битными двойными словами): 

u_short ntohs (u_short netshort); 
u_short htons (u_short hostshort ); 
u_long ntohl (u_long netlong ); 
u_long htonl (u_long hostlong);

   Имена функций интуитивно понятны: за буквой "n" скрывается сокращение "network", за "h" – "host" (подразумевается локальный), "s" и "l" соответственно короткое (short) и длинное (long) беззнаковые целые, а "to" и обозначает преобразование. Например, "htons" расшифровывается так: “Host а Network (short )” т.е. “преобразовать короткое целое из формата локального хоста в сетевой формат”. 

   Внимание: все значения, возвращенные socket-функциями, уже находятся в сетевом формате и "вручную" их преобразовывать нельзя! Это преобразование исказит результат и приведен к неработоспособности.
Чаще всего к вызовам этих функций прибегают для преобразования номера порта согласно сетевому порядку. Например: dest_addr.sin_port = htons(110).

   Дополнительные возможности

   Для "тонкой" настойки сокетов предусмотрена функция 
int setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen)

   Первый аргумент – дескриптор сокета, который собираются настраивать; level – уровень настойки. С каждым уровнем связан свой набор опций. Всего определено два уровня - SOL_SOCKET и IPPROTO_TCP. В ограниченном объеме журнальной статьи перечислить все опции невозможно, поэтому расскажем только о самых интересных из них, а сведения обо всех остальных можно почерпнуть из Winsock SDK.

   Третий аргумент представляет собой указатель на переменную, содержащую значение опции. Ее размер варьируется в зависимости от рода опции и передается через четвертый аргумент.

   Уровень SOL_SOCKET: 
SO_RCVBUF (int) – задает размер входного буфера для приема данных. К TCP-окну никакого отношения не имеет, поэтому может безболезненно варьироваться в широких пределах.
SO_SNDBUF (int) – задает размер входного буфера для передачи данных. Увеличение размера буферов на медленных каналах приводит к задержкам и снижает производительность.

   Уровень IPPROTO_TCP
TCP_NODELAY (BOOL) – выключает Алгоритм Нагла. Алгоритм Нагла был разработан специально для прозрачного кэширования крохотных пакетов (тиниграмм). Когда один узел посылает другому несколько байт, к ним дописываются заголовки TCP и IP, которые в совокупности обычно занимают более 50 байт. Таким образом, при побайтовом обмене между узлами свыше 98% передаваемой по сети информации будет приходиться на служебные данные!
   Алгоритм Нагла состоит в следующем: отправляем первый пакет – и, до тех пор, пока получатель не возвратит TCP-уведомление успешности доставки, не передаем в сеть никаких пакетов, а накапливаем их на локальном узле, собирая в один большой пакет. Такая техника совершенно прозрачна для прикладных приложений, и в то же время позволяет значительно оптимизировать трафик; но в некоторых (достаточно экзотических) случаях, когда требуется действительно побайтовый обмен, Алгоритм Нагла приходится отключать (по умолчанию он включен).

   Для получения текущих значений опций сокета предусмотрена функция 
int getsockopt (SOCKET s, int level, int optname, char FAR* optval, int FAR* optlen), которая полностью аналогична предыдущей – за исключением того, что не устанавливает опции, а возвращает их значения.

Сырые сокеты

   Помимо потоковых и датаграммных сокетов, существуют так называемые сырые (RAW) сокеты. Они предоставляют возможность “ручного” формирования TCP/IP-пакетов, равно как и полного доступа к содержимому заголовков полученных TCP/IP-пакетов – что необходимо многим сетевым сканерам, Firewall’ам, брандмауэрам, и, разумеется, атакующим программам – например, устанавливающим в поле "адрес отправителя" адрес самого получателя.

   Спецификация Winsock 1.x категорически не поддерживала сырых сокетов. В Winsock 2.x положение будто бы исправлено: по крайней мере, формально такая поддержка появилась, и в SDK даже входил пример, демонстрирующий использование сырых сокетов для реализации утилиты ping. Однако, попытки использования сырых сокетов для всех остальных целей с треском проваливались – система упрямо игнорировала сформированный "вручную" IP- (или TCP-) пакет и создавала его самостоятельно.
   Документация объясняла, что для самостоятельной обработки заголовков пакетов, опция IP_HDRINCL должна быть установлена. Весь фокус в том, что вызов setsockopt(my_sock,IPPROTO_IP, IP_HDRINCL, &oki, sizeof(oki))
всегда возвращал ошибку! 

   Таким образом, на прикладном уровне получить непосредственный доступ к заголовкам TCP/IP невозможно. Это препятствует переносу многих приложений из UNIX в Windows; более того, определенным образом ущемляет возможности самой Windows, не позволяя ей решать целый ряд задач, требующих поддержки сырых сокетов.

   Законченные реализации 

   Ниже приведено четыре подробно комментированных исходных текста, реализующих простые TCP и UDP эхо-сервера и TCP и UDP клиентов. (Эхо-сервер просто возвращает клиенту полученные от него данные).
Для их компиляции с помощью Microsoft Visual C++ достаточно отдать команду: 
cl.exe имя_файла.cpp ws2_32.lib

   Проверка работоспособности TCP-сервера: запустите TCP-сервер и наберите в командной строке Windows "telnet.exe 127.0.0.1 666", где 127.0.0.1 обозначает локальный адрес вашего узла (это специально зарезервированный для этой цели адрес; он выглядит одинаково для всех узлов), а 666 – номер порта на который "сел" сервер. Если все работает успешно, то telnet установит соединение и на экране появится приветствие "Hello, Sailor!". Теперь можно набирать на клавиатуре некоторый текст и получать его назад от сервера.
   Проверка работоспособности TCP-клиента: запустите TCP-сервер и затем одну или несколько копий клиента. В каждом из них можно набирать некоторый текст на клавиатуре, и после нажатия на Enter, получать его обратно от сервера.
   Проверка работоспособности UDP-сервера и клиента: запустите UDP-сервер и одну или несколько копий клиента – в каждой из них можно набирать на клавиатуре некоторые данные и получать их обратно от сервера.

   Внимание: работая с серверными приложениями, вы (если не предпримете дополнительных мер) даете возможность ими воспользоваться любому абоненту интернет (если в момент работы сервера вы подключены к Сети). Проблема в том, что ошибки реализации (в особенности - переполняющиеся буфера) могут позволить удаленному злоумышленнику выполнить на вашей машине любой код, со всеми вытекающими отсюда последствиями.
   Будьте очень внимательны; а еще лучше – не входите в Интернет, пока не будете полностью уверенны, что сервера отлажены и не содержат ошибок!

   Пример реализации TCP эхо-сервера

// Пример простого TCP – эхо сервера

#include <stdio.h>
#include <winsock2.h> // Wincosk2.h должен быть раньше windows!
#include <windows.h>

#define MY_PORT 666 // Порт, который слушает сервер

// макрос для печати количества активных пользователей 
#define PRINTNUSERS if (nclients) printf("%d user on-line\n",nclients);else printf("No User on line\n");

// прототип функции, обслуживающий подключившихся пользователей
DWORD WINAPI SexToClient(LPVOID client_socket);

// глобальная переменная – количество активных пользователей 
int nclients = 0;

int main(int argc, char* argv[])
{
char buff[1024]; // Буфер для различных нужд

printf("TCP SERVER DEMO\n");

// Шаг 1 - Инициализация Библиотеки Сокетов
// Т.к. возвращенная функцией информация не используется
// ей передается указатель на рабочий буфер, преобразуемый к указателю 
// на структуру WSADATA.
// Такой прием позволяет сэкономить одну переменную, однако, буфер
// должен быть не менее полкилобайта размером (структура WSADATA
// занимает 400 байт)
if (WSAStartup(0x0202,(WSADATA *) &buff[0])) 
{
// Ошибка!
printf("Error WSAStartup %d\n",WSAGetLastError());
return -1;
}

// Шаг 2 - создание сокета
SOCKET mysocket;
// AF_INET - сокет Интернета
// SOCK_STREAM - потоковый сокет (с установкой соединения)
// 0 - по умолчанию выбирается TCP протокол
if ((mysocket=socket(AF_INET,SOCK_STREAM,0))<0)
{
// Ошибка!
printf("Error socket %d\n",WSAGetLastError());
WSACleanup(); // Деиницилизация библиотеки Winsock
return -1;
}

// Шаг 3 связывание сокета с локальным адресом
sockaddr_in local_addr;
local_addr.sin_family=AF_INET;
local_addr.sin_port=htons(MY_PORT); // не забываем о сетевом порядке!!!
local_addr.sin_addr.s_addr=0; // сервер принимает подключения
// на все свои IP-адреса

// вызываем bind для связывания
if (bind(mysocket,(sockaddr *) &local_addr, sizeof(local_addr)))
{
// Ошибка
printf("Error bind %d\n",WSAGetLastError());
closesocket(mysocket); // закрываем сокет!
WSACleanup();
return -1;
}

// Шаг 4 ожидание подключений
// размер очереди – 0x100
if (listen(mysocket, 0x100))
{
// Ошибка
printf("Error listen %d\n",WSAGetLastError());
closesocket(mysocket);
WSACleanup();
return -1;
}

printf("Ожидание подключений…\n");

// Шаг 5 извлекаем сообщение из очереди
SOCKET client_socket; // сокет для клиента
sockaddr_in client_addr; // адрес клиента (заполняется системой)

// функции accept необходимо передать размер структуры
int client_addr_size=sizeof(client_addr);

// цикл извлечения запросов на подключение из очереди
while((client_socket=accept(mysocket, (sockaddr *) &client_addr, &client_addr_size)))
{
nclients++; // увеличиваем счетчик подключившихся клиентов

// пытаемся получить имя хоста
HOSTENT *hst;
hst=gethostbyaddr((char *) &client_addr.sin_addr.s_addr,4,AF_INET);

// вывод сведений о клиенте
printf("+%s [%s] new connect!\n",
(hst)?hst->h_name:"",inet_ntoa(client_addr.sin_addr));
PRINTNUSERS

// Вызов нового потока для обслужвания клиента
// Да, для этого рекомендуется использовать _beginthreadex
// но, поскольку никаких вызов функций стандартной Си библиотеки
// поток не делает, можно обойтись и CreateThread
DWORD thID;
CreateThread(NULL,NULL,SexToClient,&client_socket,NULL,&thID);
}
return 0;
}

// Эта функция создается в отдельном потоке
// и обсуживает очередного подключившегося клиента независимо от остальных
DWORD WINAPI SexToClient(LPVOID client_socket)
{
SOCKET my_sock;
my_sock=((SOCKET *) client_socket)[0];
char buff[20*1024];
#define sHELLO "Hello, Sailor\r\n"

// отправляем клиенту приветствие 
send(my_sock,sHELLO,sizeof(sHELLO),0);

// цикл эхо-сервера: прием строки от клиента и возвращение ее клиенту
while( (int bytes_recv=recv(my_sock,&buff[0],sizeof(buff),0)) &&
bytes_recv !=SOCKET_ERROR)
send(my_sock,&buff[0],bytes_recv,0);

// если мы здесь, то произошел выход из цикла по причине 
// возращения функцией recv ошибки – соединение с клиентом разорвано
nclients--; // уменьшаем счетчик активных клиентов
printf("-disconnect\n"); PRINTNUSERS

// закрываем сокет
closesocket(my_sock);
return 0;
}

   Пример реализации TCP-клиента

// Пример простого TCP клиента
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>

#define PORT 666
#define SERVERADDR "127.0.0.1"

int main(int argc, char* argv[])
{
char buff[1024];
printf("TCP DEMO CLIENT\n");

// Шаг 1 - инициализация библиотеки Winsock
if (WSAStartup(0x202,(WSADATA *)&buff[0]))
{
printf("WSAStart error %d\n",WSAGetLastError());
return -1;
}

// Шаг 2 - создание сокета
SOCKET my_sock;
my_sock=socket(AF_INET,SOCK_STREAM,0);
if (my_sock<0)
{
printf("Socket() error %d\n",WSAGetLastError());
return -1;
}

// Шаг 3 - установка соединения

// заполнение структуры sockaddr_in – указание адреса и порта сервера
sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(PORT);
HOSTENT *hst;

// преобразование IP адреса из символьного в сетевой формат
if (inet_addr(SERVERADDR)!=INADDR_NONE)
dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
else
// попытка получить IP адрес по доменному имени сервера
if (hst=gethostbyname(SERVERADDR))
// hst->h_addr_list содержит не массив адресов, а массив указателей на адреса
((unsigned long *)&dest_addr.sin_addr)[0]=((unsigned long **)hst->h_addr_list)[0][0];
else 
{
printf("Invalid address %s\n",SERVERADDR);
closesocket(my_sock);
WSACleanup();
return -1;
}

// адрес сервера получен – пытаемся установить соединение 
if (connect(my_sock,(sockaddr *)&dest_addr,sizeof(dest_addr)))
{
printf("Connect error %d\n",WSAGetLastError());
return -1;
}

printf("Соединение с %s успешно установлено\n\ Type quit for quit\n\n",SERVERADDR);

// Шаг 4 - чтение и передача сообщений
int nsize;
while((nsize=recv(my_sock,&buff[0],sizeof(buff)-1,0))!=SOCKET_ERROR)
{
// ставим завершающий ноль в конце строки 
buff[nsize]=0;

// выводим на экран 
printf("S=>C:%s",buff);

// читаем пользовательский ввод с клавиатуры
printf("S<=C:"); fgets(&buff[0],sizeof(buff)-1,stdin);

// проверка на "quit"
if (!strcmp(&buff[0],"quit\n"))
{
// Корректный выход
printf("Exit...");
closesocket(my_sock);
WSACleanup();
return 0;
}

// передаем строку клиента серверу
send(my_sock,&buff[0],nsize,0);
}

printf("Recv error %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}

   Пример реализации UDP-сервера

// Пример простого UDP-эхо сервера
#include <stdio.h>
#include <winsock2.h>

#define PORT 666 // порт сервера
#define sHELLO "Hello, %s [%s] Sailor\n"

int main(int argc, char* argv[])
{
char buff[1024];

printf("UDP DEMO echo-Server\n");

// шаг 1 - подключение библиотеки 
if (WSAStartup(0x202,(WSADATA *) &buff[0]))
{
printf("WSAStartup error: %d\n",WSAGetLastError());
return -1;
}

// шаг 2 - создание сокета
SOCKET my_sock;
my_sock=socket(AF_INET,SOCK_DGRAM,0);
if (my_sock==INVALID_SOCKET)
{
printf("Socket() error: %d\n",WSAGetLastError());
WSACleanup();
return -1;
}

// шаг 3 - связывание сокета с локальным адресом 
sockaddr_in local_addr;
local_addr.sin_family=AF_INET;
local_addr.sin_addr.s_addr=INADDR_ANY;
local_addr.sin_port=htons(PORT);

if (bind(my_sock,(sockaddr *) &local_addr, sizeof(local_addr)))
{
printf("bind error: %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}

// шаг 4 обработка пакетов, присланных клиентами
while(1)
{
sockaddr_in client_addr;
int client_addr_size = sizeof(client_addr);
int bsize=recvfrom(my_sock,&buff[0],sizeof(buff)-1,0,
(sockaddr *) &client_addr, &client_addr_size);
if (bsize==SOCKET_ERROR)
printf("recvfrom() error: %d\n",WSAGetLastError());

// Определяем IP-адрес клиента и прочие атрибуты
HOSTENT *hst;
hst=gethostbyaddr((char *) &client_addr.sin_addr,4,AF_INET);
printf("+%s [%s:%d] new DATAGRAM!\n",
(hst)?hst->h_name:"Unknown host",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));

// добавляем завершающий нуль
buff[bsize]=0;

// Вывод на экран 
printf("C=>S:%s\n",&buff[0]);

// посылка датаграммы клиенту
sendto(my_sock,&buff[0],bsize,0,
(sockaddr *)&client_addr, sizeof(client_addr));
}
return 0;
}

   Пример реализации UDP-клиента

// пример простого UDP-клиента
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>

#define PORT 666
#define SERVERADDR "127.0.0.1"

int main(int argc, char* argv[])
{
char buff[10*1014];
printf("UDP DEMO Client\nType quit to quit\n");

// Шаг 1 - иницилизация библиотеки Winsocks
if (WSAStartup(0x202,(WSADATA *)&buff[0]))
{
printf("WSAStartup error: %d\n",WSAGetLastError());
return -1;
}

// Шаг 2 - открытие сокета
SOCKET my_sock=socket(AF_INET, SOCK_DGRAM, 0);
if (my_sock==INVALID_SOCKET)
{
printf("socket() error: %d\n",WSAGetLastError());
WSACleanup();
return -1;
}

// Шаг 3 - обмен сообщений с сервером
HOSTENT *hst;
sockaddr_in dest_addr;

dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(PORT);

// определение IP-адреса узла
if (inet_addr(SERVERADDR))
dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
else
if (hst=gethostbyname(SERVERADDR))
dest_addr.sin_addr.s_addr=((unsigned long **) hst->h_addr_list)[0][0];
else
{
printf("Unknown host: %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}

while(1)
{
// чтение сообщения с клавиатуры
printf("S<=C:");fgets(&buff[0],sizeof(buff)-1,stdin);
if (!strcmp(&buff[0],"quit\n")) break;

// Передача сообщений на сервер
sendto(my_sock,&buff[0],strlen(&buff[0]), 0, 
(sockaddr *) &dest_addr,sizeof(dest_addr));

// Прием сообщения с сервера
sockaddr_in server_addr;
int server_addr_size=sizeof(server_addr);

int n=recvfrom(my_sock,&buff[0],sizeof(buff)-1,0,
(sockaddr *) &server_addr, &server_addr_size);

if (n==SOCKET_ERROR)
{
printf("recvfrom() error: %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}

buff[n]=0;

// Вывод принятого с сервера сообщения на экран
printf("S=>C:%s",&buff[0]);
}

// шаг последний - выход
closesocket(my_sock);
WSACleanup();

return 0;
}

 

Rambler's Top100 Rambler's Top100

©  Adept Design Studio

Используются технологии uCoz