Смекни!
smekni.com

Учебно-методическое пособие рекомендовано учебно-методическим советом Международного университета природы, общества и человека (стр. 8 из 11)

Во всем остальном обе пары функций полностью идентичны и работают с теми самыми флагами - MSG_PEEK и MSG_OOB.

Все четыре функции при возникновении ошибки возвращают значение SOCKET_ERROR (== -1).

Примечание: в UNIX с сокетами можно обращаться точно также, как и с обычными файлами, в частности писать и читать в них функциями write и read. ОС Windows 3.1 не поддерживала такой возможности, поэтому при переносе приложений их UNIX в Windows все вызовы write и read должны были быть заменены на send и recv соответственно. В Windows 95 с установленным Windows 2.x это упущение исправлено, теперь дескрипторы сокетов можно передавать функциям ReadFil, WriteFile, DuplicateHandle и др.

Шаг последний

Для закрытия соединения и уничтожения сокета предназначена функция "int closesocket (SOCKET s)", которая в случае удачного завершения операции возвращает нулевое значение.

Перед выходом из программы необходимо вызвать функцию "int WSACleanup (void)" для деинициализации библиотеки WINSOCK и освобождения используемых этим приложением ресурсов.

Внимание: завершение процесса функцией ExitProcess автоматически не освобождает ресурсы сокетов!

Примечание: более сложные приемы закрытия соединения - протокол TCP позволяет выборочно закрывать соединение любой из сторон, оставляя другую сторону активной. Например, клиент может сообщить серверу, что не будет больше передавать ему никаких данных и закрывает соединение "клиент -> сервер", однако готов продолжать принимать от него данные до тех пор, пока сервер будет их посылать, т.е. хочет оставить соединение "сервер -> клиент" открытым.
Для этого необходимо вызвать функцию "int shutdown (SOCKET s ,int how )", передав в аргументе how одно из следующих значений: SD_RECEIVE для закрытия канала "сервер -> клиент", SD_SEND для закрытия канала "клиент -> сервер", и, наконец, SD_BOTH для закрытия обоих каналов.
Последний вариант выгодно отличается от closesocket "мягким" закрытием соединения - удаленному узлу будет послано уведомление о желании разорвать связь, но это желание не будет воплощено в действительность, пока тот узел не возвратит свое подтверждение. Таким образом, можно не волноваться, что соединение будет закрыто в самый неподходящий момент.

Внимание: вызов shutdown не освобождает от необходимости закрытия сокета функцией closesocket!

Адрес

С адресами как раз и наблюдается наибольшая путаница. Прежде всего, структура 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-адреса узла) и "хвоста" из восьми нулевых байт, который остался от 14-символьного массива sa_data. Для чего он нужен? Дело в том, что структура sockaddr не привязана именно к Интернет и может работать и с другими сетями. Адреса же некоторых сетей требуют для своего представления гораздо больше четырех байт - вот и приходится брать с запасом!

Структура 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;
};

Структура 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-адреса
// ноль - конец списка
};

Определение имени узла по его адресу бывает полезным для серверов, желающих "в лицо" знать своих клиентов.

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

Практическая часть

Задача 1

Наше сетевое программирование начнём с написания программы-клиента, использующей сокеты (sockets).

Итак, начнём. Создадим диалоговое приложение, не забывая поставить галочку около поддержки сокетов во вкладке Advanced. Создадим кнопку Send Request, по нажатию на которую будут происходить все действия.

Напишем функцию, сообщающую нам об ошибках. Раньше мы не писали их за отсутствием острой потребности (хотя это неправильно). Для сокетов же проверки критичны и помогут избежать многих трудностей и неполадок. Чтобы нам было понятно, в чём состоит ошибка, мы её расшифруем на нормальный человеческий язык, заставив систему саму расшифровывать нам коды ошибок. Делается это с помощью функции FormatMessage. В-общем, она принимает код ошибки в третьем параметре и записывает ответ в переменную типа char, переданную ей в качестве пятого параметра. Также ей надо знать размер этой переменной – он передаётся шестым параметром.

