Главная страница
Навигация по странице:

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

  • WSACleanup

  • Примечание: более сложные приемы закрытия соединения

  • Внимание

  • Дополнительные возможности Для "тонкой" настойки сокетов предусмотрена функция " int setsockopt

  • Крисс Касперски - Самоучитель игры на winsock. ВведениеСокеты


    Скачать 188.88 Kb.
    НазваниеВведениеСокеты
    Дата19.03.2018
    Размер188.88 Kb.
    Формат файлаpdf
    Имя файлаКрисс Касперски - Самоучитель игры на winsock.pdf
    ТипДокументы
    #38819
    страница2 из 3
    1   2   3
    Еще одна деталь – транспортный протокол UDP, на который опирают
    ся дейтаграммные сокеты, не гарантирует успешной доставки сообщений
    и эта задача ложиться на плечи самого разработчика. Решить ее можно, на
    пример, посылкой клиентом подтверждения об успешности получения данных.
    Правда, клиент тоже не может быть уверен, что подтверждение дойдет до
    сервера, а не потеряется где нибудь в дороге. Подтверждать же получение под
    тверждения – бессмысленно, т.к. это рекурсивно неразрешимо. Лучше вообще
    не использовать дейтаграммные сокеты на ненадежных каналах.
    Крис Касперски
    7 7
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    Во всем остальном обе пары функций полностью идентичны и работают с теми самыми флагами – 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!
    Крис Касперски
    8 8
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    Дерево вызовов
    Д
    ля большей наглядности демонстрации взаимосвязи socket функций друг с другом, ниже приведено дерево вызовов, показывающее в каком поряд ке должны следовать вызовы функций в зависимости от типа сокетов (потоковый или дейтаграммный) и рода обработки запросв (клиент или сервер).
    Крис Касперски
    9 9
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    Адрес раз, адрес два…
    С
    адресами как раз и наблюдается наибольшая путаница, в которую не помешает внести немного ясности. Прежде всего структура 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 не привязана именно к Интернет и мо жет работать и с другими сетями. Адреса же некоторых сетей требуют для своего представления гораздо больше четырех байт – вот и приходится брать с запасом!
    Структура 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 адресу,
    записанному в виде длинного целого!
    Крис Касперски
    1 10 0
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    На практике можно с одинаковым успехом пользоваться как "устаревшей"
    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");"
    При использовании структуры sockaddr это будет выглядеть так:
    "
    struc sockaddr
    dest_addr; ((unsigned int *)(&dest_addr.sa_data[0]+2))[0] = inet_addr("195.161.42.222");"
    Попытка передать inet_addr доменное имя узла приводит к провалу. Узнать IP
    адрес такого то домена можно с помощью функции "
    struct hostent FAR * gethostbyna
    me (const char FAR * name);". Функция обращается к DNS и возвращает свой ответ в структуре hostent или нуль если 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 адрес в списке и игнорирует все остальные! Конечно, это не смертельно,
    но все же будет лучше, если в своих программах вы будете учитывать возможность подключения к остальным IP адресам, при невозможности установить соединение с первым.
    Функция gethostbyname ожидает на входе
    только доменные имена, но не цифровые IP адреса. Между тем, правила "хорошего тона" требуют предоставления клиенту возможности как задания доменных имен, так и цифровых IP адресов.
    Крис Касперски
    1 11 1
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    Решение заключается в следующем – необходимо проанализировать пере данную клиентом строку: если это 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)"
    т. е. преобразовать короткое целое из формата локального хоста в сетевой формат.
    Крис Касперски
    1 12 2
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    Внимание: все значения, возвращенные socket функциями уже находятся
    в сетевом формате и "вручную" их преобразовывать нельзя! Т. к. это преобразо
    вание исказит результат и приведен к неработоспособности.
    Чаще всего к вызовам этих функций прибегают для преобразования номера порта согласно сетевому порядку. Например:
    dest_addr.sin_port = htons(110).
    Дополнительные возможности
    Для "тонкой" настойки сокетов предусмотрена функция "
    int setsockopt (SOCK
    ET 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 уведомление успешности достав ки, не передаем в сеть никаких пакетов, а накапливаем их на локальном узле, собирая в один большой пакет. Такая техника совершенно прозрачна для прикладных приложений, и в то же время позволяет значительно опти мизировать трафик, но в некоторых (достаточно экзотических) случаях, ког да требуется действительно побайтовый обмен, Алгоритм Нагла приходится отключать (по умолчанию он включен).
    Крис Касперски
    1 13 3
    С
    Са
    ам
    мо
    оууч
    чи
    итте
    ел
    ль
    ь и
    иггр
    ры
    ы н
    на
    а W
    WIIN
    NS
    SO
    OC
    CK
    K

    Для получения текущих значений опций сокета предусмотрена функция "
    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, не позволяя ей решать целый ряд задач, требующих поддержки сырых сокетов.
    1   2   3


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