Nov 17

takada-atです。
前回HaskellおよびRubyでエコーサーバーを発表したところ、エコーサーバーおよびネットワークプログラミングの基礎について、社内でいろいろな指摘を受けました。
今回は、指摘された点をひとつひとつ改良していきたいと思います。

リンク: Haskellでエコーサーバー

ポート番号

実は恥しながらRFCにエコーサーバーの規定があるのを知らなかったのですが、一般に「エコーサーバー」と言った場合、正式には「RFC862 - Echo Protocol」のサーバー実装を指すことが多いようです。
http://www.faqs.org/rfcs/rfc862.html

RFC862では、エコープロトコルのポート番号に 7 を割り当てています。

A server listens for TCP connections on TCP port 7.

もちろん1024以下のポート番号を利用するには、ルート権限が必要ですが、可能ならポート番号7を利用することが望ましいでしょう。

forkについて

forkしたあと、親プロセスでハンドラを閉じていないという指摘を受けましたが、これは誤解で、GHCの「forkIO」「forkOS」は、forkシステムコールとは無関係な、スレッドを新たに生成する関数です。名前が少しまぎらわしいですね。
なお余談ですが「forkIO」は疑似スレッド、「forkOS」はネイティブスレッドを生成します。
さらに余談ですが、http://ja.wikipedia.org/wiki/食事する哲学者の問題って、ひょっとして「フォーク」と「fork」をかけてるんですかね? 大発見だと思ったんですが、全然関係ない上に間違ってますか、そうですか……。

参考リンク: Control.Concurrent

シグナルハンドリングについて

いくつかのシグナルをハンドリングしておかなければ、クライアントの動作によってサーバー自体がダウンしてしまいます。
特に危険なのがSIGPIPEです。
ソケットに対し、書き込みを行なった場合、相手側がすでにclose状態だとこのシグナルが発生します。デフォルトの動作ではプロセスが強制終了してしまいます。

参考リンク:
シグナル (ソフトウェア) - Wikipedia
SIGPIPE - Wikipedia, the free encyclopedia

以上をふまえた上で、高レベルAPIを提供するNetworkライブラリではなく、より低レベルなNetwork.Socketライブラリを使うように書き換えてみます。

main部分は以下のように変わりました。


main = withSocketsDo $ do
         let port = fromIntegral 7
         soc <- socket AF_INET Stream 0
         addr <- inet_addr "0.0.0.0"
         let sockaddr = SockAddrInet port addr
         bindSocket soc sockaddr
         listen soc 5
         -- mainスレッドではいくつかのシグナルをブロック
         blockSignals $ list2set [sigPIPE]
         putStrLn $ "start server, listening on: " ++ show port
         acceptLoop soc `finally` sClose soc

list2set = foldr addSignal emptySignalSet

read, writeについて

以前のバージョンではソケットからの読み込み・書き込みにhGetLine, hPutStrLnを利用していたのですが、これを使うと、サーバー・クライアント間の改行コードの違いなどによって問題が発生しうるという指摘を受けました。
エコープロトコルの実装としては、行ごとの読み込みではなく、文字列をすぐ読み込んで書き込む方が望ましいでしょう。


def echo_do(soc)
    while true
        buf = soc.recv(1)
        soc.write(buf)
    end
end

テスト

以上の動作確認をtelnetを手動で立ち上げて確認するのではなく、Rubyによるテストスクリプトを作成し、こちらで動作確認を行なうことにしました。


require 'test/unit'
require 'socket'

class EchoTest < Test::Unit::TestCase
    def test_echo
        #エコーのテスト
        soc = TCPSocket::new("localhost", 7)
        ["abc", "ab\na", "\n"].each do |s|
            soc.write(s)
            buf = soc.read(s.size)
            assert_equal(s, buf)
            puts buf
        end
        soc.close
    end
    def test_concurrent
        #同時接続のテスト
        socs = []
        3.times do |i|
            Thread::fork(i,socs){ |i, socs|
                sleep 0.1
                soc = TCPSocket::new("localhost", 7)
                s = "hoge"
                soc.write(s)
                buf = soc.read(s.size)
                assert_equal(s, buf)
                puts buf
                socs << soc
            }
        end
        (ThreadGroup::Default.list - [Thread.current]).each {|th| th.join}
        socs.each {|s| s.close}
    end
end

ソースコード

Haskell版とRuby版、修正したものを以下に掲載します。
なお、Haskell版のコンパイルはthreadedオプションを付け以下のようにやってください。

ghc -threaded --make -o echo2 echo2.hs

Continue reading »

Oct 19

