Форматирование данных
Для настоящего протокола потребовалось бы написать не один десяток сих функций – на все возможные варианты. Можно было бы несколько упростить процесс, используя макросы или функции для обработки новых типов данных (short, long и т. п.), но и тогда подобный повторяющийся код было бы трудно воспринимать, трудно поддерживать, в итоге он стал бы потенциальным источником ошибок.
Именно повторяемость кода и является его основной чертой, и здесь-то м и может помочь грамотно подобранный способ записи. Позаимствовав идею у printf, мы можем определить свой маленький язык спецификации, в котором каждый пакет будет описываться краткой строкой, дающей информацию о размещении данных внутри него. Элементы пакета даруются последовательно: с обозначает 8-битовый символ, s – 16-битовoe короткое целое, а 1-32-битовое длинное целое. Таким образом, например, пакет первого типа (включая первый байт определения типа) может быть представлен форматной строкой cscl. Теперь мы в состоянии использовать одну-единственную функцию pack для создания пакетов обоих типов; описанный только что пакет будет создан вызовом:
pack(buf, "cscl", 0x01, count, val, data);
В нашей строке формата содержатся только описания данных, поэтому ам нет нужды использовать какие-либо специальные символы – вроде в printf.
На практике о способе декодирования данных могла бы сообщать приемнику информация, хранящаяся в начале пакета, но мы предположим, что для определения формата данных используется первый байт пакета. Передатчик кодирует данные в этом формате и передает их; приемник считывает пакет, анализирует первый байт и использует его для перекодирования всего остального.
Ниже приведена реализация pack, которая заполняет буфер buf кодированными в соответствии с форматом значениями аргументов. Мы сделали значения беззнаковыми, в том числе байты буфера пакета, чтобы избежать проблемы переноса знакового бита. Чтобы укоротить описания, мы использовали некоторые привычные определения типов:
Точно так же, как sprinth, strcopy и им подобные, наша функция предполагает, что буфер имеет достаточный размер, чтобы вместить результат; обеспечить его должна вызывающая сторона. Мы не будем делать попыток определить несоответствия между строкой формата и списком аргументов.