不久前我开始使用 Haskell,现在我专注于网络。我按照一些教程和源示例组装了一个非常简单的回显服务器:
main = withSocketsDo $ do
forkIO $ acceptor 8080
print "Server running ... " >> getLine >>= print
tcpSock :: IO Socket
tcpSock = socket AF_INET Stream 0
acceptor :: PortNumber -> IO ()
acceptor port = do
-- Setup server socket
sock <- tcpSock
setSocketOption sock ReuseAddr 1
bindSocket sock (SockAddrInet port iNADDR_ANY)
listen sock 50
-- Start with zero index
loop sock 0
where
loop sock sockId = do
-- Accept socket
(nextSock, addr) <- accept sock
-- Setup the socket for performance
(_, handle) <- setupClient nextSock
-- Run client in own thread
forkIO $ do
-- Get a stream of bytes
stream <- BS.hGetContents handle
-- Echo the first received char
BS.hPut handle $ BS.take 1 stream
-- Kill the socket
SIO.hClose handle
-- Accept next client
loop sock (sockId + 1)
setupClient :: Socket -> IO (Socket, SIO.Handle)
setupClient sock = do
-- Disable nagle
setSocketOption sock NoDelay 1
-- Disable buffering
hdl <- socketToHandle sock SIO.ReadWriteMode
SIO.hSetBuffering hdl SIO.NoBuffering
return (sock, hdl)
现在,我已经使用 ab-Tool 测试了代码以对服务器进行基准测试。代码使用 -O2 和 -thread 编译,程序使用 +RTS -N 启动以使用多个 OS 线程。
该代码为每个客户端创建了一个新的轻量级线程,据我所知,这些线程非常便宜,因为它们是由一堆真正的操作系统线程调度的。
运行该工具后,结果如下:
ab -n 10000 -c 1000 http://localhost:8080/
~ 500 - 1600 req/sec 是的,它有时会在 500 到 1600 之间变化!
一开始我觉得不错,还不错。然后我在没有“+RTS -N”的情况下运行程序,结果几乎每次都为 ~20000 req/sec。
显然线程会严重影响性能,但为什么呢?我的猜测是,IO 管理器在处理大量连接时做得很糟糕。
顺便说一句:我使用 Ubuntu 13.04 和 ghc 7.6,但我在 Windows 8 下测试了代码,结果更差,但我认为 IO 管理器针对 linux 进行了调整,这是有道理的。
我在这里做的事情真的很愚蠢吗?我知道,这个例子很简单,但这里显然出了点问题。
问候,克里斯