PHPカンファレンス2016(東京)で「PHPで学ぶソケットプログラミング入門」というトークをしました

http://slides.com/ryoutsunomiya/php-socket-programming

背景

このようなトークをしようと思った背景を説明すると、

(1) 仕事でソケットプログラミングが必要になった
(2) しかし、日本語で、かつPHPで簡単にソケットプログラミングを学べる資料が無かった

ためです。

(1)については、ネットワーク機器からUDPでログを受け取ったり、TCPで命令を送ったり、といった感じです。
(2)については、「PHPでソケットを使ったプログラムを書いてみた」みたいな記事はいくつもありましたが、「ソケットを使うと何が嬉しいのか」「ソケットはどんな技術か」といった基礎的な部分を解説している記事は見かけませんでした。
そこで、(2)を満たすような資料があるといいなということで、今回の資料を作成し、発表を行いました。

なお、今回のトーク会場や、リハーサルをさせていただいたPHP勉強会で挙手によるアンケートをとったところ、
累計約100名のPHP開発者の中で、業務でPHPでソケットを使ったプログラムを書いたことがある方は(私以外では)わずか1名でした。
仕事にすぐに役に立つ技術ではないですが、一度覚えておくと長持ちする技術です。
「そろそろPHPフレームワークを漁るのにも飽きてきたなー」みたいな人は、ソケットを学んでみると新しい発見があると思います。

ReactPHPについて

資料の中でも何度か登場しているライブラリに ReactPHP があります。
ReactPHPのリポジトリの配下には細かいライブラリがいくつかありますが、PHPでソケットプログラミングを行う上で特に参考になるのは以下の2つです。

PHP Socket Programming Handbook 読書メモ PHPによるマルチプロセスプログラミング

php_socket_programming_handbook


  • あるプロセスの実行中に、別のプロセスを立ち上げるにはfork(2)を使用する
  • PHPでfork(2)を使用するにはpcntl拡張を使用する
  • pcntl_forkを使ってforkを行うことができる

https://github.com/phpsphb/book-examples/blob/master/multiprocess/limited_forking_echo_server.php

  • pcntl_forkの戻り値は、親プロセスの場合は子プロセスのPID、子プロセスの場合は0、forkに失敗した場合は-1
  • pcntl_waitpidは終了した子プロセスの プロセス ID を返す
    • WNOHANGオプションを指定すると、子プロセスが終了していない場合に直ちに処理を返る
  • pcntl_wifexitedはステータスが正常終了したか調べる

PHP Socket Programming Handbook 読書メモ PHPによるReactorパターンの実装

php_socket_programming_handbook


https://github.com/phpsphb/book-examples/blob/master/reactor/reactor.php

Reactorパターンを使うべき場合:

  • 複数のクライアントコネクションを同時に処理する必要があるが、サーバで複雑なコネクションマネジメントをしたくない場合
  • メモリが限られていて、マシン上で1つのサーバプロセスしか実行できない場合
  • forkが遅かったりメモリ消費量が多いプラットフォーム(主にWindows)で実行する必要がある場合
  • クライアントを処理している最中にコードがクラッシュしても、新しいサーバプロセスを再起動すれば住む場合

Reactorパターンを使うべきでない場合:

  • forkが高速でメモリ効率が良いプラットフォーム(主にUNIX、Linux)で実行する場合
  • 本当に並列処理を行う必要がある場合(Reactorは完全な並列ではない)
  • メモリが潤沢にある場合

PHPライブラリ

PHPによるReactorパターンの実装として著名なものにReactPHPライブラリがある。
また、stream_selectselect(2)に依存しているが、より高パフォーマンスのシステムコールにepoll(2)kqueue(2)などがある。
PHPでepoll(2)等を使いたい場合、これらのシステムコールを抽象化したライブラリであるlibeventのPHP向けインタフェースLibevent等を使用することができる。

PHP Socket Programming Handbook 読書メモ 1プロセスで複数のクライアントを処理する

php_socket_programming_handbook


1プロセスで複数のクライアントを処理する

  • いくつかの方法がある
    • 複数のプロセスを使用する(UNIX系でのみ使用可能)
    • stream_selectを使用する(全てのOSで動作する)

実装

https://github.com/phpsphb/book-examples/blob/master/multi-connect/listing2.php

  • stream_set_blocking($server, 0) でサーバがノンブロッキングモードになる
  • stream_select ストリームに対して動作するselect(2)

PHP Socket Programming Handbook 読書メモ PHPによるソケットプログラミング

php_socket_programming_handbook


はじめに

本書を読むにあたり必要なものは以下の通り

  • Windows以外のOS(WindowsではUNIXソケットの機能の一部は使用できない)
  • コマンドライン
  • ncコマンド

やり方はひとつじゃない(TIMTOWTDI)

  • TIMTOWTDI = “There’s more than one way to do it”
  • PHPでのソケットプログラミングには2つのやり方がある

ソケット拡張

  • ソケット拡張はコンパイル時のデフォルトでは無効
  • ソケット拡張で作成したソケットリソースはsocket_xxx関数からのみ利用可能
  • UNIXのシステムコールに近い形式のAPI
  • (本書の著者は)使用を推奨しない

ストリームソケット

  • ストリーム拡張はPHP 5以降ではデフォルトで有効
  • PHP 5以降では、ストリーム(Stream)APIがソケットを扱える
  • ストリームAPIで作成したソケットリソースはファイル系の関数で扱える
  • ソケットの低レベルでの制御はできない
  • 原則として、ストリームAPIを使用してソケットを扱うべき

エコーサーバの例

古典的なサーバプログラムの処理は以下のようになる:

  1. ソケットにbindする
  2. ソケットをlistenし、コネクションを待ち受ける
  3. コネクションをacceptする
  4. クライアントが送信したデータを受け取る
  5. レスポンスを返す
  6. コネクションを切断するか、クライアントが切断するまで待つ
  7. 3に戻る

実装

  • stream_socket_serverは、ステップ1と2を実行する
  • for (;;)で無限ループを作り、内部でstream_socket_acceptでコネクションを待ち受ける(ステップ3)
  • freadでデータを読み取り(ステップ4)
  • fwriteでデータ書き込み(ステップ5)
  • fcloseでソケットを閉じる(ステップ6)

サーバの実行

  • php echo.php で起動できる
  • 初回起動時にはポート開放が必要になる
  • クライアント(netcat)はnc 127.0.0.1 1337で接続できる