Haskellで、TCPのサーバーを書いてみたいと思い、手始めにエコーサーバーを書いてみました。
エコーサーバーというのは、クライアントからの入力をそのまま返すサーバーです。

以下の記事を参考にもっと簡単なものを作ってみます。
A simple TCP server | The Haskell Sequence

main = withSocketsDo $ do
         [p] <- getArgs
         let port = fromIntegral (read p :: Int)
         soc <- listenOn $ PortNumber port
         putStrLn $ "start server, listening on: " ++ show port
         acceptLoop soc `finally` sClose soc

main部分です。
ソケットを開いてlistenします。

acceptLoop soc = do
  (hd, host, port) <- accept soc
  forkOS $ echoLoop hd
  acceptLoop soc

mainのあとの処理です。
クライアントがきたら、accept(受け付け)して、新しいスレッドを生成し、そちらに処理を任せます。
新しいクラアントに対応しつづけるため、処理が終わってはいけませんので、こちらは無限ループを続けます。

echoLoop hd = do
  sequence_ (repeat (do { -- ioアクションの無限リスト
                          l <- hGetLine hd;
                          hPutStrLn hd l;
                          hFlush hd
                     }))
  `catch` (\(SomeException e) -> return ())
  `finally` hClose hd

クライアントごとの処理です。
こちらは、一行読み込んで一行そのまま書き込むという処理を繰り返しているだけです。

さっそく実行してみましょう。

$ ghc -o echoHS -O --make -threaded echosimple.hs
$ ./echoHS 8080
start server, listening on: 8080

別のコンソールをひらきアクセスしてみます。

$ telnet localhost 8080
hoge
hoge
aiu
aiu


できました!
30行程度で、エコーサーバーが完成しました。
ついでにRubyでも同じものを書いたのでそちらも掲載しておきます。

Continue reading »

Oct 08

お初にお目にかかります。
ひよこエンジニアtakei-hです。ぴよぴよ。
さて、amo-k先輩からの課題にやっと手を付けました!
さっそく本題です!

Continue reading »

Mar 01

ども、amo-kです。

先日デプロイ時に実況中継するIRCボットを作ったのでこれについて。

KLabでは通常、
テスト/本番環境に最新のプログラムソースコードを反映する際に
以下のような手順を踏む。

1.踏み台サーバにログイン
2.SVN Export
3.アーカイヴ作成
4.テスト/本番環境にアーカイヴをアップロード
5.テスト/本番環境のコマンドを実行し、アーカイヴ解凍、各Webサーバに同期
6.お掃除

通常はこれを1コマンドで実現するためにデプロイスクリプトを書いて
そのスクリプトを実行する事でデプロイすることとなる。

その際に、デプロイした人はターミナルをチェックしていれば
スクリプトの標準出力で進捗を把握できるが、たの案件メンバは解らない。
そこで、デプロイ実況中継をするIRCボットを作ってみた。

今回は特定のキーワードに反応したり、待機する必要がないので
各ステータス毎にIRCサーバに接続、チャンネルJOIN、レポート、切断ということをする
非常に単純なボットを作った。

以下、IRCクライアント画面サンプルとサンプルソースコード
工夫した点は特に無いが、ライブラリを使わずに
TCPコネクションを張ってIRCサーバと通信している点が若干特徴的かもしれない。
IRCクライアント画面キャプチャサンプル
デプロイ時に実況中継するIRCボット

サンプルコード

#!/usr/bin/ruby

require 'socket'

chan = ARGV[0]
msg  = ARGV[1]

sock = TCPSocket.open("localhost", 6667)
sock.send("NICK DeployBot\r\n", 0);
sock.send("USER DeployBot localhost localhost :DeployBot\r\n", 0);
sock.send(sprintf("JOIN #%s\r\n", chan), 0);
sock.send(sprintf("NOTICE #%s :<-------------- %s -------------->\r\n", chan, msg), 0);
sock.send("QUIT\r\n", 0);
sock.readlines
sock.close

最初はphpでsocket関数を使って実装しようと思ったんだけど
実は踏み台サーバでsocket関数が有効になっていなかったので、設定するのが面倒でもうRubyで杜撰に書いちゃおうと思ってRubyで書きましたww

Oct 18

amo-kです。phpでSMTPクライアント書きました~

書いてみたきっかけ

PHPだとmail()mb_send_mail()でメール送信できちゃうけどよくよく見てみると、SMTPコマンドのHELOコマンドやMAIL FROMコマンドに値を指定できないぽい。最後の引数に、MTAに渡すコマンドラインオプションを指定可能だが、これはMTAに依存するということだ。MAIL FROMコマンドの値を指定したくても、この最後の引数に指定するしかないということになる。

