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__)
October 19th, 2009 at 7:12 pm
acceptしてforkしてからソケットをクローズしていないようですが、親プロセスにオープンしたままのディスクリプタがたまり続けたりはしないのでしょうか。もしかして、rubyやHaskellだとこれで問題ないのですか?お時間があるときで結構ですので教えていただけないでしょうか。