Пользователь
0,0
рейтинг
21 января 2014 в 23:27

Разработка → FORTH: наносервера и наноклиенты. Часть 1 tutorial

Пришло время рассказать о еще одной стороне применения замечательного языка Форт.
Этим циклом статей я покажу, как с его помощью можно создавать крошечные клиент-серверные приложения. Каждое из которых можно использовать как исследовательский и обучающий инструмент.
Для развлечения нам понадобится Windows вплоть до семерки и пакет SP-Forth. Андрей Черезов декларировал возможность запуска его форт-системы под Линуксом, но я это не проверял.

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


Создадим в блокноте или любом другом текстовом редакторе (Akelpad) файл datetimes.f в той же папке, где лежит файл spf4.exe.

Что же нам понадобится для создания сервера? Сокеты!
Чтобы начать с ними работать надо подключить соответствующую библиотеку.
~nn\lib\sock2.f

Удобно, все библиотеки хранятся в исходных текстах. Всегда можно заглянуть и посмотреть что это такое и как работает.

0 VALUE sockt
0 VALUE sockt_a
SocketsStartup THROW   .( Sockets started) CR 
CreateSocket   THROW   TO sockt .( Socket created) CR

Первые две строчки — очевидно определение переменных. VALUE переменные являются усовершенствованием классического Форта. Они позволяют избежать бесконечных разыменований (получения значения по адресу) и сделать текст более читаемым.
Третья строчка говорит сама за себя, по последней же надо дать пару комментариев.
Слово CreateSoket понятно и так, но есть нюансы. Сокеты-то бывают разные! Честно сказать в сортах сокетов я не слишком разбираюсь, но самый простой и, похоже, самый часто используемый это блокирующий двунаправленный потоковый TCP/IP сокет, который создается этим словом. Как создать сокеты другого типа, можно заглянуть в библиотеку. Магическое слово THROW всего лишь перехватывает коды ошибок и генерирует исключение, если код ошибки отличен от нуля. Скобочки печатают на терминале текст между ними, CR — перевод строки.
 
80 sockt BindSocket THROW .( Socket binded) CR
sockt ListenSocket THROW  .( Listen) CR
sockt AcceptSocket THROW TO sockt_a .( Accept connection from:) 

Вот почти все. Теперь мы сидим и слушаем на порту 80, пока кто-нибудь не постучится.
Безусловно, можно выбрать совершенно произвольный свободный порт.
Слово AcceptSoket остановит выполнение программы до момента подключения.
Кто же к нам подключился?

sockt_a GetPeerIP&Port THROW     SWAP    NtoA TYPE ."  port:" . 

выведет в консоль IP адрес и порт с которого произошло подключение. Единственный SWAP во всей программе возник только из-за не совсем продуманной реализации слова GetPeerIP&Port.

Подготовим дату и время к отправке.
Нам понадобится еще одна библиотека

lib\include\facil.f

Из которой нужно слово TIME&DATE. ( Пришлось его исправить в библиотеке, теперь оно отличается от комплектного. Оно стало выдавать миллисекунды, а еще изменился порядок даты. ) Это слово кладет на стек 7 значений. Миллисекунды, секунды, часы, год, месяц, число. Соответственно, первым со стека будет снято число. Имеется ввиду число месяца.

S" Current time: sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                       ( day)
S" -" sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                       ( month)
S" -" sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                      ( year)
S"   " sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                     ( hours)
S" -" sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                     ( minutes)
S" -" sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                    ( seconds)
S" -" sockt_a WriteSocket THROW
S>D (D.) sockt_a WriteSocket THROW                    ( milliseconds)


Выглядит ужасно. Прелесть Форта в том, что это безобразие можно превратить в конфетку.
Видно, что один и тот же текст повторяется 7 и два раза по 3. Конечно, можно облечь в цикл, но тогда текст будет трудно читаем.
Воспользуемся возможностями Форта.

Определим дополнительные слова

: send_value         S>D (D.) sockt_a WriteSocket THROW  ;
: send_delimeter  sockt_a WriteSocket THROW  ;


Логично, понятно и легко читаемо. { Ремарка: Слово S>D расширяет знаком верхний элемент стека до двойной точности. Слово (D.) преобразует число двойной точности в строку и кладет на стек её адрес и счетчик.}

: Day send_value ;
: Month send_value  ;
: Year send_value ;
: Hours send_value  ;
: Minutes send_value ;
: Seconds send_value ;
: Milliseconds send_value ; 


По-прежнему ужасно. Что же можно сделать?
На помощь приходит одна ключевая особенность языка Форт. Мы можем создавать свои собственные определяющие слова.

