FORTH: POP3 наноклиент. Часть 2

от автора

Во второй части попробуем написать минимального POP3 клиента. Он будет уметь подключаться к серверу, логиниться в почтовый ящик, узнавать сколько там писем и скачивать последнее. Для иллюстрации этого будет достаточно.

Что достаточно знать из POP3 протокола. Пара команд USER PASS нужна для логина, STAT чтобы получить количество писем в ящике и RETR для получения самого письма. Мы не будем удалять письма командой DELE. Команду QUIT будем отдавать из вежливости.
Посмотрим на команды повнимательнее. Можно заметить, что их можно разделить на две группы: команды без аргументов NOOP QUIT STAT RSET и команды, имеющие аргумент: DELE LIST RETR TOP APOP USER PASS. Причем последние три в качестве аргумента требуют текстовых строк. Строго говоря, все команды требуют строкового аргумента. В одном случае это пустая строка, в другом строка состоит из десятичных цифр, в третьем из произвольных символов.

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

S" NOOP" sockt WriteSocketLine THROW и так 4 раза.  S" DELE" sockt WriteSocket THROW     S>D (D.) sockt WriteSocketLine THROW 

тоже 3 раза

( TOP APOP не будем реализовывать, а с USER PASS пока не будем мудрить. )

Но мы же пишем на Форте, а в нем есть изящный механизм создания определяющих слов. Используем его.
Для начала вынесем sockt WriteSocketLine THROW и buf bufsize sockt ReadSocket THROW TO bytes_read в отдельные определения. Лень уже выписывать столько букв.

 : >sockt                  sockt WriteSocketLine THROW ;  : sockt>               buf bufsize sockt ReadSocket THROW TO bytes_read ;  

И начнем создавать создающие слова

 : pop3_noarg                       CREATE LATEST ,                       DOES> @ COUNT  >sockt   ;   : pop3_noargs                      0 DO pop3_noarg LOOP ;  4 pop3_noargs NOOP QUIT STAT RSET 

Таким образом, тремя строчками мы определили четыре однотипных команды. Поясню немного поподробнее, что же здесь происходит.
Через двоеточие определено новое слово pop3_noarg, со следующим действием: выбрать из входного потока литеры, ограниченные пробелом, использовать их для создания на вершине словаря слова с действием VARIABLE. Это все делает слово CREATE, в момент исполнения слова pop3_noarg. Следующее слово LATEST кладет на стек адрес поля имени определяемого слова, например NOOP. Запятая компилирует значения со стека на вершину словаря. Если бы далее не было части DOES>, вызов NOOP положил бы на стек некий адрес, разыменовав который мы получили бы адрес строки со счетчиком, превратив который в адрес строки счетчик словом COUNT мы могли бы напечатать ее словом TYPE.
В результате увидели бы NOOP. Словами выглядит очень сложно, быстрее попробовать самостоятельно в командной строке форт-системы.
Что же далее? Далее у нас присутствует самое загадочное слово Форта DOES>. Достаточно осознать, что это слово меняет действие по-умолчанию слова, созданного через CREATE, на действие, которое мы пожелаем. Все, что написано после DOES> является общим действием для всех слов, определенных через pop3_noarg. Здесь оно заключается в получении адреса и счетчика строки — имени слова и отправки его в сокет.

: pop3_digiarg                 CREATE LATEST ,                 DOES>    @ >R       <#    S>D  #S     BL HOLD     R> COUNT HOLDS #>   >sockt   ;  : pop3_digiargs  0 DO pop3_digiarg LOOP ;  3 pop3_digiargs  DELE LIST RETR 

Очень похоже на предыдущую группу слов с заметным отличием в части DOES>. Понятно, что что-то отправляется в сокет. А вот что?
Здесь мы используем еще одну замечательную возможность Форта. У нас есть прекрасное слово (D.) преобразующее число двойной точности (64 бит) со стека в строку. И мы могли бы его легко использовать, но для этого нам бы пришлось написать что-то вроде

 S" LIST" sockt WriteSocket S>D (D.) >sockt

Не страшно, но как-то уныло.

Раз уж мы решили сделать красиво, почему бы не поискать другие решения. Если заглянуть в исходники Форта, и найти там определение (D.) можно увидеть что оно состоит из <# #S #>. Эти три слова изобретены еще Чаком Муром и раскладывают преобразование числа в строку на три этапа. Первый — подготовка преобразования, второй — собственно преобразование, и третий — выдача результата. Из-за того, что преобразование числа происходит с младших разрядов, а печать идет со старших разрядов — для преобразования нужен некоторый временный буфер, куда будут складываться цифры и откуда будет забираться результат. Размер буфера берется с хорошим запасом. Слово HOLD как раз и убирает очередной символ со стека в буфер. HOLDS, множественное число от HOLD, делает то же самое с несколькими символами ака строкой. Пара слов >R R> используется как легкая подручная реализация локальных переменных, когда надо некоторое значение со стека ненадолго отложить.
Подобная эквилибристика позволяет не заводить дополнительные буфера, не копировать одно и тоже в кучу мест, не возиться с библиотеками для слияния строк, а сделать все в системном буфере практически предназначенном для этой цели.

