サーバーを作ろう (毎週のハンズオン勉強会の資料)

22
ササササササササ PHP ササササ ( ω )

Upload: -

Post on 30-Nov-2014

26.404 views

Category:

Documents


0 download

DESCRIPTION

シス創勉強会資料

TRANSCRIPT

サーバーを作ろう

PHP でやるお ( ^ ω ^ )

目次• 勉強しよう– TCP/IP– Socket API– クライアントとサーバー

• 設計しよう– サーバーの多重化

• ウェブプログラマが何故サーバーを作るの?

TCP/IP

• ストリーム型の通信プロトコル– 送ったら、送った順に届く• 順序を持った可変長バイト列(文字列に変換しや

すい!)として送ったり、受け取ったりできるのでプログラミングしやすい

– ちゃんと届けられる• 相手が通信可能な状態かどうか分かる

– 「相手が通信可能な状態じゃなかったら」という処理を書ける

• 届いたかどうか分かる– 「届かなかったら」という処理を書ける

TCP/IP

• 送ったら送った順に届く

$fp = stream_socket_client('tcp://www.nicovideo.jp:80');

fwrite($fp, "GET / HTTP/1.0\r\n");fwrite($fp, "Host: www.nicovideo.jp\r\n\r\n");

echo stream_get_contents($fp);

fclose($fp);

送ったら送った順に届く

TCP/IPTCP/IP

www.nicovideo.jp

PHP Web Server

HTTP/1.1 200 OK<html>…</html>

HTTP/1.1 200 OK<html>…</html>

送ったら送った順に届く

HTML がそのまま読めるよ

TCP/IP

• ちゃんと届けられる

$fp = stream_socket_client('tcp://www.nicovideo.jp:80', $errno, $errstr);

if ($fp === false) { throw new Exception($errstr);}

if (fwrite($fp, "GET / HTTP/1.0\r\n") === false) { throw new Exception($php_errormsg);}

if (fwrite($fp, "Host: www.nicovideo.jp\r\n\r\n") === false) { throw new Exception($php_errormsg);}

echo stream_get_contents($fp);

fclose($fp);

ちゃんと届けられる

TCP/IPTCP/IP

www.nicovideo.jp

PHP Web Server

届いた届いた

GET / HTTP/1.0Host: www.nicovideo.jp

GET / HTTP/1.0Host: www.nicovideo.jp

ちゃんと届いてるみたいだ

TCP/IP

• 考えてみよう– ストリーム型じゃない通信プロトコルってど

んなのだろう– TCP/IP 以外のストリーム型通信プロトコルを

考えてみよう

Socket API

• TCP/IP などで通信相手と接続するためのライブラリが socket

• 接続後は、ファイルに対する読み書きの関数がそのまま使える

ファイル操作の関数が使える

$fp = stream_socket_client(             'tcp://www.nicovideo.jp:80');fwrite($fp, "GET / HTTP/1.0\r\n");fwrite($fp, "Host: www.nicovideo.jp\r\n\r\n");while (fgets($fp) !== "\r\n") { }

$content = stream_get_contents($fp);if (!mb_check_encoding($content, 'UTF-8')) { throw new Exception('Invalid encoding!');}fclose($fp);

libxml_use_internal_errors(true);$doc = new DOMDocument();$doc->loadHTML($content);libxml_clear_errors();

echo $doc->getElementsByTagName('title')       ->item(0)->textContent;

$fp = fopen('test.html', 'r');

$content = stream_get_contents($fp);if (!mb_check_encoding($content, 'UTF-8')) { throw new Exception('Invalid encoding!');}fclose($fp);

libxml_use_internal_errors(true);$doc = new DOMDocument();$doc->loadHTML($content);libxml_clear_errors();

echo $doc->getElementsByTagName('title')       ->item(0)->textContent;まったく同じコードを使える

ソケットから読む ファイルから読む

Socket API

• 考えてみよう– なんで、通信なのにファイルで抽象化してる

んだろう?

クライアントとサーバー• 接続する側をクライアントと言う• 接続を待つ側をサーバーと言う• それぞれで、接続が確立するまでのコード

が違う

作ってみよう• Mecab で形態素解析して、結果を返す

サーバーと、そのクライアントを作る• Mecab のインストール

