elisp でのネットワークプログラム

※2007年に書いた記事です。

elispプログラミング言語として(勿論emacs環境上ではあるが)十分実用に耐え、 ちょっとしたアプリケーションなどはすぐ実現できる。筆者もこのページの日記書き用にelisp でアプリケーションを組んで定型文の入力などを行っていたが、どうせなら、ネットワークを介してサーバーにアップロード までできればより便利だと思った。elispならばelispのみの実装でこれは可能である。 例えば秀丸に付属のマクロ言語も相当な能力があるが、このような機能を実現するためには、外部にDLLを用意するなど マクロ外での補助的な機構が必要となる。この点、elispを用いるならば"emacs"さえあれば、当該elispプログラムを インストールするだけで、そのプログラムが動かすことができ非常に便利だ。

本文章は、日記エディタにネットワーク更新機能を付加した際に調べた、elispにおけるネットワーク の取扱いについて纏めておく。なにぶん素人なもので、間違いなど含まれていることもあると思われることを あらかじめ断っておく。


elisp におけるプロセス

Emacs Lispプログラムで、同一マシンや別のマシン上の他のプロセスに対して TCPネットワーク接続を開く場合、ネットワーク接続を、非同期サブプロセスとして扱う。 一般的なサブプロセスは、Emacs内部では『プロセス』と呼ばれるLispオブジェクトで 表現される。Lispプログラムはこのオブジェクトを用いてサブプロセスと通信したりそれ を制御する。一方、ネットワークでの通信相手のプロセスはEmacsプロセスの子プロセスでは ないため、データの送受信のみが可能で、キルしたりシグナルを送 ることはできないという大きな違いがある。

elisp によるTCPネットワーク接続

elisp を用いてTCPネットワーク接続を開くには`open-network-stream' 関数を用いる。引数は、 `NAME'、`BUFFER-OR-NAME'、`HOST'、`SERVICE'の4つである。

open-network-stream 関数

  • 書式:
    open-network-stream NAME BUFFER-OR-NAME HOST SERVICE
    
  • 引数:
    NAME プロセスオブジェクトに付ける名前を指定する。必要に応じて一意にするために修正される。
    BUFFER-OR-NAME 接続に対応付けるバッファである。出力を扱うフィルタ関数を指定しない限り、接続からの出力はそのバッファに挿入される。 BUFFER-OR-NAMEが`nil'であると、接続にはバッファを対応付けないことを意味する。
    HOST 接続先のホスト名(文字列)を指定する。
    SERVICE 接続先のポート番号(整数)を指定する。ポート番号は、定義済みのネットワークサービス(文字列)で代用できる。

ネットワーク接続におけるプロセス情報

`process-status' 関数は、引数 `PROCESS-NAME' にプロセス、バッファ、 プロセス名(文字列)、バッファ名(文字列)のいずれかを与えることで、PROCESS-NAMEの状態をシンボルとして返す。 一般のサブプロセスで得られるシンボルは `run'、`stop'、`exit'、`signal'、`nil'であるが、 ネットワーク接続に対しては`open'か`closed'をつねに返し、一般のサブプロセスに対してはこれらの いずれの値もけっして返さない。この性質を利用して、プロセスが一般のサブプロセスかネットワーク接続かを区別するのに用いる こともできる。

process-status 関数

  • 書式:
    process-status PROCESS-NAME
    
  • 結果のシンボル:
    open ネットワーク接続を開いている。
    closed ネットワーク接続は閉じている。接続をいったん閉じるとそれを再度開 くことはできないが、同じ接続先へ新たな接続を開くことはできる。

コーディングシステム

プロセスへの入力、プロセスからの出力に対するコーディングシステムは、変数 `coding-system-for-write'、`coding-system-for-read' が`nil'以外であればこれを用いられ、それ以外ではデフォルトの機構で決まるものが用いられる。また、 関数 `set-process-coding-system'を用いて、プロセスPROCESSからの出力および入力 に用いるコーディングシステムを指定することも出きる。

set-process-coding-system 関数

  • 書式:
    set-process-coding-system PROCESS DECODING-SYSTEM ENCODING-SYSTEM
    
  • 引数:
    PROCESS コーディングシステムを指定するプロセス
    DECODING-SYSTEM サブプロセスからの出力に対する コーディングシステム
    ENCODING-SYSTEM サブプロセスからの入力に対する コーディングシステム

プロセスへの入力

ネットワーク接続を確立した後は、以下の関数を用いてEmacsから入力を送ることができる。 サブプロセスへの入力は、サブプロセスがそれを受け取るまえにコーディングシステム を用いて普通は符号化される。以下の関数における、引数 PROCESS-NAME は、プロセスかプロセス名であること

process-send-string 関数

この関数は、文字列STRINGの内容を標準入力としてプロセス PROCESS-NAME に送る。

  • 書式:
    process-send-string PROCESS-NAME STRING
    
  • 引数:
    PROCESS-NAME 入力を送るプロセス。プロセスオブジェクトか、プロセスの名前を指定する。 これが`nil'であると、カレントバッファのプロセスを用いる。
    STRING 入力する文字列

process-send-region 関数

この関数は、STARTとENDで定義される領域内のテキストを標準入力としてプロセスPROCESS-NAMEへ送る。 STARTとENDのどちらかがカレントバッファ内の位置を表す整数でもマーカでもないと、エラーを通知する。 (どちらが大きな数であるかは重要ではない。)

  • 書式:
    process-send-region PROCESS-NAME START END
    
  • 引数:
    PROCESS-NAME 入力を送るプロセス。プロセスオブジェクトか、プロセスの名前 を指定する。これが`nil'であると、カレントバッファのプロセスを用いる。
    START カレントバッファ内の位置を表す整数あるいはマーカ
    END カレントバッファ内の位置を表す整数あるいはマーカ

