Pull to refresh

FORTH: наносервера и наноклиенты. Часть 1

Reading time 4 min
Views 13K
Пришло время рассказать о еще одной стороне применения замечательного языка Форт.
Этим циклом статей я покажу, как с его помощью можно создавать крошечные клиент-серверные приложения. Каждое из которых можно использовать как исследовательский и обучающий инструмент.
Для развлечения нам понадобится 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  
Tags:
Hubs:
+30
Comments 20
Comments Comments 20

Articles