Common Lisp でサーバプログラム - その 1
CL でのサーバサイドプログラミングを手探りで色々試す。今回は超単純エコーサーバを書く。仕様は以下。
- 一度に扱えるクライアントは 1 つだけ。
- クライアントから "stop-server" と入力するとサーバを終了させる。
- クライアントで EOF を入力するとクライアントを切断し、次のクライアントの接続を待つ。
ソケットライブラリには usocket を使う。何はともあれ usocket をインストールする。
CL-USER> (asdf-install:install :usocket)
次は asd ファイルを作る。
(defsystem echo-server :components ((:file "packages") (:file "echo-server" :depends-on ("packages"))) :depends-on (:usocket))
asd ファイルに定義した通り、ファイル構成は packages.lisp と echo-server.lisp の 2 つ。とりあえず package の定義を含める packages.lisp を作る。
(defpackage :echo-server (:use :cl) (:export :start))
最後にサーバ本体の echo-server.lisp を作る。
(in-package :echo-server) (define-condition stop-server (condition) ()) (defun make-server-socket (port) (usocket:socket-listen "localhost" port :reuseaddress t)) (defun dispose (server) (format t "close...~%") (usocket:socket-close server)) (defun accept-client (server) (prog1 (usocket:socket-accept server) (format t "accept a client!~%"))) (defun trim-input (input) (string-trim " ^M" input)) (defun echo-input (client-stream input) (format t "echo to client!~%") (format client-stream "~A~%" (trim-input input)) (force-output client-stream)) (defun input= (input expected) (string= (trim-input input) expected)) (defun handle-client (client) (with-open-stream (stream (usocket:socket-stream client)) (loop for input = (read-line stream nil nil) while input if (input= input "stop-server") do (error 'stop-server) else do (echo-input stream input)) t)) (defun start (&key (port 8080)) (let ((server-sock (make-server-socket port))) (handler-case (loop while (handle-client (accept-client server-sock))) (stop-server () (format t "exit...~%")) (condition () (format t "unexpected exit!~%"))) (dispose server-sock)))
使ってみる。まずはサーバを起動する。
CL-USER> (echo-server:start)
次にクライアント。クライアントプログラムは作ってないので、telnet で代用。
$ telnet telnet> open localhost 8080 Trying ::1... Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. hoge hoge fuga fuga
クライアントから "stop-server" と入力すればサーバが終了する機能はちゃんと動いてるのを確認できた。が、EOF (Ctrl-D 入力でいいんだよなぁ) でのクライアント切断はうまくいかず。Ctrl-D には無反応だった。
以下、今後の課題。
- EOF の問題を解決する (ネットワークプログラミング関係ないけど...)。
- libevent 的なものを使って非同期 IO で多重化する。
- その他なにか思いつけば。