Основы разработки программного обеспечения на примере языка С

Обмен данными и вопросы кодирования

Разбить на страницы
Показывать лекцию целиком

Любые предложения люди понимают иначе, чем тот, кто их вносит.

Следствие:

Даже если ваше объяснение настолько ясно, что исключает всякое ложное толкование, все равно найдется человек, который поймет вас неправильно.

9.1. Соглашения о связи

При общении с формальными устройствами следует строго придерживаться принятых договоренностей - протоколов взаимодействия. Например, перед обращением к устройству надо проверить, что оно включено и не занято взаимодействием с другим партнером (с другой программой). Такое обычно возможно, если мы "тесно" связаны с устройством, живем в одном адресном пространстве, на одном компьютере.

Но это далеко не всегда так. Часто мы взаимодействуем с другим формальным партнером только через канал передачи данных. Да еще этот канал подвержен внешним воздействиям, которые могут исказить передаваемый сигнал. Тогда кроме соблюдения протокола важны еще и средства контроля приема/передачи данных.

Протокол подобного обмена обычно служит для посылки сообщения от одного источника к нескольким потребителям. Поэтому адрес (идентификационный код) источника включается в заголовок передаваемого блока данных. Для этой цели в начале передачи сообщения следует признак начала (STX), в который включают и идентификатор, и длину передаваемой последовательности.

Получив его, принимающая сторона может зарезервировать буфер необходимого размера для ввода сообщения, после чего отправляет приглашение к продолжению диалога (RTS). По окончании передачи источником данных посылается признак конца сообщения (ETX). В состав завершающей посылки обычно включается контрольная сумма. На переданное сообщение источником ожидается либо подтверждение (AK), либо указание на сбой в приеме данных (NAK). В последнем случае передача повторяется.

Любое принятое сообщение сначала контролируется на целостность, а уже потом расшифровывается (интерпретируется). Принимающая сторона может проверить и то, что отдельные элементы последовательности получены, и то, что все сообщение принято правильно (принятая контрольная сумма совпадает с рассчитанной по тексту сообщения).

При нарушении целостности партнеру обычно направляется просьба повторить посылку. Конечно, при большой "зашумленности" канала связи/передачи оба партнера будут все время переспрашивать друг друга, так и не произведя собственно обмен данными. Поэтому в сообщения стараются ввести такую избыточность, которая позволила бы в ряде случаев не только обнаружить искажение, но и исправить отдельные ошибки в принятом блоке данных.

9.2. Помехозащищенное кодирование

Первый уровень избыточности вводится на уровне передаваемых символов (8 бит), слов, размер которых может быть 16, 32, 64 бита в зависимости от "ширины" линии передачи данных. Например, для этого в состав символа, для кодирования которого используется 8 бит (256 возможных значений), добавляется дополнительный "контрольный" разряд. Этот избыточный бит не создает новых символов, но его значение устанавливается, например, так, чтобы сумма единиц в передаваемом коде была всегда четная.

Другими словами, складывая биты принятого символа по модулю два с учетом контрольного разряда, принимающая сторона должна всегда получать 0. При этом способе избыточного кодирования, передавая код 01001101, мы должны добавить контрольный разряд, равный 0, а для кода 11101100 контрольный разряд должен быть равным 1.

Такая система избыточного кодирования позволяет обнаружить неверно переданный символ, если в его коде произошла одна ошибка (пропуск - замена 1 на 0 или ложное срабатывание - замена 0 на 1).

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

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

Пример сообщения из пяти байт
Разряды: 0 1 2 3 4 5 6 7 к.р.
0 байт 1 0 1 1 0 1 0 1 1
1 байт 1 1 1 0 0 1 0 1 1
2 байт 0 0 1 0 0 0 1 0 0
3 байт 1 1 1 1 0 0 1 0 1
4 байт 0 0 0 1 1 0 1 1 0
Контр.байт 1 0 0 1 1 0 1 1 1

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

При одиночной ошибке (табл. 9.1(б)) четвертого бита в первом байте мы легко установим, что байт был принят неверно. Количество единичных разрядов в нем оказалось при приеме нечетным (семь). Но мы не знаем, какой разряд был искажен.

Пример сообщения с ошибкой
Разряды: 0 1 2 3 4 5 6 7 к.р.
0 байт 1 0 1 1 0 1 0 1 1
1 байт 1 1 1 0 0 1 0 1 1
2 байт 0 0 1 0 0 0 1 0 0
3 байт 1 1 1 1 0 0 1 0 1
4 байт 0 0 0 1 1 0 1 1 0
Контр.байт 1 0 0 1 1 0 1 1 1

