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

  • 0A 74 6F 6D 00 00 00 00 00 48 69* (length) T o m (padding) H И второй пакет подобен 18 42 65 6E 6A 61 6D 69 6E 48 65 79 20 67 75 79 73 20 77 ...*

  • Guide to Network Сетевое программирование от Биджа


    Скачать 1.34 Mb.
    НазваниеGuide to Network Сетевое программирование от Биджа
    Дата02.05.2019
    Размер1.34 Mb.
    Формат файлаpdf
    Имя файлаbgnet_A4_rus.pdf
    ТипGuide
    #75918
    страница7 из 13
    1   2   3   4   5   6   7   8   9   10   ...   13
    send(). double d = 3490.15926535;
    45
    http://en.wikipedia.org/wiki/Internet_Relay_Chat
    28

    Beej's Guide to Network Programming
    !
    send(s, &d, sizeof d, 0); /* Опасно - не портируется! */
    Приёмник получает их так double d;
    !
    recv(s, &d, sizeof d, 0); /* Опасно - не портируется! */ Быстро, просто - что не нравится Оказывается, не все архитектуры представляют числа с плавающей запятой (или даже целые) стем же расположением бит или даже порядком байт Код вообще решительно непереносим. (А может переносимость вам ненужна, в таком случае он быстр и замечательно подходит) Упаковывая целочисленные типы мы уже видели как функции класса htons() могут помочь сохранить переносимость преобразовывая числа в Порядок Байтов Сети и как это Надо Делать. К сожалению, для плавающих типов таких функций нет. Все надежды пропали Боюсь, что нет (Вы этого хоть на секунду опасались Нет Даже чуть-чуть?) Мы можем кое-что сделать мы можем упаковать (или “маршализировать” или “ сериализировать” или любое из тысяч миллионов других названий) данные в известный двоичный формат, который приёмник может распаковать на удалённой стороне. Что я подразумеваю под известным двоичным форматом Мы уже видели пример функции htons(), правда Она преобразует (или перекодирует, если так вам хочется думать) число из любого формата хоста в Порядок Байтов Сети. Для обратного преобразования (“раскодирования”) числа приёмник вызывает ntohs(). Но разве я только что не говорил, что таких функций для других нецелых форматов нет Да, говорил. Это немножко прискорбно, поскольку в C стандартного способа для этого нет (бесплатный каламбур для фанатов Python). Что нужно сделать, так это упаковать данные в известный формат и послать их по проводам для раскодирования. Например, для упаковки плавающих есть кое-что быстрое и убогое с изобилием мест для улучшения :
    29
    #include
    !
    uint32_t htonf(float f)
    { uint32_t p; uint32_t sign;
    !
    if (f < 0) { sign = 1; f = -f; } else { sign = 0; }
    !
    p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31);
    // целое и знак p |= (uint32_t)(((f - (int)f) * 65536.0f))&0xffff;
    // дробная часть
    !
    return p;
    }
    !
    float ntohf(uint32_t p)
    { float f = ((p>>16)&0x7fff);
    // целое и знак f += (p&0xffff) / 65536.0f;
    // дробная часть
    !
    if (((p>>31)&0x1) == 0x1) { f = -f; }
    // установка бита знака return f;
    }
    46
    http://beej.us/guide/bgnet/examples/pack.c
    29

    Beej's Guide to Network Это бесхитростная программка, которая сохраняет плавающее в 32-битном числе. Старший бит (31) используется для хранения знака (“1” означает отрицательное, следующие пятнадцать бит (30-16) используются для хранения целой части числа с плавающей запятой. Оставшаяся часть битов (15-0) используется для хранения дробной части числа. Использование довольно просто
    #include
    !
    int main(void)
    { float f = 3.1415926, f2; uint32_t netf;
    !
    netf = htonf(f);
    // преобразовать в сетевую форму f2 = ntohf(netf);
    // обратно для теста
    !
    printf("Original: %f\n", f);
    // 3.141593 printf(" Network: 0x%08X\n", netf);
    // 0x0003243F printf("Unpacked: %f\n", f2);
    // 3.141586
    !
    return 0;
    } Из плюсов, она маленькая, простая и быстрая, Из минусов, неэффективное использование пространства и очень ограниченный диапазон - попробуйте сохранить здесь число больше 32767 ивы не будете очень счастливы Также в этом примере видно, что две последние десятичные цифры сохраняются неправильно. Что можно сделать взамен Хорошо, конкретно Стандарт хранения чисел с плавающей запятой известен как IEEE-754 . Большинство компьютеров используют этот формат для выполнения вычислений с плавающей запятой, так что, строго говоря, в данном случае преобразование ненужно. Но если вы хотите добиться переносимости вашего исходного кода, то полагаться на это нельзя. (С другой стороны, если хотите добиться скорости, вы должны оптимизировать код для платформы, на которой этого не требуется Как поступают htons() и её семейство) Теперь немного кода, который преобразует числа с плавающей запятой одинарной и двойной точности в формат IEEE-754. (Обычно он не преобразует NaN и
    Неопределённость, но может быть модифицирован для этого) Вот он :
    31
    #define pack754_32(f) (pack754((f), 32, 8))
    #define pack754_64(f) (pack754((f), 64, 11))
    #define unpack754_32(i) (unpack754((i), 32, 8))
    #define unpack754_64(i) (unpack754((i), 64, 11))
    !
    uint64_t pack754(long double f, unsigned bits, unsigned expbits)
    { long double fnorm; int shift; long long sign, exp, significand; unsigned significandbits = bits - expbits - 1;
    // -1 для бита знака
    !
    if (f == 0.0) return 0;
    // особый случай, нельзя
    !
    // проверить знаки начать нормализацию if (f < 0) { sign = 1; fnorm = -f; }
    47
    http://en.wikipedia.org/wiki/IEEE_754 30
    http://beej.us/guide/bgnet/examples/ieee754.c
    31

    Beej's Guide to Network Programming
    else { sign = 0; fnorm = f; }
    !
    // получить нормализованную форму f и отследить экспоненту shift = 0; while(fnorm >= 2.0) { fnorm /= 2.0; shift++; } while(fnorm < 1.0) { fnorm *= 2.0; shift--; } fnorm = fnorm - 1.0;
    !
    // вычислить двоичную (не-плавающую) форму мантиссы significand = fnorm * ((1LL<!
    // получить смещённую экспоненту exp = shift + ((1<<(expbits-1)) - 1);
    // сдвиг + смещение
    !
    // вернуть ответ return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
    }
    !
    long double unpack754(uint64_t i, unsigned bits, unsigned expbits)
    { long double result; long long shift; unsigned bias; unsigned significandbits = bits - expbits - 1;
    // -1 для бита знака
    !
    if (i == 0) return 0.0;
    !
    // извлечь мантиссу result = (i&((1LL<// маска result /= (1LL<// обратно в плавающую result += 1.0f;
    // добавить назад единицу
    !
    // работа с экспонентой bias = (1<<(expbits-1)) - 1; shift = ((i>>significandbits)&((1LL< 0) { result *= 2.0; shift--; } while(shift < 0) { result /= 2.0; shift++; }
    !
    // установить знак result *= (i>>(bits-1))&1? -1.0: 1.0; return result;
    } Сверху я поставил макросы для упаковки и распаковки 32-битных (возможно float) и
    64-битных (возможно double) чисел, но pack754() может быть вызвана непосредственно с указанием битовой значимости данных (expbits которых зарезервировано для нормализованного порядка числа. Вот пример использования
    #include
    #include
    // определяет типы uintN_t
    #include
    // определяет макросы PRIx
    !
    int main(void)
    { float f = 3.1415926, f2; double d = 3.14159265358979323, d2; uint32_t fi; uint64_t di;
    !
    fi = pack754_32(f); f2 = unpack754_32(fi);
    48

    Beej's Guide to Network Programming
    di = pack754_64(d); d2 = unpack754_64(di);
    !
    printf("float before : %.7f\n", f); printf("float encoded: 0x%08" PRIx32 "\n", fi); printf("float after : %.7f\n\n", f2);
    !
    printf("double before : %.20lf\n", d); printf("double encoded: 0x%016" PRIx64 "\n", di); printf("double after : %.20lf\n", d2);
    !
    return 0;
    } Этот код выдаёт вот это float before : 3.1415925 float encoded: 0x40490FDA float after : 3.1415925
    !
    double before : 3.14159265358979311600 double encoded: 0x400921FB54442D18 double after : 3.14159265358979311600 У вас может возникнуть вопрос, как вы упаковываете структуры К несчастью для вас, компилятор волен размещать всё в структурах сами, значит, вы не можете переносимо послать её по проводам одним куском. ( Вы ещё не устали слышать нельзя то, нельзя это Извините Процитирую друга Какая бы неприятность ни случилась, я всегда виню
    Microsoft.” Надо сказать, что может быть в данном случае это вина не Microsoft, но утверждение моего друга полная правда)
    Вернёмся к нашим баранам лучший способ посылать структуру по проводам, это упаковать каждое поле независимо и распаковать их в структуру на другой стороне. Выдумаете, это же много работы. Да, это так. Новы можете написать вспомогательную функцию, которая поможет упаковать данные. Это будет весело Действительно В книге Практика Программирования ” Кернигана и Пайка (K&P) они привели подобные функции pack() и unpack(), которые выполняют тоже самое. Я заходил на этот сайт, но, вероятно, этих функций, как и остального исходного кода из книги, там нет. Практика Программирования превосходное чтиво. Каждый раз, когда я рекомендую эту книгу, Зевс спасает котёнка.) Здесь я хочу указать на лицензированную BSD книгу Typed Parameter Language C API , достойную уважения, но которую я никогда не использовал. Программисты Python и Perl захотят проверить функции pack() и unpack() своих языков, выполняющих тоже самое. И Ява имеет весьма внушительный Сериализуемый Интерфейс, который может быть использован подобным образом. Но если вы захотите написать свою утилиту упаковки на C, для построения пакета воспользуйтесь трюком от K&P - переменным списком аргументов для создания подобных функций. Основываясь на этом я состряпал свою собственную версию , которой, надеюсь, будет достаточно, чтобы дать вам представление как это работает.
    49
    //cm.bell-­‐labs.com/cm/cs/tpop/
    32
    http://tpl.sourceforge.net/
    33
    http://beej.us/guide/bgnet/examples/pack2.c
    34

    Beej's Guide to Network Этот код обращается к описанным выше функциям pack754(). Функции packi*() действуют подобно знакомому семейству htons(), разве что они упаковывают в символьный массив вместо другого целого)
    #include
    #include
    #include
    #include
    #include
    !
    // распределение битов в плавающих типах
    // отличается в разных архитектурах typedef float float32_t; typedef double float64_t;
    !
    /*
    ** packi16() -- сохранить битв буфере (как htons())
    */ void packi16(unsigned char *buf, unsigned int i)
    {
    *buf++ = i>>8; *buf++ = i;
    }
    !
    /*
    ** packi32() -- сохранить битв буфере (как htonl())
    */ void packi32(unsigned char *buf, unsigned long i)
    {
    *buf++ = i>>24; *buf++ = i>>16;
    *buf++ = i>>8; *buf++ = i;
    }
    !
    /*
    ** unpacki16() -- распаковать бит int из char буфера (как ntohs())
    */ unsigned int unpacki16(unsigned char *buf)
    { return (buf[0]<<8) | buf[1];
    }
    !
    /*
    ** unpacki32() -- распаковать бит int из char буфера (как ntohl())
    */ unsigned long unpacki32(unsigned char *buf)
    { return (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3];
    }
    !
    /*
    ** pack() -- упаковать данные в буфер согласно формату
    **
    ** h - бит l - бит
    ** c - бит char f - плавающее, бит
    ** s - строка (начинается сбит длины)
    */ int32_t pack(unsigned char *buf, char *format, ...)
    { va_list ap; int16_t h; int32_t l; int8_t c;
    50

    Beej's Guide to Network Programming
    float32_t f; char *s; int32_t size = 0, len;
    !
    va_start(ap, format);
    !
    for(; *format != '\0'; format++) { switch(*format) { case 'h': // бит size += 2; h = (int16_t)va_arg(ap, int); // продвинуто packi16(buf, h); buf += 2; break;
    !
    case 'l': // 32-bit size += 4; l = va_arg(ap, int32_t); packi32(buf, l); buf += 4; break;
    !
    case 'c': // бит size += 1; c = (int8_t)va_arg(ap, int); // продвинуто
    *buf++ = (c>>0)&0xff; break;
    !
    case 'f': // плавающее size += 4; f = (float32_t)va_arg(ap, double); // продвинуто l = pack754_32(f); // преобразовать в IEEE 754 packi32(buf, l); buf += 4; break;
    !
    case 's': // строка s = va_arg(ap, char*); len = strlen(s); size += len + 2; packi16(buf, len); buf += 2; memcpy(buf, s, len); buf += len; break;
    }
    }
    !
    va_end(ap);
    !
    return size;
    }
    !
    /*
    ** unpack() -- распаковать данные в буфер согласно формату
    */ void unpack(unsigned char *buf, char *format, ...)
    { va_list ap; int16_t *h; int32_t *l;
    51

    Beej's Guide to Network Programming
    int32_t pf; int8_t *c; float32_t *f; char *s; int32_t len, count, maxstrlen=0;
    !
    va_start(ap, format);
    !
    for(; *format != '\0'; format++) { switch(*format) { case 'h': // бит h = va_arg(ap, int16_t*);
    *h = unpacki16(buf); buf += 2; break;
    !
    case 'l': // бит l = va_arg(ap, int32_t*);
    *l = unpacki32(buf); buf += 4; break;
    !
    case 'c': // бит c = va_arg(ap, int8_t*);
    *c = *buf++; break;
    !
    case 'f': // плавающее f = va_arg(ap, float32_t*); pf = unpacki32(buf); buf += 4;
    *f = unpack754_32(pf); break;
    !
    case 's': // строка s = va_arg(ap, char*); len = unpacki16(buf); buf += 2; if (maxstrlen > 0 && len > maxstrlen) count = maxstrlen - 1; else count = len; memcpy(s, buf, count); s[count] = '\0'; buf += len; break;
    !
    default: if (isdigit(*format)) { // отследить максимальную длину строки maxstrlen = maxstrlen * 10 + (*format-'0');
    }
    }
    !
    if (!isdigit(*format)) maxstrlen = 0;
    }
    !
    va_end(ap);
    } Вот демонстрационная программа , использующая этот код, которая упаковывает
    35
    какие-то данные в buf и затем распаковывает их в переменные. Обратите внимание на
    52
    http://beej.us/guide/bgnet/examples/pack2.c
    35

    Beej's Guide to Network вызов unpack() со строковым аргументом (спецификатор “s”), очень мудро ставить вначале счётчик максимальной длины, во избежание переполнения буфера, например
    “96s”. Будьте осторожны при распаковке полученных посети данных - злокозненный пользователь может послать вредно построенные пакеты в попытке атаковать вашу систему
    #include
    !
    // распределение битов в плавающих типах
    // отличается в разных архитектурах typedef float float32_t; typedef double float64_t;
    !
    int main(void)
    { unsigned char buf[1024]; int8_t magic; int16_t monkeycount; int32_t altitude; float32_t absurdityfactor; char *s = "Great unmitigated Zot! You've found the Runestaff!"; char s2[96]; int16_t packetsize, ps2;
    !
    packetsize = pack(buf, "chhlsf", (int8_t)'B', (int16_t)0, (int16_t)37,
    (int32_t)-5, s, (float32_t)-3490.6677);
    !
    packi16(buf+1, packetsize);
    // запомнить размер пакета
    !
    printf("packet is %" PRId32 " bytes\n", packetsize);
    !
    unpack(buf, "chhl96sf", &magic, &ps2, &monkeycount, &altitude, s2,
    &absurdityfactor);
    !
    printf("'%c' %" PRId32" %" PRId16 " %" PRId32
    " \"%s\" %f\n", magic, ps2, monkeycount, altitude, s2, absurdityfactor);
    !
    return 0;
    } Прокручиваете вы свой собственный код или что-либо ещё, неплохо иметь общий набор программ упаковки данных, чем каждый раз упаковывать каждый бит вручную. Какой формат годится для упаковки данных Прекрасный вопрос. К счастью, RFC
    4506 , Стандарт Представления Внешних Данных, уже определил двоичные форматы для связывания данных различных типов, как то, с плавающей запятой, целочисленных, массивов, произвольных данных и т.д. Я предлагаю придерживаться его если вы собираетесь делать всё вручную. Новы не обязаны. Полиция Пакетов не стоит за вашей дверью. По крайней мере, я не думаю, что они там. В любом случае, так или эдак закодировать данные перед посылкой, это правильно
    7.5. Дитя Инкапсуляции Данных Что, в конце концов, означает инкапсуляция данных В простейшем случае, это значит, что вы подклеиваете к ним заголовок, содержащий либо идентифицирующую информацию, либо длину, либо обе.
    53
    http://tools.ietf.org/html/rfc4506 36

    Beej's Guide to Network Как должен выглядеть ваш заголовок Ну, это просто некоторые двоичные данные, представляющие нечто, по вашему мнению необходимое для завершения проекта. Ух. Туманно. Ладно. Например, скажем у вас программа многопользовательского чата, которая использует SOCK_STREAM. Когда пользователь что-то печатает (говорит) серверу надо передавать два вида информации кто сказали что сказал. Пока что всё хорошо Выспросите В чём проблема Проблема в том, что сообщения могут быть переменной длины. Некто по имени “tom” может сказать “Hi”, и другой, по имени “Benjamin” может сказать “Hey guys what is up?” Ивы посылаете всё это клиенту так как оно пришло. Ваш исходящий поток выглядит вот так
    t o m H i B e n j a m i n H e y g u y s w ha t i s u p Итак далее. Как клиент узнаёт, где заканчивается одно сообщение и начинается другое Вы можете, если хотите, сделать все сообщения одной длина и просто вызвать ранее сделанную sendall(). Но это растрата полосы пропускания Мы не хотим посылать 1024 байта чтобы “tom” мог сказать “Hi”. И мы инкапсулируем данные в крохотный заголовок и структуру пакета. И клиент и сервер знают, как упаковать и распаковать маршал из и ров ать и
    “размаршализировать”) эти данные. Теперь не смотрите, а мы начинаем определять протокол, по которому клиент и сервер общаются Для этого случая давайте примем, что имя пользователя фиксированной длины 8 байт, дополненное ’\0’. И затем примем, что данные у нас переменной длины до 128 байт. Взглянем на структуру пакета, которую мы можем использовать в этой ситуации
    !
    1. len
    (1 байт, без знака) - Общая длина пакета, включая 8-байтное имя пользователя и данные чата.
    2. name (8 байт) - имя пользователя, при необходимости дополненное нулями.
    3. байт) - сами данные, не более 128 байт. Длина пакета вычисляется как сумма этих данных плюс 8 (длина поля name). Почему я выбрали байтовые пределы для полей Я вытащил их на свет божий полагая, что они достаточно длинны. Хотя, если 8 байт может быть маловато для ваших нужд, вы можете иметь байтовое поле имени или ещё как. Выбор завами. При использовании этого определения первый пакет будет содержать следующую информацию ( в шестнадцатиричном и символьном виде
    0A 74 6F 6D 00 00 00 00 00 48 69*
    (length) T o m (padding) H И второй пакет подобен
    18 42 65 6E 6A 61 6D 69 6E 48 65 79 20 67 75 79 73 20 77 ...*
    1   2   3   4   5   6   7   8   9   10   ...   13


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