では、何処でHELOコマンドやMAIL FROMコマンドの値を設定しているかというと、php.iniのSMTPディレクティヴやsendmail_fromディレクティヴの値となる。(Windows版phpのみ)つまり、アプリケーションレベルでこれ等の値を指定したい場合に、明確に指定するIFが無いということだ。

あれやこれやと考える時間がもったいないので、この前HTTPクライアント書いたし、せっかくなのでSMTPクライアントも書いて見たw

コード:
Continue reading »

Oct 17

takada-atです。
Rubyでソケットをいじっていたら、同じものをC/C++でも書いてみたくなりました。
そこで、C++でもHTTPクライアントに挑戦してみました。C/C++はよくわからないので、変なコードになっていると思いますが、遠慮なくつっこみをいただけるとうれしいです。
(そもそもコードが長すぎる気がします。。)

Continue reading »

Oct 17

amo-kさんにつづき、私(takda-at)もHTTPクライアントを実装してみました。
まずはRubyのコードです。Rubyでは、socketというネットワークプログラミング用のライブラリが標準で用意されています。その中でもTCPSocketなどのクラスを利用するとHTTPクライアントなども非常に簡単につくれるのですが、今回は勉強のため、あえて低レイヤーなところから書いています。

socketライブラリの中でも、Socketクラスは、ソケットをシステムコールレベルで操作するための機能を提供しています。メソッド名などもシステムコールと同じ名前が採用されているようです。
Rubyリファレンスマニュアルの説明にはそこまで詳細な解説が無いので、LinuxなどのManPageも合わせて見た方が参考になります。
また今回は勉強のために、socketライブラリのソースコードも少しのぞいてみました。socketライブラリはC言語で書かれたRubyの拡張ライブラリです。最新の安定板であるRuby1.8.7では、ruby-1.8.7-p72/ext/socket/socket.c にソースコードがあります。

参考
-Socket - Rubyリファレンスマニュアル-
-Manpage of SOCKET
-Manpage of GETHOSTBYNAME

難しかったのはSocket::connectを呼び出し、接続を行なうところです。このメソッドの引数には、バイナリデータを文字列の形でわたします。C 言語のconnect関数には、引数として、sockaddr構造体というものをわたすのですが、Socket::conncetメソッドの場合、Rubyの側からCの構造体を文字列の形でわたしてやる必要があります。
Rubyで、データをバイナリ文字列に変換するにはArrayクラスのpackメソッドを利用します。
Rubyでバイナリデータを扱うプログラムを書いたのははじめてだったので、非常に勉強になりました。

参考
-Manpage of CONNECT
-Array::pack - Rubyリファレンスマニュアル
-packテンプレート文字列 - Rubyリファレンスマニュアル

Continue reading »

Oct 16

Ruby でHTTP通信をする方法はいくつかあります。
最も簡単なのは、open-uriを使う方法でしょう。

単純にあるURIに対してGETリクエストを送り、返されたHTMLを表示するだけなら、以下のように1行で済ませることもできます。

$ ruby -ropen-uri -e 'open(ARGV[0]){|f| puts f.read }' http://www.klab.jp/

Continue reading »

Oct 11

最初の投稿はSocketプログラミング!

最初のネタは、Socketプログラミングをしてみよう!というネタです。

通常、我々はライブラリやパッケージを使ってWebアプリケーションを実装します。
例えばアプリケーションから任意のHTTPリクエストするような処理を実装する際は、TCPクライアントの処理を意識せずにライブラリを呼び出すだけで実装できたりします。それでもいいっちゃいいのですが、あえて自前で実装してみようという試みです。

こうすることで、普段我々があまり意識していないTCP/IPの世界を意識するきっかけとなるのではと考えております。ということで、まずはSocket関数を用いてHTTPクライアントを書いてみることにします。

書いてみたきっかけ

PHPやRuby等で何気に使っているHTTPクライアント。

PHPだとfopen()cURLでHTTPリクエストできたり、Rubyだとopen-uriのopen()等でHTTPリクエストができちゃいます。

ある日、自社Web API “FlaMixer” と連携する部分をPHPで書く機会があった。前述のfopen()やcURLのオプション指定だけでは連携が困難だったので、モジュールの使い方であれこれと悩むくらいなら自分で書いちゃえって事で書いてみました。これがきっかけでSocket関数を使ったコーディングを経験し、他の若手メンバにも経験してもらおう思いました。

ということで、言語はなんでもいいからHTTPクライアントを書いてみよう~

まずは言いだしっぺの若手amo-kのコード:

Continue reading »