Это может быть любой из информационных разрядов. А может, мы неверно приняли контрольный разряд байта? Выяснить это удастся только после приема контрольной суммы всего сообщения. Сам байт контрольной суммы дополнен контрольным разрядом и принят правильно (содержит шесть единиц). А при расчете контрольной суммы по колонкам (разрядам) сообщения мы поймем, что в четвертом разряде в контрольной сумме допущена ошибка.

Это позволяет установить, что при приеме первого байта сообщения четвертый разряд был искажен. Мы получили 1, а должны были получить 0.

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

9.3. Протоколы обмена

Ранее уже говорилось, что при взаимодействии с формальными механизмами обе стороны общения обязаны соблюдать некоторый протокол (договоренность) обмена данными. Так, для случая, когда имеются один источник данных (передатчик) и несколько приемников, часто используется протокол ARINC 429 (ГОСТ 18977-79). Он использует в качестве единицы передачи слово, содержащее 32 разряда.

Формат слов ГОСТ 18977-79
32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
к/р Сп.Код Поле данных Ид.Ист. Метка (код команды)
к/р Количество слов сообщения STX
к/р Символ 3 Символ 2 Символ 1 TXT
к/р

Обычно контрольный разряд (к/р) ARINC-слова выбирается так, чтобы сумма единиц в слове была нечетной. При этом, как и в большинстве систем передачи данных, контроль целостности принятого слова выполняется на аппаратном уровне. За программой остается только проверка контрольной суммы и интерпретация принятого сообщения.

Младшие разряды слова отводятся под двоично-восьмеричный код метки слова. Слова с различными метками имеют различную интерпретацию в протоколе и различный формат. Сама метка записывается по традиции справа налево. Так, код метки с восьмеричным номером 012 (прямое представление 0 001 010) будет в разрядах 1-8 выглядеть так: 0101000.

Такое представление сохранилось с тех пор, когда слово передавалось по последовательному каналу, принималось и обрабатывалось в сдвиговом регистре. При этом метка поступала на обработку первой. А по завершении приема слова можно было сформировать и проверить контрольный разряд. В настоящее время чаще слово передается побайтово, но традиции кодирования справа налево сохранились.

Поле "Ид.Ист." позволяет закодировать источник данных (сообщения). Понятно, что их не может быть более четырех. В ряде случаев это поле служит для указания адресата - приемника данных.

Поле "Сп.Код" применяется для кодирования специальной информации - расширения поля данных. Например, в нем может указываться знак числа, направление ветра (юг, восток, север, запад) и т.п.

В тех случаях, когда передается пакет текстовых данных, в теле сообщения используются слова с TXT-метками, содержательная часть которых включает три символа (7-разрядных). В поле "Сп.Код" указываются признаки "первый/последний". В тех случаях, когда длина сообщения не кратна трем, по соглашению полагается заполнять поля неиспользуемых символов нулями.

Кроме упомянутых в разделе 9.1 меток STX, ETX, RTS, ACK, NAK, ориентированных на передачу пакетов данных, протокол ГОСТ 18977-79 предусматривает и такие слова, которые полностью самодостаточны. Их поля данных могут интерпретироваться как текущая высота, скорость, сила ветра, состояние устройства (биты признаков) и т.п.

Как правило, протокол подобного обмена включает периодическую посылку "пустого" слова (Zero Label). Его появление на шине обмена говорит о том, что источник данных "жив" и нормально функционирует. Отсутствие подобного слова в течение двойного периода посылки означает для приемника либо обрыв линии передачи, либо отказ источника данных.

Другой протокол (ГОСТ 27765.52-87) предназначен для организации связи нескольких устройств по магистральной (обычно последовательной) шине данных. При этом используются 20-битовые слова, содержащие в начале 3 специальных синхронизирующих бита (sss). По ним аппаратура связи может настроиться на прием слова.

Формат слов ГОСТ 27765.52-87
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
s s s Адрес ОУ Длина сообщения
s s s Данные
s s s Адрес ОУ Ош Ос Зн н/и к/р

При приеме слова аппаратура обслуживания линии связи использует первые три разряда для настройки на прием передаваемых данных. Последним принимается контрольный разряд, который, как правило, проверяется на аппаратном уровне. Бит четности (к/р) формируется так, чтобы количество единиц у 17-и информационных разрядов было нечетно.

В содержательной части (оставшиеся 16 бит) содержится код (адрес оконечного устройства) приемника (адресата), длина сообщения и различная управляющая информация. В ее состав, так же как и в ARINC-протоколе, может включаться дополнительно код источника данных. В ответном слове (10-й разряд Ос = 1) принимающая сторона может установить код ошибки (9-й разряд), признак "Абонент занят" (16-й разряд), "Абонент неисправен" (17-й разряд) и т.п. Традиционно посылка пакета начинается по инициативе передатчика (источника) данных и заканчивается ответным словом приемника.

Как правило, код оконечного устройства 11111 предназначен для одновременной передачи команды нескольким приемникам сразу. При этом не предусматривается прием ответного слова. Их может быть больше одного и разных по содержанию (кто-то принял, а кто-то не принял). Такие "широковещательные" команды могут обеспечить повышение скорости передачи данных, но, безусловно, снижают надежность доставки.

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

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.

(рис 9.1) Семибитное кодирование строки "Hello C!" в SMS (рис 9.2) Работа функции encodeToPDU для строки "Hello C!"

Реализация метода кодирования на языке Си может иметь следующий вид:

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

И только для первого входного символа и соответствующего ему первого выходного байта не надо сохранять ничего в предыдущий выходной байт.

Для восьмого входного символа все семь значащих бит записываются в 7-й байт результата. После каждого перехода к следующему символу во входной строке необходимо сохранить текущий выходной байт в переменную tmpVall, чтобы "освободить" для следующего шага переменную tmpVal2:

if (shift != 7)
{
  ...
    tmpVal1 = tmpVal2;
}  
    

Такое перемещение не надо выполнять только для каждого восьмого входного символа, когда все семь значащих бит сохраняются в предыдущий выходной байт, а текущий оказывается незаполненным (shift = 7).

Как уже было отмечено, кодирование происходит в один проход. В результате выполнения функции получается не бинарный код, реально отправляемый в SMS, а его шестнадцатеричное представление, которое можно передать GSM-модему посредством терминала. По-этому каждый выходной байт представляется в виде пары шестнадцатеричных цифр:

res[ j] = decToHex(tmpVal1 / 16);
res[ j + 1] = decToHex(tmpVal1 % 16);
  
    

Функция decToHex преобразует десятичную цифру в шестнадцатеричную.

char decToHex(unsigned char val)
{
  if ( (val >= 0)  (val <= 9) )
  {
    return (char)(val + (unsigned char)'0');
  } else
  {
    if ( (val >= 10)  (val <= 15) )
    {
      return (char)(val + (unsigned char)'A' - 10);
    } else
    { return 0; }
  }
}  
    

Деление десятичного числа на 16 дает первый разряд шестнадцатеричного числа, а остаток от деления - второй. Отметим, что в выходную строку записывается не текущий получаемый выходной байт (tmpVal2), а предыдущий - tmpVal1, так как к нему в старшие биты уже добавлены биты следующего входного символа (получаемые как curNum << (8 - shift)). Поэтому после прохождения всей входной строки в цикле while (i < len) {...} в переменной tmpVal2 остается последний формируемый выходной символ, который в конце цикла записывается в tmpVal1. Его приходится дополнительно кодировать в шестнадцатеричное представление после завершения цикла:

res[j] = decToHex(tmpVal1 / 16);
res[j + 1] = decToHex(tmpVal1 % 16);
res[j + 2] = '\0';  
    

В конец строки согласно правилам представления строки в языке Си дописывается символ '\0 '.

Теперь рассмотрим функцию декодирования сообщения decodeFromPDU:

void decodeFromPDU(char *str, char *res)
{
  int len;
  int i, j;
  int shift;
  unsigned char prevNum, d1, d2, curNum, curRes;
  j = 0;
  len = length(str);
  i = 1;
  prevNum = 0;
  while(str[i-1])
  {
    if(str[i])
    {
      d1 = hexToDec(str[i-1]);
      d2 = hexToDec(str[i]);
      curNum = d1*16 + d2;
      shift = ((((i + 1) / 2)-1) % 7);
      if(((shift) == 0)(i>1))
      {
        prevNum = prevNum  127;
        res[j++] = prevNum;
        /* special code for symbol @ */
        if (res[(j-1)] == 0)
        {res[(j-1)] = (unsigned char)'@';}
        prevNum = (curNum >> (7 - shift));
      }
      curRes = (curNum << shift) + prevNum;
      curRes = curRes  127;
      res[j++] = curRes;
      /* special code for symbol @ */
      if (res[(j-1)] == 0)
      {res[(j-1)] = (unsigned char)'@';}
      prevNum = (curNum >> (7 - shift));
    } else
    {
      break;
    }
    i+=2;
  }
  if(shift == 6)
  {
    prevNum = prevNum  127;
    /* special code for symbol @ */
    if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';}
    res[j++] = prevNum;
  }
  res[j] = '\0';
}  
    

Она также последовательно движется по входной строке str, в которой закодированный текст SMS-сообщения представлен в шестнадцатеричном текстовом виде.

Поэтому шаг равен двум (на каждой итерации цикла прохода по строке str выполняется i+=2) и очередные два символа переводятся в один байт кода SMS:

d1 = hexToDec(str[i-1]);
d2 = hexToDec(str[i]);
curNum = d1*16 + d2;  
    

Функция hexToDec переводит шестнадцатеричную текстовую цифру в десятичное число:

unsigned char hexToDec(char hexDigit)
{
  if ((hexDigit >= '0')  (hexDigit <= '9'))
  {
    return (unsigned char)hexDigit - (unsigned char)'0';
  } else
  {
    if ((hexDigit >= 'A')  (hexDigit <= 'F'))
    {
      return (unsigned char)hexDigit -
      (unsigned char)'A' + 10;
    } else
    {
      return 0;
    }
  }
}  
    

Далее рассчитывается сдвиг, на который в текущем кодированном байте сдвинут код текущего символа: shift =( (((i + 1) / 2) -1) % 7);

Здесь ((i + 1) / 2) дает номер входного байта (i - это счетчик шестнадцатеричных символов, начинающийся с 0, на каждый входной кодированный байт занято две шестнадцатеричные цифры).

На первом шаге (i=1) сдвиг равен нулю, на втором он равен 1, для 7-го входного кодированного байта (i=13) сдвиг равен 6. Для 8-го входного байта сдвиг снова равен 0, так как в его 7-ми младших байтах полностью содержится 9-й выходной символ. А 8-й выходной символ хранится в 7-ми старших битах 7-го входного байта.

На каждом 7-м входном байте, для которого сдвиг оказывается равен 0, надо сделать двойную обработку - "вынуть" символ из предыдущего байта с учетом одного бита из текущего байта и "вынуть" символ из текущего байта, для чего дополнительно выполняется следующий код:

if(((shift) == 0)(i>1))
{
  prevNum = prevNum  127;
  res[j++] = prevNum;
  /* special code for symbol @ */
  if (res[(j-1)] == 0)
  {res[(j-1)] = (unsigned char)'@';}
  prevNum = (curNum >> (7 - shift));
}  
    

Следует отметить, что если количество байт кода кратно восьми, то на последнем шаге обрабатывается лишь один младший бит последнего байта кода, а сдвиг равен шести. "Вынимание" последнего закодированного символа (из первых семи бит последнего байта кода) по алгоритму выполняется отдельно, поэтому надо после цикла сформировать последний символ:

if(shift == 6)
{
prevNum = prevNum  127;
/* special code for symbol @ */
if (res[(j-1)] == 0)
{res[(j-1)] = (unsigned char)'@';}
res[j++] = prevNum;
}
res[j] = '\0';  
    

В GSM кодировка символов определяется стандартом GSM 03.38 и несколько отличается от стандартной таблицы ASCII. В частности, символ '@' имеет нулевой код и совпадает с кодом конца строки в языке Си. Из-за этого в приведенном примере символ с кодом '\0' заменяется символом '@':

if (res[ (j-1)] == 0) {res[ (j-1)] = (unsigned char)'@';}  
    

А при работе с кодировкой GSM 03.38 на языке Си необходимо знать длину строки, иначе в случае восьми символов текста, в которых последний символ равен '@', последний значащий байт строки равен нулю, что соответствует символу конца строки, и он не будет корректно обрабатываться стандартными функциями как значащий.

В случае когда необходимо передать символы, отсутствующие в GSM 03.38, используется Unicode, в котором один символ кодируется двумя байтами. В Unicode можно хранить сразу несколько алфавитов, но в этом случае 140 байт вмещают лишь 70 символов текста. Поэтому если в SMS-сообщении содержатся русские буквы (которых нет в таблице GSM 03.38), то оно кодируется в формате Unicode и содержит максимум 70 символов. Если же использовать только английский алфавит, то, как уже было показано, в сообщении можно использовать целых 160 символов!

