Когда я читал исходный код сетевой системы, htons() и ntohs() были самыми первыми двумя функциями, которые меня сбили с толку. Чтобы понять их, я решил освежить угасающие знания, полученные в колледже, чтобы выжать последнюю долю концептуальных сомнений в порядке байтов.

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

бит - это просто 0 или 1. Физически он иногда представляется как крошечная магнитная область на металлической пластине.

Поднимаясь вверх, у нас есть байт, то есть 8 бит. Обычно мы используем байт для измерения размеров данных, как мы используем счетчик для измерения размеров объектов. Поэтому вместо того, чтобы сказать «что-то 32 бита», мы обычно говорим «это 4 байта».

Двигаясь вверх, мы получаем слово размером 4 байта (или 32 бита) в 32-битной архитектуре. Обратите внимание, что в этом тексте я использую 32-битную архитектуру, чтобы он был кратким, и эту концепцию можно легко перенести на 64-битные архитектуры.

Порядок байтов сети и хоста

порядок байтов (также известный как порядок байтов) определяет, как слово сохраняется в памяти и как оно передается по сети. В обратном порядке байтов старший значащий байт байт устанавливается по младшему адресу; в то время как в маленькой Индии некоторые блюда действительно горячие… нет… в little-endian старший байт установлен по старшему адресу.

Укладка байтов на физический носитель (то есть в память и сеть) аналогична укладке полов, в обоих случаях можно положить деревянную плитку или 4- байта word в обоих направлениях. Однако системный разработчик должен принять решение, чтобы стиль оставался неизменным. В соответствии с RFC 1700 разработчики сетевых протоколов выбрали метод big-endian. Однако некоторые разработчики хост-систем не согласны с этим. В X86 это прямой порядок байтов; В ARM могло быть и то, и другое. Это означает, что фактические данные одного и того же значения различаются на разных физических носителях. Например, значение A1B2C3D4 (2712847316 в десятичной системе) может иметь две формы, как показано ниже:

На приведенном выше рисунке каждое поле - например, поле, содержащее A1 - представляет байт . Обратите внимание, что «порядок следования байтов» основан на единице измерения байт, а не бит.

Машины могут обрабатывать оба формата почти одинаково хорошо, но люди жалуются, что числа в little-endian поменялись местами. Почему? Разве не логичнее установить менее значимый байт на меньший адрес, а больший - на более высокий?

Причина в том, что когда мы пишем цифровую форму на бумаге (то есть на другом физическом носителе), мы бессознательно используем метод big endian. Взяв в качестве примера указанное выше число (A1B2C3D4), наше подсознание рисует младший и высокий адреса слева направо, даже если их там нет:

Но если мы поручаем нашему подсознанию рисовать их справа налево, возможно, мы сможем примирить конфликты между логическим обоснованием и интуицией.

На мой взгляд, это вполне приемлемо, потому что мы используем всевозможные системы координат при размещении компонентов пользовательского интерфейса на экране (например, приложения, разработки игр), например:

Что вы думаете?

Затем мы посмотрим, как теории используются и почему они имеют значение на практике.

htons() ntohs()

Эти две функции заполняют пробел в формате между сетью и хостами. Технически, когда хосты обмениваются данными по сети, они используются для преобразования порядка байтов пакета. Если порядок байтов хоста и сети одинаковый (это означает, что они оба используют обратный порядок байтов), две функции просто ничего не делают. Когда они различаются (это означает, что хост использует обратный порядок байтов), htons() преобразует данные из обратного порядка байтов в обратный порядок байтов, и ntohs() преобразует прямой порядок байтов обратно в прямой порядок байтов.

Есть еще одна пара таких функций, htonl() и ntohl(), которые работают с числами, большими, чем htons() и ntohs(). Они основаны на том же принципе, поэтому я не буду их обсуждать.

Проверка фактов

Для уверенности мне всегда нужен конкретный код.

#include <stdio.h>
#define BITS_PER_BYTE 8
int main() {
  unsigned int anint = 0xa1b2c3d4;
  unsigned char *truth = &anint;
  printf("value in decimal: %u\n", anint);
  printf("0x");
  for (int i = 0; i < sizeof(anint); i++) {
    printf("%2X", truth[i]);
  }
  printf("\n");
  
  unsigned int anint_net = htons(anint);
  truth = &anint_net;
  printf("value in decimal after hton: %u\n", anint_net);
  printf("0x");
  for (int i = 0; i < sizeof(anint_net); i++) {
    printf("%02X", truth[i]);
  }
  printf("\n");
}

Результат на моей машине:

value in decimal: 2712847316
0xD4C3B2A1
value in decimal after hton: 54467
0xC3D40000

Как показано в примере, htons() изменяет исходное значение anint с использованием обратного порядка байтов для подготовки к передаче по сети, и исходное значение будет восстановлено на принимающем узле с помощью ntohs(). Хотя в этом примере не показаны реальные сетевые операции, я думаю, вы поняли идею.

Как определить

Мы можем повторно использовать некоторый код из приведенного выше примера, чтобы запустить функцию, которая может отображать порядок байтов машины:

int isLittle() {
  unsigned int anint = 0xa1b2c3d4;
  unsigned char *truth = &anint;
  return truth[0] == 0xd4;
}

На самом деле, есть гораздо более простой способ - напрямую читать информацию о процессоре. Если спрашиваешь Ubuntu, ты знаешь метод без кода:

cpu | grep "Byte Order"

Я думаю, что текст достаточно лаконичен, чтобы можно было сделать вывод.

Если вам нравится это чтение, пожалуйста, хлопайте по нему или следуйте, нажимая кнопки. Спасибо за ваше время и надеюсь увидеть вас в следующий раз.

Изначально опубликовано на holmeshe.me.