Поперёк потока

Материал из wiki.lissyara.su
Перейти к: навигация, поиск

Ранее Я написал, как работать с материалом Главы 7 из книги Land of Lisp в SBCL. Для закрепления пройденного материала из книги, Я буду работать с однострочниками и, кроме того, приведённые примеры будут работать прямо из REPL.

Глава 12 рассказывает о работе с потоками. Она начинается с файлового ввода/вывода, затем материал немного усложняется и рассказывает о создании соединений между сервером и клиентом через сокет. Текст в книге простой, приведены элегантные примеры кода, но, код для работы с сокетами не переносим на SBCL.

В странице 245 рассказывается о двух библиотеках: cl-sockets и usocket. Эти библиотеки предназначены для стандартизации работы с сокетами в Common Lisp. После исследования их обоих, Я остановился на usocket по двум причинам:

  1. Он используется в web сервере hunchentoot, который является самым распространённым web сервером в Common Lisp.
  2. Он доступен через Quicklisp.

Quicklisp - это удобный менеджер пакетов, содержащий большое количество пакетов для Common Lisp. Мои предыдущие попытки изучить этот язык натыкались на проблему отсутствия менеджера пакетов. Quicklisp решил эту проблему для меня. Если Я Вас убедил использовать Quicklisp, загрузка usocket производится простой командой:

(ql:quickload "usocket")

И можно начинать работать с usocket. Документацию по API можно найти здесь.

Следующие примеры взяты из книги, и состоит из трёх частей настройки и запуска:

  1. Определить сокет, который будет использоваться на "сервере".
  2. Настроить прослушивающий процесс на сервере. Этот процесс будет слушать заданный сокет и отвечать в соответствии с прослушанными данными.
  3. Настроить процесс на клиенте. Этот процесс будет соединяться к сокету на сервере.

Для выполнения первой части, которая определяет порт 4321 для использования на localhost "127.0.0.1", Я использовал этот код:

(defparameter my-socket (usocket:socket-listen "127.0.0.1" 4321))

Я буду вставлять в текст свои соображения. Ранее в книге мы получали строгие инструкции о применении "наушников" в глобальных переменных. Это означает, что my-socket должен быть написан как *my-socket*. Однако, в Главе 12 пример кода не имеет символов * *. Здесь Я следую примеру приведённому в книге.

Таким образом мы объявили сокет, теперь нужно настроить процесс для работы с этим сокетом. После прочтения API, Я не совсем понял где надо использовать socket-accept, а где socket-listen. Я выбрал socket-accept поскольку он работал так, как мне хотелось.

(defparameter my-stream (usocket:socket-accept my-socket))

REPL может застыть после этой команды и ожидает соединения на этот порт. Теперь надо перейти на сторону клиента.

Откройте новый REPL в другом окне. (Я использую emacs/slime, но этот пример также будет работать через терминал.) Помните, что надо загрузить usocket в новый экземпляр REPL, так, как описано выше.

После того, как будет загружен клиентский REPL, Вам нужно будет создать процесс подключающийся к порту на сервере REPL.

(defparameter my-stream (usocket:socket-connect "127.0.0.1" 4321))

Теперь мы получили процессы и на клиентской и на серверной стороне, подключённые к порту 4321 и адресу "127.0.0.1". Теперь можно отправить наше сообщение с клиентского REPL, (следуя примерам из книги).

(print "Yo, Server!" (usocket:socket-stream my-stream))
(force-output (usocket:socket-stream my-stream))

Помните, что мы используем (force-output). Это потому, что с usocket поток буферизируется.

На сервере REPL мы увидим:

(read (usocket:socket-stream my-stream))
;; "Yo, Server!"

Ожидается с сервера:

(print "What up, Client!" (usocket:socket-stream my-stream))
(force-output (usocket:socket-stream my-stream))

Проверяем что отправлено клиентскому REPL:

(read (usocket:socket-stream my-stream))
;; "What up, Client!!"

После окончания работы с соединениями, Вам надо закрыть соединение, с помощью следующей команды на обоих REPL'ах:

(usocket:socket-close my-stream)

Как Вы видите, использование этих команд является несколько громоздкой вещью, поэтому, Я написал пару функций предназначенных для упрощения работы. Эти функции Я назвал (stream-read) и (stream-print)

(defun stream-read (stream)   
"Reads from a usocket connected stream"   
(read (usocket:socket-stream stream)))   
 
(defun stream-print (string stream)   
"Prints to a usocket connected stream"   
(print string (usocket:socket-stream stream))   
(force-output (usocket:socket-stream stream)))

Для чтения потока передавайте поток как параметр и Вам будет возвращено текущее значение потока.

(stream-read my-stream)

Для отправки сообщения в поток введите сообщение и функция отправит его

(stream-print "Hello!" my-stream)

Следующая глава будет посвящена работе над созданием web сервера. Я думаю, что потребуется проделать много работы, и если Я справлюсь (с этой работой) эта "серия" будет продолжаться. :)

Также Я изучаю использование github'а, на нём я создал два сценария "server.lisp" и "client.lisp", они содержат код для настройки и запуска системы сообщений. Репозиторий здесь.

Оригинал здесь. Переводчик: Charlz_Klug.