: make_value     CREATE DOES> DROP send_value ;
: make_values   0 DO make_value LOOP ; 


Слово make_value во время своего исполнения выберет из входного потока следующий набор литер, ограниченный пробелами и поместит его на вершину текущего словаря. Семантику этому новоиспеченному слову задаст слово после DOES>. То-есть у нас есть инструмент, с помощью которого можно создавать понятия, объединенные общей семантикой.

7 make_values        Day  Month  Year  Hours  Minutes  Seconds  Milliseconds 


И для разделителей

: make_delimeter CREATE , DOES>  1 send_delimeter ;
: make_delimeters 0 DO make_delimeter LOOP ;

BL CHAR - CHAR : 3 make_delimeters : - _


Теперь можем написать красиво

TIME&DATE
Day - Month - Year _ Hours : Minutes : Seconds : Milliseconds 


Рвем соединение

sockt_a CloseSocket THROW


И очищаем сокеты

SocketsCleanup THROW 


Все.

Как можно видеть этапы исполнения и компиляции могут чередоваться. Это еще одна базовая особенность Форта. Также хорошо написанная Форт-программа практически не требует комментариев.
Кто был достаточно внимателен, то заметил что мы переопределили базовые слова двоеточие и минус. Как быть с этим? Ответ ниже.

Причесанный код


~nn\lib\sock2.f 
lib\include\facil.f

80 CONSTANT port
0   VALUE          sockt
0   VALUE          sockt_a

: send_value   S>D (D.) sockt_a WriteSocket THROW ;
: send_delimeter sockt_a WriteSocket THROW ;

: make_value CREATE DOES> DROP send_value ;
: make_values 0 DO make_value LOOP ;

7 make_values    Day  Month  Year  Hours  Minutes  Seconds  Milliseconds 

: make_delimeter CREATE , DOES>  1 send_delimeter ;
: make_delimeters 0 DO make_delimeter LOOP ;

: define: : ;

BL  CHAR -   CHAR  :    3 make_delimeters  : - _



define:  time&date_server 

                SocketsStartup THROW   ." Sockets started" CR 
                CreateSocket   THROW   TO sockt ." Socket created" CR

                port sockt BindSocket THROW ." Socket binded" CR
                sockt ListenSocket THROW  ." Listen" CR

                sockt AcceptSocket THROW TO sockt_a ." Accept connection from: " 
                sockt_a GetPeerIP&Port THROW     SWAP        NtoA TYPE ."  port:" . 

                S" Current time:" sockt_a WriteSocket THROW

                TIME&DATE 

                Day - Month - Year _ Hours : Minutes : Seconds : Milliseconds  

                sockt_a CloseSocket THROW 

                SocketsCleanup THROW 
;