Вопросы и задачи для самостоятельного решения

  • Придумайте и опишите протокол общения двух программ при игре "Морской бой". Предусмотрите упаковку координат (X, Y) на поле боя в один байт 16-битовой команды протокола.
  • Реализуйте программу кодирования (шифрования) и декодирования сообщения по алгоритму Цезаря (каждая буква сообщения заменяется на другую букву алфавита, циклически сдвинутую на позицию буквы ключа).
  • Страницы:

    Любые предложения люди понимают иначе, чем тот, кто их вносит.

    Следствие:

    Даже если ваше объяснение настолько ясно, что исключает всякое ложное толкование, все равно найдется человек, который поймет вас неправильно.

    9.1. Соглашения о связи

    При общении с формальными устройствами следует строго придерживаться принятых договоренностей - протоколов взаимодействия. Например, перед обращением к устройству надо проверить, что оно включено и не занято взаимодействием с другим партнером (с другой программой). Такое обычно возможно, если мы "тесно" связаны с устройством, живем в одном адресном пространстве, на одном компьютере.

    Но это далеко не всегда так. Часто мы взаимодействуем с другим формальным партнером только через канал передачи данных. Да еще этот канал подвержен внешним воздействиям, которые могут исказить передаваемый сигнал. Тогда кроме соблюдения протокола важны еще и средства контроля приема/передачи данных.

    Протокол подобного обмена обычно служит для посылки сообщения от одного источника к нескольким потребителям. Поэтому адрес (идентификационный код) источника включается в заголовок передаваемого блока данных. Для этой цели в начале передачи сообщения следует признак начала (STX), в который включают и идентификатор, и длину передаваемой последовательности.

    Получив его, принимающая сторона может зарезервировать буфер необходимого размера для ввода сообщения, после чего отправляет приглашение к продолжению диалога (RTS). По окончании передачи источником данных посылается признак конца сообщения (ETX). В состав завершающей посылки обычно включается контрольная сумма. На переданное сообщение источником ожидается либо подтверждение (AK), либо указание на сбой в приеме данных (NAK). В последнем случае передача повторяется.

    Любое принятое сообщение сначала контролируется на целостность, а уже потом расшифровывается (интерпретируется). Принимающая сторона может проверить и то, что отдельные элементы последовательности получены, и то, что все сообщение принято правильно (принятая контрольная сумма совпадает с рассчитанной по тексту сообщения).

    При нарушении целостности партнеру обычно направляется просьба повторить посылку. Конечно, при большой "зашумленности" канала связи/передачи оба партнера будут все время переспрашивать друг друга, так и не произведя собственно обмен данными. Поэтому в сообщения стараются ввести такую избыточность, которая позволила бы в ряде случаев не только обнаружить искажение, но и исправить отдельные ошибки в принятом блоке данных.

    9.2. Помехозащищенное кодирование

    Первый уровень избыточности вводится на уровне передаваемых символов (8 бит), слов, размер которых может быть 16, 32, 64 бита в зависимости от "ширины" линии передачи данных. Например, для этого в состав символа, для кодирования которого используется 8 бит (256 возможных значений), добавляется дополнительный "контрольный" разряд. Этот избыточный бит не создает новых символов, но его значение устанавливается, например, так, чтобы сумма единиц в передаваемом коде была всегда четная.

    Другими словами, складывая биты принятого символа по модулю два с учетом контрольного разряда, принимающая сторона должна всегда получать 0. При этом способе избыточного кодирования, передавая код 01001101, мы должны добавить контрольный разряд, равный 0, а для кода 11101100 контрольный разряд должен быть равным 1.

    Такая система избыточного кодирования позволяет обнаружить неверно переданный символ, если в его коде произошла одна ошибка (пропуск - замена 1 на 0 или ложное срабатывание - замена 0 на 1).

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

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

    Пример сообщения из пяти байт
    Разряды: 0 1 2 3 4 5 6 7 к.р.
    0 байт 1 0 1 1 0 1 0 1 1
    1 байт 1 1 1 0 0 1 0 1 1
    2 байт 0 0 1 0 0 0 1 0 0
    3 байт 1 1 1 1 0 0 1 0 1
    4 байт 0 0 0 1 1 0 1 1 0
    Контр.байт 1 0 0 1 1 0 1 1 1

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

    При одиночной ошибке (табл. 9.1(б)) четвертого бита в первом байте мы легко установим, что байт был принят неверно. Количество единичных разрядов в нем оказалось при приеме нечетным (семь). Но мы не знаем, какой разряд был искажен.

    Пример сообщения с ошибкой
    Разряды: 0 1 2 3 4 5 6 7 к.р.
    0 байт 1 0 1 1 0 1 0 1 1
    1 байт 1 1 1 0 0 1 0 1 1
    2 байт 0 0 1 0 0 0 1 0 0
    3 байт 1 1 1 1 0 0 1 0 1
    4 байт 0 0 0 1 1 0 1 1 0
    Контр.байт 1 0 0 1 1 0 1 1 1

    Это может быть любой из информационных разрядов. А может, мы неверно приняли контрольный разряд байта? Выяснить это удастся только после приема контрольной суммы всего сообщения. Сам байт контрольной суммы дополнен контрольным разрядом и принят правильно (содержит шесть единиц). А при расчете контрольной суммы по колонкам (разрядам) сообщения мы поймем, что в четвертом разряде в контрольной сумме допущена ошибка.

    Это позволяет установить, что при приеме первого байта сообщения четвертый разряд был искажен. Мы получили 1, а должны были получить 0.

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

    9.3. Протоколы обмена

    Ранее уже говорилось, что при взаимодействии с формальными механизмами обе стороны общения обязаны соблюдать некоторый протокол (договоренность) обмена данными. Так, для случая, когда имеются один источник данных (передатчик) и несколько приемников, часто используется протокол ARINC 429 (ГОСТ 18977-79). Он использует в качестве единицы передачи слово, содержащее 32 разряда.

    Формат слов ГОСТ 18977-79
    32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
    к/р Сп.Код Поле данных Ид.Ист. Метка (код команды)
    к/р Количество слов сообщения STX
    к/р Символ 3 Символ 2 Символ 1 TXT
    к/р

    Обычно контрольный разряд (к/р) ARINC-слова выбирается так, чтобы сумма единиц в слове была нечетной. При этом, как и в большинстве систем передачи данных, контроль целостности принятого слова выполняется на аппаратном уровне. За программой остается только проверка контрольной суммы и интерпретация принятого сообщения.

    Младшие разряды слова отводятся под двоично-восьмеричный код метки слова. Слова с различными метками имеют различную интерпретацию в протоколе и различный формат. Сама метка записывается по традиции справа налево. Так, код метки с восьмеричным номером 012 (прямое представление 0 001 010) будет в разрядах 1-8 выглядеть так: 0101000.

    Такое представление сохранилось с тех пор, когда слово передавалось по последовательному каналу, принималось и обрабатывалось в сдвиговом регистре. При этом метка поступала на обработку первой. А по завершении приема слова можно было сформировать и проверить контрольный разряд. В настоящее время чаще слово передается побайтово, но традиции кодирования справа налево сохранились.

    Поле "Ид.Ист." позволяет закодировать источник данных (сообщения). Понятно, что их не может быть более четырех. В ряде случаев это поле служит для указания адресата - приемника данных.

    Поле "Сп.Код" применяется для кодирования специальной информации - расширения поля данных. Например, в нем может указываться знак числа, направление ветра (юг, восток, север, запад) и т.п.

    В тех случаях, когда передается пакет текстовых данных, в теле сообщения используются слова с TXT-метками, содержательная часть которых включает три символа (7-разрядных). В поле "Сп.Код" указываются признаки "первый/последний". В тех случаях, когда длина сообщения не кратна трем, по соглашению полагается заполнять поля неиспользуемых символов нулями.

    Кроме упомянутых в разделе 9.1 меток STX, ETX, RTS, ACK, NAK, ориентированных на передачу пакетов данных, протокол ГОСТ 18977-79 предусматривает и такие слова, которые полностью самодостаточны. Их поля данных могут интерпретироваться как текущая высота, скорость, сила ветра, состояние устройства (биты признаков) и т.п.

    Как правило, протокол подобного обмена включает периодическую посылку "пустого" слова (Zero Label). Его появление на шине обмена говорит о том, что источник данных "жив" и нормально функционирует. Отсутствие подобного слова в течение двойного периода посылки означает для приемника либо обрыв линии передачи, либо отказ источника данных.

    Другой протокол (ГОСТ 27765.52-87) предназначен для организации связи нескольких устройств по магистральной (обычно последовательной) шине данных. При этом используются 20-битовые слова, содержащие в начале 3 специальных синхронизирующих бита (sss). По ним аппаратура связи может настроиться на прием слова.

    Формат слов ГОСТ 27765.52-87
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    s s s Адрес ОУ Длина сообщения
    s s s Данные
    s s s Адрес ОУ Ош Ос Зн н/и к/р

    При приеме слова аппаратура обслуживания линии связи использует первые три разряда для настройки на прием передаваемых данных. Последним принимается контрольный разряд, который, как правило, проверяется на аппаратном уровне. Бит четности (к/р) формируется так, чтобы количество единиц у 17-и информационных разрядов было нечетно.

    В содержательной части (оставшиеся 16 бит) содержится код (адрес оконечного устройства) приемника (адресата), длина сообщения и различная управляющая информация. В ее состав, так же как и в ARINC-протоколе, может включаться дополнительно код источника данных. В ответном слове (10-й разряд Ос = 1) принимающая сторона может установить код ошибки (9-й разряд), признак "Абонент занят" (16-й разряд), "Абонент неисправен" (17-й разряд) и т.п. Традиционно посылка пакета начинается по инициативе передатчика (источника) данных и заканчивается ответным словом приемника.

    Как правило, код оконечного устройства 11111 предназначен для одновременной передачи команды нескольким приемникам сразу. При этом не предусматривается прием ответного слова. Их может быть больше одного и разных по содержанию (кто-то принял, а кто-то не принял). Такие "широковещательные" команды могут обеспечить повышение скорости передачи данных, но, безусловно, снижают надежность доставки.

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

    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.

    (рис 9.1) Семибитное кодирование строки "Hello C!" в SMS (рис 9.2) Работа функции encodeToPDU для строки "Hello C!"

    Реализация метода кодирования на языке Си может иметь следующий вид:

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

    И только для первого входного символа и соответствующего ему первого выходного байта не надо сохранять ничего в предыдущий выходной байт.

    Для восьмого входного символа все семь значащих бит записываются в 7-й байт результата. После каждого перехода к следующему символу во входной строке необходимо сохранить текущий выходной байт в переменную tmpVall, чтобы "освободить" для следующего шага переменную tmpVal2:

    if (shift != 7)
    {
      ...
        tmpVal1 = tmpVal2;
    }  
        

    Такое перемещение не надо выполнять только для каждого восьмого входного символа, когда все семь значащих бит сохраняются в предыдущий выходной байт, а текущий оказывается незаполненным (shift = 7).

    Как уже было отмечено, кодирование происходит в один проход. В результате выполнения функции получается не бинарный код, реально отправляемый в SMS, а его шестнадцатеричное представление, которое можно передать GSM-модему посредством терминала. По-этому каждый выходной байт представляется в виде пары шестнадцатеричных цифр:

    res[ j] = decToHex(tmpVal1 / 16);
    res[ j + 1] = decToHex(tmpVal1 % 16);
      
        

    Функция decToHex преобразует десятичную цифру в шестнадцатеричную.

    char decToHex(unsigned char val)
    {
      if ( (val >= 0)  (val <= 9) )
      {
        return (char)(val + (unsigned char)'0');
      } else
      {
        if ( (val >= 10)  (val <= 15) )
        {
          return (char)(val + (unsigned char)'A' - 10);
        } else
        { return 0; }
      }
    }  
        

    Деление десятичного числа на 16 дает первый разряд шестнадцатеричного числа, а остаток от деления - второй. Отметим, что в выходную строку записывается не текущий получаемый выходной байт (tmpVal2), а предыдущий - tmpVal1, так как к нему в старшие биты уже добавлены биты следующего входного символа (получаемые как curNum << (8 - shift)). Поэтому после прохождения всей входной строки в цикле while (i < len) {...} в переменной tmpVal2 остается последний формируемый выходной символ, который в конце цикла записывается в tmpVal1. Его приходится дополнительно кодировать в шестнадцатеричное представление после завершения цикла:

    res[j] = decToHex(tmpVal1 / 16);
    res[j + 1] = decToHex(tmpVal1 % 16);
    res[j + 2] = '\0';  
        

    В конец строки согласно правилам представления строки в языке Си дописывается символ '\0 '.

    Теперь рассмотрим функцию декодирования сообщения decodeFromPDU:

    void decodeFromPDU(char *str, char *res)
    {
      int len;
      int i, j;
      int shift;
      unsigned char prevNum, d1, d2, curNum, curRes;
      j = 0;
      len = length(str);
      i = 1;
      prevNum = 0;
      while(str[i-1])
      {
        if(str[i])
        {
          d1 = hexToDec(str[i-1]);
          d2 = hexToDec(str[i]);
          curNum = d1*16 + d2;
          shift = ((((i + 1) / 2)-1) % 7);
          if(((shift) == 0)(i>1))
          {
            prevNum = prevNum  127;
            res[j++] = prevNum;
            /* special code for symbol @ */
            if (res[(j-1)] == 0)
            {res[(j-1)] = (unsigned char)'@';}
            prevNum = (curNum >> (7 - shift));
          }
          curRes = (curNum << shift) + prevNum;
          curRes = curRes  127;
          res[j++] = curRes;
          /* special code for symbol @ */
          if (res[(j-1)] == 0)
          {res[(j-1)] = (unsigned char)'@';}
          prevNum = (curNum >> (7 - shift));
        } else
        {
          break;
        }
        i+=2;
      }
      if(shift == 6)
      {
        prevNum = prevNum  127;
        /* special code for symbol @ */
        if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';}
        res[j++] = prevNum;
      }
      res[j] = '\0';
    }  
        

    Она также последовательно движется по входной строке str, в которой закодированный текст SMS-сообщения представлен в шестнадцатеричном текстовом виде.

    Поэтому шаг равен двум (на каждой итерации цикла прохода по строке str выполняется i+=2) и очередные два символа переводятся в один байт кода SMS:

    d1 = hexToDec(str[i-1]);
    d2 = hexToDec(str[i]);
    curNum = d1*16 + d2;  
        

    Функция hexToDec переводит шестнадцатеричную текстовую цифру в десятичное число:

    unsigned char hexToDec(char hexDigit)
    {
      if ((hexDigit >= '0')  (hexDigit <= '9'))
      {
        return (unsigned char)hexDigit - (unsigned char)'0';
      } else
      {
        if ((hexDigit >= 'A')  (hexDigit <= 'F'))
        {
          return (unsigned char)hexDigit -
          (unsigned char)'A' + 10;
        } else
        {
          return 0;
        }
      }
    }  
        

    Далее рассчитывается сдвиг, на который в текущем кодированном байте сдвинут код текущего символа: shift =( (((i + 1) / 2) -1) % 7);

    Здесь ((i + 1) / 2) дает номер входного байта (i - это счетчик шестнадцатеричных символов, начинающийся с 0, на каждый входной кодированный байт занято две шестнадцатеричные цифры).

    На первом шаге (i=1) сдвиг равен нулю, на втором он равен 1, для 7-го входного кодированного байта (i=13) сдвиг равен 6. Для 8-го входного байта сдвиг снова равен 0, так как в его 7-ми младших байтах полностью содержится 9-й выходной символ. А 8-й выходной символ хранится в 7-ми старших битах 7-го входного байта.

    На каждом 7-м входном байте, для которого сдвиг оказывается равен 0, надо сделать двойную обработку - "вынуть" символ из предыдущего байта с учетом одного бита из текущего байта и "вынуть" символ из текущего байта, для чего дополнительно выполняется следующий код:

    if(((shift) == 0)(i>1))
    {
      prevNum = prevNum  127;
      res[j++] = prevNum;
      /* special code for symbol @ */
      if (res[(j-1)] == 0)
      {res[(j-1)] = (unsigned char)'@';}
      prevNum = (curNum >> (7 - shift));
    }  
        

    Следует отметить, что если количество байт кода кратно восьми, то на последнем шаге обрабатывается лишь один младший бит последнего байта кода, а сдвиг равен шести. "Вынимание" последнего закодированного символа (из первых семи бит последнего байта кода) по алгоритму выполняется отдельно, поэтому надо после цикла сформировать последний символ:

    if(shift == 6)
    {
    prevNum = prevNum  127;
    /* special code for symbol @ */
    if (res[(j-1)] == 0)
    {res[(j-1)] = (unsigned char)'@';}
    res[j++] = prevNum;
    }
    res[j] = '\0';  
        

    В GSM кодировка символов определяется стандартом GSM 03.38 и несколько отличается от стандартной таблицы ASCII. В частности, символ '@' имеет нулевой код и совпадает с кодом конца строки в языке Си. Из-за этого в приведенном примере символ с кодом '\0' заменяется символом '@':

    if (res[ (j-1)] == 0) {res[ (j-1)] = (unsigned char)'@';}  
        

    А при работе с кодировкой GSM 03.38 на языке Си необходимо знать длину строки, иначе в случае восьми символов текста, в которых последний символ равен '@', последний значащий байт строки равен нулю, что соответствует символу конца строки, и он не будет корректно обрабатываться стандартными функциями как значащий.

    В случае когда необходимо передать символы, отсутствующие в GSM 03.38, используется Unicode, в котором один символ кодируется двумя байтами. В Unicode можно хранить сразу несколько алфавитов, но в этом случае 140 байт вмещают лишь 70 символов текста. Поэтому если в SMS-сообщении содержатся русские буквы (которых нет в таблице GSM 03.38), то оно кодируется в формате Unicode и содержит максимум 70 символов. Если же использовать только английский алфавит, то, как уже было показано, в сообщении можно использовать целых 160 символов!

    Вопросы и задачи для самостоятельного решения

  • Придумайте и опишите протокол общения двух программ при игре "Морской бой". Предусмотрите упаковку координат (X, Y) на поле боя в один байт 16-битовой команды протокола.
  • Реализуйте программу кодирования (шифрования) и декодирования сообщения по алгоритму Цезаря (каждая буква сообщения заменяется на другую букву алфавита, циклически сдвинутую на позицию буквы ключа).
  • Вернуться к учебному плану