Обмен данными и вопросы кодирования
9.4. Кодирование для сжатия информации
Алгоритмы кодирования нередко используются для сжатия не только при передаче, но и при архивации данных. Одним из таких примеров является кодирование текстовой информации в сервисе коротких сообщений в мобильной связи (SMS-сервис). Текст в сообщении передается в сжатом виде.
В большинстве случаев для каждого символа текста отводится один байт, что верно и для представления текстовой (строковой) информации в языке Си. Однако из ASCII-таблицы чаще всего используются лишь первые 127 символов, включающие в себя английский алфавит, цифры, основные знаки препинания и служебные дополнительные символы (например, * или \ ). Очевидно, что для хранения этих символов достаточно лишь 7 бит (127 значений для кода символа), т.е. несколько меньше байта. Такой подход как раз и используется при кодировании SMS-сообщений, что позволяет передать больше символов в одном сообщении.
Первые поколения GSM-сетей не позволяли передавать данные на большой скорости и не позволяли совмещать передачу данных и голосовой звонок. Поэтому чем короче сообщение, тем меньше занимает эфирного времени его передача и тем меньше времени абонент занят для голосового звонка. Стандарт GSM выделяет максимум 160 байт для одного текстового сообщения, 20 из которых - служебная информация. На сам текст остается 140 байт, в которых при 7-битовом кодировании можно разметить 160 текстовых символов.
Рассмотрим алгоритм реализации такого кодирования. Однако прежде чем перейти к коду алгоритма, следует учесть еще один факт. Большинство устройств, отвечающих за передачу данных в GSM-сетях, являются модемами, которые взаимодействуют с другими устройствами посредством стандартного протокола, т.е. при помощи так называемых AT-команд. Эти команды можно посылать модему через терминал (последовательный поток ввода и поток вывода). При этом передаваемая бинарная информация представляется побайтово в шестнадцатеричном виде, чтобы не возникало конфликтов с управляющими AT-командами. Например, если в тексте сообщения содержится символ "1" (имеющий ASCII-код 49), то для 1-байтового кодирования он имел бы двоичное представление: 00110001. Для 7битного кодирования он имеет вид: 0110001.
А при передаче его через терминал он будет представлен двумя символами как "31", т.е. в виде шестнадцатеричного числа 31, представленного как текст (именно двумя символами, а не одним символом с кодом 31). Поэтому при кодировании текста для передачи SMS необходимо его не просто закодировать в 7-битный код, но затем еще и представить его в виде шестнадцатеричного текстового кода, "понятного" GSM-модему.
Из 1-байтового представления текста 7-битный код получается следующим образом. Для первого символа берутся семь младших бит и записываются в семь младших бит первого байта результата. Далее от второго символа исходного текста берутся шесть младших бит и записываются в шесть младших бит второго байта результата. От второго символа остается один (7-й) значащий бит, который записывается в старший 8-й бит первого байта результата (который как раз не использован для хранения первого символа). Для третьего входного символа в третий байт результата записывается только пять младших бит. А два остающихся значащих бита (7-й и 6-й) сохраняются соответственно в 8-м и 7-м битах второго байта результата (они как раз не использованы для хранения второго символа). При обработке восьмого символа оказывается, что его нулевой бит надо записать в восьмой байт результата, а все его значащие биты поместятся в старшие семь разрядов 7-го байта результата. Таким образом, для восьми символов потребуется всего семь байт. Далее процесс повторяется. Описанная схема представлена на рис. 9.1.
Реализация метода кодирования на языке Си может иметь следующий вид:
void encodeToPDU(char *text, char *res) { int len; int i, j; int shift; unsigned char prevNum, curNum, tmpVal1, tmpVal2, curValue; j = 0; i = 0; prevNum = 0; len = length(text); tmpVal1 = 0; tmpVal2 = 0; while(i < len) { curNum = (unsigned char)text[i]; /* special code for symbol @ */ if (text[i] == '@') {curNum = 0;} shift = (i %8); curValue = curNum & 127; curValue = (curValue >> shift); tmpVal2 = curValue; if (i > 0) { prevNum = (curNum << (8 - shift)); tmpVal1 = tmpVal1 | prevNum; } if (shift != 7) { if (i > 0) { res[j] = decToHex(tmpVal1 / 16); res[j + 1] = decToHex(tmpVal1 % 16); j+=2; } tmpVal1 = tmpVal2; } i++; } res[j] = decToHex(tmpVal1 / 16);; res[j + 1] = decToHex(tmpVal1 % 16); res[j + 2] = '\0'; }
Здесь используется функция нахождения длины строки length:
int length(char *str) { int i; i = 0; while(*str++) { i++; } return i; }
Выделение нужного количества бит в байте реализуется в языке Си посредством сдвига. При помощи этой же операции можно выставить значащие биты на нужные позиции. Для "удаления" незначащих бит можно использовать логическую операцию И с числом-маской, в котором на позиции незначащих бит выставляются нули, а на позиции значащих - единицы. После операции И с таким числом-маской все незначащие биты становятся равными нулю.
Приведенная функция encodeToPDU принимает на вход текст из параметра text и двигается по нему посимвольно, для задания текущей позиции используется переменная i. Выходная строка res также формируется последовательно в один проход (рис. 9.2).
Для всех входных символов перед их обработкой выполняется операция И с маской 127 (01111111) для выставления в 0 незначащего старшего бита:
curNum = (unsigned char) text[i] ; curValue = curNum & 127;
После этого для каждого символа входной строки вычисляется сдвиг, на который необходимо сдвинуть значащие биты вправо, чтобы далее записать оставшееся количество значащих бит в текущий выходной байт. Текущий выходной байт сохраняется в переменную
tmpVal2: curValue = (curValue >> shift); tmpVal2 = curValue;
Количество бит, записываемых в текущий выходной байт, как раз равно сдвигу. Сдвиг считается как
shift = (i % 8);
Для первого символа он равен нулю, далее возрастает на каждом шаге на единицу. Для восьмого символа он оказывается равен семи, т.е. все значащие биты сдвигаются и ни один из них не записывается в текущий выходной байт (в 8-й выходной байт). Все сдвинутые биты записываются в предыдущий выходной байт, который сохраняется в переменной tmpVal1. Для этого текущий входной байт сдвигается на величину (8 - shift) влево, т.е. нужное количество значащих бит оказывается в старших разрядах:
if (i > 0) { prevNum = (curNum << (8 - shift)); tmpVal1 = tmpVal1 | prevNum; }
И только для первого входного символа и соответствующего ему первого выходного байта не надо сохранять ничего в предыдущий выходной байт.