time&date_server  
@V2008n
карма
23,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (20)

  • +14
    Ох, форт, оргазмы юности… Я думал он уже кремирован!
    Хочу его!

    Работа с сокетами это как раз хардкор для форта.
    Много ли сейчас интересующихся фортом? Живо ли сообщество? Современные фреймворки для форта — было бы весьма увлекательно.
    • +1
      Разделяю ваше возбуждение! Нужна FORTH-машина на JavaScript, с возможностью выполнять FORTH в браузере, и с прозрачным взаимодействием с FORTH-машиной, работающей на сервере! Хорошая тема для дипломной работы студентам!
      • 0
        О боже! Зачем городить такой огород? Форт тем и прелестен, что он вещь в себе. Нет никаких проблем сделать так, чтобы весь Форт-интерактив шел через браузер. Сделать все это средствами самого Форта.
        80 порт в приведенной программе я назначил не просто так. К этому наносерверу можно подключиться любым браузером из любой точки интернета, если позволяет файервол.
        Придется во второй части развить тему.

        • 0
          Уже пройденный этап — так неудобно. Удобнее всего с браузером взаимодействовать через веб-сокеты: использовал в одном проекте форт сокет-сервер для точных и быстрых вычислений, а GUI был на веб-ките. Вся графика на jQuery+Html, а ядро программы и основная логика — на форте (СПФ).
      • 0
        Например: github.com/yarus23/SPF.JS — заявлена совместимость с СПФ, но я его особо не ковырял — так что ничего конкретного сказать не могу.
    • +1
      Моя цель — подогреть интерес к Форту. Не как просто к одному из языков программирования, а как к целой концепции.
  • 0
    Стек-машины (форпт-машины) живут и здравствуют, а вот сам язык подзабыт, да. Печально.
    Но это беда его эффективности, как это не странно. Во времена 16-ти бит, он творил натуральное волшебство, не претендуя на мегабайты памяти.

    Наступила эпоха 32-х бит, а затем и 64-х, а у людей в мозгу до сих пор лежит устаревшая информация про эти самые 64 Килобайта. Как говорится «мем» на лицо.

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

    ЕМНИП, каноническая реализация JVM стек-машиной и является.

    P.S. Дорогой автор, ждем от вас больше информации и лучшего оформления исходников.
    • 0
      Да, например загрузчик БСД или вот тот же самый ПДФ вырос из форта.
    • 0
      Спасибо! Оформление — большой больной вопрос. Непонятно как что работает.
  • +1
    Кто был достаточно внимателен, то заметил что мы переопределили базовые слова двоеточие и минус. Как быть с этим? Ответ ниже.

    Эмм… Зачем же так извращаться-то? А дальше-то как писать? Таки немного покритикую выбранный подход: для демонстрации тех или иных способов решения задачи в целом пример годится. Но вот в продакшене данный код бесполезен. Во-первых: совершенно ненужное переопределение стандартных и привычных слов. Во-вторых, это нужно было делать в отдельный словарь и просто переключать поиск на него в тот момент, когда это нужно. В третьих: существует куча библиотек для строковых операций в подкаталоге ~devel, в т.ч. для сложения строк/чисел, так же существуют опенсорсные либы для работы со строками (например плагины к ннкрон). Достаточно подключить и можно использовать что-то типа такого:
    " %DD%-%MM%-%YYYY%/%HH%:%MM%:%SS%"
    В четвертых: можно использовать встроенный механизм конвертации чисел для получения строки с нужным форматированием.
    Пример(код одноразовый — написал и забыл, приоритет — скорость):
    Код из реального проекта для отдачи времени в заголовках браузеру по стандарту RFC 822
    WINAPI: GetSystemTime Kernel32.dll
    
    0
    2 -- -Year		        \ Указывает текущий год
    2 -- -Month		        \ Текущий месяц; Январь = 1, Февраль = 2, и так далее
    2 -- -DayOfWeek       \ Текущий день недели; Воскресенье = 0, Понедельник = 1, и так далее
    2 -- -Day             \ Текущий день месяца
    2 -- -Hour            \ Час
    2 -- -Minute          \ Минуты
    2 -- -Second          \ Секунды
    2 -- -Milliseconds    \ Миллисекунды
    CONSTANT /SYSTEMTIME
    USER-CREATE SYSTEMTIME-BUF /SYSTEMTIME USER-ALLOT
    USER-CREATE WebTime[] 30 USER-ALLOT
    29 CONSTANT WebTime#
    
    CREATE WebDays S" Sun, Mon, Tue, Wed, Thu, Fri, Sat, " HERE SWAP DUP ALLOT 0 C, CMOVE
    CREATE WebMonths  S"     Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec " HERE SWAP DUP ALLOT 0 C, CMOVE
    
    : WebTime!   \ ( addr -- ) \ Записать по адресу addr строку с текущей датой вида "Fri, 04 Jul 2008 08:42:36 GMT"
      >R SYSTEMTIME-BUF GetSystemTime                               \ Получаем текущее системное время
      DUP -DayOfWeek W@  5 * WebDays + R@ 5 CMOVE  R> 5 + >R        \ и конвертируем его в формат RFC 822
      DUP -Day W@ 0 <# # # #> R@ SWAP CMOVE  R> 2 + >R              \ Код хоть и немного жуткий, зато быстрый — а это главное
      BL R> DUP 1+ >R C!
      DUP -Month W@  CELLS WebMonths + R@ 4 CMOVE  R> 4 + >R
      DUP -Year W@ 0 <# # # # # #> R@ SWAP CMOVE  R> 4 + >R
      BL R> DUP 1+ >R C!
      DUP -Hour W@ 0 <# # # #> R@ SWAP CMOVE R> 2 + >R
      [CHAR] : R> DUP 1+ >R C!
      DUP -Minute W@ 0 <# # # #> R@ SWAP CMOVE R> 2 + >R
      [CHAR] : R> DUP 1+ >R C!
      -Second W@ 0 <# # # #> R@ SWAP CMOVE R> 2 + >R
      S"  GMT" R> SWAP CMOVE
    ;
    
    : GetWebTime   \ ( -- a u )
      WebTime[] WebTime!
      WebTime[] 29
    ;

    В пятых:: оптимальнее сначала собирать все данные для отправки браузеру в один буфер и одним вызовом передавать в сокеты, а не делать овер 9000 апи-вызовов.
    В шестых: можно использовать вектор NOTFOUND для сбора строк, не являющихся словами. Т.е. создаем словарь с нужным набором слов, которые просто пишут в текущую позицию в буфере. Далее, когда нужно сделать форматированную строку, то просто переключаем словарь и записываем в вектор NOTFOUND слово, которое будет неизвестные слова складывать в буфер. Но это уже ближе к экзотическим способам решения задачи.
    • 0
      Совершенно справедливо. Да, пример безусловно демонстрационный. Цель была — подогреть интерес. Приятно слышать, что язык активно используется и есть люди хорошо его знающие.
  • +1
    Буквально пару недель назад впервые попробовал Forth. С непривычки мозг вскипает.
    Есть версия GNU Forth для Android, часто обновляется: gforth.
    • 0
      Глянул глазом, весьма крутая штука. И с документацией там все хорошо.
      • 0
        Да, gForth в целом довольно неплох и вполне юзабелен. Но и у него есть свои особенности, плюсы и недостатки. Один из немногих х64 фортов. Вот его репозиторий на гитхабе: github.com/forthy42/gforth — там всегда самая свежая версия. В официальных репозиториях линуксов отставание в год и более. На данный момент главное преимущество данной версии в полной поддержке стандартной либы ptheard — т.е. многопоточности. А вот с юзер-переменными там немного печальнее.
        Андроидная версия все таки еще сырая, но в целом работает.
  • +2
    Неделя Форта на Хабре
  • 0
    Когда я вижу или слышу слово «серверА» — моя рука тянется к пистолету.
  • +1
    Со своим небольшим знанием Фактора набросал нечто подобное:
    USING:  io io.sockets kernel calendar 
    io.streams.duplex accessors prettyprint
    calendar.format io.encodings.utf8 
    math.parser math.functions ;
    
    IN: my.server
    
    CONSTANT: port 80
    CONSTANT: host "127.0.0.1"
    
    : create-server ( -- server ) host port <inet4> utf8 <server> ;
    
    : make-value ( -- ) 
    
    : _ ( -- ) " " write ;
    
    : - ( -- ) "-" write ;
    
    : _: ( -- ) ":" write ;
    
    : HOURS  ( time  -- ) hour>> number>string write ;
    : MINUTES  ( time  -- ) minute>> number>string write ;
    : SECONDS  ( time  -- ) second>> round number>string write ;
    
    : print-date-now ( -- ) 
        now dup dup dup dup dup
        DAY - MONTH - YYYY _ HOURS _: MINUTES _: SECONDS ;
    
    "Starting server..." print flush
    
    create-server
    
    "Start listening..." print flush
    
    accept
    
    "Connected: " write
    [ host>> write ] [ ":" write port>> number>string print ] bi
    [ 
      print-date-now
      "" print
      flush 
    ] with-stream
    

    Было бы неплохо, если кто-нибудь расскажет, как сделать аналог make_delimeter, а то я с with-compilation-unit не могу разобраться.
    • 0
      Ох, блин, там ошибка, вот нормальный текст:
      USING:  io io.sockets kernel calendar 
      io.streams.duplex accessors prettyprint
      calendar.format io.encodings.utf8 
      math.parser math.functions ;
      
      IN: my.server
      
      CONSTANT: port 80
      CONSTANT: host "127.0.0.1"
      
      : create-server ( -- server ) host port <inet4> utf8 <server> ;
      
      : _ ( -- ) " " write ;
      
      : - ( -- ) "-" write ;
      
      : _: ( -- ) ":" write ;
      
      : HOURS  ( time  -- ) hour>> number>string write ;
      : MINUTES  ( time  -- ) minute>> number>string write ;
      : SECONDS  ( time  -- ) second>> round number>string write ;
      
      : print-date-now ( -- ) 
          now dup dup dup dup dup
          DAY - MONTH - YYYY _ HOURS _: MINUTES _: SECONDS ;
      
      "Starting server..." print flush
      
      create-server
      
      "Start listening..." print flush
      
      accept
      
      "Connected: " write
      [ host>> write ] [ ":" write port>> number>string print ] bi
      
      [ 
        print-date-now
        "" print
        flush 
      ] with-stream
      

      • 0
        Да, фактор как-то поинтереснее будет. Ну проще так точно :) Возможностей больше, довольно крутой рантайм, гора биндингов. Но у меня, к сожалению, ничего сложнее хелловорлда как-то не получалось на нём написать :(
    • +1
      Беглый просмотр документации на Фактор вызвал подозрение, что в нем нет возможности создавать определяющие слова. В большинстве языков способы создания новых понятий жестко зашиты в язык. У Форта имеется механизм создания определяющих слов. Которыми в примере являются make_value и make_delimeter. В Форте можно не просто создавать новые понятия, а создавать необходимые инструменты для создания понятий.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.