---
title: Работа с бинарными данными в php
date: 2012-11-14T10:00
tags: [php]
---

PHP как язык плохо подходит для работы бинарными данными напрямую. Но иногда приложения должны взаимодействовать по таким протоколам, где размер пакетов очень важен или [родным форматом данных](http://thefrozenfire.com/2010/12/nbt-decoderencoder-for-php/) для какого-то приложения, который никем в красивый json или xml не переводится.

### Целые типы данных

Напоминаю какие типы данных есть в Си, на котором основан php

|   |   |   |
|---|---|---|
|Тип|Размер памяти|Значений всего|
|char|1 байт = 8 бит|256|
|int|2 байт = 16 бит|2^16=65536|
|short|2 байта||
|long|4 байта = 32 бита|4294967295|

Char при этом используется универсально согласно ASCII табличке как в качестве явного кодирования текста, так и вспомогательными маркерами. Про float я умолчу, ибо мне не понадобилось.

---

  

### Нотация

Если с бинарными данными не работать, то можно и забыть основы языка. С целыми числами на основании 10 всё понятно, но обычно значения длинных данных в них не пишут. Это объясняет табличку выше.

- Бинарная, например **0b**1011
- Восьмеричная, например **0**123  
    
- Шестнадцатиричная, например **0x**F560B1A9

<iframe width="934" height="350" src="https://www.youtube.com/embed/eAB6i4lDU-k" title="PHP 5.4 Support for Binary Format" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
Кроме этого, если вы переписываетесь с коллегами которые пишут на си, то они могут обозначать приставками или окончаниями

- Unsigned int 14**u**
- Long double или long int. Например 1.2**l** или 1**L** - фиг поймёшь, это единица или long
- Float (с плавающей запятой) 1.3**f** или 1.3**F**

Итого, у нас **char a** будет хранить значение от -128 до 127.. ну или 256 если это extended ASCII. Отлично. Теперь как это использовать в php? 

### Порядок битов

Честно, для меня было откровением что порядок в данных имеет значение. Я привык что 123 уже подразумевает где сотни, десятки и единицы, но для компьютера ведь всё равно. Конечно одно дело порядок написания для человека.. но тут другое - порядок записи байтов в зависимости от адреса. И больше всего удивительно что архитектуры на уровне работы с памятью [разделились](http://en.wikipedia.org/wiki/Endianness) - x86 на стороне little endian, а за big endian SPARC и прочие. Поэтому если вы интегрируете разные архитектуры с бинарным форматом данных - договоритесь которая система будет работать.

  

### Операции

1. Бинарное чтение файла - fopen("binaryfile","b");

2. [Битовые маски](http://stackoverflow.com/questions/5319475/bitmask-in-php-for-settings) - популярный метод хранить много булевых значений в одной переменной и включать/проверять с помощью AND/OR операций. Если вы когда либо выполняли **chmod 755**, то уже включали эти флажки на привилий.  

3. [Побитовые сдвиги](http://php.net/manual/en/language.operators.bitwise.php) - помогают работать с битовыми масками и с битами в вообще

`$b = $a **>>** 2; - сдвиг битов на две позиции вправо, тут может пригодится константа PHP_INT_SIZE $b = 8 >> 3; //1`

#### 4. Cyclic redundancy check

Проверка данных с помощью контрольных сумм увы для меня осталась за пределами понимания. Важно знать что биты суммируются, и что полиномы бывают разные. Соответсвенно алгоритмы тоже бывают разные и как правило неограничивается функцией [crc32()](http://php.net/manual/en/function.crc32.php). Гляньте на [мою коллекцию](https://gist.github.com/4251018) и [онлайн валидатор](http://www.zorc.breitbandkatze.de/crc.html) .

Вот пример самого короткого кода для CRC16 CCITT

```php
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)|
|- s - short (16 bit)- S - unsigned short- **n** - short (big endian)- v - unsigned short (little endian)- i - integer (размер и порядок байтов определяется архитектурой)- I - unsigned integer|- l - знаковое длинное целое (32 бита, порядок знаков определяется архитектурой)- L - беззнаковое длинное целое- N - беззнаковое длинное целое (32 бита, старшие разряды в конце)- V - беззнаковое целое (32 бита, младшие разряды в конце)|

Сам не проверял, но говорят что [unsigned int получить](http://proger.i-forge.net/Forcing_PHP_signed_integer_as_unsigned/O1p) не так то просто

#### 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.

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

```php
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;
}
```

По теме советую почитать

- [Binary parsing with PHP](https://igor.io/2012/09/24/binary-parsing.html)
- [Функции pack и unpack. Работа с бинарными данными](http://kamashev.name/2012/06/function-pack-and-unpack-process-binary-data/)

## Related

- [CSV - кодировки, импорт и экспорт](/ru/blog/tech/backend/php/csv-kodirovki-import-i-eksport/)
- [Drupal 7](/ru/blog/tech/backend/php/drupal-7/)
- [Mysql import - export](/ru/blog/tech/backend/DB/mysql/mysql-import-export/)
