PHP как язык плохо подходит для работы бинарными данными напрямую. Но иногда приложения должны взаимодействовать по таким протоколам, где размер пакетов очень важен или родным форматом данных для какого-то приложения, который никем в красивый json или xml не переводится.
Целые типы данных
Напоминаю какие типы данных есть в Си, на котором основан php
Тип | Размер памяти | Значений всего |
char | 1 байт = 8 бит | 256 |
int | 2 байт = 16 бит | 2^16=65536 |
short | 2 байта | |
long | 4 байта = 32 бита | 4294967295 |
Char при этом используется универсально согласно ASCII табличке как в качестве явного кодирования текста, так и вспомогательными маркерами. Про float я умолчу, ибо мне не понадобилось.
Нотация
Если с бинарными данными не работать, то можно и забыть основы языка. С целыми числами на основании 10 всё понятно, но обычно значения длинных данных в них не пишут. Это объясняет табличку выше.
- Бинарная, например 0b1011
- Восьмеричная, например 0123
- Шестнадцатиричная, например 0xF560B1A9
Кроме этого, если вы переписываетесь с коллегами которые пишут на си, то они могут обозначать приставками или окончаниями
- Unsigned int 14u
- Long double или long int. Например 1.2l или 1L — фиг поймёшь, это единица или long
- Float (с плавающей запятой) 1.3f или 1.3F
Итого, у нас char a будет хранить значение от -128 до 127.. ну или 256 если это extended ASCII. Отлично. Теперь как это использовать в php?
Порядок битов
Честно, для меня было откровением что порядок в данных имеет значение. Я привык что 123 уже подразумевает где сотни, десятки и единицы, но для компьютера ведь всё равно. Конечно одно дело порядок написания для человека.. но тут другое — порядок записи байтов в зависимости от адреса. И больше всего удивительно что архитектуры на уровне работы с памятью разделились — x86 на стороне little endian, а за big endian SPARC и прочие. Поэтому если вы интегрируете разные архитектуры с бинарным форматом данных — договоритесь которая система будет работать.
Операции
1. Бинарное чтение файла — fopen(«binaryfile»,»b»);
2. Битовые маски — популярный метод хранить много булевых значений в одной переменной и включать/проверять с помощью AND/OR операций. Если вы когда либо выполняли chmod 755, то уже включали эти флажки на привилий.
3. Побитовые сдвиги — помогают работать с битовыми масками и с битами в вообще
$b = $a >> 2; - сдвиг битов на две позиции вправо, тут может пригодится константа PHP_INT_SIZE
$b = 8 >> 3; //1
4. Cyclic redundancy check
Проверка данных с помощью контрольных сумм увы для меня осталась за пределами понимания. Важно знать что биты суммируются, и что полиномы бывают разные. Соответсвенно алгоритмы тоже бывают разные и как правило неограничивается функцией crc32(). Гляньте на мою коллекцию и онлайн валидатор .
Вот пример самого короткого кода для CRC16 CCITT
function crc16_ССITT($data) {
$crc = 0xFFFF;
for($i = 0; $i < strlen($data); $i++) {
$x = (($crc >> 8) ^ ord($data[$i])) & 0xFF;
$x ^= $x >> 4;
$crc = (($crc << 8) ^ ($x << 12) ^ ($x << 5) ^ $x) & 0xFFFF;
}
return $crc;
}
5. Упаковка
Если вы обычную переменную в php начнёте сохранять в файл, то она наверняка не будет оптимальной. А если таких однородных данных много — то тем более имеет смысл создать более компактную версию.
Например, если вы хотите число 250 записать в один байт как char-тип, вместо того что-бы писать «строкой» в три символа, как то будет делать php по умолчанию, пишем:
pack('c', 250);
Первый аргумент это формат данных по длине. Если все данные char-типа, то можно написать c* как в регулярных выражениях, получить повторение типа. Можно написать c4, что будет аналогично cccc (четыре повторения данных char-типа)
Всего типов данных много..
- a — строка, свободные места в поле заполняются символом с кодом 0
- A — строка, свободные места заполняются пробелами
- h — шестнадцатеричная строка, младшие разряды в начале (little endian)
- H — шестнадцатеричная строка, старшие разряды в начале (big endian)
- c — 1 байт (signed char)
- C — 1 байт (unsigned char)
- x — символ с нулевым кодом
- X — возврат назад на 1 байт
- @ — заполнение нулевым кодом до заданной абсолютной позиции
Float
- f — число с плавающей точкой
- d — число двойной точности
2 байта (целые, integer) | 4 байта (long) |
|
|
Сам не проверял, но говорят что unsigned int получить не так то просто
6. Распаковка
Для раскодирования используется unpack() с аналогичным форматом типов данных, только теперь можно добавлять названия ключей для результатов ассоциативного массива
unpack("cmessageid/Vtimestamp", pack("H*",FFFF));
Заметьте что pack я тут использую вместо hex2bin, которая недоступна для версий php менее 5.4
7. Конвертирование
base_convert — конвертирование строковых представлений чисел из любого основания в другое (скажем 16 в 2)
bindec, decbin — конвертирование [2-10] двоичных и десятичных данных
octdec, decoct — конвертирование [8-10] десятичных и восьмеричных данных
hexdec, dechex — конвертирование [16-10] десятичных и шестнадцатиричных данных
ord, chr — конвертирование [256-10] десятичных и символьных (ascii) данных
base64_encode, base64_decode — конвертирование [256-256] данных, но в отличие от предыдущих форматов, данные одного значения не кодируются в 6 битах что-бы получить 64 значения, а по прежнему в 8 битах — остальные символы просто не используются.. Из-за этого формат менее эффективен в хранении, но для человека в виде текста более компактен чем нули, единицы или hex.
Отличительная и неприятная особенность, конечно в том что каждая из функций конвертирует данные со строго определённым размером данных. Обычно же у нас есть какой-то пакет данных. Их можно проконвертировать в цикле с определённым шагом..
function str2bin($str, $mode=0, $visual_aid=true) {
$out = false;
for($a=0; $a < strlen($str); $a++) {
$dec = ord(substr($str,$a,1));
$bin = '';
for($i=7; $i>=0; $i--) {
if ( $dec >= pow(2, $i) ) {
$bin .= "1";
$dec -= pow(2, $i);
} else {
$bin .= "0";
}
}
/* Default-mode */
if ( $mode == 0 ) $out .= $bin;
/* Human-mode (easy to read) */
if ( $mode == 1 ) $out .= $bin . " ";
/* Array-mode (easy to use) */
if ( $mode == 2 ) $out[$a] = $bin;
if($visual_aid){
$out.=" ";
}
}
return $out;
}
function str2dec($string, $visual_aid=true){
$hex='';
for ($i=0; $i < strlen($string); $i++) {
$hex .= ord($string[$i]);
if($visual_aid){
$hex.= "(".$string[$i].") ";
}
}
return $hex;
}
function str2hex($string, $visual_aid=true){
$hex='';
for ($i=0; $i < strlen($string); $i++) {
$hex .= dechex(ord($string[$i]));
if($visual_aid){
$hex.= " ";
}
}
return $hex;
}
function hex2str($hex){
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2) {
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
return $string;
}