sudo apt-get install mecab # mecab プログラムsudo apt-get install libmecab-dev # mecab.so を作るのに必要sudo apt-get install mecab-naist-jdic mecab-jumandic-utf8 # mecab の UTF-8 の辞書sudo apt-get install build-essentials # php 拡張をコンパイルするためのツール群sudo pear channel-discover pecl.opendogs.orgsudo pear remote-list -c opendogssudo pear install opendogs/mecab-beta# 以下のファイルに extension=mecab.so を追加sudo vim /etc/php5/cli/php.ini

クライアント側のコード• 繋ぐだけ– エラー処理とかは各自勉強してね

$fp = stream_socket_client('tcp://127.0.0.1:1111');

fwrite($fp, json_encode($argv[1]) . "\r\n");$list = json_decode(fgets($fp, 1024));var_dump($list);

fclose($fp);

サーバー側のコード• 接続を待って、 接続を確立する– エラー処理とかは各自勉強してね

$server = stream_socket_server('tcp://127.0.0.1:1111');

while (true) { $fp = stream_socket_accept($server);

while (!feof($fp)) { $str = fgets($fp, 1024); fwrite($fp, json_encode(mecab_split(json_decode($str))) . "\r\n"); }

fclose($fp);}fclose($server);

サーバー側の処理の多重化• 前の例では、サーバー側のコードは、常

に一つのクライアントに対しての処理しかしない

• 複数のクライアントに対して処理をしたい場合は、以下のいずれかの方法でサーバーの処理を多重化する必要がある– フォークによる多重化– イベントループによる多重化– スレッドによる多重化

フォークによる多重化• プロセスを分身させる。• メモリは COW で節約されるけど、スレッドと比べてメモリを食う• 処理ごとに値が独立してるので、プログラミングが楽

$server = stream_socket_server('tcp://127.0.0.1:1111');

while (true) { $fp = stream_socket_accept($server); if (!pcntl_fork()) { while (!feof($fp)) { $str = fgets($fp, 1024); fwrite($fp, json_encode(mecab_split(json_decode($str))) . "\r\n"); } fclose($fp); exit; }}fclose($server);

スレッドによる多重化• PHP では出来ない??• フォークするより、省メモリ。• とはいえ、スタック領域などのメモリは

食う。• マルチスレッドプログラミング難しい>

イベントループによる多重化• フォークや、スレッドと比べて軽量

$server = stream_socket_server('tcp://127.0.0.1:1111');stream_set_blocking($server, 0);$base = event_base_new();

$event = event_new();event_set($event, $server, EV_READ | EV_PERSIST, 'ev_accept', $base);event_base_set($event, $base);event_add($event);

event_base_loop($base);

$id = 0;$fps = array();$bufs = array();

イベントループによる多重化

function ev_accept($server, $flag, $base) { global $id; global $fps; global $bufs;

$fp = stream_socket_accept($server); stream_set_blocking($fp, 0);

$id++;

$buf = event_buffer_new($fp, 'ev_read', NULL, 'ev_error', $id); event_buffer_base_set($buf, $base); event_buffer_timeout_set($buf, 30, 30); event_buffer_watermark_set($buf, EV_READ, 0, 0xffffff); event_buffer_priority_set($buf, 10); event_buffer_enable($buf, EV_READ | EV_PERSIST);

$fps[$id] = $fp; $bufs[$id] = $buf;}

イベントループによる多重化

function ev_error($buf, $error, $id) { global $id; global $fps; global $bufs;

event_buffer_disable($bufs[$id], EV_READ | EV_WRITE); event_buffer_free($bufs[$id]); fclose($fps[$id]); unset($fps[$id], $bufs[$id]);}

function ev_read($buf, $id) { global $id; global $fps; global $bufs;

while ($str = event_buffer_read($buf, 1024)) { $fp = $fps[$id]; fwrite($fp, json_encode(mecab_split(json_decode($str))) . "\r\n"); }}

ウェブプログラマが何故サーバーを作るの?

• プロセスやマシンに縛られるのはやめよう– 1プロセスに拘らなくなる• プログラミング言語に拘らなくてよくなる• 使いたいライブラリがあったら、なんでも使える。

ソケットでプロセスを繋げばいい– 1マシンに拘らなくなる• OS に拘らなくてもよくなる• 負荷分散も自由自在