process-send-eof 関数

この関数は、プロセスPROCESS-NAMEが入力で「ファイルの終りEOF」を見る ようにする。EOFはそれまでに送ったテキストのあとにある。

  • 書式:
    process-send-eof &optional PROCESS-NAME
    
  • 引数:
    PROCESS-NAME(オプション) 入力を送るプロセス。プロセスオブジェクトか、プロセスの名前を指定する。 これを指定しなかったり、`nil'であると、カレントバッファのプロセスにEOFを送る。

プロセスからの出力

サブプロセスが標準出力に書く出力を受け取る方法としては、以下の2通りがある。

  • プロセスに対応付けられたバッファに出力を挿入する。
  • "フィルタ関数"(filter function)と呼ばれる関数を出力に対して作用させる。

プロセスにバッファもフィルタ関数もなければ、その出力は破棄される。

サブプロセスからの出力は、端末入力を読んでいるとき、`sit-for'や`sleep-for'等の 待機関数を実行中のとき、或いは`accept-process-output'を実行中のとき等、 Emacsが待機している時にだけ到着する。 これにより、並行プログラムで一般に悩まされるタイミングエラーの問題が減少する。 たとえば、安全にプロセスを作成してから、バッファかフィルタ関数を指定する。この処理の途中で、 待機を行うような基本関数を呼び出さなければ、出力は到着しない。

ファイルから読むテキストと同様に、サブプロセスの出力は、バッファやフィル タ関数が受け取るまえにコーディングシステムを用いて普通は復号化する。

プロセスバッファ

プロセスにフィルタ関数がなければ、その出力はプロセスに対応付けられたバッファ(プロセスバッファ) に挿入される。プロセスバッファはプロセスがキルされたことを判定することにも用いられる。

process-buffer 関数

この関数は、プロセスPROCESSに対応付けられているバッファを返す。

  • 書式:
    process-buffer PROCESS
    
  • 引数:
    PROCESS 戻り値のバッファに対応するプロセス

set-process-buffer 関数

この関数は、プロセスPROCESSにバッファBUFFERを対応付ける。

  • 書式:
    set-process-buffer PROCESS BUFFER
    
  • 引数:
    PROCESS バッファを対応つけるプロセス
    BUFFER プロセスに対応つけられるバッファ

get-buffer-process 関数

この関数はBUFFER-OR-NAMEに対応付けられたプロセスを返す。バッファに 複数のプロセスが対応付けられている場合には、それらの1つを選ぶ。(現 状では、もっとも最近に作られたプロセスを選ぶ。)同じバッファに複数 のプロセスを対応付けることは一般にはよくない。

  • 書式:
    get-buffer-process BUFFER-OR-NAME
    
  • 引数:
    BUFFER-OR-NAME 得たいプロセスに対応付けられたプロセスバッファ

accept-process-output 関数

この関数は、Lispプログラムから特定の場面で出力の到着を明示的に許す為に用いられる。 その出力は、対応付けられたバッファに挿入されるか、フィルタ関数に与えられる。PROCESSが `nil'以外であると、この関数は、PROCESSからなんらかの出力を得るまで戻らない。

  • 書式:
    accept-process-output &optional PROCESS SECONDS MILLISEC
    
  • 引数:
    PROCESS(オプション) 出力を受け取るプロセス。
    SECONDS(オプション) 秒単位のタイムアウト時間を指定する。
    MILLISEC(オプション) ミリ秒単位のタイムアウト時間を指定する。

指定された2つの時間は合計され、任意のサブプロセスの出力を受け取ったどうかに関わらず、その時間だけ経過 すると`accept-process-output'は処理をemacsに戻す。この関数は、出力を得ると`nil'以外を返す。あるい は、出力が到着するまえに時間切れすると`nil'を返す。

プログラム例:httpクライアント

elispを用いたネットワークプログラミングの例として、httpクライアントを作成した。 これは、httpサーバに問い合わせて、ファイルを読み込むだけで、レンダリングもしない単純なものである。

1: (defun test-web-client ()
2:   "get file from http server"
3:   (interactive)
4:   (let ((buf (get-buffer-create "web conection"))
5:         (proc nil))
6:      (setq proc (open-network-stream
7:              "web-connection"
8:              buf
9:              "www.asahi-net.or.jp"
10:             80))
11:     (set-process-coding-system proc 'binary 'binary)
12:     (display-buffer buf)
13:     (process-send-string
14:          proc
15:          (format (concat
16:               "GET /~pw9s-szk/ HTTP/1.0\r\n"
17:               "MIME-Version: 1.0\r\n"
18:               "\r\n")))))

動作は以下の様になっている。

  1. 4行目:ローカル変数`buf'として、プロセスバッファを作成する。
  2. 5行目:プロセス用の変数`proc'をローカル変数として宣言する。
  3. 6~10行目:`open-network-stream' 関数を用いてネットワーク 接続を開き、ローカル変数`proc'にプロセスを割り当てている。プロセスバッファとして4行目で定義した変数`buf'を指定している。
  4. 11行目:プロセスのコーディングシステムを指定している。
  5. 13行目:process-send-string 関数を用いてHttpサーバーに`GET' コマンドを送信している。サーバーより送信されたファイルの内容は、プロセスバッファ`buf'に挿入される。

参考文献