天下一プログラマー本戦1問目の解説 ICPC東京大会を観戦してきました!
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でも同じものを書いたのでそちらも掲載しておきます。

module Main where
import Network
import Monad
import System.IO
import System.Environment (getArgs)

import Control.Exception
import Control.Concurrent
import Prelude hiding (catch)

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

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

echoLoop hd = do
  sequence_ (repeat (do { -- ioアクションの無限リスト
                          l <- hGetLine hd;
                          hPutStrLn hd l;
                          hFlush hd
                     }))
  `catch` (\(SomeException e) -> return ())
  `finally` hClose hd
require 'socket'
def main
    unless ARGV[0]
        puts "usage: #{$0} PORTNUMBER"
        exit(true)
    end
    soc = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
    sockaddr = Socket.sockaddr_in(ARGV[0].to_i, "0.0.0.0")
    soc.bind(sockaddr)
    soc.listen(5)
    puts "start server, listening on #{ARGV[0]}"
    accept_do(soc)
end

def accept_do(serv)
    while(true)
        soc, addr = serv.accept
        puts "new client"
        Thread.new(soc, &self::method(:echo_do))
    end
end
def echo_do(soc)
    while true
        buf = soc.gets
        puts "data: #{buf}"
        soc.puts(buf)
    end
end

main if($0==__FILE__)

Trackback URL

One Response to “Haskellでエコーサーバー”

  1. 通りすがり Says:

    acceptしてforkしてからソケットをクローズしていないようですが、親プロセスにオープンしたままのディスクリプタがたまり続けたりはしないのでしょうか。もしかして、rubyやHaskellだとこれで問題ないのですか?お時間があるときで結構ですので教えていただけないでしょうか。

Leave a Reply