void ReportError(HRESULT errorCode, const char *whichFunc)

{

//расшифровываем ошибку

char chErrMsg[1024];

FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), chErrMsg, sizeof(chErrMsg), NULL);

//форматим для вывода пользователю

CString strErrorMsg;

strErrorMsg.Format("Вызов функции %s вернул следующую ошибку (код%d): \n%s", (char*)whichFunc, errorCode, chErrMsg);

MessageBox(NULL, strErrorMsg, "socketIndication", MB_OK);

//возврат к диалоговому окну :-)

return;

}

Сама функция ReportMessage() принимает в качестве первого параметра код ошибки, в качестве второго параметра она принимает имя функции, из которой сигнализируется об ошибке.

Начнём описывать реакцию программы на нажатие кнопки SendRequest. Реализация начинается с инициализации Winsock’а.

WORD sockVersion;

WSADATA wsaData;

sockVersion = MAKEWORD(1, 1); //используем версию 1.1

WSAStartup(sockVersion, &wsaData); //инициализируем Winsock

Ещё нам понадобится переменная для хранения результата операций

int nret;

и переменная для кода ошибки

HRESULT hr;

Теперь заполним структуру HOSTENT, которая говорит сокету с каким компом и портом связываться. Эта структура обычно фигурирует как переменные типа LPHOSTENT, которые являются попросту указателями на HOSTENT.

LPHOSTENT hostEntry;

in_addr iaHost;

iaHost.s_addr=inet_addr(“192.168.0.253”);//это адрес университетского сервера Debian

hostEntry = gethostbyaddr((const char*) &iaHost, sizeof(struct in_addr), AF_INET);

Функция gethostbyaddr заполняет HOSTENT пригодными для дальнейшего использования значениями в случае, когда известен IP-адрес сервера. Иначе

hostEntry = gethostbyname(“www.uni-protvino.ru”); /*адрес того же университетского сервера */

и, соответственно, переменная iaHost нам не нужна.

Можно выбрать функцию на свой вкус. Так как мы знаем IP-адрес сервера, то удобнее пользоваться gethostbyaddr.

Далее проверим, что мы получили.

if (hostEntry == NULL)

{

hr = HRESULT_FROM_WIN32(WSAGetLastError());

ReportError(hr, "gethostbyname()");

WSACleanup();

return;

}

Здесь мы получаем код ошибки (в случае, если ошибка есть) с помощью конструкции HRESULT_FROM_WIN32(WSAGetLastError()). Функция FormatMessage требует для работы переменной типа HRESULT – для этого код, полученный с помощью функции WSAGetLastError, мы преобразуем с помощью макроса HRESULT_FROM_WIN32.

Затем создаём сокет и проверяем на правильность создания.

SOCKET theSocket;

theSocket = socket(AF_INET, //go over TCP/IP

SOCK_STREAM, //stream-oriented socket

IPPROTO_TCP); //TCP

if (theSocket == INVALID_SOCKET)

{

hr = HRESULT_FROM_WIN32(WSAGetLastError());

ReportError(hr, "socket()");

WSACleanup();

return;

}

Для дальнейшей работы заполним структуру SOCKADDR_IN:

SOCKADDR_IN serverInfo;

serverInfo.sin_family = AF_INET;

serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);

serverInfo.sin_port = htons(80);

Остаётся добавить, что функция htons() переводит прямой порядок байт в порядок, используемый в сети.

Мы заполнили все нужные структуры, знаем порт и IP-адрес, теперь пора связываться с сервером. Проверка того же типа, что и раньше.

nret = connect(theSocket, (LPSOCKADDR) &serverInfo, sizeof(struct sockaddr));

if (nret != 0)

{

hr = HRESULT_FROM_WIN32(WSAGetLastError());

ReportError(hr, "connect()");

WSACleanup();

return;

}

Начнём процедуру общения с сервером. Вопросы он понимает не все, а только правильно сформулированные. Мы попросим дать нам страницу index.htm.