Теперь посмотрим на ответы сервера. Их всего два: +OK или -ERR, за которыми могут идти, а могут не идти какой-нибудь текст или числа. Было бы прелестно, если бы ответы сервера как-нибудь сами делали всю работу. Чтобы нам не приходилось скакать по бесконечным ветвлениям. И Форт может нам это позволить.
Представим буфер, в котором сохранен ответ сервера как входной поток для интерпретации. Словом EVALUATE мы можем интерпретировать произвольную строку. Используем эту возможность.
Ответ сервера всегда начинается с +OK или -ERR. Вложим же смысл в эти ответы.

Проще всего с -ERR. В случае ошибки неплохо бы вывести остаток строки на терминал и прервать исполнение программы.

 : -ERR     1 PARSE TYPE CR ABORT ; 

PARSE служит для получения хвоста интерпретируемой строки.

+OK не так прост. Он всегда выскакивает, если команда обработана успешно. Говорит нам все хорошо, но каков контекст этого хорошо?
Первый +OK сообщает об успешном подключении к серверу. Нам надо принять этот факт и вывести на терминал приветствие сервера.
Второй и третий +OK возникают в процедуре логина. Просто игнорируем остаток строки.
Четвертый +OK появляется в ответ на STAT и к нему, к +OKею, прилагаются два числа. Первое — количество сообщений в ящике. Второе — число попугаев, занимаемых ними. Попугаи нам не нужны. Значит нам надо будет превратить текстовое число из ответа сервера в число на стеке, а остаток строки игнорировать.
Пятый +OK сообщит что следом за ним будет нужное нам письмо. Тут есть один нюанс в протоколе. Во-первых, пауза между ответом сервера и передачей письма. Во-вторых, два числа следом за +OK — номер сообщения и его размер в попугаях.
Шестого +OK, можно не ждать, а можно обработать также, как и первый.
Итак, +OK должен уметь три разных действия.
Форт позволяет обойтись без флагов и сложной логики. В нем есть так называемые векторные слова. Слова, действие которым можно назначить в любой нужный момент.

 VECT +OK  : do_type       1 PARSE TYPE CR ; : do_ignore    1 PARSE 2DROP ; : do_number     1 PARSE ?SLITERAL ; : do_msg                          sockt>                                  BEGIN   buf bytes_read  TYPE CR                  bytes_read  bufsize - 0< 0= WHILE  sockt>  REPEAT do_ignore ; 

Теперь у нас есть все, чтобы собрать код воедино.

Код

  ~nn/lib/sock2.f    0 VALUE sockt 0 VALUE bytes_read 0 VALUE num_of_msgs  8192 CONSTANT bufsize bufsize ALLOCATE THROW CONSTANT buf   MODULE:  : >sockt                  sockt WriteSocketLine THROW ;  : sockt>               buf bufsize sockt ReadSocket THROW TO bytes_read ;  : response                       sockt>                      buf bytes_read EVALUATE ;   : pop3_noarg                       CREATE LATEST ,                       DOES> @ COUNT  >sockt   response ;   : pop3_noargs                      0 DO pop3_noarg LOOP ;  4 pop3_noargs NOOP QUIT STAT RSET   : pop3_digiarg                 CREATE LATEST ,                 DOES>    @ >R       <#    S>D  #S     BL HOLD     R> COUNT HOLDS #>   >sockt  response  ;  : pop3_digiargs  0 DO pop3_digiarg LOOP ;  3 pop3_digiargs  DELE LIST RETR   : user              S" USER username"  >sockt response  ; : pass             S" PASS password"  >sockt  response ;   VECT +OK  : do_type           1 PARSE TYPE CR ; : do_ignore         1 PARSE 2DROP ; : do_number    1 PARSE ?SLITERAL do_ignore ; : do_msg                          sockt>                                  BEGIN   buf bytes_read  TYPE CR                  bytes_read  bufsize - 0< 0= WHILE  sockt>  REPEAT do_ignore ;  : -ERR    do_type ABORT ;   : start_sockets                           SocketsStartup THROW                        CreateSocket THROW TO sockt ;  : connect_to_host                       GetHostIP THROW                       110 sockt ConnectSocket THROW                        response  ;  \   Ниже исполняемый код. Выше - необходимые определения.  start_sockets  ' do_type TO +OK   S" pop.server.com" connect_to_host   ' do_ignore TO +OK  user  pass    ' do_number TO +OK   STAT   TO  num_of_msgs  ' do_msg TO +OK  num_of_msgs RETR    ' do_type TO +OK QUIT

Данный код несложно дополнить, чтобы он выкачивал все или какое-то количество сообщений с сервера, сохранял их на локальный диск, удалял с сервера и т.п. Здесь я постарался продемонстрировать важный для программирования на языке Форт принцип декомпозиции, развитый и описанный Лео Броуди, и некоторые имеющиеся в языке инструменты для решения задач.

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

PS: При написании топика Хабр несколько пострадал.

ссылка на оригинал статьи http://habrahabr.ru/post/210128/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *