From 0df1e7d3790ad8ca6e1d8d1bfd5f534635b49e6d Mon Sep 17 00:00:00 2001 From: taynpg <taynpg@163.com> Date: Fri, 8 Mar 2024 14:03:37 +0800 Subject: [PATCH] first commit --- .clang-format | 17 + .clangd | 13 + .gitignore | 6 + .travis.yml | 19 + .vscode/settings.json | 21 + BUILD.bazel | 1 + ChangeLog | 441 +++++++++ ChangeLog2 | 12 + License | 29 + README | 32 + build.sh | 27 + contrib/hiredis/Hiredis.cc | 239 +++++ contrib/hiredis/Hiredis.h | 91 ++ contrib/hiredis/README.md | 5 + contrib/hiredis/mrediscli.cc | 141 +++ contrib/thrift/ThriftConnection.cc | 121 +++ contrib/thrift/ThriftConnection.h | 66 ++ contrib/thrift/ThriftServer.cc | 51 ++ contrib/thrift/ThriftServer.h | 222 +++++ contrib/thrift/tests/.gitignore | 2 + contrib/thrift/tests/echo/EchoServer.cc | 51 ++ contrib/thrift/tests/echo/echo.thrift | 8 + contrib/thrift/tests/echo/echoclient.py | 28 + contrib/thrift/tests/ping/PingServer.cc | 47 + contrib/thrift/tests/ping/ping.thrift | 8 + contrib/thrift/tests/ping/pingclient.py | 26 + examples/ace/logging/client.cc | 141 +++ examples/ace/logging/logrecord.proto | 29 + examples/ace/logging/server.cc | 131 +++ examples/ace/ttcp/common.cc | 76 ++ examples/ace/ttcp/common.h | 37 + examples/ace/ttcp/main.cc | 24 + examples/ace/ttcp/ttcp.cc | 214 +++++ examples/ace/ttcp/ttcp_asio_async.cc | 183 ++++ examples/ace/ttcp/ttcp_asio_sync.cc | 86 ++ examples/ace/ttcp/ttcp_blocking.cc | 204 +++++ examples/asio/chat/client.cc | 103 +++ examples/asio/chat/codec.h | 68 ++ examples/asio/chat/loadtest.cc | 168 ++++ examples/asio/chat/server.cc | 86 ++ examples/asio/chat/server_threaded.cc | 98 ++ .../asio/chat/server_threaded_efficient.cc | 113 +++ .../chat/server_threaded_highperformance.cc | 128 +++ examples/asio/echo_see_simple | 0 examples/asio/tutorial/daytime_see_simple | 0 examples/asio/tutorial/there_is_no_timer1 | 0 examples/asio/tutorial/timer2/timer.cc | 16 + examples/asio/tutorial/timer3/timer.cc | 29 + examples/asio/tutorial/timer4/timer.cc | 47 + examples/asio/tutorial/timer5/timer.cc | 74 ++ examples/asio/tutorial/timer6/timer.cc | 114 +++ examples/cdns/Resolver.cc | 201 +++++ examples/cdns/Resolver.h | 77 ++ examples/cdns/dns.cc | 51 ++ examples/curl/Curl.cc | 270 ++++++ examples/curl/Curl.h | 143 +++ examples/curl/README | 6 + examples/curl/download.cc | 207 +++++ examples/curl/mcurl.cc | 48 + examples/fastcgi/fastcgi.cc | 247 +++++ examples/fastcgi/fastcgi.h | 68 ++ examples/fastcgi/fastcgi_test.cc | 73 ++ examples/fastcgi/nginx.conf | 110 +++ examples/filetransfer/download.cc | 70 ++ examples/filetransfer/download2.cc | 81 ++ examples/filetransfer/download3.cc | 73 ++ examples/filetransfer/loadtest/Client.java | 62 ++ examples/filetransfer/loadtest/Handler.java | 68 ++ examples/hub/README | 5 + examples/hub/codec.cc | 53 ++ examples/hub/codec.h | 27 + examples/hub/hub.cc | 217 +++++ examples/hub/pub.cc | 82 ++ examples/hub/pubsub.cc | 106 +++ examples/hub/pubsub.h | 47 + examples/hub/sub.cc | 69 ++ examples/idleconnection/echo.cc | 99 ++ examples/idleconnection/echo.h | 55 ++ examples/idleconnection/main.cc | 40 + examples/idleconnection/sortedlist.cc | 179 ++++ examples/maxconnection/echo.cc | 56 ++ examples/maxconnection/echo.h | 28 + examples/maxconnection/main.cc | 26 + examples/memcached/README | 19 + examples/memcached/client/bench.cc | 239 +++++ examples/memcached/server/Item.cc | 63 ++ examples/memcached/server/Item.h | 129 +++ examples/memcached/server/MemcacheServer.cc | 174 ++++ examples/memcached/server/MemcacheServer.h | 87 ++ examples/memcached/server/Session.cc | 423 +++++++++ examples/memcached/server/Session.h | 114 +++ examples/memcached/server/footprint_test.cc | 66 ++ examples/memcached/server/server.cc | 55 ++ examples/multiplexer/demux.cc | 240 +++++ examples/multiplexer/harness/run.sh | 8 + .../muduo/example/multiplexer/DataEvent.java | 22 + .../muduo/example/multiplexer/Event.java | 5 + .../muduo/example/multiplexer/EventQueue.java | 25 + .../example/multiplexer/EventSource.java | 5 + .../multiplexer/MockBackendServer.java | 126 +++ .../muduo/example/multiplexer/MockClient.java | 143 +++ .../example/multiplexer/MultiplexerTest.java | 104 +++ .../example/multiplexer/MyCountDownLatch.java | 26 + .../muduo/example/multiplexer/TestCase.java | 48 + .../multiplexer/TestFailedException.java | 9 + .../testcase/TestOneClientBackendSend.java | 75 ++ .../testcase/TestOneClientBothSend.java | 64 ++ .../testcase/TestOneClientNoData.java | 43 + .../testcase/TestOneClientSend.java | 80 ++ .../multiplexer/testcase/TestTwoClients.java | 85 ++ examples/multiplexer/multiplexer.cc | 315 +++++++ examples/multiplexer/multiplexer_simple.cc | 269 ++++++ examples/netty/discard/client.cc | 95 ++ examples/netty/discard/server.cc | 96 ++ examples/netty/echo/client.cc | 87 ++ examples/netty/echo/server.cc | 96 ++ examples/netty/echo/server2.cc | 139 +++ examples/netty/uptime/uptime.cc | 67 ++ examples/pingpong/bench.cc | 142 +++ examples/pingpong/client.cc | 214 +++++ examples/pingpong/server.cc | 63 ++ examples/procmon/dummyload.cc | 181 ++++ examples/procmon/plot.cc | 120 +++ examples/procmon/plot.h | 46 + examples/procmon/plot_test.cc | 27 + examples/procmon/procmon.cc | 541 +++++++++++ examples/protobuf/codec/client.cc | 121 +++ examples/protobuf/codec/codec.cc | 235 +++++ examples/protobuf/codec/codec.h | 98 ++ examples/protobuf/codec/codec_test.cc | 264 ++++++ examples/protobuf/codec/dispatcher.h | 101 +++ examples/protobuf/codec/dispatcher_lite.h | 70 ++ .../protobuf/codec/dispatcher_lite_test.cc | 55 ++ examples/protobuf/codec/dispatcher_test.cc | 66 ++ examples/protobuf/codec/query.proto | 22 + examples/protobuf/codec/server.cc | 105 +++ examples/protobuf/resolver/client.cc | 117 +++ examples/protobuf/resolver/resolver.proto | 19 + examples/protobuf/resolver/server.cc | 84 ++ examples/protobuf/rpc/client.cc | 86 ++ examples/protobuf/rpc/server.cc | 44 + examples/protobuf/rpc/sudoku.proto | 18 + examples/protobuf/rpcbalancer/balancer.cc | 246 +++++ examples/protobuf/rpcbalancer/balancer_raw.cc | 358 ++++++++ examples/protobuf/rpcbench/client.cc | 148 +++ examples/protobuf/rpcbench/echo.proto | 19 + examples/protobuf/rpcbench/server.cc | 45 + examples/roundtrip/roundtrip.cc | 128 +++ examples/roundtrip/roundtrip_udp.cc | 151 ++++ examples/shorturl/shorturl.cc | 199 ++++ examples/simple/allinone/allinone.cc | 37 + examples/simple/chargen/chargen.cc | 73 ++ examples/simple/chargen/chargen.h | 31 + examples/simple/chargen/main.cc | 18 + .../simple/chargenclient/chargenclient.cc | 61 ++ examples/simple/daytime/daytime.cc | 44 + examples/simple/daytime/daytime.h | 25 + examples/simple/daytime/main.cc | 18 + examples/simple/discard/discard.cc | 38 + examples/simple/discard/discard.h | 25 + examples/simple/discard/main.cc | 20 + examples/simple/echo/echo.cc | 43 + examples/simple/echo/echo.h | 25 + examples/simple/echo/main.cc | 20 + examples/simple/time/main.cc | 18 + examples/simple/time/time.cc | 40 + examples/simple/time/time.h | 23 + examples/simple/timeclient/timeclient.cc | 77 ++ examples/socks4a/balancer.cc | 91 ++ examples/socks4a/socks4a.cc | 142 +++ examples/socks4a/tcprelay.cc | 88 ++ examples/socks4a/tunnel.h | 195 ++++ examples/sudoku/batch.cc | 235 +++++ examples/sudoku/loadtest.cc | 248 +++++ examples/sudoku/percentile.h | 82 ++ examples/sudoku/pipeline.cc | 224 +++++ examples/sudoku/server_basic.cc | 128 +++ examples/sudoku/server_hybrid.cc | 195 ++++ examples/sudoku/server_multiloop.cc | 137 +++ examples/sudoku/server_prod.cc | 246 +++++ examples/sudoku/server_threadpool.cc | 146 +++ examples/sudoku/stat.h | 179 ++++ examples/sudoku/stat_unittest.cc | 134 +++ examples/sudoku/sudoku.cc | 292 ++++++ examples/sudoku/sudoku.h | 12 + examples/twisted/finger/README | 12 + examples/twisted/finger/finger01.cc | 10 + examples/twisted/finger/finger02.cc | 13 + examples/twisted/finger/finger03.cc | 22 + examples/twisted/finger/finger04.cc | 24 + examples/twisted/finger/finger05.cc | 25 + examples/twisted/finger/finger06.cc | 44 + examples/twisted/finger/finger07.cc | 45 + examples/wordcount/README | 19 + examples/wordcount/gen.py | 14 + examples/wordcount/hash.h | 8 + examples/wordcount/hasher.cc | 236 +++++ examples/wordcount/receiver.cc | 107 +++ examples/wordcount/slowsink.py | 62 ++ examples/zeromq/README | 3 + examples/zeromq/local_lat.cc | 64 ++ examples/zeromq/remote_lat.cc | 96 ++ muduo/base/AsyncLogging.cc | 124 +++ muduo/base/AsyncLogging.h | 70 ++ muduo/base/Atomic.h | 74 ++ muduo/base/BUILD.bazel | 23 + muduo/base/BlockingQueue.h | 72 ++ muduo/base/BoundedBlockingQueue.h | 94 ++ muduo/base/Condition.cc | 28 + muduo/base/Condition.h | 44 + muduo/base/CountDownLatch.cc | 36 + muduo/base/CountDownLatch.h | 31 + muduo/base/CurrentThread.cc | 73 ++ muduo/base/CurrentThread.h | 51 ++ muduo/base/Date.cc | 72 ++ muduo/base/Date.h | 100 ++ muduo/base/Exception.cc | 18 + muduo/base/Exception.h | 33 + muduo/base/FileUtil.cc | 148 +++ muduo/base/FileUtil.h | 79 ++ muduo/base/GzipFile.h | 84 ++ muduo/base/LogFile.cc | 115 +++ muduo/base/LogFile.h | 52 ++ muduo/base/LogStream.cc | 337 +++++++ muduo/base/LogStream.h | 190 ++++ muduo/base/Logging.cc | 227 +++++ muduo/base/Logging.h | 159 ++++ muduo/base/Mutex.h | 207 +++++ muduo/base/ProcessInfo.cc | 210 +++++ muduo/base/ProcessInfo.h | 67 ++ muduo/base/Singleton.h | 75 ++ muduo/base/StringPiece.h | 200 ++++ muduo/base/Thread.cc | 198 ++++ muduo/base/Thread.h | 53 ++ muduo/base/ThreadLocal.h | 59 ++ muduo/base/ThreadLocalSingleton.h | 73 ++ muduo/base/ThreadPool.cc | 133 +++ muduo/base/ThreadPool.h | 62 ++ muduo/base/TimeZone.cc | 321 +++++++ muduo/base/TimeZone.h | 51 ++ muduo/base/Timestamp.cc | 60 ++ muduo/base/Timestamp.h | 117 +++ muduo/base/Types.h | 123 +++ muduo/base/WeakCallback.h | 61 ++ muduo/base/copyable.h | 17 + muduo/base/noncopyable.h | 18 + muduo/base/tests/AsyncLogging_test.cc | 64 ++ muduo/base/tests/Atomic_unittest.cc | 38 + muduo/base/tests/BlockingQueue_bench.cc | 106 +++ muduo/base/tests/BlockingQueue_test.cc | 106 +++ muduo/base/tests/BoundedBlockingQueue_test.cc | 110 +++ muduo/base/tests/Date_unittest.cc | 88 ++ muduo/base/tests/Exception_test.cc | 51 ++ muduo/base/tests/FileUtil_test.cc | 30 + muduo/base/tests/Fork_test.cc | 46 + muduo/base/tests/GzipFile_test.cc | 54 ++ muduo/base/tests/LogFile_test.cc | 34 + muduo/base/tests/LogStream_bench.cc | 82 ++ muduo/base/tests/LogStream_test.cc | 286 ++++++ muduo/base/tests/Logging_test.cc | 122 +++ muduo/base/tests/Mutex_test.cc | 82 ++ muduo/base/tests/ProcessInfo_test.cc | 17 + muduo/base/tests/SingletonThreadLocal_test.cc | 58 ++ muduo/base/tests/Singleton_test.cc | 65 ++ muduo/base/tests/ThreadLocalSingleton_test.cc | 58 ++ muduo/base/tests/ThreadLocal_test.cc | 61 ++ muduo/base/tests/ThreadPool_test.cc | 64 ++ muduo/base/tests/Thread_bench.cc | 86 ++ muduo/base/tests/Thread_test.cc | 91 ++ muduo/base/tests/TimeZone_unittest.cc | 276 ++++++ muduo/base/tests/Timestamp_unittest.cc | 66 ++ muduo/net/Acceptor.cc | 100 ++ muduo/net/Acceptor.h | 69 ++ muduo/net/BUILD.bazel | 51 ++ muduo/net/Buffer.cc | 78 ++ muduo/net/Buffer.h | 374 ++++++++ muduo/net/Callbacks.h | 84 ++ muduo/net/Channel.cc | 167 ++++ muduo/net/Channel.h | 156 ++++ muduo/net/Connector.cc | 208 +++++ muduo/net/Connector.h | 74 ++ muduo/net/Endian.h | 65 ++ muduo/net/EventLoop.cc | 301 ++++++ muduo/net/EventLoop.h | 198 ++++ muduo/net/EventLoopThread.cc | 87 ++ muduo/net/EventLoopThread.h | 52 ++ muduo/net/EventLoopThreadPool.cc | 90 ++ muduo/net/EventLoopThreadPool.h | 63 ++ muduo/net/InetAddress.cc | 139 +++ muduo/net/InetAddress.h | 86 ++ muduo/net/Poller.cc | 29 + muduo/net/Poller.h | 78 ++ muduo/net/Socket.cc | 114 +++ muduo/net/Socket.h | 84 ++ muduo/net/SocketsOps.cc | 290 ++++++ muduo/net/SocketsOps.h | 57 ++ muduo/net/TcpClient.cc | 167 ++++ muduo/net/TcpClient.h | 91 ++ muduo/net/TcpConnection.cc | 386 ++++++++ muduo/net/TcpConnection.h | 186 ++++ muduo/net/TcpServer.cc | 107 +++ muduo/net/TcpServer.h | 131 +++ muduo/net/Timer.cc | 23 + muduo/net/Timer.h | 58 ++ muduo/net/TimerId.h | 54 ++ muduo/net/TimerQueue.cc | 276 ++++++ muduo/net/TimerQueue.h | 114 +++ muduo/net/ZlibStream.h | 123 +++ muduo/net/boilerplate.cc | 15 + muduo/net/boilerplate.h | 28 + muduo/net/http/BUILD.bazel | 9 + muduo/net/http/HttpContext.cc | 90 ++ muduo/net/http/HttpContext.h | 72 ++ muduo/net/http/HttpRequest.h | 150 +++ muduo/net/http/HttpResponse.cc | 44 + muduo/net/http/HttpResponse.h | 79 ++ muduo/net/http/HttpServer.cc | 100 ++ muduo/net/http/HttpServer.h | 68 ++ muduo/net/http/tests/HttpRequest_unittest.cc | 79 ++ muduo/net/http/tests/HttpServer_test.cc | 150 +++ muduo/net/inspect/BUILD.bazel | 9 + muduo/net/inspect/Inspector.cc | 383 ++++++++ muduo/net/inspect/Inspector.h | 67 ++ muduo/net/inspect/PerformanceInspector.cc | 106 +++ muduo/net/inspect/PerformanceInspector.h | 40 + muduo/net/inspect/ProcessInspector.cc | 239 +++++ muduo/net/inspect/ProcessInspector.h | 38 + muduo/net/inspect/SystemInspector.cc | 132 +++ muduo/net/inspect/SystemInspector.h | 37 + muduo/net/inspect/tests/BUILD.bazel | 7 + muduo/net/inspect/tests/Inspector_test.cc | 15 + muduo/net/poller/DefaultPoller.cc | 24 + muduo/net/poller/EPollPoller.cc | 182 ++++ muduo/net/poller/EPollPoller.h | 51 ++ muduo/net/poller/PollPoller.cc | 161 ++++ muduo/net/poller/PollPoller.h | 44 + muduo/net/protobuf/BufferStream.h | 56 ++ muduo/net/protobuf/ProtobufCodecLite.cc | 243 +++++ muduo/net/protobuf/ProtobufCodecLite.h | 192 ++++ muduo/net/protorpc/README | 5 + muduo/net/protorpc/RpcChannel.cc | 184 ++++ muduo/net/protorpc/RpcChannel.h | 152 ++++ muduo/net/protorpc/RpcCodec.cc | 37 + muduo/net/protorpc/RpcCodec.h | 45 + muduo/net/protorpc/RpcCodec_test.cc | 81 ++ muduo/net/protorpc/RpcServer.cc | 68 ++ muduo/net/protorpc/RpcServer.h | 57 ++ muduo/net/protorpc/google-inl.h | 84 ++ muduo/net/protorpc/rpc.proto | 36 + muduo/net/protorpc/rpcservice.proto | 44 + muduo/net/tests/Buffer_unittest.cc | 170 ++++ muduo/net/tests/Channel_test.cc | 112 +++ muduo/net/tests/EchoClient_unittest.cc | 114 +++ muduo/net/tests/EchoServer_unittest.cc | 86 ++ .../net/tests/EventLoopThreadPool_unittest.cc | 68 ++ muduo/net/tests/EventLoopThread_unittest.cc | 48 + muduo/net/tests/EventLoop_unittest.cc | 42 + muduo/net/tests/InetAddress_unittest.cc | 47 + muduo/net/tests/TcpClient_reg1.cc | 29 + muduo/net/tests/TcpClient_reg2.cc | 30 + muduo/net/tests/TcpClient_reg3.cc | 24 + muduo/net/tests/TimerQueue_unittest.cc | 66 ++ muduo/net/tests/ZlibStream_unittest.cc | 91 ++ patches/MacOSX.diff | 854 ++++++++++++++++++ patches/armlinux.diff | 137 +++ patches/backport.diff | 551 +++++++++++ 366 files changed, 36387 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 .vscode/settings.json create mode 100644 BUILD.bazel create mode 100644 ChangeLog create mode 100644 ChangeLog2 create mode 100644 License create mode 100644 README create mode 100644 build.sh create mode 100644 contrib/hiredis/Hiredis.cc create mode 100644 contrib/hiredis/Hiredis.h create mode 100644 contrib/hiredis/README.md create mode 100644 contrib/hiredis/mrediscli.cc create mode 100644 contrib/thrift/ThriftConnection.cc create mode 100644 contrib/thrift/ThriftConnection.h create mode 100644 contrib/thrift/ThriftServer.cc create mode 100644 contrib/thrift/ThriftServer.h create mode 100644 contrib/thrift/tests/.gitignore create mode 100644 contrib/thrift/tests/echo/EchoServer.cc create mode 100644 contrib/thrift/tests/echo/echo.thrift create mode 100644 contrib/thrift/tests/echo/echoclient.py create mode 100644 contrib/thrift/tests/ping/PingServer.cc create mode 100644 contrib/thrift/tests/ping/ping.thrift create mode 100644 contrib/thrift/tests/ping/pingclient.py create mode 100644 examples/ace/logging/client.cc create mode 100644 examples/ace/logging/logrecord.proto create mode 100644 examples/ace/logging/server.cc create mode 100644 examples/ace/ttcp/common.cc create mode 100644 examples/ace/ttcp/common.h create mode 100644 examples/ace/ttcp/main.cc create mode 100644 examples/ace/ttcp/ttcp.cc create mode 100644 examples/ace/ttcp/ttcp_asio_async.cc create mode 100644 examples/ace/ttcp/ttcp_asio_sync.cc create mode 100644 examples/ace/ttcp/ttcp_blocking.cc create mode 100644 examples/asio/chat/client.cc create mode 100644 examples/asio/chat/codec.h create mode 100644 examples/asio/chat/loadtest.cc create mode 100644 examples/asio/chat/server.cc create mode 100644 examples/asio/chat/server_threaded.cc create mode 100644 examples/asio/chat/server_threaded_efficient.cc create mode 100644 examples/asio/chat/server_threaded_highperformance.cc create mode 100644 examples/asio/echo_see_simple create mode 100644 examples/asio/tutorial/daytime_see_simple create mode 100644 examples/asio/tutorial/there_is_no_timer1 create mode 100644 examples/asio/tutorial/timer2/timer.cc create mode 100644 examples/asio/tutorial/timer3/timer.cc create mode 100644 examples/asio/tutorial/timer4/timer.cc create mode 100644 examples/asio/tutorial/timer5/timer.cc create mode 100644 examples/asio/tutorial/timer6/timer.cc create mode 100644 examples/cdns/Resolver.cc create mode 100644 examples/cdns/Resolver.h create mode 100644 examples/cdns/dns.cc create mode 100644 examples/curl/Curl.cc create mode 100644 examples/curl/Curl.h create mode 100644 examples/curl/README create mode 100644 examples/curl/download.cc create mode 100644 examples/curl/mcurl.cc create mode 100644 examples/fastcgi/fastcgi.cc create mode 100644 examples/fastcgi/fastcgi.h create mode 100644 examples/fastcgi/fastcgi_test.cc create mode 100644 examples/fastcgi/nginx.conf create mode 100644 examples/filetransfer/download.cc create mode 100644 examples/filetransfer/download2.cc create mode 100644 examples/filetransfer/download3.cc create mode 100644 examples/filetransfer/loadtest/Client.java create mode 100644 examples/filetransfer/loadtest/Handler.java create mode 100644 examples/hub/README create mode 100644 examples/hub/codec.cc create mode 100644 examples/hub/codec.h create mode 100644 examples/hub/hub.cc create mode 100644 examples/hub/pub.cc create mode 100644 examples/hub/pubsub.cc create mode 100644 examples/hub/pubsub.h create mode 100644 examples/hub/sub.cc create mode 100644 examples/idleconnection/echo.cc create mode 100644 examples/idleconnection/echo.h create mode 100644 examples/idleconnection/main.cc create mode 100644 examples/idleconnection/sortedlist.cc create mode 100644 examples/maxconnection/echo.cc create mode 100644 examples/maxconnection/echo.h create mode 100644 examples/maxconnection/main.cc create mode 100644 examples/memcached/README create mode 100644 examples/memcached/client/bench.cc create mode 100644 examples/memcached/server/Item.cc create mode 100644 examples/memcached/server/Item.h create mode 100644 examples/memcached/server/MemcacheServer.cc create mode 100644 examples/memcached/server/MemcacheServer.h create mode 100644 examples/memcached/server/Session.cc create mode 100644 examples/memcached/server/Session.h create mode 100644 examples/memcached/server/footprint_test.cc create mode 100644 examples/memcached/server/server.cc create mode 100644 examples/multiplexer/demux.cc create mode 100644 examples/multiplexer/harness/run.sh create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java create mode 100644 examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java create mode 100644 examples/multiplexer/multiplexer.cc create mode 100644 examples/multiplexer/multiplexer_simple.cc create mode 100644 examples/netty/discard/client.cc create mode 100644 examples/netty/discard/server.cc create mode 100644 examples/netty/echo/client.cc create mode 100644 examples/netty/echo/server.cc create mode 100644 examples/netty/echo/server2.cc create mode 100644 examples/netty/uptime/uptime.cc create mode 100644 examples/pingpong/bench.cc create mode 100644 examples/pingpong/client.cc create mode 100644 examples/pingpong/server.cc create mode 100644 examples/procmon/dummyload.cc create mode 100644 examples/procmon/plot.cc create mode 100644 examples/procmon/plot.h create mode 100644 examples/procmon/plot_test.cc create mode 100644 examples/procmon/procmon.cc create mode 100644 examples/protobuf/codec/client.cc create mode 100644 examples/protobuf/codec/codec.cc create mode 100644 examples/protobuf/codec/codec.h create mode 100644 examples/protobuf/codec/codec_test.cc create mode 100644 examples/protobuf/codec/dispatcher.h create mode 100644 examples/protobuf/codec/dispatcher_lite.h create mode 100644 examples/protobuf/codec/dispatcher_lite_test.cc create mode 100644 examples/protobuf/codec/dispatcher_test.cc create mode 100644 examples/protobuf/codec/query.proto create mode 100644 examples/protobuf/codec/server.cc create mode 100644 examples/protobuf/resolver/client.cc create mode 100644 examples/protobuf/resolver/resolver.proto create mode 100644 examples/protobuf/resolver/server.cc create mode 100644 examples/protobuf/rpc/client.cc create mode 100644 examples/protobuf/rpc/server.cc create mode 100644 examples/protobuf/rpc/sudoku.proto create mode 100644 examples/protobuf/rpcbalancer/balancer.cc create mode 100644 examples/protobuf/rpcbalancer/balancer_raw.cc create mode 100644 examples/protobuf/rpcbench/client.cc create mode 100644 examples/protobuf/rpcbench/echo.proto create mode 100644 examples/protobuf/rpcbench/server.cc create mode 100644 examples/roundtrip/roundtrip.cc create mode 100644 examples/roundtrip/roundtrip_udp.cc create mode 100644 examples/shorturl/shorturl.cc create mode 100644 examples/simple/allinone/allinone.cc create mode 100644 examples/simple/chargen/chargen.cc create mode 100644 examples/simple/chargen/chargen.h create mode 100644 examples/simple/chargen/main.cc create mode 100644 examples/simple/chargenclient/chargenclient.cc create mode 100644 examples/simple/daytime/daytime.cc create mode 100644 examples/simple/daytime/daytime.h create mode 100644 examples/simple/daytime/main.cc create mode 100644 examples/simple/discard/discard.cc create mode 100644 examples/simple/discard/discard.h create mode 100644 examples/simple/discard/main.cc create mode 100644 examples/simple/echo/echo.cc create mode 100644 examples/simple/echo/echo.h create mode 100644 examples/simple/echo/main.cc create mode 100644 examples/simple/time/main.cc create mode 100644 examples/simple/time/time.cc create mode 100644 examples/simple/time/time.h create mode 100644 examples/simple/timeclient/timeclient.cc create mode 100644 examples/socks4a/balancer.cc create mode 100644 examples/socks4a/socks4a.cc create mode 100644 examples/socks4a/tcprelay.cc create mode 100644 examples/socks4a/tunnel.h create mode 100644 examples/sudoku/batch.cc create mode 100644 examples/sudoku/loadtest.cc create mode 100644 examples/sudoku/percentile.h create mode 100644 examples/sudoku/pipeline.cc create mode 100644 examples/sudoku/server_basic.cc create mode 100644 examples/sudoku/server_hybrid.cc create mode 100644 examples/sudoku/server_multiloop.cc create mode 100644 examples/sudoku/server_prod.cc create mode 100644 examples/sudoku/server_threadpool.cc create mode 100644 examples/sudoku/stat.h create mode 100644 examples/sudoku/stat_unittest.cc create mode 100644 examples/sudoku/sudoku.cc create mode 100644 examples/sudoku/sudoku.h create mode 100644 examples/twisted/finger/README create mode 100644 examples/twisted/finger/finger01.cc create mode 100644 examples/twisted/finger/finger02.cc create mode 100644 examples/twisted/finger/finger03.cc create mode 100644 examples/twisted/finger/finger04.cc create mode 100644 examples/twisted/finger/finger05.cc create mode 100644 examples/twisted/finger/finger06.cc create mode 100644 examples/twisted/finger/finger07.cc create mode 100644 examples/wordcount/README create mode 100644 examples/wordcount/gen.py create mode 100644 examples/wordcount/hash.h create mode 100644 examples/wordcount/hasher.cc create mode 100644 examples/wordcount/receiver.cc create mode 100644 examples/wordcount/slowsink.py create mode 100644 examples/zeromq/README create mode 100644 examples/zeromq/local_lat.cc create mode 100644 examples/zeromq/remote_lat.cc create mode 100644 muduo/base/AsyncLogging.cc create mode 100644 muduo/base/AsyncLogging.h create mode 100644 muduo/base/Atomic.h create mode 100644 muduo/base/BUILD.bazel create mode 100644 muduo/base/BlockingQueue.h create mode 100644 muduo/base/BoundedBlockingQueue.h create mode 100644 muduo/base/Condition.cc create mode 100644 muduo/base/Condition.h create mode 100644 muduo/base/CountDownLatch.cc create mode 100644 muduo/base/CountDownLatch.h create mode 100644 muduo/base/CurrentThread.cc create mode 100644 muduo/base/CurrentThread.h create mode 100644 muduo/base/Date.cc create mode 100644 muduo/base/Date.h create mode 100644 muduo/base/Exception.cc create mode 100644 muduo/base/Exception.h create mode 100644 muduo/base/FileUtil.cc create mode 100644 muduo/base/FileUtil.h create mode 100644 muduo/base/GzipFile.h create mode 100644 muduo/base/LogFile.cc create mode 100644 muduo/base/LogFile.h create mode 100644 muduo/base/LogStream.cc create mode 100644 muduo/base/LogStream.h create mode 100644 muduo/base/Logging.cc create mode 100644 muduo/base/Logging.h create mode 100644 muduo/base/Mutex.h create mode 100644 muduo/base/ProcessInfo.cc create mode 100644 muduo/base/ProcessInfo.h create mode 100644 muduo/base/Singleton.h create mode 100644 muduo/base/StringPiece.h create mode 100644 muduo/base/Thread.cc create mode 100644 muduo/base/Thread.h create mode 100644 muduo/base/ThreadLocal.h create mode 100644 muduo/base/ThreadLocalSingleton.h create mode 100644 muduo/base/ThreadPool.cc create mode 100644 muduo/base/ThreadPool.h create mode 100644 muduo/base/TimeZone.cc create mode 100644 muduo/base/TimeZone.h create mode 100644 muduo/base/Timestamp.cc create mode 100644 muduo/base/Timestamp.h create mode 100644 muduo/base/Types.h create mode 100644 muduo/base/WeakCallback.h create mode 100644 muduo/base/copyable.h create mode 100644 muduo/base/noncopyable.h create mode 100644 muduo/base/tests/AsyncLogging_test.cc create mode 100644 muduo/base/tests/Atomic_unittest.cc create mode 100644 muduo/base/tests/BlockingQueue_bench.cc create mode 100644 muduo/base/tests/BlockingQueue_test.cc create mode 100644 muduo/base/tests/BoundedBlockingQueue_test.cc create mode 100644 muduo/base/tests/Date_unittest.cc create mode 100644 muduo/base/tests/Exception_test.cc create mode 100644 muduo/base/tests/FileUtil_test.cc create mode 100644 muduo/base/tests/Fork_test.cc create mode 100644 muduo/base/tests/GzipFile_test.cc create mode 100644 muduo/base/tests/LogFile_test.cc create mode 100644 muduo/base/tests/LogStream_bench.cc create mode 100644 muduo/base/tests/LogStream_test.cc create mode 100644 muduo/base/tests/Logging_test.cc create mode 100644 muduo/base/tests/Mutex_test.cc create mode 100644 muduo/base/tests/ProcessInfo_test.cc create mode 100644 muduo/base/tests/SingletonThreadLocal_test.cc create mode 100644 muduo/base/tests/Singleton_test.cc create mode 100644 muduo/base/tests/ThreadLocalSingleton_test.cc create mode 100644 muduo/base/tests/ThreadLocal_test.cc create mode 100644 muduo/base/tests/ThreadPool_test.cc create mode 100644 muduo/base/tests/Thread_bench.cc create mode 100644 muduo/base/tests/Thread_test.cc create mode 100644 muduo/base/tests/TimeZone_unittest.cc create mode 100644 muduo/base/tests/Timestamp_unittest.cc create mode 100644 muduo/net/Acceptor.cc create mode 100644 muduo/net/Acceptor.h create mode 100644 muduo/net/BUILD.bazel create mode 100644 muduo/net/Buffer.cc create mode 100644 muduo/net/Buffer.h create mode 100644 muduo/net/Callbacks.h create mode 100644 muduo/net/Channel.cc create mode 100644 muduo/net/Channel.h create mode 100644 muduo/net/Connector.cc create mode 100644 muduo/net/Connector.h create mode 100644 muduo/net/Endian.h create mode 100644 muduo/net/EventLoop.cc create mode 100644 muduo/net/EventLoop.h create mode 100644 muduo/net/EventLoopThread.cc create mode 100644 muduo/net/EventLoopThread.h create mode 100644 muduo/net/EventLoopThreadPool.cc create mode 100644 muduo/net/EventLoopThreadPool.h create mode 100644 muduo/net/InetAddress.cc create mode 100644 muduo/net/InetAddress.h create mode 100644 muduo/net/Poller.cc create mode 100644 muduo/net/Poller.h create mode 100644 muduo/net/Socket.cc create mode 100644 muduo/net/Socket.h create mode 100644 muduo/net/SocketsOps.cc create mode 100644 muduo/net/SocketsOps.h create mode 100644 muduo/net/TcpClient.cc create mode 100644 muduo/net/TcpClient.h create mode 100644 muduo/net/TcpConnection.cc create mode 100644 muduo/net/TcpConnection.h create mode 100644 muduo/net/TcpServer.cc create mode 100644 muduo/net/TcpServer.h create mode 100644 muduo/net/Timer.cc create mode 100644 muduo/net/Timer.h create mode 100644 muduo/net/TimerId.h create mode 100644 muduo/net/TimerQueue.cc create mode 100644 muduo/net/TimerQueue.h create mode 100644 muduo/net/ZlibStream.h create mode 100644 muduo/net/boilerplate.cc create mode 100644 muduo/net/boilerplate.h create mode 100644 muduo/net/http/BUILD.bazel create mode 100644 muduo/net/http/HttpContext.cc create mode 100644 muduo/net/http/HttpContext.h create mode 100644 muduo/net/http/HttpRequest.h create mode 100644 muduo/net/http/HttpResponse.cc create mode 100644 muduo/net/http/HttpResponse.h create mode 100644 muduo/net/http/HttpServer.cc create mode 100644 muduo/net/http/HttpServer.h create mode 100644 muduo/net/http/tests/HttpRequest_unittest.cc create mode 100644 muduo/net/http/tests/HttpServer_test.cc create mode 100644 muduo/net/inspect/BUILD.bazel create mode 100644 muduo/net/inspect/Inspector.cc create mode 100644 muduo/net/inspect/Inspector.h create mode 100644 muduo/net/inspect/PerformanceInspector.cc create mode 100644 muduo/net/inspect/PerformanceInspector.h create mode 100644 muduo/net/inspect/ProcessInspector.cc create mode 100644 muduo/net/inspect/ProcessInspector.h create mode 100644 muduo/net/inspect/SystemInspector.cc create mode 100644 muduo/net/inspect/SystemInspector.h create mode 100644 muduo/net/inspect/tests/BUILD.bazel create mode 100644 muduo/net/inspect/tests/Inspector_test.cc create mode 100644 muduo/net/poller/DefaultPoller.cc create mode 100644 muduo/net/poller/EPollPoller.cc create mode 100644 muduo/net/poller/EPollPoller.h create mode 100644 muduo/net/poller/PollPoller.cc create mode 100644 muduo/net/poller/PollPoller.h create mode 100644 muduo/net/protobuf/BufferStream.h create mode 100644 muduo/net/protobuf/ProtobufCodecLite.cc create mode 100644 muduo/net/protobuf/ProtobufCodecLite.h create mode 100644 muduo/net/protorpc/README create mode 100644 muduo/net/protorpc/RpcChannel.cc create mode 100644 muduo/net/protorpc/RpcChannel.h create mode 100644 muduo/net/protorpc/RpcCodec.cc create mode 100644 muduo/net/protorpc/RpcCodec.h create mode 100644 muduo/net/protorpc/RpcCodec_test.cc create mode 100644 muduo/net/protorpc/RpcServer.cc create mode 100644 muduo/net/protorpc/RpcServer.h create mode 100644 muduo/net/protorpc/google-inl.h create mode 100644 muduo/net/protorpc/rpc.proto create mode 100644 muduo/net/protorpc/rpcservice.proto create mode 100644 muduo/net/tests/Buffer_unittest.cc create mode 100644 muduo/net/tests/Channel_test.cc create mode 100644 muduo/net/tests/EchoClient_unittest.cc create mode 100644 muduo/net/tests/EchoServer_unittest.cc create mode 100644 muduo/net/tests/EventLoopThreadPool_unittest.cc create mode 100644 muduo/net/tests/EventLoopThread_unittest.cc create mode 100644 muduo/net/tests/EventLoop_unittest.cc create mode 100644 muduo/net/tests/InetAddress_unittest.cc create mode 100644 muduo/net/tests/TcpClient_reg1.cc create mode 100644 muduo/net/tests/TcpClient_reg2.cc create mode 100644 muduo/net/tests/TcpClient_reg3.cc create mode 100644 muduo/net/tests/TimerQueue_unittest.cc create mode 100644 muduo/net/tests/ZlibStream_unittest.cc create mode 100644 patches/MacOSX.diff create mode 100644 patches/armlinux.diff create mode 100644 patches/backport.diff diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b63abc9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +# .clang-format + +# 风格格式化 +BasedOnStyle: Google +# 4 空格缩进 +IndentWidth: 4 +# 连续对齐变量的声明 +AlignConsecutiveDeclarations: true +# 指针左侧对齐 +PointerAlignment: Left +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 +# 大括号 +BreakBeforeBraces: Custom +BraceWrapping: + # 函数定义后面大括号在新行 + AfterFunction: true \ No newline at end of file diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..2911e29 --- /dev/null +++ b/.clangd @@ -0,0 +1,13 @@ +Hover: + ShowAKA: Yes +Diagnostics: + UnusedIncludes: None # 禁用未使用头文件提示 + Suppress: [ + anon_type_definition, # 禁用匿名的typedef提示 + unused-variable, # 禁用未使用变量提示 + unused-function, # 禁用未使用函数提示 + unused-includes, # 禁用未使用的头文件提示 + sign-conversion + ] + ClangTidy: + Remove: misc-unused-alias-decls \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7c7004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.swp +bazel-* +compile_commands.json +.cache +build +cmake* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a568370 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: cpp +sudo: required +dist: trusty +compiler: + - gcc + - clang +os: + - linux +install: + - sudo apt-get install libboost-dev + - sudo apt-get install libprotobuf-dev protobuf-compiler libprotoc-dev libgoogle-perftools-dev + - sudo apt-get install libboost-test-dev libboost-program-options-dev libboost-system-dev + - sudo apt-get install libc-ares-dev libcurl4-openssl-dev + - sudo apt-get install zlib1g-dev libgd-dev +env: + - BUILD_TYPE=debug + - BUILD_TYPE=release +script: + - ./build.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4179146 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "files.autoSave": "onFocusChange", + "editor.fontSize": 14, + "editor.fontFamily": "'FiraCode Nerd Font Mono', 'FiraCode Nerd Font Mono', 'FiraCode Nerd Font Mono'", + "cmake.configureOnOpen": true, + "cmake.options.statusBarVisibility": "visible", + "editor.inlayHints.enabled": "off", + "C_Cpp.intelliSenseEngine": "disabled", + "clangd.arguments": [ + "--header-insertion=never", + "--all-scopes-completion", + "--completion-style=detailed", + "--clang-tidy", + "-j=4", + "--pch-storage=memory", + "--compile-commands-dir=build", + "--background-index", + "--ranking-model=heuristics", + "--query-driver=/usr/bin/g++" + ] +} \ No newline at end of file diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..1fdfcc2 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1 @@ +# See https://github.com/chenshuo/muduo-tutorial for how to use muduo in your project. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..3e94641 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,441 @@ +2018-10-22 Shuo Chen <chenshuo@chenshuo.com> + * Last version in C++98/03, next version will use C++11 + * Enable Clang Thread Safety Analysis. + * Fix "off_t does not name a type" for CentOS 7 (#316) by qiao hai-jun + * Fix warnings for gcc 8. + * Add ttcp asio examples + * Implement Procmon::listFiles() + +2018-01-18 Shuo Chen <chenshuo@chenshuo.com> + * Fix race condition between Thread::tid() and Thread::start(). + * Change protorpc format, add back package name. (Go does this since 1.2) + * examples/socks4a/tunnel.h stops reading if output buffer is full. + * Fixes for GCC 6/7 + * Minor fixes by huntinux, liangshaocong, zhoudayang2, octocat_lee, + jack.xsuperman, ligewei, yqsy. + * Version 1.1.0 + +2016-10-25 Shuo Chen <chenshuo@chenshuo.com> + * Add Travis CI + * Add EvevtLoop::queueSize() by <zhuangshi23> + * Implement TcpClient::retry() by fdxuwei + * Change Condition::waitForSeconds() parameter type from int to double by ChaoShu + * Minor fixes by JackDrogon, YuCong, zieckey, wuzhaogai + * Version 1.0.9 + +2016-02-11 Shuo Chen <chenshuo@chenshuo.com> + * Preliminary support of IPv6. + * Add stop/startRead in TcpConnection by <zhang.jako> + * Version 1.0.8 + +2015-11-09 Shuo Chen <chenshuo@chenshuo.com> + * Add stats to Sudoku examples. + * Add example of PeriodicTimer class. + * Add thrift examples by <decimalbell>. + * Move hiredis example by <decimalbell> to contrib/. + * Move HTTP parseRequest to HttpContext class by <decimalbell>. + * Other fixes from <harrywong>, <cfreestar>, <qlhuangrui>, <lidw1988>. + * Version 1.0.7 + +2015-04-03 Shuo Chen <chenshuo@chenshuo.com> + * Fix ProcessInspector::threads(). + * Minor fixes and improvements from liyuan989 and zieckey. + * More Sudoku examples. + * Version 1.0.6 + +2015-01-30 Shuo Chen <chenshuo@chenshuo.com> + * Add examples/procmon + * EventLoop supports set/get context by <zieckey> + * Fix bug #107 + * Version 1.0.5 + +2014-10-05 Shuo Chen <chenshuo@chenshuo.com> + * Enrich interfaces of EventLoopThreadPool by <zieckey> + * Buffer supports reading int64_t by <alisper> + * Add hiredis example by <decimalbell> + * Fix bug about TcpClient life time again. + * Other minor fixes, including some from <huahang> + * Version 1.0.4 + +2014-08-02 Shuo Chen <chenshuo@chenshuo.com> + * Singleton supports 'no_destroy'. + * Get tcp_info in TcpConnection. + * Add CurrentThread::tidStringLength(). + * Fix bug about TcpClient life time. More checks. + * Version 1.0.3 + +2014-06-30 Shuo Chen <chenshuo@chenshuo.com> + * Fix boundary check in Buffer::findEOL() by <renxingsong>. + * Fix typos in InetAddress.cc by <huangml.zh>. + * Fix 32-bit integer overflow bug in time_client by <guochy2012>. + * Update comments in Buffer::readFd() by <huahang>. + * Add ThreadPool::setThreadInitCallback(). + * Rename GzipStream to ZlibStream. + * Version 1.0.2 + +2014-04-10 Shuo Chen <chenshuo@chenshuo.com> + * More ProcessInfo functions. + * Add GzipFile (in C++11 only) and GzipOutputStream. + * Add SystemInspector. + * muduo::Threads now sets thread name with prctl(). + * Version 1.0.1 + +2014-03-12 Shuo Chen <chenshuo@chenshuo.com> + * Add TCP and RPC balancer examples + * Version 1.0.0 + +2014-03-05 Shuo Chen <chenshuo@chenshuo.com> + * Introduce class StringArg for passing C-style string arguments. + * Support localtime in logging. + * Version 1.0.0-rc2 + +2014-02-22 Shuo Chen <chenshuo@chenshuo.com> + * Default to release build. + * Version 1.0.0-rc1 + +2014-02-22 Shuo Chen <chenshuo@chenshuo.com> + * Add base/WeakCallback.h + * Add TcpConnection::forceCloseWithDelay(). + * Add InetAddress::resolve for sync DNS resolving. + * Add simple Protobuf codec for single message type. + * Add ACE ttcp and logging examples. + * Fix race conditoin in RpcChannel::CallMethod(). + * Version 0.9.8 + +2014-01-11 Shuo Chen <chenshuo@chenshuo.com> + * Add TcpConnection::forceClose(). + * Add fastcgi nginx.conf example + * Fix iterator invalidation in hub.cc. + * Version 0.9.7 + +2013-10-21 Shuo Chen <chenshuo@chenshuo.com> + * Minor fixes. + * Version 0.9.6 + +2013-08-31 Shuo Chen <chenshuo@chenshuo.com> + * Add C++11 rvalue overloads for boost::function parameters + * Add PerformanceInspector, support remote profiling with gperftools + * Add examples of memcached server and client + * Version 0.9.5 + +2013-07-28 Shuo Chen <chenshuo@chenshuo.com> + * Protobuf RPC wire protocol changed, + package name removed in 'service' field. + * Add roundtrip_udp as a UDP example + * More inspect + * Fix Connector::stop() + * Fix for protobuf 2.5.0 + * Version 0.9.4 + +2013-05-11 Shuo Chen <chenshuo@chenshuo.com> + * ThreadPool can be blocking + * Support SO_REUSEPORT, added in kernel 3.9.0 + * Fix Mutex::isLockedByThisThread() + * Version 0.9.3 + +2013-03-22 Shuo Chen <chenshuo@chenshuo.com> + * Fix bugs + * Add Sudoku client + * Version 0.9.2 + +2013-01-16 Shuo Chen <chenshuo@chenshuo.com> + * Fix bug introduced in dd26871 + * Version 0.9.1 + +2013-01-09 Shuo Chen <chenshuo@chenshuo.com> + * Add single thread concurrent download example in examples/curl. + * Add distributed word counting example. + * Add simple FastCGI example. + * Fix HttpRequest for empty header value, contributed by SeasonLee + * Fix Connector destruction + * Version 0.9.0 + +2012-11-06 Shuo Chen <chenshuo@chenshuo.com> + * Version for the book + * Fix Buffer::shrink() + * Fix race condition of ThreadPool::stop() + * Version 0.8.2 + +2012-09-30 Shuo Chen <chenshuo@chenshuo.com> + * Add Channel::remove() + * Logger::SourceFile supports char* + * Fix for g++ 4.7 + * Version 0.8.1 + +2012-09-06 Shuo Chen <chenshuo@chenshuo.com> + * More Buffer member functions, contributed by SeasonLee + * Add unit tests for Buffer + * Fix wait condition in AsyncLogging::threadFunc() + * Rename fromHostPort to fromIpPort + * Add hash_value for shared_ptr + * Add TcpConnection::getMutableContext() + * Remove unnecessary code, header + * Add another example in idleconnection + * Version 0.8.0 + +2012-06-26 Shuo Chen <chenshuo@chenshuo.com> + + * Add TimeZone class and unit tests. + * Inline Buffer::appendInt32() and Buffer::peekInt32(). + * Catch exception in Thread::runInThread(). + Rethrow in catch(...) to make pthread_cancel() working. + * Avoid deleting incomplete types. + * Replace delete with boost::ptr_vector + * Destructs ThreadLocalSingleton + * Replace __thread object with ThreadLocalSingleton in examples/asio/chat/ + * Fix compile with g++ 4.6 + * With armlinux.diff, muduo compiles on Raspberry Pi with g++ 4.5. + * Version 0.7.0 + +2012-06-11 Shuo Chen <chenshuo@chenshuo.com> + + * Put hostname as part of log file name. + * Extract muduo/base/CurrentThread.h + * Optimize logging for thread id and source filename. + * Add BlockingQueue_bench, improve Thread_bench. + * Add examples/zeromq, for round-trip latency tests. + * Demonstrate HighWaterMark callback and weak callback in tcp tunnel. + * Fix chat codec for invalid length. + * Version 0.6.0 + +2012-06-03 Shuo Chen <chenshuo@chenshuo.com> + + * Replace std::ostream with LogStream. + * Add LogFile and AsyncLogging. + * Set SO_KEEPALIVE by default. + * Add HighWaterMark callback to TcpConnection. + * Add EventLoop::getEventLoopOfCurrentThread(), + Add ThreadInitCallback to EventLoopThreadPool. + * Add asio_chat_server_threaded_highperformance + * Version 0.5.0 + +2012-05-18 Shuo Chen <chenshuo@chenshuo.com> + + * Add FileUtil. + * Add new functions in ProcessInfo + * Add example for curl. + * Add add RPC meta service proto. + * Add loadtest for asio chat. + * Version 0.3.5 + +2012-03-22 Shuo Chen <chenshuo@chenshuo.com> + + * Add example for async rpc (resolver). + * Install muduo_cdns + * Version 0.3.4 + +2012-03-16 Shuo Chen <chenshuo@chenshuo.com> + + * Remove net/protorpc2 + moved to http://github.com/chenshuo/muduo-protorpc + * Install EventLoopThreadPool.h, rpc.proto and rpc.pb.h + * Version 0.3.3 + +2012-03-11 Shuo Chen <chenshuo@chenshuo.com> + + * Add asynchronous DNS stub resolver based on c-ares. + See also https://github.com/chenshuo/muduo-udns + * Replace string with StringPiece for function parameters. + * Change default log level from DEBUG to INFO, + set MUDUO_LOG_DEBUG=1 to revert. + * Install Channel.h + * Version 0.3.2 + +2012-03-01 Shuo Chen <chenshuo@chenshuo.com> + + * Support multi-threaded http server. + * Do not install SocketsOps.h + * Version 0.3.1 + +2012-02-24 Shuo Chen <chenshuo@chenshuo.com> + + * Support Keep-Alive for HTTP/1.0. + * Check return value of pthread_create. + * Minor fixes (set TcpNoDelay, stop() in ThreadPool::dtor) + * Version 0.3.0 + +2011-09-18 Shuo Chen <chenshuo@chenshuo.com> + + * EventLoop now supports cancelling timer. + * Add two examples of asio chat server, demo copy-on-write + in multithreaded program. + * Version 0.2.9 + +2011-09-04 Shuo Chen <chenshuo@chenshuo.com> + + * Refactored RPC implementation of version 1 and 2, + programming interface differ, interoperable. + version 2 is incomplete yet. + * Find protobuf with cmake find_package(). + * Version 0.2.8 + +2011-09-03 Shuo Chen <chenshuo@chenshuo.com> + + * Add a proof of concept implementation of Protobuf RPC. + * Version 0.2.7 + +2011-06-27 Shuo Chen <chenshuo@chenshuo.com> + + * Fix decoding of Sudoku request. + * Backport to older Linux. + * Add BoundedBlockingQueue + * Version 0.2.6 + +2011-06-15 Shuo Chen <chenshuo@chenshuo.com> + + * Add examples/sudoku. + * Add thread benchmark. + * Version 0.2.5 + +2011-06-02 Shuo Chen <chenshuo@chenshuo.com> + + * Add examples/shorturl. + * Version 0.2.4 + +2011-05-24 Shuo Chen <chenshuo@chenshuo.com> + + * Fix warnings on Arch Linux (GCC 4.6.0), thanks to ifreedom + * Add CMake install instructions, thanks to ifreedom + * Fix warnings on 32-bit Linux, thanks to highshow + * Version 0.2.3 + +2011-05-15 Shuo Chen <chenshuo@chenshuo.com> + + * Changes from reactor tutorial + * Version 0.2.2 + +2011-05-07 Shuo Chen <chenshuo@chenshuo.com> + + * Try making TcpClient destructable + * Add demux in examples/multiplexer + * Add examples/socks4a + * Changes for reactor tutorial + * Version 0.2.1 + +2011-04-27 Shuo Chen <chenshuo@chenshuo.com> + + * Add kick idle connection example in examples/idleconnection. + * Add test harness to examples/multiplexer + * Replace std::list with std::set in TimerQueue. + * Version 0.2.0 + +2011-04-11 Shuo Chen <chenshuo@chenshuo.com> + + * Add Google Protobuf codec and dispatcher + * Revert 'Add max connection limit to simple echo example.' + * Add max connection limit example in examples/maxconnection. + * Version 0.1.9 + +2011-03-27 Shuo Chen <chenshuo@chenshuo.com> + + * Add file transfer download examples. + * Add max connection limit to simple echo example. + * Make inputBuffer accessible in TcpConnection. + * Const-ness correct in Buffer class. + * Add Mutex test for benchmarking. + * Replace anonymous namespace with muduo::detail in muduo/base. + * Version 0.1.8 + +2011-02-03 Shuo Chen <chenshuo@chenshuo.com> + + * Fix LengthHeaderCodec::onMessage() in examples/asio/chat. + * Version 0.1.7 + +2011-02-01 Shuo Chen <chenshuo@chenshuo.com> + + * Fix onConnection() in simple examples. + * Reset t_cachedTid after fork(). + * Version 0.1.6 + +2010-12-15 Shuo Chen <chenshuo@chenshuo.com> + + * Add examples/multiplexer + * Fix epoll kNoneEvent + * Version 0.1.5 + +2010-11-20 Shuo Chen <chenshuo@chenshuo.com> + + * Fix retry logic + * Version 0.1.4 + +2010-09-26 Shuo Chen <chenshuo@chenshuo.com> + + * Check SO_ERROR when connection is made. + +2010-09-11 Shuo Chen <chenshuo@chenshuo.com> + + * Gracefully refuse clients when accept(2) returns EMFILE. + * Version 0.1.3 + +2010-09-07 Shuo Chen <chenshuo@chenshuo.com> + + * Libevent benchmark for event handling. + * Version 0.1.2 + +2010-09-04 Shuo Chen <chenshuo@chenshuo.com> + + * Ping-pong benchmark, version 0.1.1 + +2010-08-30 Shuo Chen <chenshuo@chenshuo.com> + + * First pre-alpha release, version 0.1.0 + +2010-08-29 Shuo Chen <chenshuo@chenshuo.com> + + * Sub works. + +2010-08-28 Shuo Chen <chenshuo@chenshuo.com> + + * Add twisted finger examples. + +2010-08-27 Shuo Chen <chenshuo@chenshuo.com> + + * Add simple chargen example. + +2010-08-07 Shuo Chen <chenshuo@chenshuo.com> + + * Add Date. + +2010-05-15 Shuo Chen <chenshuo@chenshuo.com> + + * Hub works. + +2010-05-14 Shuo Chen <chenshuo@chenshuo.com> + + * Inspects opened files and threads. + +2010-05-11 Shuo Chen <chenshuo@chenshuo.com> + + * Add inspector for process info. + +2010-05-04 Shuo Chen <chenshuo@chenshuo.com> + + * Add simple http server and client. + +2010-04-25 Shuo Chen <chenshuo@chenshuo.com> + + * Add examples. + +2010-04-11 Shuo Chen <chenshuo@chenshuo.com> + + * TcpClient works. + +2010-04-03 Shuo Chen <chenshuo@chenshuo.com> + + * TcpServer works. + +2010-03-15 Shuo Chen <chenshuo@chenshuo.com> + + * TcpConnection at server side works. + +2010-03-14 Shuo Chen <chenshuo@chenshuo.com> + + * Acceptor works. + +2010-03-13 Shuo Chen <chenshuo@chenshuo.com> + + * TimerQueue works. + +2010-03-12 Shuo Chen <chenshuo@chenshuo.com> + + * Starts working on Muduo. diff --git a/ChangeLog2 b/ChangeLog2 new file mode 100644 index 0000000..0b69e99 --- /dev/null +++ b/ChangeLog2 @@ -0,0 +1,12 @@ +2018-10-24 Shuo Chen <chenshuo@chenshuo.com> + * First release of C++11 version of muduo. + * Forked after v1.0.9, e6c04c43 is the base. changes in cpp98 branch are integrated + * Replace boost::shared_ptr/boost::weak_ptr with std::shared_ptr/std::weak_ptr. + * Replace boost::function/boost::bind with std::function/std::bind/lambda. + * Replace boost::ptr_vector<T> with std::vector<std::unique_ptr<T>>. + * Replace boost::noncopyable with muduo::noncopyable. + * Replace boost::scoped_ptr with std::unique_ptr. + * Replace BOOST_STATIC_ASSERT with static_assert. + * Replace boost type_traits with std type_traits. + * Version 2.0.0 + diff --git a/License b/License new file mode 100644 index 0000000..836aa8b --- /dev/null +++ b/License @@ -0,0 +1,29 @@ +// Muduo - A reactor-based C++ network library for Linux +// Copyright (c) 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Shuo Chen nor the names of other contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README b/README new file mode 100644 index 0000000..2cb2607 --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +Muduo is a multithreaded C++ network library based on +the reactor pattern. + +http://github.com/chenshuo/muduo + +Copyright (c) 2010, Shuo Chen. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the License file. + +Requires: + Linux kernel version >= 2.6.28. + GCC >= 4.7 or Clang >= 3.5 + Boost (for boost::any only.) + +Tested on: + Debian 7 and above + Unbuntu 14.04 and above + CentOS 7 and above + +To build, run: + ./build.sh + +See https://github.com/chenshuo/muduo-tutorial for +how to use muduo in your project. + __ __ _ + | \/ | | | + | \ / |_ _ __| |_ _ ___ + | |\/| | | | |/ _` | | | |/ _ \ + | | | | |_| | (_| | |_| | (_) | + |_| |_|\__,_|\__,_|\__,_|\___/ + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..3458320 --- /dev/null +++ b/build.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -x + +SOURCE_DIR=`pwd` +BUILD_DIR=${BUILD_DIR:-../build} +BUILD_TYPE=${BUILD_TYPE:-debug} +INSTALL_DIR=${INSTALL_DIR:-../${BUILD_TYPE}-install-cpp11} +CXX=${CXX:-g++} + +ln -sf $BUILD_DIR/$BUILD_TYPE-cpp11/compile_commands.json + +mkdir -p $BUILD_DIR/$BUILD_TYPE-cpp11 \ + && cd $BUILD_DIR/$BUILD_TYPE-cpp11 \ + && cmake \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + $SOURCE_DIR \ + && make $* + +# Use the following command to run all the unit tests +# at the dir $BUILD_DIR/$BUILD_TYPE : +# CTEST_OUTPUT_ON_FAILURE=TRUE make test + +# cd $SOURCE_DIR && doxygen + diff --git a/contrib/hiredis/Hiredis.cc b/contrib/hiredis/Hiredis.cc new file mode 100644 index 0000000..3b62930 --- /dev/null +++ b/contrib/hiredis/Hiredis.cc @@ -0,0 +1,239 @@ +#include "contrib/hiredis/Hiredis.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/SocketsOps.h" + +#include <hiredis/async.h> + +using namespace muduo; +using namespace muduo::net; +using namespace hiredis; + +static void dummy(const std::shared_ptr<Channel>&) +{ +} + +Hiredis::Hiredis(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + serverAddr_(serverAddr), + context_(NULL) +{ +} + +Hiredis::~Hiredis() +{ + LOG_DEBUG << this; + assert(!channel_ || channel_->isNoneEvent()); + ::redisAsyncFree(context_); +} + +bool Hiredis::connected() const +{ + return channel_ && context_ && (context_->c.flags & REDIS_CONNECTED); +} + +const char* Hiredis::errstr() const +{ + assert(context_ != NULL); + return context_->errstr; +} + +void Hiredis::connect() +{ + assert(!context_); + + context_ = ::redisAsyncConnect(serverAddr_.toIp().c_str(), serverAddr_.toPort()); + + context_->ev.addRead = addRead; + context_->ev.delRead = delRead; + context_->ev.addWrite = addWrite; + context_->ev.delWrite = delWrite; + context_->ev.cleanup = cleanup; + context_->ev.data = this; + + setChannel(); + + assert(context_->onConnect == NULL); + assert(context_->onDisconnect == NULL); + ::redisAsyncSetConnectCallback(context_, connectCallback); + ::redisAsyncSetDisconnectCallback(context_, disconnectCallback); +} + +void Hiredis::disconnect() +{ + if (connected()) + { + LOG_DEBUG << this; + ::redisAsyncDisconnect(context_); + } +} + +int Hiredis::fd() const +{ + assert(context_); + return context_->c.fd; +} + +void Hiredis::setChannel() +{ + LOG_DEBUG << this; + assert(!channel_); + channel_.reset(new Channel(loop_, fd())); + channel_->setReadCallback(std::bind(&Hiredis::handleRead, this, _1)); + channel_->setWriteCallback(std::bind(&Hiredis::handleWrite, this)); +} + +void Hiredis::removeChannel() +{ + LOG_DEBUG << this; + channel_->disableAll(); + channel_->remove(); + loop_->queueInLoop(std::bind(dummy, channel_)); + channel_.reset(); +} + +void Hiredis::handleRead(muduo::Timestamp receiveTime) +{ + LOG_TRACE << "receiveTime = " << receiveTime.toString(); + ::redisAsyncHandleRead(context_); +} + +void Hiredis::handleWrite() +{ + if (!(context_->c.flags & REDIS_CONNECTED)) + { + removeChannel(); + } + ::redisAsyncHandleWrite(context_); +} + +/* static */ Hiredis* Hiredis::getHiredis(const redisAsyncContext* ac) +{ + Hiredis* hiredis = static_cast<Hiredis*>(ac->ev.data); + assert(hiredis->context_ == ac); + return hiredis; +} + +void Hiredis::logConnection(bool up) const +{ + InetAddress localAddr(sockets::getLocalAddr(fd())); + InetAddress peerAddr(sockets::getPeerAddr(fd())); + + LOG_INFO << localAddr.toIpPort() << " -> " + << peerAddr.toIpPort() << " is " + << (up ? "UP" : "DOWN"); +} + +/* static */ void Hiredis::connectCallback(const redisAsyncContext* ac, int status) +{ + LOG_TRACE; + getHiredis(ac)->connectCallback(status); +} + +void Hiredis::connectCallback(int status) +{ + if (status != REDIS_OK) + { + LOG_ERROR << context_->errstr << " failed to connect to " << serverAddr_.toIpPort(); + } + else + { + logConnection(true); + setChannel(); + } + + if (connectCb_) + { + connectCb_(this, status); + } +} + +/* static */ void Hiredis::disconnectCallback(const redisAsyncContext* ac, int status) +{ + LOG_TRACE; + getHiredis(ac)->disconnectCallback(status); +} + +void Hiredis::disconnectCallback(int status) +{ + logConnection(false); + removeChannel(); + + if (disconnectCb_) + { + disconnectCb_(this, status); + } +} + +void Hiredis::addRead(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast<Hiredis*>(privdata); + hiredis->channel_->enableReading(); +} + +void Hiredis::delRead(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast<Hiredis*>(privdata); + hiredis->channel_->disableReading(); +} + +void Hiredis::addWrite(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast<Hiredis*>(privdata); + hiredis->channel_->enableWriting(); +} + +void Hiredis::delWrite(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast<Hiredis*>(privdata); + hiredis->channel_->disableWriting(); +} + +void Hiredis::cleanup(void* privdata) +{ + Hiredis* hiredis = static_cast<Hiredis*>(privdata); + LOG_DEBUG << hiredis; +} + +int Hiredis::command(const CommandCallback& cb, muduo::StringArg cmd, ...) +{ + if (!connected()) return REDIS_ERR; + + LOG_TRACE; + CommandCallback* p = new CommandCallback(cb); + va_list args; + va_start(args, cmd); + int ret = ::redisvAsyncCommand(context_, commandCallback, p, cmd.c_str(), args); + va_end(args); + return ret; +} + +/* static */ void Hiredis::commandCallback(redisAsyncContext* ac, void* r, void* privdata) +{ + redisReply* reply = static_cast<redisReply*>(r); + CommandCallback* cb = static_cast<CommandCallback*>(privdata); + getHiredis(ac)->commandCallback(reply, cb); +} + +void Hiredis::commandCallback(redisReply* reply, CommandCallback* cb) +{ + (*cb)(this, reply); + delete cb; +} + +int Hiredis::ping() +{ + return command(std::bind(&Hiredis::pingCallback, this, _1, _2), "PING"); +} + +void Hiredis::pingCallback(Hiredis* me, redisReply* reply) +{ + assert(this == me); + LOG_DEBUG << reply->str; +} diff --git a/contrib/hiredis/Hiredis.h b/contrib/hiredis/Hiredis.h new file mode 100644 index 0000000..a48af03 --- /dev/null +++ b/contrib/hiredis/Hiredis.h @@ -0,0 +1,91 @@ +#ifndef MUDUO_CONTRIB_HIREDIS_HIREDIS_H +#define MUDUO_CONTRIB_HIREDIS_HIREDIS_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" +#include "muduo/net/Callbacks.h" +#include "muduo/net/InetAddress.h" + +#include <hiredis/hiredis.h> + +struct redisAsyncContext; + +namespace muduo +{ +namespace net +{ +class Channel; +class EventLoop; +} +} + +namespace hiredis +{ + +class Hiredis : public std::enable_shared_from_this<Hiredis>, + muduo::noncopyable +{ + public: + typedef std::function<void(Hiredis*, int)> ConnectCallback; + typedef std::function<void(Hiredis*, int)> DisconnectCallback; + typedef std::function<void(Hiredis*, redisReply*)> CommandCallback; + + Hiredis(muduo::net::EventLoop* loop, const muduo::net::InetAddress& serverAddr); + ~Hiredis(); + + const muduo::net::InetAddress& serverAddress() const { return serverAddr_; } + // redisAsyncContext* context() { return context_; } + bool connected() const; + const char* errstr() const; + + void setConnectCallback(const ConnectCallback& cb) { connectCb_ = cb; } + void setDisconnectCallback(const DisconnectCallback& cb) { disconnectCb_ = cb; } + + void connect(); + void disconnect(); // FIXME: implement this with redisAsyncDisconnect + + int command(const CommandCallback& cb, muduo::StringArg cmd, ...); + + int ping(); + + private: + void handleRead(muduo::Timestamp receiveTime); + void handleWrite(); + + int fd() const; + void logConnection(bool up) const; + void setChannel(); + void removeChannel(); + + void connectCallback(int status); + void disconnectCallback(int status); + void commandCallback(redisReply* reply, CommandCallback* privdata); + + static Hiredis* getHiredis(const redisAsyncContext* ac); + + static void connectCallback(const redisAsyncContext* ac, int status); + static void disconnectCallback(const redisAsyncContext* ac, int status); + // command callback + static void commandCallback(redisAsyncContext* ac, void*, void*); + + static void addRead(void* privdata); + static void delRead(void* privdata); + static void addWrite(void* privdata); + static void delWrite(void* privdata); + static void cleanup(void* privdata); + + void pingCallback(Hiredis* me, redisReply* reply); + + private: + muduo::net::EventLoop* loop_; + const muduo::net::InetAddress serverAddr_; + redisAsyncContext* context_; + std::shared_ptr<muduo::net::Channel> channel_; + ConnectCallback connectCb_; + DisconnectCallback disconnectCb_; +}; + +} // namespace hiredis + +#endif // MUDUO_CONTRIB_HIREDIS_HIREDIS_H diff --git a/contrib/hiredis/README.md b/contrib/hiredis/README.md new file mode 100644 index 0000000..a856574 --- /dev/null +++ b/contrib/hiredis/README.md @@ -0,0 +1,5 @@ +# Hiredis + +The version of hiredis must be 0.11.0 or greater + +See also issue [#92](https://github.com/chenshuo/muduo/issues/92) diff --git a/contrib/hiredis/mrediscli.cc b/contrib/hiredis/mrediscli.cc new file mode 100644 index 0000000..b509323 --- /dev/null +++ b/contrib/hiredis/mrediscli.cc @@ -0,0 +1,141 @@ +#include "contrib/hiredis/Hiredis.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <string> + +using namespace muduo; +using namespace muduo::net; + +string toString(long long value) +{ + char buf[32]; + snprintf(buf, sizeof buf, "%lld", value); + return buf; +} + +string redisReplyToString(const redisReply* reply) +{ + static const char* const types[] = { "", + "REDIS_REPLY_STRING", "REDIS_REPLY_ARRAY", + "REDIS_REPLY_INTEGER", "REDIS_REPLY_NIL", + "REDIS_REPLY_STATUS", "REDIS_REPLY_ERROR" }; + string str; + if (!reply) return str; + + str += types[reply->type] + string("(") + toString(reply->type) + ") "; + + str += "{ "; + if (reply->type == REDIS_REPLY_STRING || + reply->type == REDIS_REPLY_STATUS || + reply->type == REDIS_REPLY_ERROR) + { + str += '"' + string(reply->str, reply->len) + '"'; + } + else if (reply->type == REDIS_REPLY_INTEGER) + { + str += toString(reply->integer); + } + else if (reply->type == REDIS_REPLY_ARRAY) + { + str += toString(reply->elements) + " "; + for (size_t i = 0; i < reply->elements; i++) + { + str += " " + redisReplyToString(reply->element[i]); + } + } + str += " }"; + + return str; +} + +void connectCallback(hiredis::Hiredis* c, int status) +{ + if (status != REDIS_OK) + { + LOG_ERROR << "connectCallback Error:" << c->errstr(); + } + else + { + LOG_INFO << "Connected..."; + } +} + +void disconnectCallback(hiredis::Hiredis* c, int status) +{ + if (status != REDIS_OK) + { + LOG_ERROR << "disconnectCallback Error:" << c->errstr(); + } + else + { + LOG_INFO << "Disconnected..."; + } +} + +void timeCallback(hiredis::Hiredis* c, redisReply* reply) +{ + LOG_INFO << "time " << redisReplyToString(reply); +} + +void echoCallback(hiredis::Hiredis* c, redisReply* reply, string* echo) +{ + LOG_INFO << *echo << " " << redisReplyToString(reply); + c->disconnect(); +} + +void dbsizeCallback(hiredis::Hiredis* c, redisReply* reply) +{ + LOG_INFO << "dbsize " << redisReplyToString(reply); +} + +void selectCallback(hiredis::Hiredis* c, redisReply* reply, uint16_t* index) +{ + LOG_INFO << "select " << *index << " " << redisReplyToString(reply); +} + +void authCallback(hiredis::Hiredis* c, redisReply* reply, string* password) +{ + LOG_INFO << "auth " << *password << " " << redisReplyToString(reply); +} + +void echo(hiredis::Hiredis* c, string* s) +{ + c->command(std::bind(echoCallback, _1, _2, s), "echo %s", s->c_str()); +} + +int main(int argc, char** argv) +{ + Logger::setLogLevel(Logger::DEBUG); + + EventLoop loop; + + InetAddress serverAddr("127.0.0.1", 6379); + hiredis::Hiredis hiredis(&loop, serverAddr); + + hiredis.setConnectCallback(connectCallback); + hiredis.setDisconnectCallback(disconnectCallback); + hiredis.connect(); + + //hiredis.ping(); + loop.runEvery(1.0, std::bind(&hiredis::Hiredis::ping, &hiredis)); + + hiredis.command(timeCallback, "time"); + + string hi = "hi"; + hiredis.command(std::bind(echoCallback, _1, _2, &hi), "echo %s", hi.c_str()); + loop.runEvery(2.0, std::bind(echo, &hiredis, &hi)); + + hiredis.command(dbsizeCallback, "dbsize"); + + uint16_t index = 8; + hiredis.command(std::bind(selectCallback, _1, _2, &index), "select %d", index); + + string password = "password"; + hiredis.command(std::bind(authCallback, _1, _2, &password), "auth %s", password.c_str()); + + loop.loop(); + + return 0; +} diff --git a/contrib/thrift/ThriftConnection.cc b/contrib/thrift/ThriftConnection.cc new file mode 100644 index 0000000..89c094b --- /dev/null +++ b/contrib/thrift/ThriftConnection.cc @@ -0,0 +1,121 @@ +#include "contrib/thrift/ThriftConnection.h" + +#include <boost/bind.hpp> + +#include "muduo/base/Logging.h" + +#include <thrift/transport/TTransportException.h> + +#include "contrib/thrift/ThriftServer.h" + +using namespace muduo; +using namespace muduo::net; + +ThriftConnection::ThriftConnection(ThriftServer* server, + const TcpConnectionPtr& conn) + : server_(server), + conn_(conn), + state_(kExpectFrameSize), + frameSize_(0) +{ + conn_->setMessageCallback(boost::bind(&ThriftConnection::onMessage, + this, _1, _2, _3)); + nullTransport_.reset(new TNullTransport()); + inputTransport_.reset(new TMemoryBuffer(NULL, 0)); + outputTransport_.reset(new TMemoryBuffer()); + + factoryInputTransport_ = server_->getInputTransportFactory()->getTransport(inputTransport_); + factoryOutputTransport_ = server_->getOutputTransportFactory()->getTransport(outputTransport_); + + inputProtocol_ = server_->getInputProtocolFactory()->getProtocol(factoryInputTransport_); + outputProtocol_ = server_->getOutputProtocolFactory()->getProtocol(factoryOutputTransport_); + + processor_ = server_->getProcessor(inputProtocol_, outputProtocol_, nullTransport_); +} + +void ThriftConnection::onMessage(const TcpConnectionPtr& conn, + Buffer* buffer, + Timestamp receiveTime) +{ + bool more = true; + while (more) + { + if (state_ == kExpectFrameSize) + { + if (buffer->readableBytes() >= 4) + { + frameSize_ = static_cast<uint32_t>(buffer->readInt32()); + state_ = kExpectFrame; + } + else + { + more = false; + } + } + else if (state_ == kExpectFrame) + { + if (buffer->readableBytes() >= frameSize_) + { + uint8_t* buf = reinterpret_cast<uint8_t*>((const_cast<char*>(buffer->peek()))); + + inputTransport_->resetBuffer(buf, frameSize_, TMemoryBuffer::COPY); + outputTransport_->resetBuffer(); + outputTransport_->getWritePtr(4); + outputTransport_->wroteBytes(4); + + if (server_->isWorkerThreadPoolProcessing()) + { + server_->workerThreadPool().run(boost::bind(&ThriftConnection::process, this)); + } + else + { + process(); + } + + buffer->retrieve(frameSize_); + state_ = kExpectFrameSize; + } + else + { + more = false; + } + } + } +} + +void ThriftConnection::process() +{ + try + { + processor_->process(inputProtocol_, outputProtocol_, NULL); + + uint8_t* buf; + uint32_t size; + outputTransport_->getBuffer(&buf, &size); + + assert(size >= 4); + uint32_t frameSize = static_cast<uint32_t>(htonl(size - 4)); + memcpy(buf, &frameSize, 4); + + conn_->send(buf, size); + } catch (const TTransportException& ex) + { + LOG_ERROR << "ThriftServer TTransportException: " << ex.what(); + close(); + } catch (const std::exception& ex) + { + LOG_ERROR << "ThriftServer std::exception: " << ex.what(); + close(); + } catch (...) + { + LOG_ERROR << "ThriftServer unknown exception"; + close(); + } +} + +void ThriftConnection::close() +{ + nullTransport_->close(); + factoryInputTransport_->close(); + factoryOutputTransport_->close(); +} diff --git a/contrib/thrift/ThriftConnection.h b/contrib/thrift/ThriftConnection.h new file mode 100644 index 0000000..394097b --- /dev/null +++ b/contrib/thrift/ThriftConnection.h @@ -0,0 +1,66 @@ +#ifndef MUDUO_CONTRIB_THRIFT_THRIFTCONNECTION_H +#define MUDUO_CONTRIB_THRIFT_THRIFTCONNECTION_H + +#include <boost/enable_shared_from_this.hpp> +#include <boost/noncopyable.hpp> + +#include "muduo/net/TcpConnection.h" + +#include <thrift/protocol/TProtocol.h> +#include <thrift/transport/TBufferTransports.h> +#include <thrift/transport/TTransportUtils.h> + +using apache::thrift::TProcessor; +using apache::thrift::protocol::TProtocol; +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::transport::TNullTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; + +class ThriftServer; + +class ThriftConnection : boost::noncopyable, + public boost::enable_shared_from_this<ThriftConnection> +{ + public: + enum State + { + kExpectFrameSize, + kExpectFrame + }; + + ThriftConnection(ThriftServer* server, const muduo::net::TcpConnectionPtr& conn); + + private: + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buffer, + muduo::Timestamp receiveTime); + + void process(); + + void close(); + + private: + ThriftServer* server_; + muduo::net::TcpConnectionPtr conn_; + + boost::shared_ptr<TNullTransport> nullTransport_; + + boost::shared_ptr<TMemoryBuffer> inputTransport_; + boost::shared_ptr<TMemoryBuffer> outputTransport_; + + boost::shared_ptr<TTransport> factoryInputTransport_; + boost::shared_ptr<TTransport> factoryOutputTransport_; + + boost::shared_ptr<TProtocol> inputProtocol_; + boost::shared_ptr<TProtocol> outputProtocol_; + + boost::shared_ptr<TProcessor> processor_; + + enum State state_; + uint32_t frameSize_; +}; + +typedef boost::shared_ptr<ThriftConnection> ThriftConnectionPtr; + +#endif // MUDUO_CONTRIB_THRIFT_THRIFTCONNECTION_H diff --git a/contrib/thrift/ThriftServer.cc b/contrib/thrift/ThriftServer.cc new file mode 100644 index 0000000..672a308 --- /dev/null +++ b/contrib/thrift/ThriftServer.cc @@ -0,0 +1,51 @@ +#include "contrib/thrift/ThriftServer.h" + +#include <boost/bind.hpp> + +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +ThriftServer::~ThriftServer() = default; + +void ThriftServer::serve() +{ + start(); +} + +void ThriftServer::start() +{ + if (numWorkerThreads_ > 0) + { + workerThreadPool_.start(numWorkerThreads_); + } + server_.start(); +} + +void ThriftServer::stop() +{ + if (numWorkerThreads_ > 0) + { + workerThreadPool_.stop(); + } + server_.getLoop()->runAfter(3.0, boost::bind(&EventLoop::quit, + server_.getLoop())); +} + +void ThriftServer::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + ThriftConnectionPtr ptr(new ThriftConnection(this, conn)); + MutexLockGuard lock(mutex_); + assert(conns_.find(conn->name()) == conns_.end()); + conns_[conn->name()] = ptr; + } + else + { + MutexLockGuard lock(mutex_); + assert(conns_.find(conn->name()) != conns_.end()); + conns_.erase(conn->name()); + } +} diff --git a/contrib/thrift/ThriftServer.h b/contrib/thrift/ThriftServer.h new file mode 100644 index 0000000..c90c99e --- /dev/null +++ b/contrib/thrift/ThriftServer.h @@ -0,0 +1,222 @@ +#ifndef MUDUO_CONTRIB_THRIFT_THRIFTSERVER_H +#define MUDUO_CONTRIB_THRIFT_THRIFTSERVER_H + +#include <map> + +#include <boost/bind.hpp> +#include <boost/noncopyable.hpp> + +#include "muduo/base/ThreadPool.h" +#include "muduo/net/TcpServer.h" + +#include <thrift/server/TServer.h> + +#include "contrib/thrift/ThriftConnection.h" + +using apache::thrift::TProcessor; +using apache::thrift::TProcessorFactory; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::server::TServer; +using apache::thrift::transport::TTransportFactory; + +class ThriftServer : boost::noncopyable, + public TServer +{ + public: + template <typename ProcessorFactory> + ThriftServer(const boost::shared_ptr<ProcessorFactory>& processorFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(ProcessorFactory, TProcessorFactory)) + : TServer(processorFactory), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + } + + template <typename Processor> + ThriftServer(const boost::shared_ptr<Processor>& processor, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(Processor, TProcessor)) + : TServer(processor), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + } + + template <typename ProcessorFactory> + ThriftServer(const boost::shared_ptr<ProcessorFactory>& processorFactory, + const boost::shared_ptr<TProtocolFactory>& protocolFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(ProcessorFactory, TProcessorFactory)) + : TServer(processorFactory), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + setInputProtocolFactory(protocolFactory); + setOutputProtocolFactory(protocolFactory); + } + + template <typename Processor> + ThriftServer(const boost::shared_ptr<Processor>& processor, + const boost::shared_ptr<TProtocolFactory>& protocolFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(Processor, TProcessor)) + : TServer(processor), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + setInputProtocolFactory(protocolFactory); + setOutputProtocolFactory(protocolFactory); + } + + template <typename ProcessorFactory> + ThriftServer(const boost::shared_ptr<ProcessorFactory>& processorFactory, + const boost::shared_ptr<TTransportFactory>& transportFactory, + const boost::shared_ptr<TProtocolFactory>& protocolFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(ProcessorFactory, TProcessorFactory)) + : TServer(processorFactory), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + setInputTransportFactory(transportFactory); + setOutputTransportFactory(transportFactory); + setInputProtocolFactory(protocolFactory); + setOutputProtocolFactory(protocolFactory); + } + + template <typename Processor> + ThriftServer(const boost::shared_ptr<Processor>& processor, + const boost::shared_ptr<TTransportFactory>& transportFactory, + const boost::shared_ptr<TProtocolFactory>& protocolFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(Processor, TProcessor)) + : TServer(processor), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + setInputTransportFactory(transportFactory); + setOutputTransportFactory(transportFactory); + setInputProtocolFactory(protocolFactory); + setOutputProtocolFactory(protocolFactory); + } + + template <typename ProcessorFactory> + ThriftServer(const boost::shared_ptr<ProcessorFactory>& processorFactory, + const boost::shared_ptr<TTransportFactory>& inputTransportFactory, + const boost::shared_ptr<TTransportFactory>& outputTransportFactory, + const boost::shared_ptr<TProtocolFactory>& inputProtocolFactory, + const boost::shared_ptr<TProtocolFactory>& outputProtocolFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(ProcessorFactory, TProcessorFactory)) + : TServer(processorFactory), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + setInputTransportFactory(inputTransportFactory); + setOutputTransportFactory(outputTransportFactory); + setInputProtocolFactory(inputProtocolFactory); + setOutputProtocolFactory(outputProtocolFactory); + } + + template <typename Processor> + ThriftServer(const boost::shared_ptr<Processor>& processor, + const boost::shared_ptr<TTransportFactory>& inputTransportFactory, + const boost::shared_ptr<TTransportFactory>& outputTransportFactory, + const boost::shared_ptr<TProtocolFactory>& inputProtocolFactory, + const boost::shared_ptr<TProtocolFactory>& outputProtocolFactory, + muduo::net::EventLoop* eventloop, + const muduo::net::InetAddress& addr, + const muduo::string& name, + THRIFT_OVERLOAD_IF(Processor, TProcessor)) + : TServer(processor), + server_(eventloop, addr, name), + numWorkerThreads_(0), + workerThreadPool_(name + muduo::string("WorkerThreadPool")) + { + server_.setConnectionCallback(boost::bind(&ThriftServer::onConnection, + this, _1)); + setInputTransportFactory(inputTransportFactory); + setOutputTransportFactory(outputTransportFactory); + setInputProtocolFactory(inputProtocolFactory); + setOutputProtocolFactory(outputProtocolFactory); + } + + virtual ~ThriftServer(); + + void serve(); + + void start(); + + void stop(); + + muduo::ThreadPool& workerThreadPool() + { + return workerThreadPool_; + } + + bool isWorkerThreadPoolProcessing() const + { + return numWorkerThreads_ != 0; + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void setWorkerThreadNum(int numWorkerThreads) + { + assert(numWorkerThreads > 0); + numWorkerThreads_ = numWorkerThreads; + } + + private: + friend class ThriftConnection; + + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + private: + muduo::net::TcpServer server_; + int numWorkerThreads_; + muduo::ThreadPool workerThreadPool_; + muduo::MutexLock mutex_; + std::map<muduo::string, ThriftConnectionPtr> conns_; +}; + +#endif // MUDUO_CONTRIB_THRIFT_THRIFTSERVER_H diff --git a/contrib/thrift/tests/.gitignore b/contrib/thrift/tests/.gitignore new file mode 100644 index 0000000..159151f --- /dev/null +++ b/contrib/thrift/tests/.gitignore @@ -0,0 +1,2 @@ +gen-cpp +gen-py diff --git a/contrib/thrift/tests/echo/EchoServer.cc b/contrib/thrift/tests/echo/EchoServer.cc new file mode 100644 index 0000000..568c3dc --- /dev/null +++ b/contrib/thrift/tests/echo/EchoServer.cc @@ -0,0 +1,51 @@ +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include "ThriftServer.h" + +#include "Echo.h" + +using namespace muduo; +using namespace muduo::net; + +using namespace echo; + +class EchoHandler : virtual public EchoIf +{ + public: + EchoHandler() + { + } + + void echo(std::string& str, const std::string& s) + { + LOG_INFO << "EchoHandler::echo:" << s; + str = s; + } + +}; + +int NumCPU() +{ + return static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN)); +} + +int main(int argc, char **argv) +{ + EventLoop eventloop; + InetAddress addr("127.0.0.1", 9090); + string name("EchoServer"); + + boost::shared_ptr<EchoHandler> handler(new EchoHandler()); + boost::shared_ptr<TProcessor> processor(new EchoProcessor(handler)); + + ThriftServer server(processor, &eventloop, addr, name); + server.setWorkerThreadNum(NumCPU() * 2); + server.start(); + eventloop.loop(); + + return 0; +} + diff --git a/contrib/thrift/tests/echo/echo.thrift b/contrib/thrift/tests/echo/echo.thrift new file mode 100644 index 0000000..af714d5 --- /dev/null +++ b/contrib/thrift/tests/echo/echo.thrift @@ -0,0 +1,8 @@ +namespace cpp echo +namespace py echo + +service Echo +{ + string echo(1: string arg); +} + diff --git a/contrib/thrift/tests/echo/echoclient.py b/contrib/thrift/tests/echo/echoclient.py new file mode 100644 index 0000000..e7b3b00 --- /dev/null +++ b/contrib/thrift/tests/echo/echoclient.py @@ -0,0 +1,28 @@ +import sys +sys.path.append('gen-py') + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol + +from echo import Echo + + +def echo(s): + transport = TSocket.TSocket('127.0.0.1', 9090) + tranport = TTransport.TFramedTransport(transport) + protocol = TBinaryProtocol.TBinaryProtocol(tranport) + client = Echo.Client(protocol) + tranport.open() + s = client.echo(s) + tranport.close() + + return s + + +def main(): + print(echo('42')) + + +if __name__ == '__main__': + main() diff --git a/contrib/thrift/tests/ping/PingServer.cc b/contrib/thrift/tests/ping/PingServer.cc new file mode 100644 index 0000000..88a2a8f --- /dev/null +++ b/contrib/thrift/tests/ping/PingServer.cc @@ -0,0 +1,47 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <thrift/protocol/TCompactProtocol.h> + +#include "ThriftServer.h" + +#include "Ping.h" + +using namespace muduo; +using namespace muduo::net; + +using apache::thrift::protocol::TCompactProtocolFactory; + +using namespace ping; + +class PingHandler : virtual public PingIf +{ + public: + PingHandler() + { + } + + void ping() + { + LOG_INFO << "ping"; + } + +}; + +int main(int argc, char **argv) +{ + EventLoop eventloop; + InetAddress addr("127.0.0.1", 9090); + string name("PingServer"); + + boost::shared_ptr<PingHandler> handler(new PingHandler()); + boost::shared_ptr<TProcessor> processor(new PingProcessor(handler)); + boost::shared_ptr<TProtocolFactory> protcolFactory(new TCompactProtocolFactory()); + + ThriftServer server(processor, protcolFactory, &eventloop, addr, name); + server.start(); + eventloop.loop(); + + return 0; +} + diff --git a/contrib/thrift/tests/ping/ping.thrift b/contrib/thrift/tests/ping/ping.thrift new file mode 100644 index 0000000..e38a0e8 --- /dev/null +++ b/contrib/thrift/tests/ping/ping.thrift @@ -0,0 +1,8 @@ +namespace cpp ping +namespace py ping + +service Ping +{ + void ping(); +} + diff --git a/contrib/thrift/tests/ping/pingclient.py b/contrib/thrift/tests/ping/pingclient.py new file mode 100644 index 0000000..166bc48 --- /dev/null +++ b/contrib/thrift/tests/ping/pingclient.py @@ -0,0 +1,26 @@ +import sys +sys.path.append('gen-py') + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TCompactProtocol + +from ping import Ping + + +def ping(): + transport = TSocket.TSocket('127.0.0.1', 9090) + tranport = TTransport.TFramedTransport(transport) + protocol = TCompactProtocol.TCompactProtocol(tranport) + client = Ping.Client(protocol) + tranport.open() + client.ping() + tranport.close() + + +def main(): + ping() + + +if __name__ == '__main__': + main() diff --git a/examples/ace/logging/client.cc b/examples/ace/logging/client.cc new file mode 100644 index 0000000..3cdd5dc --- /dev/null +++ b/examples/ace/logging/client.cc @@ -0,0 +1,141 @@ +#include "examples/ace/logging/logrecord.pb.h" + +#include "muduo/base/Mutex.h" +#include "muduo/base/Logging.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/protobuf/ProtobufCodecLite.h" + +#include <iostream> +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +// just to verify the protocol, not for practical usage. + +namespace logging +{ +extern const char logtag[] = "LOG0"; +typedef ProtobufCodecLiteT<LogRecord, logtag> Codec; + +// same as asio/char/client.cc +class LogClient : noncopyable +{ + public: + LogClient(EventLoop* loop, const InetAddress& serverAddr) + : client_(loop, serverAddr, "LogClient"), + codec_(std::bind(&LogClient::onMessage, this, _1, _2, _3)) + { + client_.setConnectionCallback( + std::bind(&LogClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&Codec::onMessage, &codec_, _1, _2, _3)); + client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + void disconnect() + { + client_.disconnect(); + } + + void write(const StringPiece& message) + { + MutexLockGuard lock(mutex_); + updateLogRecord(message); + if (connection_) + { + codec_.send(connection_, logRecord_); + } + else + { + LOG_WARN << "NOT CONNECTED"; + } + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + MutexLockGuard lock(mutex_); + if (conn->connected()) + { + connection_ = conn; + LogRecord_Heartbeat* hb = logRecord_.mutable_heartbeat(); + hb->set_hostname(ProcessInfo::hostname().c_str()); + hb->set_process_name(ProcessInfo::procname().c_str()); + hb->set_process_id(ProcessInfo::pid()); + hb->set_process_start_time(ProcessInfo::startTime().microSecondsSinceEpoch()); + hb->set_username(ProcessInfo::username().c_str()); + updateLogRecord("Heartbeat"); + codec_.send(connection_, logRecord_); + logRecord_.clear_heartbeat(); + LOG_INFO << "Type message below:"; + } + else + { + connection_.reset(); + } + } + + void onMessage(const TcpConnectionPtr&, + const MessagePtr& message, + Timestamp) + { + // SHOULD NOT HAPPEN + LogRecord* logRecord = muduo::down_cast<LogRecord*>(message.get()); + LOG_WARN << logRecord->DebugString(); + } + + void updateLogRecord(const StringPiece& message) REQUIRES(mutex_) + { + mutex_.assertLocked(); + logRecord_.set_level(1); + logRecord_.set_thread_id(CurrentThread::tid()); + logRecord_.set_timestamp(Timestamp::now().microSecondsSinceEpoch()); + logRecord_.set_message(message.data(), message.size()); + } + + TcpClient client_; + Codec codec_; + MutexLock mutex_; + LogRecord logRecord_ GUARDED_BY(mutex_); + TcpConnectionPtr connection_ GUARDED_BY(mutex_); +}; + +} // namespace logging + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + printf("usage: %s server_ip server_port\n", argv[0]); + } + else + { + EventLoopThread loopThread; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + logging::LogClient client(loopThread.startLoop(), serverAddr); + client.connect(); + std::string line; + while (std::getline(std::cin, line)) + { + client.write(line); + } + client.disconnect(); + CurrentThread::sleepUsec(1000*1000); // wait for disconnect, then safe to destruct LogClient (esp. TcpClient). Otherwise mutex_ is used after dtor. + } + google::protobuf::ShutdownProtobufLibrary(); +} diff --git a/examples/ace/logging/logrecord.proto b/examples/ace/logging/logrecord.proto new file mode 100644 index 0000000..2000905 --- /dev/null +++ b/examples/ace/logging/logrecord.proto @@ -0,0 +1,29 @@ +package logging; + +message LogRecord { + // must present in first message + message Heartbeat { + required string hostname = 1; + required string process_name = 2; + required int32 process_id = 3; + required int64 process_start_time = 4; // microseconds sinch epoch + required string username = 5; + } + + optional Heartbeat heartbeat = 1; + // muduo/base/Logging.h + // enum LogLevel + // { + // TRACE, // 0 + // DEBUG, // 1 + // INFO, // 2 + // WARN, // 3 + // ERROR, // 4 + // FATAL, // 5 + // }; + required int32 level = 2; + required int32 thread_id = 3; + required int64 timestamp = 4; // microseconds sinch epoch + required string message = 5; + // optional: source file, source line, function name +} diff --git a/examples/ace/logging/server.cc b/examples/ace/logging/server.cc new file mode 100644 index 0000000..3b21fae --- /dev/null +++ b/examples/ace/logging/server.cc @@ -0,0 +1,131 @@ +#include "examples/ace/logging/logrecord.pb.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" +#include "muduo/net/protobuf/ProtobufCodecLite.h" + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +namespace logging +{ +extern const char logtag[] = "LOG0"; +typedef ProtobufCodecLiteT<LogRecord, logtag> Codec; + +class Session : noncopyable +{ + public: + explicit Session(const TcpConnectionPtr& conn) + : codec_(std::bind(&Session::onMessage, this, _1, _2, _3)), + file_(getFileName(conn)) + { + conn->setMessageCallback( + std::bind(&Codec::onMessage, &codec_, _1, _2, _3)); + } + + private: + + // FIXME: duplicate code LogFile + // or use LogFile instead + string getFileName(const TcpConnectionPtr& conn) + { + string filename; + filename += conn->peerAddress().toIp(); + + char timebuf[32]; + struct tm tm; + time_t now = time(NULL); + gmtime_r(&now, &tm); // FIXME: localtime_r ? + strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm); + filename += timebuf; + + char buf[32]; + snprintf(buf, sizeof buf, "%d", globalCount_.incrementAndGet()); + filename += buf; + + filename += ".log"; + LOG_INFO << "Session of " << conn->name() << " file " << filename; + return filename; + } + + void onMessage(const TcpConnectionPtr& conn, + const MessagePtr& message, + Timestamp time) + { + LogRecord* logRecord = muduo::down_cast<LogRecord*>(message.get()); + if (logRecord->has_heartbeat()) + { + // FIXME ? + } + const char* sep = "==========\n"; + std::string str = logRecord->DebugString(); + file_.append(str.c_str(), str.size()); + file_.append(sep, strlen(sep)); + LOG_DEBUG << str; + } + + Codec codec_; + FileUtil::AppendFile file_; + static AtomicInt32 globalCount_; +}; +typedef std::shared_ptr<Session> SessionPtr; + +AtomicInt32 Session::globalCount_; + +class LogServer : noncopyable +{ + public: + LogServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads) + : loop_(loop), + server_(loop_, listenAddr, "AceLoggingServer") + { + server_.setConnectionCallback( + std::bind(&LogServer::onConnection, this, _1)); + if (numThreads > 1) + { + server_.setThreadNum(numThreads); + } + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + SessionPtr session(new Session(conn)); + conn->setContext(session); + } + else + { + conn->setContext(SessionPtr()); + } + } + + EventLoop* loop_; + TcpServer server_; +}; + +} // namespace logging + +int main(int argc, char* argv[]) +{ + EventLoop loop; + int port = argc > 1 ? atoi(argv[1]) : 50000; + LOG_INFO << "Listen on port " << port; + InetAddress listenAddr(static_cast<uint16_t>(port)); + int numThreads = argc > 2 ? atoi(argv[2]) : 1; + logging::LogServer server(&loop, listenAddr, numThreads); + server.start(); + loop.loop(); + +} diff --git a/examples/ace/ttcp/common.cc b/examples/ace/ttcp/common.cc new file mode 100644 index 0000000..dc5e6c7 --- /dev/null +++ b/examples/ace/ttcp/common.cc @@ -0,0 +1,76 @@ +#include "examples/ace/ttcp/common.h" +#include "muduo/base/Types.h" + +#include <boost/program_options.hpp> + +#include <iostream> + +#include <netdb.h> +#include <stdio.h> + +namespace po = boost::program_options; + +bool parseCommandLine(int argc, char* argv[], Options* opt) +{ + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "Help") + ("port,p", po::value<uint16_t>(&opt->port)->default_value(5001), "TCP port") + ("length,l", po::value<int>(&opt->length)->default_value(65536), "Buffer length") + ("number,n", po::value<int>(&opt->number)->default_value(8192), "Number of buffers") + ("trans,t", po::value<std::string>(&opt->host), "Transmit") + ("recv,r", "Receive") + ("nodelay,D", "set TCP_NODELAY") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + opt->transmit = vm.count("trans"); + opt->receive = vm.count("recv"); + opt->nodelay = vm.count("nodelay"); + if (vm.count("help")) + { + std::cout << desc << std::endl; + return false; + } + + if (opt->transmit == opt->receive) + { + printf("either -t or -r must be specified.\n"); + return false; + } + + printf("port = %d\n", opt->port); + if (opt->transmit) + { + printf("buffer length = %d\n", opt->length); + printf("number of buffers = %d\n", opt->number); + } + else + { + printf("accepting...\n"); + } + return true; +} + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +struct sockaddr_in resolveOrDie(const char* host, uint16_t port) +{ + struct hostent* he = ::gethostbyname(host); + if (!he) + { + perror("gethostbyname"); + exit(1); + } + assert(he->h_addrtype == AF_INET && he->h_length == sizeof(uint32_t)); + struct sockaddr_in addr; + muduo::memZero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr = *reinterpret_cast<struct in_addr*>(he->h_addr); + return addr; +} + diff --git a/examples/ace/ttcp/common.h b/examples/ace/ttcp/common.h new file mode 100644 index 0000000..ab76428 --- /dev/null +++ b/examples/ace/ttcp/common.h @@ -0,0 +1,37 @@ +#pragma once + +#include <string> +#include <stdint.h> + +struct Options +{ + uint16_t port; + int length; + int number; + bool transmit, receive, nodelay; + std::string host; + Options() + : port(0), length(0), number(0), + transmit(false), receive(false), nodelay(false) + { + } +}; + +bool parseCommandLine(int argc, char* argv[], Options* opt); +struct sockaddr_in resolveOrDie(const char* host, uint16_t port); + +struct SessionMessage +{ + int32_t number; + int32_t length; +} __attribute__ ((__packed__)); + +struct PayloadMessage +{ + int32_t length; + char data[0]; +}; + +void transmit(const Options& opt); + +void receive(const Options& opt); diff --git a/examples/ace/ttcp/main.cc b/examples/ace/ttcp/main.cc new file mode 100644 index 0000000..08dabf3 --- /dev/null +++ b/examples/ace/ttcp/main.cc @@ -0,0 +1,24 @@ +#include "examples/ace/ttcp/common.h" + +#include <assert.h> + +int main(int argc, char* argv[]) +{ + Options options; + if (parseCommandLine(argc, argv, &options)) + { + if (options.transmit) + { + transmit(options); + } + else if (options.receive) + { + receive(options); + } + else + { + assert(0); + } + } +} + diff --git a/examples/ace/ttcp/ttcp.cc b/examples/ace/ttcp/ttcp.cc new file mode 100644 index 0000000..97f839b --- /dev/null +++ b/examples/ace/ttcp/ttcp.cc @@ -0,0 +1,214 @@ +#include "examples/ace/ttcp/common.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +EventLoop* g_loop; + +struct Context +{ + int count; + int64_t bytes; + SessionMessage session; + Buffer output; + + Context() + : count(0), + bytes(0) + { + session.number = 0; + session.length = 0; + } +}; + +///////////////////////////////////////////////////////////////////// +// T R A N S M I T +///////////////////////////////////////////////////////////////////// + +namespace trans +{ + +void onConnection(const Options& opt, const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + printf("connected\n"); + Context context; + context.count = 1; + context.bytes = opt.length; + context.session.number = opt.number; + context.session.length = opt.length; + context.output.appendInt32(opt.length); + context.output.ensureWritableBytes(opt.length); + for (int i = 0; i < opt.length; ++i) + { + context.output.beginWrite()[i] = "0123456789ABCDEF"[i % 16]; + } + context.output.hasWritten(opt.length); + conn->setContext(context); + + SessionMessage sessionMessage = { 0, 0 }; + sessionMessage.number = htonl(opt.number); + sessionMessage.length = htonl(opt.length); + conn->send(&sessionMessage, sizeof(sessionMessage)); + + conn->send(context.output.toStringPiece()); + } + else + { + const Context& context = boost::any_cast<Context>(conn->getContext()); + LOG_INFO << "payload bytes " << context.bytes; + conn->getLoop()->quit(); + } +} + +void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) +{ + Context* context = boost::any_cast<Context>(conn->getMutableContext()); + while (buf->readableBytes() >= sizeof(int32_t)) + { + int32_t length = buf->readInt32(); + if (length == context->session.length) + { + if (context->count < context->session.number) + { + conn->send(context->output.toStringPiece()); + ++context->count; + context->bytes += length; + } + else + { + conn->shutdown(); + break; + } + } + else + { + conn->shutdown(); + break; + } + } +} + +} // namespace trans + +void transmit(const Options& opt) +{ + InetAddress addr(opt.port); + if (!InetAddress::resolve(opt.host, &addr)) + { + LOG_FATAL << "Unable to resolve " << opt.host; + } + muduo::Timestamp start(muduo::Timestamp::now()); + EventLoop loop; + g_loop = &loop; + TcpClient client(&loop, addr, "TtcpClient"); + client.setConnectionCallback( + std::bind(&trans::onConnection, opt, _1)); + client.setMessageCallback( + std::bind(&trans::onMessage, _1, _2, _3)); + client.connect(); + loop.loop(); + double elapsed = timeDifference(muduo::Timestamp::now(), start); + double total_mb = 1.0 * opt.length * opt.number / 1024 / 1024; + printf("%.3f MiB transferred\n%.3f MiB/s\n", total_mb, total_mb / elapsed); +} + +///////////////////////////////////////////////////////////////////// +// R E C E I V E +///////////////////////////////////////////////////////////////////// + +namespace receiving +{ + +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + Context context; + conn->setContext(context); + } + else + { + const Context& context = boost::any_cast<Context>(conn->getContext()); + LOG_INFO << "payload bytes " << context.bytes; + conn->getLoop()->quit(); + } +} + +void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) +{ + while (buf->readableBytes() >= sizeof(int32_t)) + { + Context* context = boost::any_cast<Context>(conn->getMutableContext()); + SessionMessage& session = context->session; + if (session.number == 0 && session.length == 0) + { + if (buf->readableBytes() >= sizeof(SessionMessage)) + { + session.number = buf->readInt32(); + session.length = buf->readInt32(); + context->output.appendInt32(session.length); + printf("receive number = %d\nreceive length = %d\n", + session.number, session.length); + } + else + { + break; + } + } + else + { + const unsigned total_len = session.length + static_cast<int>(sizeof(int32_t)); + const int32_t length = buf->peekInt32(); + if (length == session.length) + { + if (buf->readableBytes() >= total_len) + { + buf->retrieve(total_len); + conn->send(context->output.toStringPiece()); + ++context->count; + context->bytes += length; + if (context->count >= session.number) + { + conn->shutdown(); + break; + } + } + else + { + break; + } + } + else + { + printf("wrong length %d\n", length); + conn->shutdown(); + break; + } + } + } +} + +} // namespace receiving + +void receive(const Options& opt) +{ + EventLoop loop; + g_loop = &loop; + InetAddress listenAddr(opt.port); + TcpServer server(&loop, listenAddr, "TtcpReceive"); + server.setConnectionCallback( + std::bind(&receiving::onConnection, _1)); + server.setMessageCallback( + std::bind(&receiving::onMessage, _1, _2, _3)); + server.start(); + loop.loop(); +} diff --git a/examples/ace/ttcp/ttcp_asio_async.cc b/examples/ace/ttcp/ttcp_asio_async.cc new file mode 100644 index 0000000..ffa138b --- /dev/null +++ b/examples/ace/ttcp/ttcp_asio_async.cc @@ -0,0 +1,183 @@ +#include "examples/ace/ttcp/common.h" + +#include "muduo/base/Logging.h" +#include <boost/asio.hpp> +#include <stdio.h> + +using boost::asio::ip::tcp; + +void transmit(const Options& opt) +{ + try + { + } + catch (std::exception& e) + { + LOG_ERROR << e.what(); + } +} + +class TtcpServerConnection : public std::enable_shared_from_this<TtcpServerConnection>, + muduo::noncopyable +{ + public: +#if BOOST_VERSION < 107000L + TtcpServerConnection(boost::asio::io_service& io_service) + : socket_(io_service), count_(0), payload_(NULL), ack_(0) +#else + TtcpServerConnection(const boost::asio::executor& executor) + : socket_(executor), count_(0), payload_(NULL), ack_(0) +#endif + { + sessionMessage_.number = 0; + sessionMessage_.length = 0; + } + + ~TtcpServerConnection() + { + ::free(payload_); + } + + tcp::socket& socket() { return socket_; } + + void start() + { + std::ostringstream oss; + oss << socket_.remote_endpoint(); + LOG_INFO << "Got connection from " << oss.str(); + doReadSession(); + } + + private: + void doReadSession() + { + auto self(shared_from_this()); + boost::asio::async_read( + socket_, boost::asio::buffer(&sessionMessage_, sizeof(sessionMessage_)), + [this, self](const boost::system::error_code& error, size_t len) + { + if (!error && len == sizeof sessionMessage_) + { + sessionMessage_.number = ntohl(sessionMessage_.number); + sessionMessage_.length = ntohl(sessionMessage_.length); + printf("receive number = %d\nreceive length = %d\n", + sessionMessage_.number, sessionMessage_.length); + const int total_len = static_cast<int>(sizeof(int32_t) + sessionMessage_.length); + payload_ = static_cast<PayloadMessage*>(::malloc(total_len)); + doReadLength(); + } + else + { + LOG_ERROR << "read session message: " << error.message(); + } + }); + } + + void doReadLength() + { + auto self(shared_from_this()); + payload_->length = 0; + boost::asio::async_read( + socket_, boost::asio::buffer(&payload_->length, sizeof payload_->length), + [this, self](const boost::system::error_code& error, size_t len) + { + if (!error && len == sizeof payload_->length) + { + payload_->length = ntohl(payload_->length); + doReadPayload(); + } + else + { + LOG_ERROR << "read length: " << error.message(); + } + }); + } + + void doReadPayload() + { + assert(payload_->length == sessionMessage_.length); + auto self(shared_from_this()); + boost::asio::async_read( + socket_, boost::asio::buffer(&payload_->data, payload_->length), + [this, self](const boost::system::error_code& error, size_t len) + { + if (!error && len == static_cast<size_t>(payload_->length)) + { + doWriteAck(); + } + else + { + LOG_ERROR << "read payload data: " << error.message(); + } + }); + } + + void doWriteAck() + { + auto self(shared_from_this()); + ack_ = htonl(payload_->length); + boost::asio::async_write( + socket_, boost::asio::buffer(&ack_, sizeof ack_), + [this, self](const boost::system::error_code& error, size_t len) + { + if (!error && len == sizeof ack_) + { + if (++count_ < sessionMessage_.number) + { + doReadLength(); + } + else + { + LOG_INFO << "Done"; + } + } + else + { + LOG_ERROR << "write ack: " << error.message(); + } + }); + } + + tcp::socket socket_; + int count_; + struct SessionMessage sessionMessage_; + struct PayloadMessage* payload_; + int32_t ack_; +}; +typedef std::shared_ptr<TtcpServerConnection> TtcpServerConnectionPtr; + +void doAccept(tcp::acceptor& acceptor) +{ +#if BOOST_VERSION < 107000L + // no need to pre-create new_connection if we use asio 1.12 or boost 1.66+ + TtcpServerConnectionPtr new_connection(new TtcpServerConnection(acceptor.get_io_service())); +#else + TtcpServerConnectionPtr new_connection(new TtcpServerConnection(acceptor.get_executor())); +#endif + acceptor.async_accept( + new_connection->socket(), + [&acceptor, new_connection](boost::system::error_code error) // move new_connection in C++14 + { + if (!error) + { + new_connection->start(); + } + doAccept(acceptor); + }); +} + +void receive(const Options& opt) +{ + try + { + boost::asio::io_service io_service; + tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), opt.port)); + doAccept(acceptor); + io_service.run(); + } + catch (std::exception& e) + { + LOG_ERROR << e.what(); + } +} + diff --git a/examples/ace/ttcp/ttcp_asio_sync.cc b/examples/ace/ttcp/ttcp_asio_sync.cc new file mode 100644 index 0000000..708d34f --- /dev/null +++ b/examples/ace/ttcp/ttcp_asio_sync.cc @@ -0,0 +1,86 @@ +#include "examples/ace/ttcp/common.h" + +#include "muduo/base/Logging.h" +#include <boost/asio.hpp> +#include <stdio.h> + +using boost::asio::ip::tcp; + +void transmit(const Options& opt) +{ + try + { + } + catch (std::exception& e) + { + LOG_ERROR << e.what(); + } +} + +void receive(const Options& opt) +{ + try + { + boost::asio::io_service io_service; + tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), opt.port)); + tcp::socket socket(io_service); + acceptor.accept(socket); + + struct SessionMessage sessionMessage = { 0, 0 }; + boost::system::error_code error; + size_t nr = boost::asio::read(socket, boost::asio::buffer(&sessionMessage, sizeof sessionMessage), +#if BOOST_VERSION < 104700L + boost::asio::transfer_all(), +#endif + error); + if (nr != sizeof sessionMessage) + { + LOG_ERROR << "read session message: " << error.message(); + exit(1); + } + + sessionMessage.number = ntohl(sessionMessage.number); + sessionMessage.length = ntohl(sessionMessage.length); + printf("receive number = %d\nreceive length = %d\n", + sessionMessage.number, sessionMessage.length); + const int total_len = static_cast<int>(sizeof(int32_t) + sessionMessage.length); + PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len)); + std::unique_ptr<PayloadMessage, void (*)(void*)> freeIt(payload, ::free); + assert(payload); + + for (int i = 0; i < sessionMessage.number; ++i) + { + payload->length = 0; + if (boost::asio::read(socket, boost::asio::buffer(&payload->length, sizeof(payload->length)), +#if BOOST_VERSION < 104700L + boost::asio::transfer_all(), +#endif + error) != sizeof(payload->length)) + { + LOG_ERROR << "read length: " << error.message(); + exit(1); + } + payload->length = ntohl(payload->length); + assert(payload->length == sessionMessage.length); + if (boost::asio::read(socket, boost::asio::buffer(payload->data, payload->length), +#if BOOST_VERSION < 104700L + boost::asio::transfer_all(), +#endif + error) != static_cast<size_t>(payload->length)) + { + LOG_ERROR << "read payload data: " << error.message(); + exit(1); + } + int32_t ack = htonl(payload->length); + if (boost::asio::write(socket, boost::asio::buffer(&ack, sizeof(ack))) != sizeof(ack)) + { + LOG_ERROR << "write ack: " << error.message(); + exit(1); + } + } + } + catch (std::exception& e) + { + LOG_ERROR << e.what(); + } +} diff --git a/examples/ace/ttcp/ttcp_blocking.cc b/examples/ace/ttcp/ttcp_blocking.cc new file mode 100644 index 0000000..72c9e46 --- /dev/null +++ b/examples/ace/ttcp/ttcp_blocking.cc @@ -0,0 +1,204 @@ +#include "examples/ace/ttcp/common.h" +#include "muduo/base/Timestamp.h" +#include "muduo/base/Types.h" + +#undef NDEBUG + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +static int acceptOrDie(uint16_t port) +{ + int listenfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert(listenfd >= 0); + + int yes = 1; + if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) + { + perror("setsockopt"); + exit(1); + } + + struct sockaddr_in addr; + muduo::memZero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + if (bind(listenfd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))) + { + perror("bind"); + exit(1); + } + + if (listen(listenfd, 5)) + { + perror("listen"); + exit(1); + } + + struct sockaddr_in peer_addr; + muduo::memZero(&peer_addr, sizeof(peer_addr)); + socklen_t addrlen = 0; + int sockfd = ::accept(listenfd, reinterpret_cast<struct sockaddr*>(&peer_addr), &addrlen); + if (sockfd < 0) + { + perror("accept"); + exit(1); + } + ::close(listenfd); + return sockfd; +} + +static int write_n(int sockfd, const void* buf, int length) +{ + int written = 0; + while (written < length) + { + ssize_t nw = ::write(sockfd, static_cast<const char*>(buf) + written, length - written); + if (nw > 0) + { + written += static_cast<int>(nw); + } + else if (nw == 0) + { + break; // EOF + } + else if (errno != EINTR) + { + perror("write"); + break; + } + } + return written; +} + +static int read_n(int sockfd, void* buf, int length) +{ + int nread = 0; + while (nread < length) + { + ssize_t nr = ::read(sockfd, static_cast<char*>(buf) + nread, length - nread); + if (nr > 0) + { + nread += static_cast<int>(nr); + } + else if (nr == 0) + { + break; // EOF + } + else if (errno != EINTR) + { + perror("read"); + break; + } + } + return nread; +} + +void transmit(const Options& opt) +{ + struct sockaddr_in addr = resolveOrDie(opt.host.c_str(), opt.port); + printf("connecting to %s:%d\n", inet_ntoa(addr.sin_addr), opt.port); + + int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert(sockfd >= 0); + int ret = ::connect(sockfd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)); + if (ret) + { + perror("connect"); + printf("Unable to connect %s\n", opt.host.c_str()); + ::close(sockfd); + return; + } + + printf("connected\n"); + muduo::Timestamp start(muduo::Timestamp::now()); + struct SessionMessage sessionMessage = { 0, 0 }; + sessionMessage.number = htonl(opt.number); + sessionMessage.length = htonl(opt.length); + if (write_n(sockfd, &sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage)) + { + perror("write SessionMessage"); + exit(1); + } + + const int total_len = static_cast<int>(sizeof(int32_t) + opt.length); + PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len)); + assert(payload); + payload->length = htonl(opt.length); + for (int i = 0; i < opt.length; ++i) + { + payload->data[i] = "0123456789ABCDEF"[i % 16]; + } + + double total_mb = 1.0 * opt.length * opt.number / 1024 / 1024; + printf("%.3f MiB in total\n", total_mb); + + for (int i = 0; i < opt.number; ++i) + { + int nw = write_n(sockfd, payload, total_len); + assert(nw == total_len); + + int ack = 0; + int nr = read_n(sockfd, &ack, sizeof(ack)); + assert(nr == sizeof(ack)); + ack = ntohl(ack); + assert(ack == opt.length); + } + + ::free(payload); + ::close(sockfd); + double elapsed = timeDifference(muduo::Timestamp::now(), start); + printf("%.3f seconds\n%.3f MiB/s\n", elapsed, total_mb / elapsed); +} + +void receive(const Options& opt) +{ + int sockfd = acceptOrDie(opt.port); + + struct SessionMessage sessionMessage = { 0, 0 }; + if (read_n(sockfd, &sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage)) + { + perror("read SessionMessage"); + exit(1); + } + + sessionMessage.number = ntohl(sessionMessage.number); + sessionMessage.length = ntohl(sessionMessage.length); + printf("receive number = %d\nreceive length = %d\n", + sessionMessage.number, sessionMessage.length); + const int total_len = static_cast<int>(sizeof(int32_t) + sessionMessage.length); + PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len)); + assert(payload); + + for (int i = 0; i < sessionMessage.number; ++i) + { + payload->length = 0; + if (read_n(sockfd, &payload->length, sizeof(payload->length)) != sizeof(payload->length)) + { + perror("read length"); + exit(1); + } + payload->length = ntohl(payload->length); + assert(payload->length == sessionMessage.length); + if (read_n(sockfd, payload->data, payload->length) != payload->length) + { + perror("read payload data"); + exit(1); + } + int32_t ack = htonl(payload->length); + if (write_n(sockfd, &ack, sizeof(ack)) != sizeof(ack)) + { + perror("write ack"); + exit(1); + } + } + ::free(payload); + ::close(sockfd); +} + diff --git a/examples/asio/chat/client.cc b/examples/asio/chat/client.cc new file mode 100644 index 0000000..7e5e303 --- /dev/null +++ b/examples/asio/chat/client.cc @@ -0,0 +1,103 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/TcpClient.h" + +#include <iostream> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class ChatClient : noncopyable +{ + public: + ChatClient(EventLoop* loop, const InetAddress& serverAddr) + : client_(loop, serverAddr, "ChatClient"), + codec_(std::bind(&ChatClient::onStringMessage, this, _1, _2, _3)) + { + client_.setConnectionCallback( + std::bind(&ChatClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + void disconnect() + { + client_.disconnect(); + } + + void write(const StringPiece& message) + { + MutexLockGuard lock(mutex_); + if (connection_) + { + codec_.send(get_pointer(connection_), message); + } + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + MutexLockGuard lock(mutex_); + if (conn->connected()) + { + connection_ = conn; + } + else + { + connection_.reset(); + } + } + + void onStringMessage(const TcpConnectionPtr&, + const string& message, + Timestamp) + { + printf("<<< %s\n", message.c_str()); + } + + TcpClient client_; + LengthHeaderCodec codec_; + MutexLock mutex_; + TcpConnectionPtr connection_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 2) + { + EventLoopThread loopThread; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + ChatClient client(loopThread.startLoop(), serverAddr); + client.connect(); + std::string line; + while (std::getline(std::cin, line)) + { + client.write(line); + } + client.disconnect(); + CurrentThread::sleepUsec(1000*1000); // wait for disconnect, see ace/logging/client.cc + } + else + { + printf("Usage: %s host_ip port\n", argv[0]); + } +} + diff --git a/examples/asio/chat/codec.h b/examples/asio/chat/codec.h new file mode 100644 index 0000000..ca264fa --- /dev/null +++ b/examples/asio/chat/codec.h @@ -0,0 +1,68 @@ +#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H +#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H + +#include "muduo/base/Logging.h" +#include "muduo/net/Buffer.h" +#include "muduo/net/Endian.h" +#include "muduo/net/TcpConnection.h" + +class LengthHeaderCodec : muduo::noncopyable +{ + public: + typedef std::function<void (const muduo::net::TcpConnectionPtr&, + const muduo::string& message, + muduo::Timestamp)> StringMessageCallback; + + explicit LengthHeaderCodec(const StringMessageCallback& cb) + : messageCallback_(cb) + { + } + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp receiveTime) + { + while (buf->readableBytes() >= kHeaderLen) // kHeaderLen == 4 + { + // FIXME: use Buffer::peekInt32() + const void* data = buf->peek(); + int32_t be32 = *static_cast<const int32_t*>(data); // SIGBUS + const int32_t len = muduo::net::sockets::networkToHost32(be32); + if (len > 65536 || len < 0) + { + LOG_ERROR << "Invalid length " << len; + conn->shutdown(); // FIXME: disable reading + break; + } + else if (buf->readableBytes() >= len + kHeaderLen) + { + buf->retrieve(kHeaderLen); + muduo::string message(buf->peek(), len); + messageCallback_(conn, message, receiveTime); + buf->retrieve(len); + } + else + { + break; + } + } + } + + // FIXME: TcpConnectionPtr + void send(muduo::net::TcpConnection* conn, + const muduo::StringPiece& message) + { + muduo::net::Buffer buf; + buf.append(message.data(), message.size()); + int32_t len = static_cast<int32_t>(message.size()); + int32_t be32 = muduo::net::sockets::hostToNetwork32(len); + buf.prepend(&be32, sizeof be32); + conn->send(&buf); + } + + private: + StringMessageCallback messageCallback_; + const static size_t kHeaderLen = sizeof(int32_t); +}; + +#endif // MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H diff --git a/examples/asio/chat/loadtest.cc b/examples/asio/chat/loadtest.cc new file mode 100644 index 0000000..a03ccb8 --- /dev/null +++ b/examples/asio/chat/loadtest.cc @@ -0,0 +1,168 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/TcpClient.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int g_connections = 0; +AtomicInt32 g_aliveConnections; +AtomicInt32 g_messagesReceived; +Timestamp g_startTime; +std::vector<Timestamp> g_receiveTime; +EventLoop* g_loop; +std::function<void()> g_statistic; + +class ChatClient : noncopyable +{ + public: + ChatClient(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + client_(loop, serverAddr, "LoadTestClient"), + codec_(std::bind(&ChatClient::onStringMessage, this, _1, _2, _3)) + { + client_.setConnectionCallback( + std::bind(&ChatClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + void disconnect() + { + // client_.disconnect(); + } + + Timestamp receiveTime() const { return receiveTime_; } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + connection_ = conn; + if (g_aliveConnections.incrementAndGet() == g_connections) + { + LOG_INFO << "all connected"; + loop_->runAfter(10.0, std::bind(&ChatClient::send, this)); + } + } + else + { + connection_.reset(); + } + } + + void onStringMessage(const TcpConnectionPtr&, + const string& message, + Timestamp) + { + // printf("<<< %s\n", message.c_str()); + receiveTime_ = loop_->pollReturnTime(); + int received = g_messagesReceived.incrementAndGet(); + if (received == g_connections) + { + Timestamp endTime = Timestamp::now(); + LOG_INFO << "all received " << g_connections << " in " + << timeDifference(endTime, g_startTime); + g_loop->queueInLoop(g_statistic); + } + else if (received % 1000 == 0) + { + LOG_DEBUG << received; + } + } + + void send() + { + g_startTime = Timestamp::now(); + codec_.send(get_pointer(connection_), "hello"); + LOG_DEBUG << "sent"; + } + + EventLoop* loop_; + TcpClient client_; + LengthHeaderCodec codec_; + TcpConnectionPtr connection_; + Timestamp receiveTime_; +}; + +void statistic(const std::vector<std::unique_ptr<ChatClient>>& clients) +{ + LOG_INFO << "statistic " << clients.size(); + std::vector<double> seconds(clients.size()); + for (size_t i = 0; i < clients.size(); ++i) + { + seconds[i] = timeDifference(clients[i]->receiveTime(), g_startTime); + } + + std::sort(seconds.begin(), seconds.end()); + for (size_t i = 0; i < clients.size(); i += std::max(static_cast<size_t>(1), clients.size()/20)) + { + printf("%6zd%% %.6f\n", i*100/clients.size(), seconds[i]); + } + if (clients.size() >= 100) + { + printf("%6d%% %.6f\n", 99, seconds[clients.size() - clients.size()/100]); + } + printf("%6d%% %.6f\n", 100, seconds.back()); +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 3) + { + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + g_connections = atoi(argv[3]); + int threads = 0; + if (argc > 4) + { + threads = atoi(argv[4]); + } + + EventLoop loop; + g_loop = &loop; + EventLoopThreadPool loopPool(&loop, "chat-loadtest"); + loopPool.setThreadNum(threads); + loopPool.start(); + + g_receiveTime.reserve(g_connections); + std::vector<std::unique_ptr<ChatClient>> clients(g_connections); + g_statistic = std::bind(statistic, std::ref(clients)); + + for (int i = 0; i < g_connections; ++i) + { + clients[i].reset(new ChatClient(loopPool.getNextLoop(), serverAddr)); + clients[i]->connect(); + usleep(200); + } + + loop.loop(); + // client.disconnect(); + } + else + { + printf("Usage: %s host_ip port connections [threads]\n", argv[0]); + } +} + + diff --git a/examples/asio/chat/server.cc b/examples/asio/chat/server.cc new file mode 100644 index 0000000..fc321cd --- /dev/null +++ b/examples/asio/chat/server.cc @@ -0,0 +1,86 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <set> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "ChatServer"), + codec_(std::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) + { + server_.setConnectionCallback( + std::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + connections_.insert(conn); + } + else + { + connections_.erase(conn); + } + } + + void onStringMessage(const TcpConnectionPtr&, + const string& message, + Timestamp) + { + for (ConnectionList::iterator it = connections_.begin(); + it != connections_.end(); + ++it) + { + codec_.send(get_pointer(*it), message); + } + } + + typedef std::set<TcpConnectionPtr> ConnectionList; + TcpServer server_; + LengthHeaderCodec codec_; + ConnectionList connections_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port\n", argv[0]); + } +} + diff --git a/examples/asio/chat/server_threaded.cc b/examples/asio/chat/server_threaded.cc new file mode 100644 index 0000000..3dc88d6 --- /dev/null +++ b/examples/asio/chat/server_threaded.cc @@ -0,0 +1,98 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <set> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "ChatServer"), + codec_(std::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) + { + server_.setConnectionCallback( + std::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + MutexLockGuard lock(mutex_); + if (conn->connected()) + { + connections_.insert(conn); + } + else + { + connections_.erase(conn); + } + } + + void onStringMessage(const TcpConnectionPtr&, + const string& message, + Timestamp) + { + MutexLockGuard lock(mutex_); + for (ConnectionList::iterator it = connections_.begin(); + it != connections_.end(); + ++it) + { + codec_.send(get_pointer(*it), message); + } + } + + typedef std::set<TcpConnectionPtr> ConnectionList; + TcpServer server_; + LengthHeaderCodec codec_; + MutexLock mutex_; + ConnectionList connections_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + if (argc > 2) + { + server.setThreadNum(atoi(argv[2])); + } + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port [thread_num]\n", argv[0]); + } +} + diff --git a/examples/asio/chat/server_threaded_efficient.cc b/examples/asio/chat/server_threaded_efficient.cc new file mode 100644 index 0000000..5bd2738 --- /dev/null +++ b/examples/asio/chat/server_threaded_efficient.cc @@ -0,0 +1,113 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <set> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "ChatServer"), + codec_(std::bind(&ChatServer::onStringMessage, this, _1, _2, _3)), + connections_(new ConnectionList) + { + server_.setConnectionCallback( + std::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + MutexLockGuard lock(mutex_); + if (!connections_.unique()) + { + connections_.reset(new ConnectionList(*connections_)); + } + assert(connections_.unique()); + + if (conn->connected()) + { + connections_->insert(conn); + } + else + { + connections_->erase(conn); + } + } + + typedef std::set<TcpConnectionPtr> ConnectionList; + typedef std::shared_ptr<ConnectionList> ConnectionListPtr; + + void onStringMessage(const TcpConnectionPtr&, + const string& message, + Timestamp) + { + ConnectionListPtr connections = getConnectionList(); + for (ConnectionList::iterator it = connections->begin(); + it != connections->end(); + ++it) + { + codec_.send(get_pointer(*it), message); + } + } + + ConnectionListPtr getConnectionList() + { + MutexLockGuard lock(mutex_); + return connections_; + } + + TcpServer server_; + LengthHeaderCodec codec_; + MutexLock mutex_; + ConnectionListPtr connections_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + if (argc > 2) + { + server.setThreadNum(atoi(argv[2])); + } + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port [thread_num]\n", argv[0]); + } +} + diff --git a/examples/asio/chat/server_threaded_highperformance.cc b/examples/asio/chat/server_threaded_highperformance.cc new file mode 100644 index 0000000..18e704a --- /dev/null +++ b/examples/asio/chat/server_threaded_highperformance.cc @@ -0,0 +1,128 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/ThreadLocalSingleton.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <set> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "ChatServer"), + codec_(std::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) + { + server_.setConnectionCallback( + std::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start() + { + server_.setThreadInitCallback(std::bind(&ChatServer::threadInit, this, _1)); + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + LocalConnections::instance().insert(conn); + } + else + { + LocalConnections::instance().erase(conn); + } + } + + void onStringMessage(const TcpConnectionPtr&, + const string& message, + Timestamp) + { + EventLoop::Functor f = std::bind(&ChatServer::distributeMessage, this, message); + LOG_DEBUG; + + MutexLockGuard lock(mutex_); + for (std::set<EventLoop*>::iterator it = loops_.begin(); + it != loops_.end(); + ++it) + { + (*it)->queueInLoop(f); + } + LOG_DEBUG; + } + + typedef std::set<TcpConnectionPtr> ConnectionList; + + void distributeMessage(const string& message) + { + LOG_DEBUG << "begin"; + for (ConnectionList::iterator it = LocalConnections::instance().begin(); + it != LocalConnections::instance().end(); + ++it) + { + codec_.send(get_pointer(*it), message); + } + LOG_DEBUG << "end"; + } + + void threadInit(EventLoop* loop) + { + assert(LocalConnections::pointer() == NULL); + LocalConnections::instance(); + assert(LocalConnections::pointer() != NULL); + MutexLockGuard lock(mutex_); + loops_.insert(loop); + } + + TcpServer server_; + LengthHeaderCodec codec_; + typedef ThreadLocalSingleton<ConnectionList> LocalConnections; + + MutexLock mutex_; + std::set<EventLoop*> loops_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + if (argc > 2) + { + server.setThreadNum(atoi(argv[2])); + } + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port [thread_num]\n", argv[0]); + } +} + + diff --git a/examples/asio/echo_see_simple b/examples/asio/echo_see_simple new file mode 100644 index 0000000..e69de29 diff --git a/examples/asio/tutorial/daytime_see_simple b/examples/asio/tutorial/daytime_see_simple new file mode 100644 index 0000000..e69de29 diff --git a/examples/asio/tutorial/there_is_no_timer1 b/examples/asio/tutorial/there_is_no_timer1 new file mode 100644 index 0000000..e69de29 diff --git a/examples/asio/tutorial/timer2/timer.cc b/examples/asio/tutorial/timer2/timer.cc new file mode 100644 index 0000000..26a46a7 --- /dev/null +++ b/examples/asio/tutorial/timer2/timer.cc @@ -0,0 +1,16 @@ +#include "muduo/net/EventLoop.h" + +#include <iostream> + +void print() +{ + std::cout << "Hello, world!\n"; +} + +int main() +{ + muduo::net::EventLoop loop; + loop.runAfter(5, print); + loop.loop(); +} + diff --git a/examples/asio/tutorial/timer3/timer.cc b/examples/asio/tutorial/timer3/timer.cc new file mode 100644 index 0000000..c6f7e63 --- /dev/null +++ b/examples/asio/tutorial/timer3/timer.cc @@ -0,0 +1,29 @@ +#include "muduo/net/EventLoop.h" + +#include <iostream> + +void print(muduo::net::EventLoop* loop, int* count) +{ + if (*count < 5) + { + std::cout << *count << "\n"; + ++(*count); + + loop->runAfter(1, std::bind(print, loop, count)); + } + else + { + loop->quit(); + } +} + +int main() +{ + muduo::net::EventLoop loop; + int count = 0; + // Note: loop.runEvery() is better for this use case. + loop.runAfter(1, std::bind(print, &loop, &count)); + loop.loop(); + std::cout << "Final count is " << count << "\n"; +} + diff --git a/examples/asio/tutorial/timer4/timer.cc b/examples/asio/tutorial/timer4/timer.cc new file mode 100644 index 0000000..ed42fe2 --- /dev/null +++ b/examples/asio/tutorial/timer4/timer.cc @@ -0,0 +1,47 @@ +#include "muduo/net/EventLoop.h" + +#include <iostream> + +class Printer : muduo::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop) + : loop_(loop), + count_(0) + { + // Note: loop.runEvery() is better for this use case. + loop_->runAfter(1, std::bind(&Printer::print, this)); + } + + ~Printer() + { + std::cout << "Final count is " << count_ << "\n"; + } + + void print() + { + if (count_ < 5) + { + std::cout << count_ << "\n"; + ++count_; + + loop_->runAfter(1, std::bind(&Printer::print, this)); + } + else + { + loop_->quit(); + } + } + +private: + muduo::net::EventLoop* loop_; + int count_; +}; + +int main() +{ + muduo::net::EventLoop loop; + Printer printer(&loop); + loop.loop(); +} + diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc new file mode 100644 index 0000000..ea54974 --- /dev/null +++ b/examples/asio/tutorial/timer5/timer.cc @@ -0,0 +1,74 @@ +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +#include <iostream> + +class Printer : muduo::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop1, muduo::net::EventLoop* loop2) + : loop1_(loop1), + loop2_(loop2), + count_(0) + { + loop1_->runAfter(1, std::bind(&Printer::print1, this)); + loop2_->runAfter(1, std::bind(&Printer::print2, this)); + } + + ~Printer() + { + std::cout << "Final count is " << count_ << "\n"; + } + + void print1() + { + muduo::MutexLockGuard lock(mutex_); + if (count_ < 10) + { + std::cout << "Timer 1: " << count_ << "\n"; + ++count_; + + loop1_->runAfter(1, std::bind(&Printer::print1, this)); + } + else + { + loop1_->quit(); + } + } + + void print2() + { + muduo::MutexLockGuard lock(mutex_); + if (count_ < 10) + { + std::cout << "Timer 2: " << count_ << "\n"; + ++count_; + + loop2_->runAfter(1, std::bind(&Printer::print2, this)); + } + else + { + loop2_->quit(); + } + } + +private: + + muduo::MutexLock mutex_; + muduo::net::EventLoop* loop1_ PT_GUARDED_BY(mutex_); + muduo::net::EventLoop* loop2_ PT_GUARDED_BY(mutex_); + int count_ GUARDED_BY(mutex_); +}; + +int main() +{ + std::unique_ptr<Printer> printer; // make sure printer lives longer than loops, to avoid + // race condition of calling print2() on destructed object. + muduo::net::EventLoop loop; + muduo::net::EventLoopThread loopThread; + muduo::net::EventLoop* loopInAnotherThread = loopThread.startLoop(); + printer.reset(new Printer(&loop, loopInAnotherThread)); + loop.loop(); +} + diff --git a/examples/asio/tutorial/timer6/timer.cc b/examples/asio/tutorial/timer6/timer.cc new file mode 100644 index 0000000..aa6e752 --- /dev/null +++ b/examples/asio/tutorial/timer6/timer.cc @@ -0,0 +1,114 @@ +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +#include <stdio.h> + +// +// Minimize locking +// + +class Printer : muduo::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop1, muduo::net::EventLoop* loop2) + : loop1_(loop1), + loop2_(loop2), + count_(0) + { + loop1_->runAfter(1, std::bind(&Printer::print1, this)); + loop2_->runAfter(1, std::bind(&Printer::print2, this)); + } + + ~Printer() + { + // cout is not thread safe + //std::cout << "Final count is " << count_ << "\n"; + printf("Final count is %d\n", count_); + } + + void print1() + { + bool shouldQuit = false; + int count = 0; + + { + muduo::MutexLockGuard lock(mutex_); + if (count_ < 10) + { + count = count_; + ++count_; + } + else + { + shouldQuit = true; + } + } + + // out of lock + if (shouldQuit) + { + // printf("loop1_->quit()\n"); + loop1_->quit(); + } + else + { + // cout is not thread safe + //std::cout << "Timer 1: " << count << "\n"; + printf("Timer 1: %d\n", count); + loop1_->runAfter(1, std::bind(&Printer::print1, this)); + } + } + + void print2() + { + bool shouldQuit = false; + int count = 0; + + { + muduo::MutexLockGuard lock(mutex_); + if (count_ < 10) + { + count = count_; + ++count_; + } + else + { + shouldQuit = true; + } + } + + // out of lock + if (shouldQuit) + { + // printf("loop2_->quit()\n"); + loop2_->quit(); + } + else + { + // cout is not thread safe + //std::cout << "Timer 2: " << count << "\n"; + printf("Timer 2: %d\n", count); + loop2_->runAfter(1, std::bind(&Printer::print2, this)); + } + } + +private: + + muduo::MutexLock mutex_; + muduo::net::EventLoop* loop1_; + muduo::net::EventLoop* loop2_; + int count_ GUARDED_BY(mutex_); +}; + +int main() +{ + std::unique_ptr<Printer> printer; // make sure printer lives longer than loops, to avoid + // race condition of calling print2() on destructed object. + muduo::net::EventLoop loop; + muduo::net::EventLoopThread loopThread; + muduo::net::EventLoop* loopInAnotherThread = loopThread.startLoop(); + printer.reset(new Printer(&loop, loopInAnotherThread)); + loop.loop(); +} + diff --git a/examples/cdns/Resolver.cc b/examples/cdns/Resolver.cc new file mode 100644 index 0000000..8a01253 --- /dev/null +++ b/examples/cdns/Resolver.cc @@ -0,0 +1,201 @@ +#include "examples/cdns/Resolver.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include <ares.h> +#include <netdb.h> +#include <arpa/inet.h> // inet_ntop +#include <netinet/in.h> + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +using namespace muduo; +using namespace muduo::net; +using namespace cdns; + +namespace +{ +double getSeconds(struct timeval* tv) +{ + if (tv) + return double(tv->tv_sec) + double(tv->tv_usec)/1000000.0; + else + return -1.0; +} + +const char* getSocketType(int type) +{ + if (type == SOCK_DGRAM) + return "UDP"; + else if (type == SOCK_STREAM) + return "TCP"; + else + return "Unknown"; +} + +const bool kDebug = false; +} // namespace + +Resolver::Resolver(EventLoop* loop, Option opt) + : loop_(loop), + ctx_(NULL), + timerActive_(false) +{ + static char lookups[] = "b"; + struct ares_options options; + int optmask = ARES_OPT_FLAGS; + options.flags = ARES_FLAG_NOCHECKRESP; + options.flags |= ARES_FLAG_STAYOPEN; + options.flags |= ARES_FLAG_IGNTC; // UDP only + optmask |= ARES_OPT_SOCK_STATE_CB; + options.sock_state_cb = &Resolver::ares_sock_state_callback; + options.sock_state_cb_data = this; + optmask |= ARES_OPT_TIMEOUT; + options.timeout = 2; + if (opt == kDNSonly) + { + optmask |= ARES_OPT_LOOKUPS; + options.lookups = lookups; + } + + int status = ares_init_options(&ctx_, &options, optmask); + if (status != ARES_SUCCESS) + { + assert(0); + } + ares_set_socket_callback(ctx_, &Resolver::ares_sock_create_callback, this); +} + +Resolver::~Resolver() +{ + ares_destroy(ctx_); +} + +bool Resolver::resolve(StringArg hostname, const Callback& cb) +{ + loop_->assertInLoopThread(); + QueryData* queryData = new QueryData(this, cb); + ares_gethostbyname(ctx_, hostname.c_str(), AF_INET, + &Resolver::ares_host_callback, queryData); + struct timeval tv; + struct timeval* tvp = ares_timeout(ctx_, NULL, &tv); + double timeout = getSeconds(tvp); + LOG_DEBUG << "timeout " << timeout << " active " << timerActive_; + if (!timerActive_) + { + loop_->runAfter(timeout, std::bind(&Resolver::onTimer, this)); + timerActive_ = true; + } + return queryData != NULL; +} + +void Resolver::onRead(int sockfd, Timestamp t) +{ + LOG_DEBUG << "onRead " << sockfd << " at " << t.toString(); + ares_process_fd(ctx_, sockfd, ARES_SOCKET_BAD); +} + +void Resolver::onTimer() +{ + assert(timerActive_ == true); + ares_process_fd(ctx_, ARES_SOCKET_BAD, ARES_SOCKET_BAD); + struct timeval tv; + struct timeval* tvp = ares_timeout(ctx_, NULL, &tv); + double timeout = getSeconds(tvp); + LOG_DEBUG << loop_->pollReturnTime().toString() << " next timeout " << timeout; + + if (timeout < 0) + { + timerActive_ = false; + } + else + { + loop_->runAfter(timeout, std::bind(&Resolver::onTimer, this)); + } +} + +void Resolver::onQueryResult(int status, struct hostent* result, const Callback& callback) +{ + LOG_DEBUG << "onQueryResult " << status; + struct sockaddr_in addr; + memZero(&addr, sizeof addr); + addr.sin_family = AF_INET; + addr.sin_port = 0; + if (result) + { + addr.sin_addr = *reinterpret_cast<in_addr*>(result->h_addr); + if (kDebug) + { + printf("h_name %s\n", result->h_name); + for (char** alias = result->h_aliases; *alias != NULL; ++alias) + { + printf("alias: %s\n", *alias); + } + // printf("ttl %d\n", ttl); + // printf("h_length %d\n", result->h_length); + for (char** haddr = result->h_addr_list; *haddr != NULL; ++haddr) + { + char buf[32]; + inet_ntop(AF_INET, *haddr, buf, sizeof buf); + printf(" %s\n", buf); + } + } + } + InetAddress inet(addr); + callback(inet); +} + +void Resolver::onSockCreate(int sockfd, int type) +{ + loop_->assertInLoopThread(); + assert(channels_.find(sockfd) == channels_.end()); + Channel* channel = new Channel(loop_, sockfd); + channel->setReadCallback(std::bind(&Resolver::onRead, this, sockfd, _1)); + channel->enableReading(); + channels_[sockfd].reset(channel); +} + +void Resolver::onSockStateChange(int sockfd, bool read, bool write) +{ + loop_->assertInLoopThread(); + ChannelList::iterator it = channels_.find(sockfd); + assert(it != channels_.end()); + if (read) + { + // update + // if (write) { } else { } + } + else + { + // remove + it->second->disableAll(); + it->second->remove(); + channels_.erase(it); + } +} + +void Resolver::ares_host_callback(void* data, int status, int timeouts, struct hostent* hostent) +{ + QueryData* query = static_cast<QueryData*>(data); + + query->owner->onQueryResult(status, hostent, query->callback); + delete query; +} + +int Resolver::ares_sock_create_callback(int sockfd, int type, void* data) +{ + LOG_TRACE << "sockfd=" << sockfd << " type=" << getSocketType(type); + static_cast<Resolver*>(data)->onSockCreate(sockfd, type); + return 0; +} + +void Resolver::ares_sock_state_callback(void* data, int sockfd, int read, int write) +{ + LOG_TRACE << "sockfd=" << sockfd << " read=" << read << " write=" << write; + static_cast<Resolver*>(data)->onSockStateChange(sockfd, read, write); +} + diff --git a/examples/cdns/Resolver.h b/examples/cdns/Resolver.h new file mode 100644 index 0000000..f33b47d --- /dev/null +++ b/examples/cdns/Resolver.h @@ -0,0 +1,77 @@ +#ifndef MUDUO_EXAMPLES_CDNS_RESOLVER_H +#define MUDUO_EXAMPLES_CDNS_RESOLVER_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Timestamp.h" +#include "muduo/net/InetAddress.h" + +#include <functional> +#include <map> +#include <memory> + +extern "C" +{ + struct hostent; + struct ares_channeldata; + typedef struct ares_channeldata* ares_channel; +} + +namespace muduo +{ +namespace net +{ +class Channel; +class EventLoop; +} +} + +namespace cdns +{ + +class Resolver : muduo::noncopyable +{ + public: + typedef std::function<void(const muduo::net::InetAddress&)> Callback; + enum Option + { + kDNSandHostsFile, + kDNSonly, + }; + + explicit Resolver(muduo::net::EventLoop* loop, Option opt = kDNSandHostsFile); + ~Resolver(); + + bool resolve(muduo::StringArg hostname, const Callback& cb); + + private: + + struct QueryData + { + Resolver* owner; + Callback callback; + QueryData(Resolver* o, const Callback& cb) + : owner(o), callback(cb) + { + } + }; + + muduo::net::EventLoop* loop_; + ares_channel ctx_; + bool timerActive_; + typedef std::map<int, std::unique_ptr<muduo::net::Channel>> ChannelList; + ChannelList channels_; + + void onRead(int sockfd, muduo::Timestamp t); + void onTimer(); + void onQueryResult(int status, struct hostent* result, const Callback& cb); + void onSockCreate(int sockfd, int type); + void onSockStateChange(int sockfd, bool read, bool write); + + static void ares_host_callback(void* data, int status, int timeouts, struct hostent* hostent); + static int ares_sock_create_callback(int sockfd, int type, void* data); + static void ares_sock_state_callback(void* data, int sockfd, int read, int write); +}; +} // namespace cdns + +#endif // MUDUO_EXAMPLES_CDNS_RESOLVER_H diff --git a/examples/cdns/dns.cc b/examples/cdns/dns.cc new file mode 100644 index 0000000..865503a --- /dev/null +++ b/examples/cdns/dns.cc @@ -0,0 +1,51 @@ +#include "examples/cdns/Resolver.h" +#include "muduo/net/EventLoop.h" +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; +using namespace cdns; + +EventLoop* g_loop; +int count = 0; +int total = 0; + +void quit() +{ + g_loop->quit(); +} + +void resolveCallback(const string& host, const InetAddress& addr) +{ + printf("resolveCallback %s -> %s\n", host.c_str(), addr.toIpPort().c_str()); + if (++count == total) + quit(); +} + +void resolve(Resolver* res, const string& host) +{ + res->resolve(host, std::bind(&resolveCallback, host, _1)); +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + loop.runAfter(10, quit); + g_loop = &loop; + Resolver resolver(&loop, + argc == 1 ? Resolver::kDNSonly : Resolver::kDNSandHostsFile); + if (argc == 1) + { + total = 3; + resolve(&resolver, "www.chenshuo.com"); + resolve(&resolver, "www.example.com"); + resolve(&resolver, "www.google.com"); + } + else + { + total = argc-1; + for (int i = 1; i < argc; ++i) + resolve(&resolver, argv[i]); + } + loop.loop(); +} diff --git a/examples/curl/Curl.cc b/examples/curl/Curl.cc new file mode 100644 index 0000000..444ab32 --- /dev/null +++ b/examples/curl/Curl.cc @@ -0,0 +1,270 @@ +#include "examples/curl/Curl.h" +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include <curl/curl.h> +#include <assert.h> + +using namespace curl; +using namespace muduo; +using namespace muduo::net; + +static void dummy(const std::shared_ptr<Channel>&) +{ +} + +Request::Request(Curl* owner, const char* url) + : owner_(owner), + curl_(CHECK_NOTNULL(curl_easy_init())) +{ + setopt(CURLOPT_URL, url); + setopt(CURLOPT_WRITEFUNCTION, &Request::writeData); + setopt(CURLOPT_WRITEDATA, this); + setopt(CURLOPT_HEADERFUNCTION, &Request::headerData); + setopt(CURLOPT_HEADERDATA, this); + setopt(CURLOPT_PRIVATE, this); + setopt(CURLOPT_USERAGENT, "curl"); + // set useragent + LOG_DEBUG << curl_ << " " << url; + curl_multi_add_handle(owner_->getCurlm(), curl_); +} + +Request::~Request() +{ + assert(!channel_ || channel_->isNoneEvent()); + curl_multi_remove_handle(owner_->getCurlm(), curl_); + curl_easy_cleanup(curl_); +} + +// NOT implemented yet +// +// void Request::allowRedirect(int redirects) +// { +// setopt(CURLOPT_FOLLOWLOCATION, 1); +// setopt(CURLOPT_MAXREDIRS, redirects); +// } + +void Request::headerOnly() +{ + setopt(CURLOPT_NOBODY, 1); +} + +void Request::setRange(const StringArg range) +{ + setopt(CURLOPT_RANGE, range.c_str()); +} + +const char* Request::getEffectiveUrl() +{ + const char* p = NULL; + curl_easy_getinfo(curl_, CURLINFO_EFFECTIVE_URL, &p); + return p; +} + +const char* Request::getRedirectUrl() +{ + const char* p = NULL; + curl_easy_getinfo(curl_, CURLINFO_REDIRECT_URL, &p); + return p; +} + +int Request::getResponseCode() +{ + long code = 0; + curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &code); + return static_cast<int>(code); +} + +Channel* Request::setChannel(int fd) +{ + assert(channel_.get() == NULL); + channel_.reset(new Channel(owner_->getLoop(), fd)); + channel_->tie(shared_from_this()); + return get_pointer(channel_); +} + +void Request::removeChannel() +{ + channel_->disableAll(); + channel_->remove(); + owner_->getLoop()->queueInLoop(std::bind(dummy, channel_)); + channel_.reset(); +} + +void Request::done(int code) +{ + if (doneCb_) + { + doneCb_(this, code); + } +} + +void Request::dataCallback(const char* buffer, int len) +{ + if (dataCb_) + { + dataCb_(buffer, len); + } +} + +void Request::headerCallback(const char* buffer, int len) +{ + if (headerCb_) + { + headerCb_(buffer, len); + } +} + +size_t Request::writeData(char* buffer, size_t size, size_t nmemb, void* userp) +{ + assert(size == 1); + Request* req = static_cast<Request*>(userp); + req->dataCallback(buffer, static_cast<int>(nmemb)); + return nmemb; +} + +size_t Request::headerData(char* buffer, size_t size, size_t nmemb, void* userp) +{ + assert(size == 1); + Request* req = static_cast<Request*>(userp); + req->headerCallback(buffer, static_cast<int>(nmemb)); + return nmemb; +} + +// ================================================================== + +void Curl::initialize(Option opt) +{ + curl_global_init(opt == kCURLnossl ? CURL_GLOBAL_NOTHING : CURL_GLOBAL_SSL); +} + +int Curl::socketCallback(CURL* c, int fd, int what, void* userp, void* socketp) +{ + Curl* curl = static_cast<Curl*>(userp); + const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" }; + LOG_DEBUG << "Curl::socketCallback [" << curl << "] - fd = " << fd + << " what = " << whatstr[what]; + Request* req = NULL; + curl_easy_getinfo(c, CURLINFO_PRIVATE, &req); + assert(req->getCurl() == c); + if (what == CURL_POLL_REMOVE) + { + muduo::net::Channel* ch = static_cast<Channel*>(socketp); + assert(req->getChannel() == ch); + req->removeChannel(); + ch = NULL; + curl_multi_assign(curl->curlm_, fd, ch); + } + else + { + muduo::net::Channel* ch = static_cast<Channel*>(socketp); + if (!ch) + { + ch = req->setChannel(fd); + ch->setReadCallback(std::bind(&Curl::onRead, curl, fd)); + ch->setWriteCallback(std::bind(&Curl::onWrite, curl, fd)); + ch->enableReading(); + curl_multi_assign(curl->curlm_, fd, ch); + LOG_TRACE << "new channel for fd=" << fd; + } + assert(req->getChannel() == ch); + // update + if (what & CURL_POLL_OUT) + { + ch->enableWriting(); + } + else + { + ch->disableWriting(); + } + } + return 0; +} + +int Curl::timerCallback(CURLM* curlm, long ms, void* userp) +{ + Curl* curl = static_cast<Curl*>(userp); + LOG_DEBUG << curl << " " << ms << " ms"; + curl->loop_->runAfter(static_cast<int>(ms)/1000.0, std::bind(&Curl::onTimer, curl)); + return 0; +} + +Curl::Curl(EventLoop* loop) + : loop_(loop), + curlm_(CHECK_NOTNULL(curl_multi_init())), + runningHandles_(0), + prevRunningHandles_(0) +{ + curl_multi_setopt(curlm_, CURLMOPT_SOCKETFUNCTION, &Curl::socketCallback); + curl_multi_setopt(curlm_, CURLMOPT_SOCKETDATA, this); + curl_multi_setopt(curlm_, CURLMOPT_TIMERFUNCTION, &Curl::timerCallback); + curl_multi_setopt(curlm_, CURLMOPT_TIMERDATA, this); +} + +Curl::~Curl() +{ + curl_multi_cleanup(curlm_); +} + +RequestPtr Curl::getUrl(StringArg url) +{ + RequestPtr req(new Request(this, url.c_str())); + return req; +} + +void Curl::onTimer() +{ + CURLMcode rc = CURLM_OK; + do { + LOG_TRACE; + rc = curl_multi_socket_action(curlm_, CURL_SOCKET_TIMEOUT, 0, &runningHandles_); + LOG_TRACE << rc << " " << runningHandles_; + } while (rc == CURLM_CALL_MULTI_PERFORM); + checkFinish(); +} + +void Curl::onRead(int fd) +{ + CURLMcode rc = CURLM_OK; + do { + LOG_TRACE << fd; + rc = curl_multi_socket_action(curlm_, fd, CURL_POLL_IN, &runningHandles_); + LOG_TRACE << fd << " " << rc << " " << runningHandles_; + } while (rc == CURLM_CALL_MULTI_PERFORM); + checkFinish(); +} + +void Curl::onWrite(int fd) +{ + CURLMcode rc = CURLM_OK; + do { + LOG_TRACE << fd; + rc = curl_multi_socket_action(curlm_, fd, CURL_POLL_OUT, &runningHandles_); + LOG_TRACE << fd << " " << rc << " " << runningHandles_; + } while (rc == CURLM_CALL_MULTI_PERFORM); + checkFinish(); +} + +void Curl::checkFinish() +{ + if (prevRunningHandles_ > runningHandles_ || runningHandles_ == 0) + { + CURLMsg* msg = NULL; + int left = 0; + while ( (msg = curl_multi_info_read(curlm_, &left)) != NULL) + { + if (msg->msg == CURLMSG_DONE) + { + CURL* c = msg->easy_handle; + CURLcode res = msg->data.result; + Request* req = NULL; + curl_easy_getinfo(c, CURLINFO_PRIVATE, &req); + assert(req->getCurl() == c); + LOG_TRACE << req << " done"; + req->done(res); + } + } + } + prevRunningHandles_ = runningHandles_; +} diff --git a/examples/curl/Curl.h b/examples/curl/Curl.h new file mode 100644 index 0000000..23cfd95 --- /dev/null +++ b/examples/curl/Curl.h @@ -0,0 +1,143 @@ +#ifndef MUDUO_EXAMPLES_CURL_CURL_H +#define MUDUO_EXAMPLES_CURL_CURL_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" + +#include "muduo/net/Callbacks.h" + +extern "C" +{ +typedef void CURLM; +typedef void CURL; +} + +namespace muduo +{ +namespace net +{ +class Channel; +class EventLoop; +} +} + +namespace curl +{ + +class Curl; + +class Request : public std::enable_shared_from_this<Request>, + muduo::noncopyable +{ + public: + typedef std::function<void(const char*, int)> DataCallback; + typedef std::function<void(Request*, int)> DoneCallback; + + Request(Curl*, const char* url); + ~Request(); + + void setDataCallback(const DataCallback& cb) + { dataCb_ = cb; } + + void setDoneCallback(const DoneCallback& cb) + { doneCb_ = cb; } + + void setHeaderCallback(const DataCallback& cb) + { headerCb_ = cb; } + + // void allowRedirect(int redirects); + void headerOnly(); + void setRange(const muduo::StringArg range); + + template<typename OPT> + int setopt(OPT opt, long p) + { + return curl_easy_setopt(curl_, opt, p); + } + + template<typename OPT> + int setopt(OPT opt, const char* p) + { + return curl_easy_setopt(curl_, opt, p); + } + + template<typename OPT> + int setopt(OPT opt, void* p) + { + return curl_easy_setopt(curl_, opt, p); + } + + template<typename OPT> + int setopt(OPT opt, size_t (*p)(char *, size_t , size_t , void *)) + { + return curl_easy_setopt(curl_, opt, p); + } + + const char* getEffectiveUrl(); + const char* getRedirectUrl(); + int getResponseCode(); + + // internal + muduo::net::Channel* setChannel(int fd); + void removeChannel(); + void done(int code); + CURL* getCurl() { return curl_; } + muduo::net::Channel* getChannel() { return muduo::get_pointer(channel_); } + + private: + + void dataCallback(const char* buffer, int len); + void headerCallback(const char* buffer, int len); + static size_t writeData(char *buffer, size_t size, size_t nmemb, void *userp); + static size_t headerData(char *buffer, size_t size, size_t nmemb, void *userp); + void doneCallback(); + + class Curl* owner_; + CURL* curl_; + std::shared_ptr<muduo::net::Channel> channel_; + DataCallback dataCb_; + DataCallback headerCb_; + DoneCallback doneCb_; +}; + +typedef std::shared_ptr<Request> RequestPtr; + +class Curl : muduo::noncopyable +{ + public: + + enum Option + { + kCURLnossl = 0, + kCURLssl = 1, + }; + + explicit Curl(muduo::net::EventLoop* loop); + ~Curl(); + + RequestPtr getUrl(muduo::StringArg url); + + static void initialize(Option opt = kCURLnossl); + + // internal + CURLM* getCurlm() { return curlm_; } + muduo::net::EventLoop* getLoop() { return loop_; } + + private: + void onTimer(); + void onRead(int fd); + void onWrite(int fd); + void checkFinish(); + + static int socketCallback(CURL*, int, int, void*, void*); + static int timerCallback(CURLM*, long, void*); + + muduo::net::EventLoop* loop_; + CURLM* curlm_; + int runningHandles_; + int prevRunningHandles_; +}; + +} // namespace curl + +#endif // MUDUO_EXAMPLES_CURL_CURL_H diff --git a/examples/curl/README b/examples/curl/README new file mode 100644 index 0000000..8ddbcf8 --- /dev/null +++ b/examples/curl/README @@ -0,0 +1,6 @@ +This is a proof-of-concept implementation of muduo-curl bridge. +It demostrates the simplest use case of curl with muduo. + +Note: +1. DNS resolving could be blocking, if your curl is not built with c-ares. +2. Request object should survive doneCallback. diff --git a/examples/curl/download.cc b/examples/curl/download.cc new file mode 100644 index 0000000..8767cbd --- /dev/null +++ b/examples/curl/download.cc @@ -0,0 +1,207 @@ +// Concurrent downloading one file from HTTP + +#include "examples/curl/Curl.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include <stdio.h> +#include <sstream> + +using namespace muduo; +using namespace muduo::net; + +typedef std::shared_ptr<FILE> FilePtr; + +template<int N> +bool startWith(const string& str, const char (&prefix)[N]) +{ + return str.size() >= N-1 && std::equal(prefix, prefix+N-1, str.begin()); +} + +class Piece : noncopyable +{ + public: + Piece(const curl::RequestPtr& req, + const FilePtr& out, + const muduo::string& range, + std::function<void()> done) + : req_(req), + out_(out), + range_(range), + doneCb_(std::move(done)) + { + LOG_INFO << "range: " << range; + req->setRange(range); + req_->setDataCallback( + std::bind(&Piece::onData, this, _1, _2)); + req_->setDoneCallback( + std::bind(&Piece::onDone, this, _1, _2)); + } + private: + void onData(const char* data, int len) + { + ::fwrite(data, 1, len, get_pointer(out_)); + } + + void onDone(curl::Request* c, int code) + { + LOG_INFO << "[" << range_ << "] is done"; + req_.reset(); + out_.reset(); + doneCb_(); + } + + curl::RequestPtr req_; + FilePtr out_; + muduo::string range_; + std::function<void()> doneCb_; +}; + +class Downloader : noncopyable +{ + public: + Downloader(EventLoop* loop, const string& url) + : loop_(loop), + curl_(loop_), + url_(url), + req_(curl_.getUrl(url_)), + found_(false), + acceptRanges_(false), + length_(0), + pieces_(kConcurrent), + concurrent_(0) + { + req_->setHeaderCallback( + std::bind(&Downloader::onHeader, this, _1, _2)); + req_->setDoneCallback( + std::bind(&Downloader::onHeaderDone, this, _1, _2)); + req_->headerOnly(); + } + + private: + void onHeader(const char* data, int len) + { + string line(data, len); + if (startWith(line, "HTTP/1.1 200") || startWith(line, "HTTP/1.0 200")) + { + found_ = true; + } + if (line == "Accept-Ranges: bytes\r\n") + { + acceptRanges_ = true; + LOG_DEBUG << "Accept-Ranges"; + } + else if (startWith(line, "Content-Length:")) + { + length_ = atoll(line.c_str() + strlen("Content-Length:")); + LOG_INFO << "Content-Length: " << length_; + } + } + + void onHeaderDone(curl::Request* c, int code) + { + LOG_DEBUG << code; + if (acceptRanges_ && length_ >= kConcurrent * 4096) + { + LOG_INFO << "Downloading with " << kConcurrent << " connections"; + concurrent_ = kConcurrent; + concurrentDownload(); + } + else if (found_) + { + LOG_WARN << "Single connection download"; + FILE* fp = ::fopen("output", "wb"); + if (fp) + { + FilePtr(fp, ::fclose).swap(out_); + req_.reset(); + req2_ = curl_.getUrl(url_); + req2_->setDataCallback( + std::bind(&Downloader::onData, this, _1, _2)); + req2_->setDoneCallback( + std::bind(&Downloader::onDownloadDone, this)); + concurrent_ = 1; + } + else + { + LOG_ERROR << "Can not create output file"; + loop_->quit(); + } + } + else + { + LOG_ERROR << "File not found"; + loop_->quit(); + } + } + + void concurrentDownload() + { + const int64_t pieceLen = length_ / kConcurrent; + for (int i = 0; i < kConcurrent; ++i) + { + char buf[256]; + snprintf(buf, sizeof buf, "output-%05d-of-%05d", i, kConcurrent); + FILE* fp = ::fopen(buf, "wb"); + if (fp) + { + FilePtr out(fp, ::fclose); + curl::RequestPtr req = curl_.getUrl(url_); + + std::ostringstream range; + if (i < kConcurrent - 1) + { + range << i * pieceLen << "-" << (i+1) * pieceLen - 1; + } + else + { + range << i * pieceLen << "-" << length_ - 1; + } + pieces_[i].reset(new Piece(req, + out, + range.str(), + std::bind(&Downloader::onDownloadDone, this))); + } + else + { + LOG_ERROR << "Can not create output file: " << buf; + loop_->quit(); + } + } + } + + void onData(const char* data, int len) + { + ::fwrite(data, 1, len, get_pointer(out_)); + } + + void onDownloadDone() + { + if (--concurrent_ <= 0) + { + loop_->quit(); + } + } + + EventLoop* loop_; + curl::Curl curl_; + string url_; + curl::RequestPtr req_; + curl::RequestPtr req2_; + bool found_; + bool acceptRanges_; + int64_t length_; + FilePtr out_; + std::vector<std::unique_ptr<Piece>> pieces_; + int concurrent_; + + const static int kConcurrent = 4; +}; + +int main(int argc, char* argv[]) +{ + EventLoop loop; + curl::Curl::initialize(curl::Curl::kCURLssl); + string url = argc > 1 ? argv[1] : "https://chenshuo-public.s3.amazonaws.com/pdf/allinone.pdf"; + Downloader d(&loop, url); + loop.loop(); +} diff --git a/examples/curl/mcurl.cc b/examples/curl/mcurl.cc new file mode 100644 index 0000000..6375fd2 --- /dev/null +++ b/examples/curl/mcurl.cc @@ -0,0 +1,48 @@ +#include "examples/curl/Curl.h" +#include "muduo/net/EventLoop.h" +#include <stdio.h> + +using namespace muduo::net; + +EventLoop* g_loop = NULL; + +void onData(const char* data, int len) +{ + printf("len %d\n", len); +} + +void done(curl::Request* c, int code) +{ + printf("done %p %s %d\n", c, c->getEffectiveUrl(), code); +} + +void done2(curl::Request* c, int code) +{ + printf("done2 %p %s %d %d\n", c, c->getRedirectUrl(), c->getResponseCode(), code); + // g_loop->quit(); +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + g_loop = &loop; + loop.runAfter(30.0, std::bind(&EventLoop::quit, &loop)); + curl::Curl::initialize(curl::Curl::kCURLssl); + curl::Curl curl(&loop); + + curl::RequestPtr req = curl.getUrl("http://chenshuo.com"); + req->setDataCallback(onData); + req->setDoneCallback(done); + + curl::RequestPtr req2 = curl.getUrl("https://github.com"); + // req2->allowRedirect(5); + req2->setDataCallback(onData); + req2->setDoneCallback(done); + + curl::RequestPtr req3 = curl.getUrl("http://example.com"); + // req3->allowRedirect(5); + req3->setDataCallback(onData); + req3->setDoneCallback(done2); + + loop.loop(); +} diff --git a/examples/fastcgi/fastcgi.cc b/examples/fastcgi/fastcgi.cc new file mode 100644 index 0000000..7017e92 --- /dev/null +++ b/examples/fastcgi/fastcgi.cc @@ -0,0 +1,247 @@ +#include "examples/fastcgi/fastcgi.h" +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" + +struct FastCgiCodec::RecordHeader +{ + uint8_t version; + uint8_t type; + uint16_t id; + uint16_t length; + uint8_t padding; + uint8_t unused; +}; + +const unsigned FastCgiCodec::kRecordHeader = static_cast<unsigned>(sizeof(FastCgiCodec::RecordHeader)); + +enum FcgiType +{ + kFcgiInvalid = 0, + kFcgiBeginRequest = 1, + kFcgiAbortRequest = 2, + kFcgiEndRequest = 3, + kFcgiParams = 4, + kFcgiStdin = 5, + kFcgiStdout = 6, + kFcgiStderr = 7, + kFcgiData = 8, + kFcgiGetValues = 9, + kFcgiGetValuesResult = 10, +}; + +enum FcgiRole +{ + // kFcgiInvalid = 0, + kFcgiResponder = 1, + kFcgiAuthorizer = 2, +}; + +enum FcgiConstant +{ + kFcgiKeepConn = 1, +}; + +using namespace muduo::net; + +bool FastCgiCodec::onParams(const char* content, uint16_t length) +{ + if (length > 0) + { + paramsStream_.append(content, length); + } + else if (!parseAllParams()) + { + LOG_ERROR << "parseAllParams() failed"; + return false; + } + return true; +} + +void FastCgiCodec::onStdin(const char* content, uint16_t length) +{ + if (length > 0) + { + stdin_.append(content, length); + } + else + { + gotRequest_ = true; + } +} + +bool FastCgiCodec::parseAllParams() +{ + while (paramsStream_.readableBytes() > 0) + { + uint32_t nameLen = readLen(); + if (nameLen == static_cast<uint32_t>(-1)) + return false; + uint32_t valueLen = readLen(); + if (valueLen == static_cast<uint32_t>(-1)) + return false; + if (paramsStream_.readableBytes() >= nameLen+valueLen) + { + std::string name = paramsStream_.retrieveAsString(nameLen); + params_[name] = paramsStream_.retrieveAsString(valueLen); + } + else + { + return false; + } + } + return true; +} + +uint32_t FastCgiCodec::readLen() +{ + if (paramsStream_.readableBytes() >= 1) + { + uint8_t byte = paramsStream_.peekInt8(); + if (byte & 0x80) + { + if (paramsStream_.readableBytes() >= sizeof(uint32_t)) + { + return paramsStream_.readInt32() & 0x7fffffff; + } + else + { + return -1; + } + } + else + { + return paramsStream_.readInt8(); + } + } + else + { + return -1; + } +} + +using muduo::net::Buffer; + +void FastCgiCodec::endStdout(Buffer* buf) +{ + RecordHeader header = + { + 1, + kFcgiStdout, + sockets::hostToNetwork16(1), + 0, + 0, + 0, + }; + buf->append(&header, kRecordHeader); +} + +void FastCgiCodec::endRequest(Buffer* buf) +{ + RecordHeader header = + { + 1, + kFcgiEndRequest, + sockets::hostToNetwork16(1), + sockets::hostToNetwork16(kRecordHeader), + 0, + 0, + }; + buf->append(&header, kRecordHeader); + buf->appendInt32(0); + buf->appendInt32(0); +} + +void FastCgiCodec::respond(Buffer* response) +{ + if (response->readableBytes() < 65536 + && response->prependableBytes() >= kRecordHeader) + { + RecordHeader header = + { + 1, + kFcgiStdout, + sockets::hostToNetwork16(1), + sockets::hostToNetwork16(static_cast<uint16_t>(response->readableBytes())), + static_cast<uint8_t>(-response->readableBytes() & 7), + 0, + }; + response->prepend(&header, kRecordHeader); + response->append("\0\0\0\0\0\0\0\0", header.padding); + } + else + { + // FIXME: + } + + endStdout(response); + endRequest(response); +} + +bool FastCgiCodec::parseRequest(Buffer* buf) +{ + while (buf->readableBytes() >= kRecordHeader) + { + RecordHeader header; + memcpy(&header, buf->peek(), kRecordHeader); + header.id = sockets::networkToHost16(header.id); + header.length = sockets::networkToHost16(header.length); + size_t total = kRecordHeader + header.length + header.padding; + if (buf->readableBytes() >= total) + { + switch (header.type) + { + case kFcgiBeginRequest: + onBeginRequest(header, buf); + // FIXME: check + break; + case kFcgiParams: + onParams(buf->peek() + kRecordHeader, header.length); + // FIXME: check + break; + case kFcgiStdin: + onStdin(buf->peek() + kRecordHeader, header.length); + break; + case kFcgiData: + // FIXME: + break; + case kFcgiGetValues: + // FIXME: + break; + default: + // FIXME: + break; + } + buf->retrieve(total); + } + else + { + break; + } + } + return true; +} + +uint16_t readInt16(const void* p) +{ + uint16_t be16 = 0; + ::memcpy(&be16, p, sizeof be16); + return sockets::networkToHost16(be16); +} + +bool FastCgiCodec::onBeginRequest(const RecordHeader& header, const Buffer* buf) +{ + assert(buf->readableBytes() >= header.length); + assert(header.type == kFcgiBeginRequest); + + if (header.length >= kRecordHeader) + { + uint16_t role = readInt16(buf->peek()+kRecordHeader); + uint8_t flags = buf->peek()[kRecordHeader + sizeof(int16_t)]; + if (role == kFcgiResponder) + { + keepConn_ = flags == kFcgiKeepConn; + return true; + } + } + return false; +} diff --git a/examples/fastcgi/fastcgi.h b/examples/fastcgi/fastcgi.h new file mode 100644 index 0000000..d630492 --- /dev/null +++ b/examples/fastcgi/fastcgi.h @@ -0,0 +1,68 @@ +#ifndef MUDUO_EXAMPLES_FASTCGI_FASTCGI_H +#define MUDUO_EXAMPLES_FASTCGI_FASTCGI_H + +#include "muduo/net/TcpConnection.h" +#include <map> + +// one FastCgiCodec per TcpConnection +// both lighttpd and nginx do not implement multiplexing, +// so there is no concurrent requests of one connection. +class FastCgiCodec : muduo::noncopyable +{ + public: + typedef std::map<std::string, std::string> ParamMap; + typedef std::function<void (const muduo::net::TcpConnectionPtr& conn, + ParamMap&, + muduo::net::Buffer*)> Callback; + + explicit FastCgiCodec(const Callback& cb) + : cb_(cb), + gotRequest_(false), + keepConn_(false) + { + } + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp receiveTime) + { + parseRequest(buf); + if (gotRequest_) + { + cb_(conn, params_, &stdin_); + stdin_.retrieveAll(); + paramsStream_.retrieveAll(); + params_.clear(); + gotRequest_ = false; + if (!keepConn_) + { + conn->shutdown(); + } + } + } + + static void respond(muduo::net::Buffer* response); + + private: + struct RecordHeader; + bool parseRequest(muduo::net::Buffer* buf); + bool onBeginRequest(const RecordHeader& header, const muduo::net::Buffer* buf); + void onStdin(const char* content, uint16_t length); + bool onParams(const char* content, uint16_t length); + bool parseAllParams(); + uint32_t readLen(); + + static void endStdout(muduo::net::Buffer* buf); + static void endRequest(muduo::net::Buffer* buf); + + Callback cb_; + bool gotRequest_; + bool keepConn_; + muduo::net::Buffer stdin_; + muduo::net::Buffer paramsStream_; + ParamMap params_; + + const static unsigned kRecordHeader; +}; + +#endif // MUDUO_EXAMPLES_FASTCGI_FASTCGI_H diff --git a/examples/fastcgi/fastcgi_test.cc b/examples/fastcgi/fastcgi_test.cc new file mode 100644 index 0000000..1c7970d --- /dev/null +++ b/examples/fastcgi/fastcgi_test.cc @@ -0,0 +1,73 @@ +#include "examples/fastcgi/fastcgi.h" +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +const string kPath = "/sudoku/"; + +void onRequest(const TcpConnectionPtr& conn, + FastCgiCodec::ParamMap& params, + Buffer* in) +{ + string uri = params["REQUEST_URI"]; + LOG_INFO << conn->name() << ": " << uri; + + for (FastCgiCodec::ParamMap::const_iterator it = params.begin(); + it != params.end(); ++it) + { + LOG_DEBUG << it->first << " = " << it->second; + } + if (in->readableBytes() > 0) + LOG_DEBUG << "stdin " << in->retrieveAllAsString(); + Buffer response; + response.append("Context-Type: text/plain\r\n\r\n"); + if (uri.size() == kCells + kPath.size() && uri.find(kPath) == 0) + { + response.append(solveSudoku(uri.substr(kPath.size()))); + } + else + { + // FIXME: set http status code 400 + response.append("bad request"); + } + + FastCgiCodec::respond(&response); + conn->send(&response); +} + +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + typedef std::shared_ptr<FastCgiCodec> CodecPtr; + CodecPtr codec(new FastCgiCodec(onRequest)); + conn->setContext(codec); + conn->setMessageCallback( + std::bind(&FastCgiCodec::onMessage, codec, _1, _2, _3)); + conn->setTcpNoDelay(true); + } +} + +int main(int argc, char* argv[]) +{ + int port = 19981; + int threads = 0; + if (argc > 1) + port = atoi(argv[1]); + if (argc > 2) + threads = atoi(argv[2]); + InetAddress addr(static_cast<uint16_t>(port)); + LOG_INFO << "Sudoku FastCGI listens on " << addr.toIpPort() + << " threads " << threads; + muduo::net::EventLoop loop; + TcpServer server(&loop, addr, "FastCGI"); + server.setConnectionCallback(onConnection); + server.setThreadNum(threads); + server.start(); + loop.loop(); +} diff --git a/examples/fastcgi/nginx.conf b/examples/fastcgi/nginx.conf new file mode 100644 index 0000000..7abcc93 --- /dev/null +++ b/examples/fastcgi/nginx.conf @@ -0,0 +1,110 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + +events { + worker_connections 1024; +} + +http { + #include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + access_log off; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + upstream muduo_backend { + server localhost:19981; + #server localhost:19982; + keepalive 32; + } + + server { + listen 10080; + server_name localhost; + + #access_log logs/host.access.log main; + + location / { + root html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # pass /sudoku/ to muduo FastCGI server listening on 127.0.0.1:19981 + # + location /sudoku/ { + fastcgi_keep_conn on; + fastcgi_pass muduo_backend; + #include fastcgi_params; + #fastcgi_param QUERY_STRING $query_string; + #fastcgi_param REQUEST_METHOD $request_method; + #fastcgi_param CONTENT_TYPE $content_type; + #fastcgi_param CONTENT_LENGTH $content_length; + + #fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + #fastcgi_param DOCUMENT_URI $document_uri; + #fastcgi_param DOCUMENT_ROOT $document_root; + #fastcgi_param SERVER_PROTOCOL $server_protocol; + #fastcgi_param HTTPS $https if_not_empty; + + #fastcgi_param GATEWAY_INTERFACE CGI/1.1; + #fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + + #fastcgi_param REMOTE_ADDR $remote_addr; + #fastcgi_param REMOTE_PORT $remote_port; + #fastcgi_param SERVER_ADDR $server_addr; + #fastcgi_param SERVER_PORT $server_port; + #fastcgi_param SERVER_NAME $server_name; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} + } +} diff --git a/examples/filetransfer/download.cc b/examples/filetransfer/download.cc new file mode 100644 index 0000000..fb0b5a3 --- /dev/null +++ b/examples/filetransfer/download.cc @@ -0,0 +1,70 @@ +#include <stdio.h> +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +const char* g_file = NULL; + +// FIXME: use FileUtil::readFile() +string readFile(const char* filename) +{ + string content; + FILE* fp = ::fopen(filename, "rb"); + if (fp) { + // inefficient!!! + const int kBufSize = 1024 * 1024; + char iobuf[kBufSize]; + ::setbuffer(fp, iobuf, sizeof iobuf); + + char buf[kBufSize]; + size_t nread = 0; + while ((nread = ::fread(buf, 1, sizeof buf, fp)) > 0) { + content.append(buf, nread); + } + ::fclose(fp); + } + return content; +} + +void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) +{ + LOG_INFO << "HighWaterMark " << len; +} + +void onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) { + LOG_INFO << "FileServer - Sending file " << g_file << " to " + << conn->peerAddress().toIpPort(); + conn->setHighWaterMarkCallback(onHighWaterMark, 64 * 1024); + string fileContent = readFile(g_file); + conn->send(fileContent); + conn->shutdown(); + LOG_INFO << "FileServer - done"; + } +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + g_file = argv[1]; + + EventLoop loop; + InetAddress listenAddr(2021); + TcpServer server(&loop, listenAddr, "FileServer"); + server.setConnectionCallback(onConnection); + server.start(); + loop.loop(); + } else { + fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); + } +} diff --git a/examples/filetransfer/download2.cc b/examples/filetransfer/download2.cc new file mode 100644 index 0000000..a1c6c40 --- /dev/null +++ b/examples/filetransfer/download2.cc @@ -0,0 +1,81 @@ +#include <stdio.h> +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) +{ + LOG_INFO << "HighWaterMark " << len; +} + +const int kBufSize = 64 * 1024; +const char* g_file = NULL; + +void onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) { + LOG_INFO << "FileServer - Sending file " << g_file << " to " + << conn->peerAddress().toIpPort(); + conn->setHighWaterMarkCallback(onHighWaterMark, kBufSize + 1); + + FILE* fp = ::fopen(g_file, "rb"); + if (fp) { + conn->setContext(fp); + char buf[kBufSize]; + size_t nread = ::fread(buf, 1, sizeof buf, fp); + conn->send(buf, static_cast<int>(nread)); + } else { + conn->shutdown(); + LOG_INFO << "FileServer - no such file"; + } + } else { + if (!conn->getContext().empty()) { + FILE* fp = boost::any_cast<FILE*>(conn->getContext()); + if (fp) { + ::fclose(fp); + } + } + } +} + +void onWriteComplete(const TcpConnectionPtr& conn) +{ + FILE* fp = boost::any_cast<FILE*>(conn->getContext()); + char buf[kBufSize]; + size_t nread = ::fread(buf, 1, sizeof buf, fp); + if (nread > 0) { + conn->send(buf, static_cast<int>(nread)); + } else { + ::fclose(fp); + fp = NULL; + conn->setContext(fp); + conn->shutdown(); + LOG_INFO << "FileServer - done"; + } +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + g_file = argv[1]; + + EventLoop loop; + InetAddress listenAddr(2021); + TcpServer server(&loop, listenAddr, "FileServer"); + server.setConnectionCallback(onConnection); + server.setWriteCompleteCallback(onWriteComplete); + server.start(); + loop.loop(); + } else { + fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); + } +} diff --git a/examples/filetransfer/download3.cc b/examples/filetransfer/download3.cc new file mode 100644 index 0000000..6962e6a --- /dev/null +++ b/examples/filetransfer/download3.cc @@ -0,0 +1,73 @@ +#include <stdio.h> +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) +{ + LOG_INFO << "HighWaterMark " << len; +} + +const int kBufSize = 64 * 1024; +const char* g_file = NULL; +typedef std::shared_ptr<FILE> FilePtr; + +void onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) { + LOG_INFO << "FileServer - Sending file " << g_file << " to " + << conn->peerAddress().toIpPort(); + conn->setHighWaterMarkCallback(onHighWaterMark, kBufSize + 1); + + FILE* fp = ::fopen(g_file, "rb"); + if (fp) { + FilePtr ctx(fp, ::fclose); + conn->setContext(ctx); + char buf[kBufSize]; + size_t nread = ::fread(buf, 1, sizeof buf, fp); + conn->send(buf, static_cast<int>(nread)); + } else { + conn->shutdown(); + LOG_INFO << "FileServer - no such file"; + } + } +} + +void onWriteComplete(const TcpConnectionPtr& conn) +{ + const FilePtr& fp = boost::any_cast<const FilePtr&>(conn->getContext()); + char buf[kBufSize]; + size_t nread = ::fread(buf, 1, sizeof buf, get_pointer(fp)); + if (nread > 0) { + conn->send(buf, static_cast<int>(nread)); + } else { + conn->shutdown(); + LOG_INFO << "FileServer - done"; + } +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + g_file = argv[1]; + + EventLoop loop; + InetAddress listenAddr(2021); + TcpServer server(&loop, listenAddr, "FileServer"); + server.setConnectionCallback(onConnection); + server.setWriteCompleteCallback(onWriteComplete); + server.start(); + loop.loop(); + } else { + fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); + } +} diff --git a/examples/filetransfer/loadtest/Client.java b/examples/filetransfer/loadtest/Client.java new file mode 100644 index 0000000..9ae0629 --- /dev/null +++ b/examples/filetransfer/loadtest/Client.java @@ -0,0 +1,62 @@ +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; + +public class Client { + + private static final class PipelineFactory implements ChannelPipelineFactory { + private final int kMinLength; + private final int kMaxLength; + private final CountDownLatch latch; + Random random = new Random(); + + private PipelineFactory(int kMinLength, int kMaxLength, CountDownLatch latch) { + this.kMinLength = kMinLength; + this.kMaxLength = kMaxLength; + this.latch = latch; + assert kMinLength <= kMaxLength; + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + int variance = random.nextInt(kMaxLength - kMinLength + 1); + int maxLength = kMinLength + variance; + return Channels.pipeline(new Handler(maxLength, latch)); + } + } + + static final int kClients = 500; + static final int kMB = 1024 * 1024; + static final int kMinLength = 1 * kMB; + static final int kMaxLength = 6 * kMB; + + public static void main(String[] args) throws Exception { + ChannelFactory channelFactory = new NioClientSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool()); + long start = System.currentTimeMillis(); + + final CountDownLatch latch = new CountDownLatch(kClients); + ChannelPipelineFactory pipelineFactory = new PipelineFactory(kMinLength, kMaxLength, latch); + for (int i = 0; i < kClients; ++i) { + ClientBootstrap bootstrap = new ClientBootstrap(channelFactory); + bootstrap.setPipelineFactory(pipelineFactory); + bootstrap.connect(new InetSocketAddress(args[0], 2021)); + } + + latch.await(); + + System.out.println(Thread.currentThread().getId() + " All done. " + + (System.currentTimeMillis() - start)); + System.exit(0); + } + +} diff --git a/examples/filetransfer/loadtest/Handler.java b/examples/filetransfer/loadtest/Handler.java new file mode 100644 index 0000000..115214d --- /dev/null +++ b/examples/filetransfer/loadtest/Handler.java @@ -0,0 +1,68 @@ +import java.math.BigInteger; +import java.security.MessageDigest; +import java.util.concurrent.CountDownLatch; + +import org.jboss.netty.buffer.BigEndianHeapChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; + +public class Handler extends SimpleChannelUpstreamHandler { + + private static int created = 0; + private int received = 0; + private final int maxLength; + private int id; + private CountDownLatch latch; + private MessageDigest digest; + + public Handler(int maxLength, CountDownLatch latch) throws Exception { + this.id = created++; + this.maxLength = maxLength; + this.latch = latch; + this.digest = MessageDigest.getInstance("MD5"); + System.out.println("Handler tid=" + Thread.currentThread().getId() + " " + id); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + System.out.println("channelConnected tid=" + Thread.currentThread().getId() + " " + id); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + byte[] md5 = digest.digest(); + BigInteger bigInt = new BigInteger(1, md5); + System.out.println("channelDisconnected tid=" + Thread.currentThread().getId() + " " + id + + " got " + + received + " " + bigInt.toString(16)); + latch.countDown(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + BigEndianHeapChannelBuffer message = (BigEndianHeapChannelBuffer) e.getMessage(); + // System.out.println("messageReceived " + ctx.getChannel() + message.readableBytes()); + received += message.readableBytes(); + digest.update(message.array(), message.readerIndex(), message.readableBytes()); + if (received > maxLength) { + System.out.println("messageReceived tid=" + Thread.currentThread().getId() + + " " + id + " got " + received); + ctx.getChannel().close(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + e.getCause().printStackTrace(); + + Channel ch = e.getChannel(); + ch.close(); + latch.countDown(); + } +} diff --git a/examples/hub/README b/examples/hub/README new file mode 100644 index 0000000..107c691 --- /dev/null +++ b/examples/hub/README @@ -0,0 +1,5 @@ +hub - a server for broadcasting +pubsub - a client library of hub +pub - a command line tool for publishing content on a topic +sub - a demo tool for subscribing a topic + diff --git a/examples/hub/codec.cc b/examples/hub/codec.cc new file mode 100644 index 0000000..67a462a --- /dev/null +++ b/examples/hub/codec.cc @@ -0,0 +1,53 @@ +#include "examples/hub/codec.h" + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +ParseResult pubsub::parseMessage(Buffer* buf, + string* cmd, + string* topic, + string* content) +{ + ParseResult result = kError; + const char* crlf = buf->findCRLF(); + if (crlf) + { + const char* space = std::find(buf->peek(), crlf, ' '); + if (space != crlf) + { + cmd->assign(buf->peek(), space); + topic->assign(space+1, crlf); + if (*cmd == "pub") + { + const char* start = crlf + 2; + crlf = buf->findCRLF(start); + if (crlf) + { + content->assign(start, crlf); + buf->retrieveUntil(crlf+2); + result = kSuccess; + } + else + { + result = kContinue; + } + } + else + { + buf->retrieveUntil(crlf+2); + result = kSuccess; + } + } + else + { + result = kError; + } + } + else + { + result = kContinue; + } + return result; +} + diff --git a/examples/hub/codec.h b/examples/hub/codec.h new file mode 100644 index 0000000..1fd3120 --- /dev/null +++ b/examples/hub/codec.h @@ -0,0 +1,27 @@ +#ifndef MUDUO_EXAMPLES_HUB_CODEC_H +#define MUDUO_EXAMPLES_HUB_CODEC_H + +// internal header file + +#include "muduo/base/Types.h" +#include "muduo/net/Buffer.h" + +namespace pubsub +{ +using muduo::string; + +enum ParseResult +{ + kError, + kSuccess, + kContinue, +}; + +ParseResult parseMessage(muduo::net::Buffer* buf, + string* cmd, + string* topic, + string* content); +} // namespace pubsub + +#endif // MUDUO_EXAMPLES_HUB_CODEC_H + diff --git a/examples/hub/hub.cc b/examples/hub/hub.cc new file mode 100644 index 0000000..a6faec2 --- /dev/null +++ b/examples/hub/hub.cc @@ -0,0 +1,217 @@ +#include "examples/hub/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <map> +#include <set> +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +namespace pubsub +{ + +typedef std::set<string> ConnectionSubscription; + +class Topic : public muduo::copyable +{ + public: + Topic(const string& topic) + : topic_(topic) + { + } + + void add(const TcpConnectionPtr& conn) + { + audiences_.insert(conn); + if (lastPubTime_.valid()) + { + conn->send(makeMessage()); + } + } + + void remove(const TcpConnectionPtr& conn) + { + audiences_.erase(conn); + } + + void publish(const string& content, Timestamp time) + { + content_ = content; + lastPubTime_ = time; + string message = makeMessage(); + for (std::set<TcpConnectionPtr>::iterator it = audiences_.begin(); + it != audiences_.end(); + ++it) + { + (*it)->send(message); + } + } + + private: + + string makeMessage() + { + return "pub " + topic_ + "\r\n" + content_ + "\r\n"; + } + + string topic_; + string content_; + Timestamp lastPubTime_; + std::set<TcpConnectionPtr> audiences_; +}; + +class PubSubServer : noncopyable +{ + public: + PubSubServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "PubSubServer") + { + server_.setConnectionCallback( + std::bind(&PubSubServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&PubSubServer::onMessage, this, _1, _2, _3)); + loop_->runEvery(1.0, std::bind(&PubSubServer::timePublish, this)); + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + conn->setContext(ConnectionSubscription()); + } + else + { + const ConnectionSubscription& connSub + = boost::any_cast<const ConnectionSubscription&>(conn->getContext()); + // subtle: doUnsubscribe will erase *it, so increase before calling. + for (ConnectionSubscription::const_iterator it = connSub.begin(); + it != connSub.end();) + { + doUnsubscribe(conn, *it++); + } + } + } + + void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) + { + ParseResult result = kSuccess; + while (result == kSuccess) + { + string cmd; + string topic; + string content; + result = parseMessage(buf, &cmd, &topic, &content); + if (result == kSuccess) + { + if (cmd == "pub") + { + doPublish(conn->name(), topic, content, receiveTime); + } + else if (cmd == "sub") + { + LOG_INFO << conn->name() << " subscribes " << topic; + doSubscribe(conn, topic); + } + else if (cmd == "unsub") + { + doUnsubscribe(conn, topic); + } + else + { + conn->shutdown(); + result = kError; + } + } + else if (result == kError) + { + conn->shutdown(); + } + } + } + + void timePublish() + { + Timestamp now = Timestamp::now(); + doPublish("internal", "utc_time", now.toFormattedString(), now); + } + + void doSubscribe(const TcpConnectionPtr& conn, + const string& topic) + { + ConnectionSubscription* connSub + = boost::any_cast<ConnectionSubscription>(conn->getMutableContext()); + + connSub->insert(topic); + getTopic(topic).add(conn); + } + + void doUnsubscribe(const TcpConnectionPtr& conn, + const string& topic) + { + LOG_INFO << conn->name() << " unsubscribes " << topic; + getTopic(topic).remove(conn); + // topic could be the one to be destroyed, so don't use it after erasing. + ConnectionSubscription* connSub + = boost::any_cast<ConnectionSubscription>(conn->getMutableContext()); + connSub->erase(topic); + } + + void doPublish(const string& source, + const string& topic, + const string& content, + Timestamp time) + { + getTopic(topic).publish(content, time); + } + + Topic& getTopic(const string& topic) + { + std::map<string, Topic>::iterator it = topics_.find(topic); + if (it == topics_.end()) + { + it = topics_.insert(make_pair(topic, Topic(topic))).first; + } + return it->second; + } + + EventLoop* loop_; + TcpServer server_; + std::map<string, Topic> topics_; +}; + +} // namespace pubsub + +int main(int argc, char* argv[]) +{ + if (argc > 1) + { + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + EventLoop loop; + if (argc > 2) + { + //int inspectPort = atoi(argv[2]); + } + pubsub::PubSubServer server(&loop, InetAddress(port)); + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s pubsub_port [inspect_port]\n", argv[0]); + } +} + diff --git a/examples/hub/pub.cc b/examples/hub/pub.cc new file mode 100644 index 0000000..4a50e2d --- /dev/null +++ b/examples/hub/pub.cc @@ -0,0 +1,82 @@ +#include "examples/hub/pubsub.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +#include <iostream> +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +EventLoop* g_loop = NULL; +string g_topic; +string g_content; + +void connection(PubSubClient* client) +{ + if (client->connected()) + { + client->publish(g_topic, g_content); + client->stop(); + } + else + { + g_loop->quit(); + } +} + +int main(int argc, char* argv[]) +{ + if (argc == 4) + { + string hostport = argv[1]; + size_t colon = hostport.find(':'); + if (colon != string::npos) + { + string hostip = hostport.substr(0, colon); + uint16_t port = static_cast<uint16_t>(atoi(hostport.c_str()+colon+1)); + g_topic = argv[2]; + g_content = argv[3]; + + string name = ProcessInfo::username()+"@"+ProcessInfo::hostname(); + name += ":" + ProcessInfo::pidString(); + + if (g_content == "-") + { + EventLoopThread loopThread; + g_loop = loopThread.startLoop(); + PubSubClient client(g_loop, InetAddress(hostip, port), name); + client.start(); + + string line; + while (getline(std::cin, line)) + { + client.publish(g_topic, line); + } + client.stop(); + CurrentThread::sleepUsec(1000*1000); + } + else + { + EventLoop loop; + g_loop = &loop; + PubSubClient client(g_loop, InetAddress(hostip, port), name); + client.setConnectionCallback(connection); + client.start(); + loop.loop(); + } + } + else + { + printf("Usage: %s hub_ip:port topic content\n", argv[0]); + } + } + else + { + printf("Usage: %s hub_ip:port topic content\n" + "Read contents from stdin:\n" + " %s hub_ip:port topic -\n", argv[0], argv[0]); + } +} diff --git a/examples/hub/pubsub.cc b/examples/hub/pubsub.cc new file mode 100644 index 0000000..1a6f1a7 --- /dev/null +++ b/examples/hub/pubsub.cc @@ -0,0 +1,106 @@ +#include "examples/hub/pubsub.h" +#include "examples/hub/codec.h" + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +PubSubClient::PubSubClient(EventLoop* loop, + const InetAddress& hubAddr, + const string& name) + : client_(loop, hubAddr, name) +{ + // FIXME: dtor is not thread safe + client_.setConnectionCallback( + std::bind(&PubSubClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&PubSubClient::onMessage, this, _1, _2, _3)); +} + +void PubSubClient::start() +{ + client_.connect(); +} + +void PubSubClient::stop() +{ + client_.disconnect(); +} + +bool PubSubClient::connected() const +{ + return conn_ && conn_->connected(); +} + +bool PubSubClient::subscribe(const string& topic, const SubscribeCallback& cb) +{ + string message = "sub " + topic + "\r\n"; + subscribeCallback_ = cb; + return send(message); +} + +void PubSubClient::unsubscribe(const string& topic) +{ + string message = "unsub " + topic + "\r\n"; + send(message); +} + + +bool PubSubClient::publish(const string& topic, const string& content) +{ + string message = "pub " + topic + "\r\n" + content + "\r\n"; + return send(message); +} + +void PubSubClient::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn_ = conn; + // FIXME: re-sub + } + else + { + conn_.reset(); + } + if (connectionCallback_) + { + connectionCallback_(this); + } +} + +void PubSubClient::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + ParseResult result = kSuccess; + while (result == kSuccess) + { + string cmd; + string topic; + string content; + result = parseMessage(buf, &cmd, &topic, &content); + if (result == kSuccess) + { + if (cmd == "pub" && subscribeCallback_) + { + subscribeCallback_(topic, content, receiveTime); + } + } + else if (result == kError) + { + conn->shutdown(); + } + } +} + +bool PubSubClient::send(const string& message) +{ + bool succeed = false; + if (conn_ && conn_->connected()) + { + conn_->send(message); + succeed = true; + } + return succeed; +} diff --git a/examples/hub/pubsub.h b/examples/hub/pubsub.h new file mode 100644 index 0000000..e6e5fc8 --- /dev/null +++ b/examples/hub/pubsub.h @@ -0,0 +1,47 @@ +#ifndef MUDUO_EXAMPLES_HUB_PUBSUB_H +#define MUDUO_EXAMPLES_HUB_PUBSUB_H + +#include "muduo/net/TcpClient.h" + +namespace pubsub +{ +using muduo::string; + +// FIXME: dtor is not thread safe +class PubSubClient : muduo::noncopyable +{ + public: + typedef std::function<void (PubSubClient*)> ConnectionCallback; + typedef std::function<void (const string& topic, + const string& content, + muduo::Timestamp)> SubscribeCallback; + + PubSubClient(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& hubAddr, + const string& name); + void start(); + void stop(); + bool connected() const; + + void setConnectionCallback(const ConnectionCallback& cb) + { connectionCallback_ = cb; } + + bool subscribe(const string& topic, const SubscribeCallback& cb); + void unsubscribe(const string& topic); + bool publish(const string& topic, const string& content); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp receiveTime); + bool send(const string& message); + + muduo::net::TcpClient client_; + muduo::net::TcpConnectionPtr conn_; + ConnectionCallback connectionCallback_; + SubscribeCallback subscribeCallback_; +}; +} // namespace pubsub + +#endif // MUDUO_EXAMPLES_HUB_PUBSUB_H diff --git a/examples/hub/sub.cc b/examples/hub/sub.cc new file mode 100644 index 0000000..f5cabd2 --- /dev/null +++ b/examples/hub/sub.cc @@ -0,0 +1,69 @@ +#include "examples/hub/pubsub.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" + +#include <vector> +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +EventLoop* g_loop = NULL; +std::vector<string> g_topics; + +void subscription(const string& topic, const string& content, Timestamp) +{ + printf("%s: %s\n", topic.c_str(), content.c_str()); +} + +void connection(PubSubClient* client) +{ + if (client->connected()) + { + for (std::vector<string>::iterator it = g_topics.begin(); + it != g_topics.end(); ++it) + { + client->subscribe(*it, subscription); + } + } + else + { + g_loop->quit(); + } +} + +int main(int argc, char* argv[]) +{ + if (argc > 2) + { + string hostport = argv[1]; + size_t colon = hostport.find(':'); + if (colon != string::npos) + { + string hostip = hostport.substr(0, colon); + uint16_t port = static_cast<uint16_t>(atoi(hostport.c_str()+colon+1)); + for (int i = 2; i < argc; ++i) + { + g_topics.push_back(argv[i]); + } + + EventLoop loop; + g_loop = &loop; + string name = ProcessInfo::username()+"@"+ProcessInfo::hostname(); + name += ":" + ProcessInfo::pidString(); + PubSubClient client(&loop, InetAddress(hostip, port), name); + client.setConnectionCallback(connection); + client.start(); + loop.loop(); + } + else + { + printf("Usage: %s hub_ip:port topic [topic ...]\n", argv[0]); + } + } + else + { + printf("Usage: %s hub_ip:port topic [topic ...]\n", argv[0]); + } +} diff --git a/examples/idleconnection/echo.cc b/examples/idleconnection/echo.cc new file mode 100644 index 0000000..48ccdad --- /dev/null +++ b/examples/idleconnection/echo.cc @@ -0,0 +1,99 @@ +#include "examples/idleconnection/echo.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <assert.h> +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int idleSeconds) + : server_(loop, listenAddr, "EchoServer"), + connectionBuckets_(idleSeconds) +{ + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + loop->runEvery(1.0, std::bind(&EchoServer::onTimer, this)); + connectionBuckets_.resize(idleSeconds); + dumpConnectionBuckets(); +} + +void EchoServer::start() +{ + server_.start(); +} + +void EchoServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + EntryPtr entry(new Entry(conn)); + connectionBuckets_.back().insert(entry); + dumpConnectionBuckets(); + WeakEntryPtr weakEntry(entry); + conn->setContext(weakEntry); + } + else + { + assert(!conn->getContext().empty()); + WeakEntryPtr weakEntry(boost::any_cast<WeakEntryPtr>(conn->getContext())); + LOG_DEBUG << "Entry use_count = " << weakEntry.use_count(); + } +} + +void EchoServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " echo " << msg.size() + << " bytes at " << time.toString(); + conn->send(msg); + + assert(!conn->getContext().empty()); + WeakEntryPtr weakEntry(boost::any_cast<WeakEntryPtr>(conn->getContext())); + EntryPtr entry(weakEntry.lock()); + if (entry) + { + connectionBuckets_.back().insert(entry); + dumpConnectionBuckets(); + } +} + +void EchoServer::onTimer() +{ + connectionBuckets_.push_back(Bucket()); + dumpConnectionBuckets(); +} + +void EchoServer::dumpConnectionBuckets() const +{ + LOG_INFO << "size = " << connectionBuckets_.size(); + int idx = 0; + for (WeakConnectionList::const_iterator bucketI = connectionBuckets_.begin(); + bucketI != connectionBuckets_.end(); + ++bucketI, ++idx) + { + const Bucket& bucket = *bucketI; + printf("[%d] len = %zd : ", idx, bucket.size()); + for (const auto& it : bucket) + { + bool connectionDead = it->weakConn_.expired(); + printf("%p(%ld)%s, ", get_pointer(it), it.use_count(), + connectionDead ? " DEAD" : ""); + } + puts(""); + } +} + diff --git a/examples/idleconnection/echo.h b/examples/idleconnection/echo.h new file mode 100644 index 0000000..475bebb --- /dev/null +++ b/examples/idleconnection/echo.h @@ -0,0 +1,55 @@ +#ifndef MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H +#define MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H + +#include "muduo/net/TcpServer.h" +// #include <muduo/base/Types.h> + +#include <boost/circular_buffer.hpp> +#include <unordered_set> + +// RFC 862 +class EchoServer { +public: + EchoServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr, int idleSeconds); + + void start(); + +private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, muduo::Timestamp time); + + void onTimer(); + + void dumpConnectionBuckets() const; + + typedef std::weak_ptr<muduo::net::TcpConnection> WeakTcpConnectionPtr; + + struct Entry : public muduo::copyable { + explicit Entry(const WeakTcpConnectionPtr& weakConn) + : weakConn_(weakConn) + { + } + + ~Entry() + { + muduo::net::TcpConnectionPtr conn = weakConn_.lock(); + if (conn) { + conn->shutdown(); + } + } + + WeakTcpConnectionPtr weakConn_; + }; + typedef std::shared_ptr<Entry> EntryPtr; + typedef std::weak_ptr<Entry> WeakEntryPtr; + typedef std::unordered_set<EntryPtr> Bucket; + typedef boost::circular_buffer<Bucket> WeakConnectionList; + + muduo::net::TcpServer server_; + WeakConnectionList connectionBuckets_; +}; + +#endif // MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H diff --git a/examples/idleconnection/main.cc b/examples/idleconnection/main.cc new file mode 100644 index 0000000..86a5184 --- /dev/null +++ b/examples/idleconnection/main.cc @@ -0,0 +1,40 @@ +#include <stdio.h> + +#include "examples/idleconnection/echo.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +/* +void testHash() +{ + boost::hash<std::shared_ptr<int> > h; + std::shared_ptr<int> x1(new int(10)); + std::shared_ptr<int> x2(new int(10)); + h(x1); + assert(h(x1) != h(x2)); + x1 = x2; + assert(h(x1) == h(x2)); + x1.reset(); + assert(h(x1) != h(x2)); + x2.reset(); + assert(h(x1) == h(x2)); +} +*/ + +int main(int argc, char* argv[]) +{ + // testHash(); + EventLoop loop; + InetAddress listenAddr(2007); + int idleSeconds = 10; + if (argc > 1) { + idleSeconds = atoi(argv[1]); + } + LOG_INFO << "pid = " << getpid() << ", idle seconds = " << idleSeconds; + EchoServer server(&loop, listenAddr, idleSeconds); + server.start(); + loop.loop(); +} diff --git a/examples/idleconnection/sortedlist.cc b/examples/idleconnection/sortedlist.cc new file mode 100644 index 0000000..cc200e8 --- /dev/null +++ b/examples/idleconnection/sortedlist.cc @@ -0,0 +1,179 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" +#include <list> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +// RFC 862 +class EchoServer +{ + public: + EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int idleSeconds); + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn); + + void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time); + + void onTimer(); + + void dumpConnectionList() const; + + typedef std::weak_ptr<TcpConnection> WeakTcpConnectionPtr; + typedef std::list<WeakTcpConnectionPtr> WeakConnectionList; + + struct Node : public muduo::copyable + { + Timestamp lastReceiveTime; + WeakConnectionList::iterator position; + }; + + TcpServer server_; + int idleSeconds_; + WeakConnectionList connectionList_; +}; + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int idleSeconds) + : server_(loop, listenAddr, "EchoServer"), + idleSeconds_(idleSeconds) +{ + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + loop->runEvery(1.0, std::bind(&EchoServer::onTimer, this)); + dumpConnectionList(); +} + +void EchoServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + Node node; + node.lastReceiveTime = Timestamp::now(); + connectionList_.push_back(conn); + node.position = --connectionList_.end(); + conn->setContext(node); + } + else + { + assert(!conn->getContext().empty()); + const Node& node = boost::any_cast<const Node&>(conn->getContext()); + connectionList_.erase(node.position); + } + dumpConnectionList(); +} + +void EchoServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " echo " << msg.size() + << " bytes at " << time.toString(); + conn->send(msg); + + assert(!conn->getContext().empty()); + Node* node = boost::any_cast<Node>(conn->getMutableContext()); + node->lastReceiveTime = time; + connectionList_.splice(connectionList_.end(), connectionList_, node->position); + assert(node->position == --connectionList_.end()); + + dumpConnectionList(); +} + +void EchoServer::onTimer() +{ + dumpConnectionList(); + Timestamp now = Timestamp::now(); + for (WeakConnectionList::iterator it = connectionList_.begin(); + it != connectionList_.end();) + { + TcpConnectionPtr conn = it->lock(); + if (conn) + { + Node* n = boost::any_cast<Node>(conn->getMutableContext()); + double age = timeDifference(now, n->lastReceiveTime); + if (age > idleSeconds_) + { + if (conn->connected()) + { + conn->shutdown(); + LOG_INFO << "shutting down " << conn->name(); + conn->forceCloseWithDelay(3.5); // > round trip of the whole Internet. + } + } + else if (age < 0) + { + LOG_WARN << "Time jump"; + n->lastReceiveTime = now; + } + else + { + break; + } + ++it; + } + else + { + LOG_WARN << "Expired"; + it = connectionList_.erase(it); + } + } +} + +void EchoServer::dumpConnectionList() const +{ + LOG_INFO << "size = " << connectionList_.size(); + + for (WeakConnectionList::const_iterator it = connectionList_.begin(); + it != connectionList_.end(); ++it) + { + TcpConnectionPtr conn = it->lock(); + if (conn) + { + printf("conn %p\n", get_pointer(conn)); + const Node& n = boost::any_cast<const Node&>(conn->getContext()); + printf(" time %s\n", n.lastReceiveTime.toString().c_str()); + } + else + { + printf("expired\n"); + } + } +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + InetAddress listenAddr(2007); + int idleSeconds = 10; + if (argc > 1) + { + idleSeconds = atoi(argv[1]); + } + LOG_INFO << "pid = " << getpid() << ", idle seconds = " << idleSeconds; + EchoServer server(&loop, listenAddr, idleSeconds); + server.start(); + loop.loop(); +} + diff --git a/examples/maxconnection/echo.cc b/examples/maxconnection/echo.cc new file mode 100644 index 0000000..721190b --- /dev/null +++ b/examples/maxconnection/echo.cc @@ -0,0 +1,56 @@ +#include "examples/maxconnection/echo.h" + +#include "muduo/base/Logging.h" + +using namespace muduo; +using namespace muduo::net; + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int maxConnections) + : server_(loop, listenAddr, "EchoServer"), + numConnected_(0), + kMaxConnections_(maxConnections) +{ + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); +} + +void EchoServer::start() +{ + server_.start(); +} + +void EchoServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + ++numConnected_; + if (numConnected_ > kMaxConnections_) + { + conn->shutdown(); + conn->forceCloseWithDelay(3.0); // > round trip of the whole Internet. + } + } + else + { + --numConnected_; + } + LOG_INFO << "numConnected = " << numConnected_; +} + +void EchoServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " echo " << msg.size() << " bytes at " << time.toString(); + conn->send(msg); +} + diff --git a/examples/maxconnection/echo.h b/examples/maxconnection/echo.h new file mode 100644 index 0000000..090cd6a --- /dev/null +++ b/examples/maxconnection/echo.h @@ -0,0 +1,28 @@ +#ifndef MUDUO_EXAMPLES_MAXCONNECTION_ECHO_H +#define MUDUO_EXAMPLES_MAXCONNECTION_ECHO_H + +#include "muduo/net/TcpServer.h" + +// RFC 862 +class EchoServer +{ + public: + EchoServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr, + int maxConnections); + + void start(); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp time); + + muduo::net::TcpServer server_; + int numConnected_; // should be atomic_int + const int kMaxConnections_; +}; + +#endif // MUDUO_EXAMPLES_MAXCONNECTION_ECHO_H diff --git a/examples/maxconnection/main.cc b/examples/maxconnection/main.cc new file mode 100644 index 0000000..25aaf9a --- /dev/null +++ b/examples/maxconnection/main.cc @@ -0,0 +1,26 @@ +#include "examples/maxconnection/echo.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2007); + int maxConnections = 5; + if (argc > 1) + { + maxConnections = atoi(argv[1]); + } + LOG_INFO << "maxConnections = " << maxConnections; + EchoServer server(&loop, listenAddr, maxConnections); + server.start(); + loop.loop(); +} + diff --git a/examples/memcached/README b/examples/memcached/README new file mode 100644 index 0000000..da9803b --- /dev/null +++ b/examples/memcached/README @@ -0,0 +1,19 @@ +Simple implementation of memcached protocol for both server and client side. +Not meant to replace memcached, but just sample code of network programming with muduo. + +Server limits: + - The memory management is not customized, just uses (tc)malloc. + - It doesn't control the memory footprint + - Unix domain socket is not supported + - Only listen on one TCP port + +Server goals: + - Pass as many feature tests as possible + - Prefer simplicity over performance + +TODO: + - incr/decr + - UDP + - Binary protocol + - expiration + - LRU diff --git a/examples/memcached/client/bench.cc b/examples/memcached/client/bench.cc new file mode 100644 index 0000000..c991898 --- /dev/null +++ b/examples/memcached/client/bench.cc @@ -0,0 +1,239 @@ +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/TcpClient.h" + +#include <boost/program_options.hpp> +#include <iostream> + +#include <stdio.h> + +namespace po = boost::program_options; +using namespace muduo; +using namespace muduo::net; + +class Client : noncopyable +{ + public: + enum Operation + { + kGet, + kSet, + }; + + Client(const string& name, + EventLoop* loop, + const InetAddress& serverAddr, + Operation op, + int requests, + int keys, + int valuelen, + CountDownLatch* connected, + CountDownLatch* finished) + : name_(name), + client_(loop, serverAddr, name), + op_(op), + sent_(0), + acked_(0), + requests_(requests), + keys_(keys), + valuelen_(valuelen), + value_(valuelen_, 'a'), + connected_(connected), + finished_(finished) + { + value_ += "\r\n"; + client_.setConnectionCallback(std::bind(&Client::onConnection, this, _1)); + client_.setMessageCallback(std::bind(&Client::onMessage, this, _1, _2, _3)); + client_.connect(); + } + + void send() + { + Buffer buf; + fill(&buf); + conn_->send(&buf); + } + + private: + + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + conn_ = conn; + connected_->countDown(); + } + else + { + conn_.reset(); + client_.getLoop()->queueInLoop(std::bind(&CountDownLatch::countDown, finished_)); + } + } + + void onMessage(const TcpConnectionPtr& conn, + Buffer* buffer, + Timestamp receiveTime) + { + if (op_ == kSet) + { + while (buffer->readableBytes() > 0) + { + const char* crlf = buffer->findCRLF(); + if (crlf) + { + buffer->retrieveUntil(crlf+2); + ++acked_; + if (sent_ < requests_) + { + send(); + } + } + else + { + break; + } + } + } + else + { + while (buffer->readableBytes() > 0) + { + const char* end = static_cast<const char*>(memmem(buffer->peek(), + buffer->readableBytes(), + "END\r\n", 5)); + if (end) + { + buffer->retrieveUntil(end+5); + ++acked_; + if (sent_ < requests_) + { + send(); + } + } + else + { + break; + } + } + } + if (acked_ == requests_) + { + conn_->shutdown(); + } + } + + void fill(Buffer* buf) + { + char req[256]; + if (op_ == kSet) + { + snprintf(req, sizeof req, "set %s%d 42 0 %d\r\n", name_.c_str(), sent_ % keys_, valuelen_); + ++sent_; + buf->append(req); + buf->append(value_); + } + else + { + snprintf(req, sizeof req, "get %s%d\r\n", name_.c_str(), sent_ % keys_); + ++sent_; + buf->append(req); + } + } + + string name_; + TcpClient client_; + TcpConnectionPtr conn_; + const Operation op_; + int sent_; + int acked_; + const int requests_; + const int keys_; + const int valuelen_; + string value_; + CountDownLatch* const connected_; + CountDownLatch* const finished_; +}; + +int main(int argc, char* argv[]) +{ + Logger::setLogLevel(Logger::WARN); + + uint16_t tcpport = 11211; + string hostIp = "127.0.0.1"; + int threads = 4; + int clients = 100; + int requests = 100000; + int keys = 10000; + bool set = false; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "Help") + ("port,p", po::value<uint16_t>(&tcpport), "TCP port") + ("ip,i", po::value<string>(&hostIp), "Host IP") + ("threads,t", po::value<int>(&threads), "Number of worker threads") + ("clients,c", po::value<int>(&clients), "Number of concurrent clients") + ("requests,r", po::value<int>(&requests), "Number of requests per clients") + ("keys,k", po::value<int>(&keys), "Number of keys per clients") + ("set,s", "Get or Set") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) + { + std::cout << desc << "\n"; + return 0; + } + set = vm.count("set"); + + InetAddress serverAddr(hostIp, tcpport); + LOG_WARN << "Connecting " << serverAddr.toIpPort(); + + EventLoop loop; + EventLoopThreadPool pool(&loop, "bench-memcache"); + + int valuelen = 100; + Client::Operation op = set ? Client::kSet : Client::kGet; + + double memoryMiB = 1.0 * clients * keys * (32+80+valuelen+8) / 1024 / 1024; + LOG_WARN << "estimated memcached-debug memory usage " << int(memoryMiB) << " MiB"; + + pool.setThreadNum(threads); + pool.start(); + + char buf[32]; + CountDownLatch connected(clients); + CountDownLatch finished(clients); + std::vector<std::unique_ptr<Client>> holder; + for (int i = 0; i < clients; ++i) + { + snprintf(buf, sizeof buf, "%d-", i+1); + holder.emplace_back(new Client(buf, + pool.getNextLoop(), + serverAddr, + op, + requests, + keys, + valuelen, + &connected, + &finished)); + } + connected.wait(); + LOG_WARN << clients << " clients all connected"; + Timestamp start = Timestamp::now(); + for (int i = 0; i < clients; ++i) + { + holder[i]->send(); + } + finished.wait(); + Timestamp end = Timestamp::now(); + LOG_WARN << "All finished"; + double seconds = timeDifference(end, start); + LOG_WARN << seconds << " sec"; + LOG_WARN << 1.0 * clients * requests / seconds << " QPS"; +} diff --git a/examples/memcached/server/Item.cc b/examples/memcached/server/Item.cc new file mode 100644 index 0000000..f3cc089 --- /dev/null +++ b/examples/memcached/server/Item.cc @@ -0,0 +1,63 @@ +#include "examples/memcached/server/Item.h" + +#include "muduo/base/LogStream.h" +#include "muduo/net/Buffer.h" + +#include <boost/functional/hash/hash.hpp> + +#include <string.h> // memcpy +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +Item::Item(StringPiece keyArg, + uint32_t flagsArg, + int exptimeArg, + int valuelen, + uint64_t casArg) + : keylen_(keyArg.size()), + flags_(flagsArg), + rel_exptime_(exptimeArg), + valuelen_(valuelen), + receivedBytes_(0), + cas_(casArg), + hash_(boost::hash_range(keyArg.begin(), keyArg.end())), + data_(static_cast<char*>(::malloc(totalLen()))) +{ + assert(valuelen_ >= 2); + assert(receivedBytes_ < totalLen()); + append(keyArg.data(), keylen_); +} + +void Item::append(const char* data, size_t len) +{ + assert(len <= neededBytes()); + memcpy(data_ + receivedBytes_, data, len); + receivedBytes_ += static_cast<int>(len); + assert(receivedBytes_ <= totalLen()); +} + +void Item::output(Buffer* out, bool needCas) const +{ + out->append("VALUE "); + out->append(data_, keylen_); + LogStream buf; + buf << ' ' << flags_ << ' ' << valuelen_-2; + if (needCas) + { + buf << ' ' << cas_; + } + buf << "\r\n"; + out->append(buf.buffer().data(), buf.buffer().length()); + out->append(value(), valuelen_); +} + +void Item::resetKey(StringPiece k) +{ + assert(k.size() <= 250); + keylen_ = k.size(); + receivedBytes_ = 0; + append(k.data(), k.size()); + hash_ = boost::hash_range(k.begin(), k.end()); +} diff --git a/examples/memcached/server/Item.h b/examples/memcached/server/Item.h new file mode 100644 index 0000000..7fa865f --- /dev/null +++ b/examples/memcached/server/Item.h @@ -0,0 +1,129 @@ +#ifndef MUDUO_EXAMPLES_MEMCACHED_SERVER_ITEM_H +#define MUDUO_EXAMPLES_MEMCACHED_SERVER_ITEM_H + +#include "muduo/base/Atomic.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" + +#include <memory> + +namespace muduo +{ +namespace net +{ +class Buffer; +} +} + +class Item; +typedef std::shared_ptr<Item> ItemPtr; // TODO: use unique_ptr +typedef std::shared_ptr<const Item> ConstItemPtr; // TODO: use unique_ptr + +// Item is immutable once added into hash table +class Item : muduo::noncopyable +{ + public: + enum UpdatePolicy + { + kInvalid, + kSet, + kAdd, + kReplace, + kAppend, + kPrepend, + kCas, + }; + + static ItemPtr makeItem(muduo::StringPiece keyArg, + uint32_t flagsArg, + int exptimeArg, + int valuelen, + uint64_t casArg) + { + return std::make_shared<Item>(keyArg, flagsArg, exptimeArg, valuelen, casArg); + //return ItemPtr(new Item(keyArg, flagsArg, exptimeArg, valuelen, casArg)); + } + + Item(muduo::StringPiece keyArg, + uint32_t flagsArg, + int exptimeArg, + int valuelen, + uint64_t casArg); + + ~Item() + { + ::free(data_); + } + + muduo::StringPiece key() const + { + return muduo::StringPiece(data_, keylen_); + } + + uint32_t flags() const + { + return flags_; + } + + int rel_exptime() const + { + return rel_exptime_; + } + + const char* value() const + { + return data_+keylen_; + } + + size_t valueLength() const + { + return valuelen_; + } + + uint64_t cas() const + { + return cas_; + } + + size_t hash() const + { + return hash_; + } + + void setCas(uint64_t casArg) + { + cas_ = casArg; + } + + size_t neededBytes() const + { + return totalLen() - receivedBytes_; + } + + void append(const char* data, size_t len); + + bool endsWithCRLF() const + { + return receivedBytes_ == totalLen() + && data_[totalLen()-2] == '\r' + && data_[totalLen()-1] == '\n'; + } + + void output(muduo::net::Buffer* out, bool needCas = false) const; + + void resetKey(muduo::StringPiece k); + + private: + int totalLen() const { return keylen_ + valuelen_; } + + int keylen_; + const uint32_t flags_; + const int rel_exptime_; + const int valuelen_; + int receivedBytes_; // FIXME: remove this member + uint64_t cas_; + size_t hash_; + char* data_; +}; + +#endif // MUDUO_EXAMPLES_MEMCACHED_SERVER_ITEM_H diff --git a/examples/memcached/server/MemcacheServer.cc b/examples/memcached/server/MemcacheServer.cc new file mode 100644 index 0000000..cb874bf --- /dev/null +++ b/examples/memcached/server/MemcacheServer.cc @@ -0,0 +1,174 @@ +#include "examples/memcached/server/MemcacheServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +muduo::AtomicInt64 g_cas; + +MemcacheServer::Options::Options() +{ + memZero(this, sizeof(*this)); +} + +struct MemcacheServer::Stats +{ +}; + +MemcacheServer::MemcacheServer(muduo::net::EventLoop* loop, const Options& options) + : loop_(loop), + options_(options), + startTime_(::time(NULL)-1), + server_(loop, InetAddress(options.tcpport), "muduo-memcached"), + stats_(new Stats) +{ + server_.setConnectionCallback( + std::bind(&MemcacheServer::onConnection, this, _1)); +} + +MemcacheServer::~MemcacheServer() = default; + +void MemcacheServer::start() +{ + server_.start(); +} + +void MemcacheServer::stop() +{ + loop_->runAfter(3.0, std::bind(&EventLoop::quit, loop_)); +} + +bool MemcacheServer::storeItem(const ItemPtr& item, const Item::UpdatePolicy policy, bool* exists) +{ + assert(item->neededBytes() == 0); + MutexLock& mutex = shards_[item->hash() % kShards].mutex; + ItemMap& items = shards_[item->hash() % kShards].items; + MutexLockGuard lock(mutex); + ItemMap::const_iterator it = items.find(item); + *exists = it != items.end(); + if (policy == Item::kSet) + { + item->setCas(g_cas.incrementAndGet()); + if (*exists) + { + items.erase(it); + } + items.insert(item); + } + else + { + if (policy == Item::kAdd) + { + if (*exists) + { + return false; + } + else + { + item->setCas(g_cas.incrementAndGet()); + items.insert(item); + } + } + else if (policy == Item::kReplace) + { + if (*exists) + { + item->setCas(g_cas.incrementAndGet()); + items.erase(it); + items.insert(item); + } + else + { + return false; + } + } + else if (policy == Item::kAppend || policy == Item::kPrepend) + { + if (*exists) + { + const ConstItemPtr& oldItem = *it; + int newLen = static_cast<int>(item->valueLength() + oldItem->valueLength() - 2); + ItemPtr newItem(Item::makeItem(item->key(), + oldItem->flags(), + oldItem->rel_exptime(), + newLen, + g_cas.incrementAndGet())); + if (policy == Item::kAppend) + { + newItem->append(oldItem->value(), oldItem->valueLength() - 2); + newItem->append(item->value(), item->valueLength()); + } + else + { + newItem->append(item->value(), item->valueLength() - 2); + newItem->append(oldItem->value(), oldItem->valueLength()); + } + assert(newItem->neededBytes() == 0); + assert(newItem->endsWithCRLF()); + items.erase(it); + items.insert(newItem); + } + else + { + return false; + } + } + else if (policy == Item::kCas) + { + if (*exists && (*it)->cas() == item->cas()) + { + item->setCas(g_cas.incrementAndGet()); + items.erase(it); + items.insert(item); + } + else + { + return false; + } + } + else + { + assert(false); + } + } + return true; +} + +ConstItemPtr MemcacheServer::getItem(const ConstItemPtr& key) const +{ + MutexLock& mutex = shards_[key->hash() % kShards].mutex; + const ItemMap& items = shards_[key->hash() % kShards].items; + MutexLockGuard lock(mutex); + ItemMap::const_iterator it = items.find(key); + return it != items.end() ? *it : ConstItemPtr(); +} + +bool MemcacheServer::deleteItem(const ConstItemPtr& key) +{ + MutexLock& mutex = shards_[key->hash() % kShards].mutex; + ItemMap& items = shards_[key->hash() % kShards].items; + MutexLockGuard lock(mutex); + return items.erase(key) == 1; +} + +void MemcacheServer::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + SessionPtr session(new Session(this, conn)); + MutexLockGuard lock(mutex_); + assert(sessions_.find(conn->name()) == sessions_.end()); + sessions_[conn->name()] = session; + // assert(sessions_.size() == stats_.current_conns); + } + else + { + MutexLockGuard lock(mutex_); + assert(sessions_.find(conn->name()) != sessions_.end()); + sessions_.erase(conn->name()); + // assert(sessions_.size() == stats_.current_conns); + } +} diff --git a/examples/memcached/server/MemcacheServer.h b/examples/memcached/server/MemcacheServer.h new file mode 100644 index 0000000..a418f01 --- /dev/null +++ b/examples/memcached/server/MemcacheServer.h @@ -0,0 +1,87 @@ +#ifndef MUDUO_EXAMPLES_MEMCACHED_SERVER_MEMCACHESERVER_H +#define MUDUO_EXAMPLES_MEMCACHED_SERVER_MEMCACHESERVER_H + +#include "examples/memcached/server/Item.h" +#include "examples/memcached/server/Session.h" + +#include "muduo/base/Mutex.h" +#include "muduo/net/TcpServer.h" +#include "examples/wordcount/hash.h" + +#include <array> +#include <unordered_map> +#include <unordered_set> + +class MemcacheServer : muduo::noncopyable +{ + public: + struct Options + { + Options(); + uint16_t tcpport; + uint16_t udpport; + uint16_t gperfport; + int threads; + }; + + MemcacheServer(muduo::net::EventLoop* loop, const Options&); + ~MemcacheServer(); + + void setThreadNum(int threads) { server_.setThreadNum(threads); } + void start(); + void stop(); + + time_t startTime() const { return startTime_; } + + bool storeItem(const ItemPtr& item, Item::UpdatePolicy policy, bool* exists); + ConstItemPtr getItem(const ConstItemPtr& key) const; + bool deleteItem(const ConstItemPtr& key); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + struct Stats; + + muduo::net::EventLoop* loop_; // not own + Options options_; + const time_t startTime_; + + mutable muduo::MutexLock mutex_; + std::unordered_map<string, SessionPtr> sessions_ GUARDED_BY(mutex_); + + // a complicated solution to save memory + struct Hash + { + size_t operator()(const ConstItemPtr& x) const + { + return x->hash(); + } + }; + + struct Equal + { + bool operator()(const ConstItemPtr& x, const ConstItemPtr& y) const + { + return x->key() == y->key(); + } + }; + + typedef std::unordered_set<ConstItemPtr, Hash, Equal> ItemMap; + + struct MapWithLock + { + ItemMap items; + mutable muduo::MutexLock mutex; + }; + + const static int kShards = 4096; + + std::array<MapWithLock, kShards> shards_; + + // NOT guarded by mutex_, but here because server_ has to destructs before + // sessions_ + muduo::net::TcpServer server_; + std::unique_ptr<Stats> stats_ PT_GUARDED_BY(mutex_); +}; + +#endif // MUDUO_EXAMPLES_MEMCACHED_SERVER_MEMCACHESERVER_H diff --git a/examples/memcached/server/Session.cc b/examples/memcached/server/Session.cc new file mode 100644 index 0000000..58a0aff --- /dev/null +++ b/examples/memcached/server/Session.cc @@ -0,0 +1,423 @@ +#include "examples/memcached/server/Session.h" +#include "examples/memcached/server/MemcacheServer.h" + +#ifdef HAVE_TCMALLOC +#include <gperftools/malloc_extension.h> +#endif + +using namespace muduo; +using namespace muduo::net; + +static bool isBinaryProtocol(uint8_t firstByte) +{ + return firstByte == 0x80; +} + +const int kLongestKeySize = 250; +string Session::kLongestKey(kLongestKeySize, 'x'); + +template <typename InputIterator, typename Token> +bool Session::SpaceSeparator::operator()(InputIterator& next, InputIterator end, Token& tok) +{ + while (next != end && *next == ' ') + ++next; + if (next == end) + { + tok.clear(); + return false; + } + InputIterator start(next); + const char* sp = static_cast<const char*>(memchr(start, ' ', end - start)); + if (sp) + { + tok.set(start, static_cast<int>(sp - start)); + next = sp; + } + else + { + tok.set(start, static_cast<int>(end - next)); + next = end; + } + return true; +} + +struct Session::Reader +{ + Reader(Tokenizer::iterator& beg, Tokenizer::iterator end) + : first_(beg), + last_(end) + { + } + + template<typename T> + bool read(T* val) + { + if (first_ == last_) + return false; + char* end = NULL; + uint64_t x = strtoull((*first_).data(), &end, 10); + if (end == (*first_).end()) + { + *val = static_cast<T>(x); + ++first_; + return true; + } + return false; + } + + private: + Tokenizer::iterator first_; + Tokenizer::iterator last_;; +}; + +void Session::onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp) + +{ + const size_t initialReadable = buf->readableBytes(); + + while (buf->readableBytes() > 0) + { + if (state_ == kNewCommand) + { + if (protocol_ == kAuto) + { + assert(bytesRead_ == 0); + protocol_ = isBinaryProtocol(buf->peek()[0]) ? kBinary : kAscii; + } + + assert(protocol_ == kAscii || protocol_ == kBinary); + if (protocol_ == kBinary) + { + // FIXME + } + else // ASCII protocol + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + int len = static_cast<int>(crlf - buf->peek()); + StringPiece request(buf->peek(), len); + if (processRequest(request)) + { + resetRequest(); + } + buf->retrieveUntil(crlf + 2); + } + else + { + if (buf->readableBytes() > 1024) + { + // FIXME: check for 'get' and 'gets' + conn_->shutdown(); + // buf->retrieveAll() ??? + } + break; + } + } + } + else if (state_ == kReceiveValue) + { + receiveValue(buf); + } + else if (state_ == kDiscardValue) + { + discardValue(buf); + } + else + { + assert(false); + } + } + bytesRead_ += initialReadable - buf->readableBytes(); +} + +void Session::receiveValue(muduo::net::Buffer* buf) +{ + assert(currItem_.get()); + assert(state_ == kReceiveValue); + // if (protocol_ == kBinary) + + const size_t avail = std::min(buf->readableBytes(), currItem_->neededBytes()); + assert(currItem_.unique()); + currItem_->append(buf->peek(), avail); + buf->retrieve(avail); + if (currItem_->neededBytes() == 0) + { + if (currItem_->endsWithCRLF()) + { + bool exists = false; + if (owner_->storeItem(currItem_, policy_, &exists)) + { + reply("STORED\r\n"); + } + else + { + if (policy_ == Item::kCas) + { + if (exists) + { + reply("EXISTS\r\n"); + } + else + { + reply("NOT_FOUND\r\n"); + } + } + else + { + reply("NOT_STORED\r\n"); + } + } + } + else + { + reply("CLIENT_ERROR bad data chunk\r\n"); + } + resetRequest(); + state_ = kNewCommand; + } +} + +void Session::discardValue(muduo::net::Buffer* buf) +{ + assert(!currItem_); + assert(state_ == kDiscardValue); + if (buf->readableBytes() < bytesToDiscard_) + { + bytesToDiscard_ -= buf->readableBytes(); + buf->retrieveAll(); + } + else + { + buf->retrieve(bytesToDiscard_); + bytesToDiscard_ = 0; + resetRequest(); + state_ = kNewCommand; + } +} + +bool Session::processRequest(StringPiece request) +{ + assert(command_.empty()); + assert(!noreply_); + assert(policy_ == Item::kInvalid); + assert(!currItem_); + assert(bytesToDiscard_ == 0); + ++requestsProcessed_; + + // check 'noreply' at end of request line + if (request.size() >= 8) + { + StringPiece end(request.end() - 8, 8); + if (end == " noreply") + { + noreply_ = true; + request.remove_suffix(8); + } + } + + SpaceSeparator sep; + Tokenizer tok(request.begin(), request.end(), sep); + Tokenizer::iterator beg = tok.begin(); + if (beg == tok.end()) + { + reply("ERROR\r\n"); + return true; + } + (*beg).CopyToString(&command_); + ++beg; + if (command_ == "set" || command_ == "add" || command_ == "replace" + || command_ == "append" || command_ == "prepend" || command_ == "cas") + { + // this normally returns false + return doUpdate(beg, tok.end()); + } + else if (command_ == "get" || command_ == "gets") + { + bool cas = command_ == "gets"; + + // FIXME: send multiple chunks with write complete callback. + while (beg != tok.end()) + { + StringPiece key = *beg; + bool good = key.size() <= kLongestKeySize; + if (!good) + { + reply("CLIENT_ERROR bad command line format\r\n"); + return true; + } + + needle_->resetKey(key); + ConstItemPtr item = owner_->getItem(needle_); + ++beg; + if (item) + { + item->output(&outputBuf_, cas); + } + } + outputBuf_.append("END\r\n"); + + if (conn_->outputBuffer()->writableBytes() > 65536 + outputBuf_.readableBytes()) + { + LOG_DEBUG << "shrink output buffer from " << conn_->outputBuffer()->internalCapacity(); + conn_->outputBuffer()->shrink(65536 + outputBuf_.readableBytes()); + } + + conn_->send(&outputBuf_); + } + else if (command_ == "delete") + { + doDelete(beg, tok.end()); + } + else if (command_ == "version") + { +#ifdef HAVE_TCMALLOC + reply("VERSION 0.01 muduo with tcmalloc\r\n"); +#else + reply("VERSION 0.01 muduo\r\n"); +#endif + } +#ifdef HAVE_TCMALLOC + else if (command_ == "memstat") + { + char buf[1024*64]; + MallocExtension::instance()->GetStats(buf, sizeof buf); + reply(buf); + } +#endif + else if (command_ == "quit") + { + conn_->shutdown(); + } + else if (command_ == "shutdown") + { + // "ERROR: shutdown not enabled" + conn_->shutdown(); + owner_->stop(); + } + else + { + reply("ERROR\r\n"); + LOG_INFO << "Unknown command: " << command_; + } + return true; +} + +void Session::resetRequest() +{ + command_.clear(); + noreply_ = false; + policy_ = Item::kInvalid; + currItem_.reset(); + bytesToDiscard_ = 0; +} + +void Session::reply(muduo::StringPiece msg) +{ + if (!noreply_) + { + conn_->send(msg.data(), msg.size()); + } +} + +bool Session::doUpdate(Session::Tokenizer::iterator& beg, Session::Tokenizer::iterator end) +{ + if (command_ == "set") + policy_ = Item::kSet; + else if (command_ == "add") + policy_ = Item::kAdd; + else if (command_ == "replace") + policy_ = Item::kReplace; + else if (command_ == "append") + policy_ = Item::kAppend; + else if (command_ == "prepend") + policy_ = Item::kPrepend; + else if (command_ == "cas") + policy_ = Item::kCas; + else + assert(false); + + // FIXME: check (beg != end) + StringPiece key = (*beg); + ++beg; + bool good = key.size() <= kLongestKeySize; + + uint32_t flags = 0; + time_t exptime = 1; + int bytes = -1; + uint64_t cas = 0; + + Reader r(beg, end); + good = good && r.read(&flags) && r.read(&exptime) && r.read(&bytes); + + int rel_exptime = static_cast<int>(exptime); + if (exptime > 60*60*24*30) + { + rel_exptime = static_cast<int>(exptime - owner_->startTime()); + if (rel_exptime < 1) + { + rel_exptime = 1; + } + } + else + { + // rel_exptime = exptime + currentTime; + } + + if (good && policy_ == Item::kCas) + { + good = r.read(&cas); + } + + if (!good) + { + reply("CLIENT_ERROR bad command line format\r\n"); + return true; + } + if (bytes > 1024*1024) + { + reply("SERVER_ERROR object too large for cache\r\n"); + needle_->resetKey(key); + owner_->deleteItem(needle_); + bytesToDiscard_ = bytes + 2; + state_ = kDiscardValue; + return false; + } + else + { + currItem_ = Item::makeItem(key, flags, rel_exptime, bytes + 2, cas); + state_ = kReceiveValue; + return false; + } +} + +void Session::doDelete(Session::Tokenizer::iterator& beg, Session::Tokenizer::iterator end) +{ + assert(command_ == "delete"); + // FIXME: check (beg != end) + StringPiece key = *beg; + bool good = key.size() <= kLongestKeySize; + ++beg; + if (!good) + { + reply("CLIENT_ERROR bad command line format\r\n"); + } + else if (beg != end && *beg != "0") // issue 108, old protocol + { + reply("CLIENT_ERROR bad command line format. Usage: delete <key> [noreply]\r\n"); + } + else + { + needle_->resetKey(key); + if (owner_->deleteItem(needle_)) + { + reply("DELETED\r\n"); + } + else + { + reply("NOT_FOUND\r\n"); + } + } +} diff --git a/examples/memcached/server/Session.h b/examples/memcached/server/Session.h new file mode 100644 index 0000000..82ef13c --- /dev/null +++ b/examples/memcached/server/Session.h @@ -0,0 +1,114 @@ +#ifndef MUDUO_EXAMPLES_MEMCACHED_SERVER_SESSION_H +#define MUDUO_EXAMPLES_MEMCACHED_SERVER_SESSION_H + +#include "examples/memcached/server/Item.h" + +#include "muduo/base/Logging.h" + +#include "muduo/net/TcpConnection.h" + +#include <boost/tokenizer.hpp> + +using muduo::string; + +class MemcacheServer; + +class Session : public std::enable_shared_from_this<Session>, + muduo::noncopyable +{ + public: + Session(MemcacheServer* owner, const muduo::net::TcpConnectionPtr& conn) + : owner_(owner), + conn_(conn), + state_(kNewCommand), + protocol_(kAscii), // FIXME + noreply_(false), + policy_(Item::kInvalid), + bytesToDiscard_(0), + needle_(Item::makeItem(kLongestKey, 0, 0, 2, 0)), + bytesRead_(0), + requestsProcessed_(0) + { + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + + conn_->setMessageCallback( + std::bind(&Session::onMessage, this, _1, _2, _3)); + } + + ~Session() + { + LOG_INFO << "requests processed: " << requestsProcessed_ + << " input buffer size: " << conn_->inputBuffer()->internalCapacity() + << " output buffer size: " << conn_->outputBuffer()->internalCapacity(); + } + + private: + enum State + { + kNewCommand, + kReceiveValue, + kDiscardValue, + }; + + enum Protocol + { + kAscii, + kBinary, + kAuto, + }; + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp); + void onWriteComplete(const muduo::net::TcpConnectionPtr& conn); + void receiveValue(muduo::net::Buffer* buf); + void discardValue(muduo::net::Buffer* buf); + // TODO: highWaterMark + // TODO: onWriteComplete + + // returns true if finished a request + bool processRequest(muduo::StringPiece request); + void resetRequest(); + void reply(muduo::StringPiece msg); + + struct SpaceSeparator + { + void reset() {} + template <typename InputIterator, typename Token> + bool operator()(InputIterator& next, InputIterator end, Token& tok); + }; + + typedef boost::tokenizer<SpaceSeparator, + const char*, + muduo::StringPiece> Tokenizer; + struct Reader; + bool doUpdate(Tokenizer::iterator& beg, Tokenizer::iterator end); + void doDelete(Tokenizer::iterator& beg, Tokenizer::iterator end); + + MemcacheServer* owner_; + muduo::net::TcpConnectionPtr conn_; + State state_; + Protocol protocol_; + + // current request + string command_; + bool noreply_; + Item::UpdatePolicy policy_; + ItemPtr currItem_; + size_t bytesToDiscard_; + // cached + ItemPtr needle_; + muduo::net::Buffer outputBuf_; + + // per session stats + size_t bytesRead_; + size_t requestsProcessed_; + + static string kLongestKey; +}; + +typedef std::shared_ptr<Session> SessionPtr; + +#endif // MUDUO_EXAMPLES_MEMCACHED_SERVER_SESSION_H diff --git a/examples/memcached/server/footprint_test.cc b/examples/memcached/server/footprint_test.cc new file mode 100644 index 0000000..9f2bace --- /dev/null +++ b/examples/memcached/server/footprint_test.cc @@ -0,0 +1,66 @@ +#include "examples/memcached/server/MemcacheServer.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/inspect/ProcessInspector.h" + +#include <stdio.h> +#ifdef HAVE_TCMALLOC +#include <gperftools/heap-profiler.h> +#include <gperftools/malloc_extension.h> +#endif + +using namespace muduo::net; + +int main(int argc, char* argv[]) +{ +#ifdef HAVE_TCMALLOC + MallocExtension::Initialize(); +#endif + int items = argc > 1 ? atoi(argv[1]) : 10000; + int keylen = argc > 2 ? atoi(argv[2]) : 10; + int valuelen = argc > 3 ? atoi(argv[3]) : 100; + EventLoop loop; + MemcacheServer::Options options; + MemcacheServer server(&loop, options); + + printf("sizeof(Item) = %zd\npid = %d\nitems = %d\nkeylen = %d\nvaluelen = %d\n", + sizeof(Item), getpid(), items, keylen, valuelen); + char key[256] = { 0 }; + string value; + for (int i = 0; i < items; ++i) + { + snprintf(key, sizeof key, "%0*d", keylen, i); + value.assign(valuelen, "0123456789"[i % 10]); + ItemPtr item(Item::makeItem(key, 0, 0, valuelen+2, 1)); + item->append(value.data(), value.size()); + item->append("\r\n", 2); + assert(item->endsWithCRLF()); + bool exists = false; + bool stored = server.storeItem(item, Item::kAdd, &exists); + assert(stored); (void) stored; + assert(!exists); + } + Inspector::ArgList arg; + printf("==========\n%s\n", + ProcessInspector::overview(HttpRequest::kGet, arg).c_str()); + // TODO: print bytes per item, overhead percent + fflush(stdout); +#ifdef HAVE_TCMALLOC + char buf[8192]; + MallocExtension::instance()->GetStats(buf, sizeof buf); + printf("%s\n", buf); + HeapProfilerDump("end"); + +/* + // only works for tcmalloc_debug + int blocks = 0; + size_t total = 0; + int histogram[kMallocHistogramSize] = { 0, }; + MallocExtension::instance()->MallocMemoryStats(&blocks, &total, histogram); + printf("==========\nblocks = %d\ntotal = %zd\n", blocks, total); + for (int i = 0; i < kMallocHistogramSize; ++i) + { + printf("%d = %d\n", i, histogram[i]); + } +*/ +#endif +} diff --git a/examples/memcached/server/server.cc b/examples/memcached/server/server.cc new file mode 100644 index 0000000..162597f --- /dev/null +++ b/examples/memcached/server/server.cc @@ -0,0 +1,55 @@ +#include "examples/memcached/server/MemcacheServer.h" + +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/inspect/Inspector.h" + +#include <boost/program_options.hpp> + +namespace po = boost::program_options; +using namespace muduo::net; + +bool parseCommandLine(int argc, char* argv[], MemcacheServer::Options* options) +{ + options->tcpport = 11211; + options->gperfport = 11212; + options->threads = 4; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "Help") + ("port,p", po::value<uint16_t>(&options->tcpport), "TCP port") + ("udpport,U", po::value<uint16_t>(&options->udpport), "UDP port") + ("gperf,g", po::value<uint16_t>(&options->gperfport), "port for gperftools") + ("threads,t", po::value<int>(&options->threads), "Number of worker threads") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) + { + //printf("memcached 1.1.0\n"); + return false; + } + return true; +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + EventLoopThread inspectThread; + MemcacheServer::Options options; + if (parseCommandLine(argc, argv, &options)) + { + // FIXME: how to destruct it safely ? + new Inspector(inspectThread.startLoop(), InetAddress(options.gperfport), "memcached-debug"); + + MemcacheServer server(&loop, options); + server.setThreadNum(options.threads); + server.start(); + loop.loop(); + } +} + diff --git a/examples/multiplexer/demux.cc b/examples/multiplexer/demux.cc new file mode 100644 index 0000000..3222254 --- /dev/null +++ b/examples/multiplexer/demux.cc @@ -0,0 +1,240 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include <queue> +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +typedef std::shared_ptr<TcpClient> TcpClientPtr; + +// const int kMaxConns = 1; +const size_t kMaxPacketLen = 255; +const size_t kHeaderLen = 3; + +const uint16_t kListenPort = 9999; +const char* socksIp = "127.0.0.1"; +const uint16_t kSocksPort = 7777; + +struct Entry +{ + int connId; + TcpClientPtr client; + TcpConnectionPtr connection; + Buffer pending; +}; + +class DemuxServer : noncopyable +{ + public: + DemuxServer(EventLoop* loop, const InetAddress& listenAddr, const InetAddress& socksAddr) + : loop_(loop), + server_(loop, listenAddr, "DemuxServer"), + socksAddr_(socksAddr) + { + server_.setConnectionCallback( + std::bind(&DemuxServer::onServerConnection, this, _1)); + server_.setMessageCallback( + std::bind(&DemuxServer::onServerMessage, this, _1, _2, _3)); + } + + void start() + { + server_.start(); + } + + void onServerConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + if (serverConn_) + { + conn->shutdown(); + } + else + { + serverConn_ = conn; + LOG_INFO << "onServerConnection set serverConn_"; + } + } + else + { + if (serverConn_ == conn) + { + serverConn_.reset(); + socksConns_.clear(); + + LOG_INFO << "onServerConnection reset serverConn_"; + } + } + } + + void onServerMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + while (buf->readableBytes() > kHeaderLen) + { + int len = static_cast<uint8_t>(*buf->peek()); + if (buf->readableBytes() < len + kHeaderLen) + { + break; + } + else + { + int connId = static_cast<uint8_t>(buf->peek()[1]); + connId |= (static_cast<uint8_t>(buf->peek()[2]) << 8); + + if (connId != 0) + { + assert(socksConns_.find(connId) != socksConns_.end()); + TcpConnectionPtr& socksConn = socksConns_[connId].connection; + if (socksConn) + { + assert(socksConns_[connId].pending.readableBytes() == 0); + socksConn->send(buf->peek() + kHeaderLen, len); + } + else + { + socksConns_[connId].pending.append(buf->peek() + kHeaderLen, len); + } + } + else + { + string cmd(buf->peek() + kHeaderLen, len); + doCommand(cmd); + } + buf->retrieve(len + kHeaderLen); + } + } + } + + void doCommand(const string& cmd) + { + static const string kConn = "CONN "; + + int connId = atoi(&cmd[kConn.size()]); + bool isUp = cmd.find(" IS UP") != string::npos; + LOG_INFO << "doCommand " << connId << " " << isUp; + if (isUp) + { + assert(socksConns_.find(connId) == socksConns_.end()); + char connName[256]; + snprintf(connName, sizeof connName, "SocksClient %d", connId); + Entry entry; + entry.connId = connId; + entry.client.reset(new TcpClient(loop_, socksAddr_, connName)); + entry.client->setConnectionCallback( + std::bind(&DemuxServer::onSocksConnection, this, connId, _1)); + entry.client->setMessageCallback( + std::bind(&DemuxServer::onSocksMessage, this, connId, _1, _2, _3)); + // FIXME: setWriteCompleteCallback + socksConns_[connId] = entry; + entry.client->connect(); + } + else + { + assert(socksConns_.find(connId) != socksConns_.end()); + TcpConnectionPtr& socksConn = socksConns_[connId].connection; + if (socksConn) + { + socksConn->shutdown(); + } + else + { + socksConns_.erase(connId); + } + } + } + + void onSocksConnection(int connId, const TcpConnectionPtr& conn) + { + assert(socksConns_.find(connId) != socksConns_.end()); + if (conn->connected()) + { + socksConns_[connId].connection = conn; + Buffer& pendingData = socksConns_[connId].pending; + if (pendingData.readableBytes() > 0) + { + conn->send(&pendingData); + } + } + else + { + if (serverConn_) + { + char buf[256]; + int len = snprintf(buf, sizeof(buf), "DISCONNECT %d\r\n", connId); + Buffer buffer; + buffer.append(buf, len); + sendServerPacket(0, &buffer); + } + else + { + socksConns_.erase(connId); + } + } + } + + void onSocksMessage(int connId, const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + assert(socksConns_.find(connId) != socksConns_.end()); + while (buf->readableBytes() > kMaxPacketLen) + { + Buffer packet; + packet.append(buf->peek(), kMaxPacketLen); + buf->retrieve(kMaxPacketLen); + sendServerPacket(connId, &packet); + } + if (buf->readableBytes() > 0) + { + sendServerPacket(connId, buf); + } + } + + void sendServerPacket(int connId, Buffer* buf) + { + size_t len = buf->readableBytes(); + LOG_DEBUG << len; + assert(len <= kMaxPacketLen); + uint8_t header[kHeaderLen] = { + static_cast<uint8_t>(len), + static_cast<uint8_t>(connId & 0xFF), + static_cast<uint8_t>((connId & 0xFF00) >> 8) + }; + buf->prepend(header, kHeaderLen); + if (serverConn_) + { + serverConn_->send(buf); + } + } + + EventLoop* loop_; + TcpServer server_; + TcpConnectionPtr serverConn_; + const InetAddress socksAddr_; + std::map<int, Entry> socksConns_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(kListenPort); + if (argc > 1) + { + socksIp = argv[1]; + } + InetAddress socksAddr(socksIp, kSocksPort); + DemuxServer server(&loop, listenAddr, socksAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/multiplexer/harness/run.sh b/examples/multiplexer/harness/run.sh new file mode 100644 index 0000000..ce5194c --- /dev/null +++ b/examples/multiplexer/harness/run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +CLASSPATH=lib/netty-3.2.4.Final.jar:lib/slf4j-api-1.6.1.jar:lib/slf4j-simple-1.6.1.jar:./bin + +export CLASSPATH +mkdir -p bin +javac -d bin ./src/com/chenshuo/muduo/example/multiplexer/*.java ./src/com/chenshuo/muduo/example/multiplexer/testcase/*.java +java -ea -Djava.net.preferIPv4Stack=true com.chenshuo.muduo.example.multiplexer.MultiplexerTest localhost diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java new file mode 100644 index 0000000..5f91379 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java @@ -0,0 +1,22 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.nio.charset.Charset; + +import org.jboss.netty.buffer.ChannelBuffer; + +public class DataEvent extends Event { + + public final EventSource source; + public final int whichClient; + public final ChannelBuffer data; + + public DataEvent(EventSource source, int whichClient, ChannelBuffer data) { + this.source = source; + this.whichClient = whichClient; + this.data = data; + } + + public String getString() { + return data.toString(Charset.defaultCharset()); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java new file mode 100644 index 0000000..f372d4b --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java @@ -0,0 +1,5 @@ +package com.chenshuo.muduo.example.multiplexer; + +public class Event { + +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java new file mode 100644 index 0000000..a375a50 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java @@ -0,0 +1,25 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +public class EventQueue { + private BlockingDeque<Event> queue = new LinkedBlockingDeque<Event>(); + + public void put(Event e) { + queue.add(e); + } + + public Event take() { + try { + return queue.poll(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return null; + } + } + + public boolean isEmpty() { + return queue.isEmpty(); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java new file mode 100644 index 0000000..79e4a4e --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java @@ -0,0 +1,5 @@ +package com.chenshuo.muduo.example.multiplexer; + +public enum EventSource { + kBackend, kClient +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java new file mode 100644 index 0000000..1e33a06 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java @@ -0,0 +1,126 @@ +package com.chenshuo.muduo.example.multiplexer; + +import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; + +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockBackendServer { + private static final Logger logger = LoggerFactory.getLogger("MockBackendServer"); + + private class Handler extends SimpleChannelHandler { + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelConnected {},, {}", ctx, e); + assert connection == null; + connection = e.getChannel(); + latch.countDown(); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelDisconnected {},, {}", ctx, e); + assert connection == e.getChannel(); + connection = null; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + logger.debug("messageReceived {},, {}", ctx, e); + assert connection == e.getChannel(); + ChannelBuffer input = (ChannelBuffer) e.getMessage(); + int len = input.readUnsignedByte(); + int whichClient = input.readUnsignedShort(); + assert len == input.readableBytes(); + logger.debug("From {}, '{}'", whichClient, input.toString(Charset.defaultCharset())); + queue.put(new DataEvent(EventSource.kBackend, whichClient, input)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + logger.error("exceptionCaught {},, {}", ctx, e); + } + } + + private final EventQueue queue; + private final int port; + private final Executor boss; + private final Executor worker; + private final CountDownLatch latch; + private Channel listener; + private volatile Channel connection; + + public MockBackendServer(EventQueue queue, int listeningPort, Executor boss, Executor worker, + CountDownLatch latch) { + this.queue = queue; + port = listeningPort; + this.boss = boss; + this.worker = worker; + this.latch = latch; + } + + public void start() { + ServerBootstrap bootstrap = getBootstrap(); + listener = bootstrap.bind(new InetSocketAddress(port)); + logger.debug("started"); + } + + public void sendToClient(int whichClient, ChannelBuffer data) { + ChannelBuffer output = data.factory().getBuffer(3); + output.writeByte(data.readableBytes()); + output.writeShort(whichClient); + connection.write(wrappedBuffer(output, data)); + } + + public ChannelBuffer sendToClient(int whichClient, String str) { + byte[] bytes = str.getBytes(); + ChannelBuffer data = MultiplexerTest.bufferFactory.getBuffer(bytes, 0, bytes.length); + sendToClient(whichClient, data); + return data; + } + + public void stop() { + listener.close(); + } + + private ServerBootstrap getBootstrap() { + ChannelFactory factory = new NioServerSocketChannelFactory(boss, worker); + ServerBootstrap bootstrap = new ServerBootstrap(factory); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline( + new LengthFieldBasedFrameDecoder(255 + 3, 0, 1, 2, 0), + new Handler()); + } + }); + bootstrap.setOption("reuseAddress", true); + bootstrap.setOption("child.tcpNoDelay", true); + bootstrap.setOption("child.bufferFactory", MultiplexerTest.bufferFactory); + return bootstrap; + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java new file mode 100644 index 0000000..4543f4a --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java @@ -0,0 +1,143 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.Timer; +import org.jboss.netty.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockClient { + private static final Logger logger = LoggerFactory.getLogger("MockClient"); + + private class Handler extends SimpleChannelHandler { + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelConnected {},, {}", ctx, e); + assert connection == null; + connection = e.getChannel(); + if (latch != null) + latch.countDown(); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelDisconnected {},, {}", ctx, e); + assert connection == e.getChannel(); + connection = null; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + logger.debug("messageReceived {},, {}", ctx, e); + assert connection == e.getChannel(); + queue.put(new DataEvent(EventSource.kClient, connId, (ChannelBuffer) e.getMessage())); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + logger.error("exceptionCaught {},, {}", ctx, e); + // reconnect(); + } + + @SuppressWarnings("unused") + private void reconnect() { + timer.newTimeout(new TimerTask() { + @Override + public void run(Timeout timeout) throws Exception { + logger.info("Reconnecting"); + bootstrap.connect(); + + } + }, 5, TimeUnit.SECONDS); + } + } + + private final EventQueue queue; + private final InetSocketAddress remoteAddress; + private final Executor boss; + private final Executor worker; + private final Timer timer; + private volatile Channel connection; + private ClientBootstrap bootstrap; + private int connId; + private MyCountDownLatch latch; + + public MockClient(EventQueue queue, InetSocketAddress remoteAddress, Executor boss, + Executor worker) { + this.queue = queue; + this.remoteAddress = remoteAddress; + this.boss = boss; + this.worker = worker; + this.timer = new HashedWheelTimer(); + connId = -1; + } + + public ChannelFuture connect() { + assert bootstrap == null; + + ChannelFactory factory = new NioClientSocketChannelFactory(boss, worker); + bootstrap = new ClientBootstrap(factory); + + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() { + return Channels.pipeline(new Handler()); + } + }); + + bootstrap.setOption("tcpNoDelay", true); + bootstrap.setOption("remoteAddress", remoteAddress); + + return bootstrap.connect(); + } + + public void connectAndWait() { + latch = new MyCountDownLatch(1); + connect(); + latch.awaitUninterruptibly(500); + assert connection != null; + } + + public void send(ChannelBuffer buf) { + connection.write(buf); + } + + public ChannelBuffer send(String str) { + byte[] bytes = str.getBytes(); + ChannelBuffer buf = MultiplexerTest.bufferFactory.getBuffer(bytes, 0, bytes.length); + connection.write(buf); + return buf; + } + + public void disconnect() { + connection.close(); + } + + public void setId(int connId) { + assert this.connId == -1; + this.connId = connId; + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java new file mode 100644 index 0000000..bb5dd78 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java @@ -0,0 +1,104 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.net.InetSocketAddress; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Pattern; + +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.buffer.HeapChannelBufferFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientBothSend; +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientNoData; +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientSend; +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientBackendSend; +import com.chenshuo.muduo.example.multiplexer.testcase.TestTwoClients; + +public class MultiplexerTest { + private static final Logger logger = LoggerFactory.getLogger("MultiplexerTest"); + public static final ChannelBufferFactory bufferFactory = + HeapChannelBufferFactory.getInstance(ByteOrder.LITTLE_ENDIAN); + + public final Pattern commandChannel = Pattern.compile("CONN (\\d+) FROM [0-9.:]+ IS ([A-Z]+)\r\n"); + + private static final int kMultiplexerServerPort = 3333; + private static final int kLogicalServerPort = 9999; + private final InetSocketAddress multiplexerAddress; + private final ExecutorService boss; + private final ExecutorService worker; + private EventQueue queue; + private MyCountDownLatch latch; + private MockBackendServer backend; + private ArrayList<TestCase> testCases; + + public MultiplexerTest(String multiplexerHost) { + multiplexerAddress = new InetSocketAddress(multiplexerHost, kMultiplexerServerPort); + boss = Executors.newCachedThreadPool(); + worker = Executors.newCachedThreadPool(); + queue = new EventQueue(); + latch = new MyCountDownLatch(1); + backend = new MockBackendServer(queue, kLogicalServerPort, boss, worker, latch); + testCases = new ArrayList<TestCase>(); + } + + public static void main(String[] args) { + if (args.length >= 1) { + String multiplexerHost = args[0]; + MultiplexerTest test = new MultiplexerTest(multiplexerHost); + test.addTestCase(new TestOneClientNoData()); + test.addTestCase(new TestOneClientSend()); + test.addTestCase(new TestOneClientBackendSend()); + test.addTestCase(new TestOneClientBothSend()); + test.addTestCase(new TestTwoClients()); + test.run(); + } else { + System.out.println("Usage: ./run.sh path_to_test_data multiplexer_host"); + System.out.println("Example: ./run.sh localhost"); + } + } + + private void addTestCase(TestCase testCase) { + testCases.add(testCase); + testCase.setOwner(this); + } + + private void run() { + logger.info("Waiting for connection"); + backend.start(); + latch.awaitUninterruptibly(); + + logger.info("Ready"); + for (TestCase testCase : testCases) { + testCase.test(); + } + System.out.flush(); + sleep(500); + logger.info("Finished"); + System.exit(0); + } + + public MockClient newClient() { + MockClient client = new MockClient(queue, multiplexerAddress, boss, worker); + client.connectAndWait(); + return client; + } + + public EventQueue getEventQueue() { + return queue; + } + + public MockBackendServer getBackend() { + return backend; + } + + public void sleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + } + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java new file mode 100644 index 0000000..4492fe4 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java @@ -0,0 +1,26 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class MyCountDownLatch extends CountDownLatch { + + public MyCountDownLatch(int count) { + super(count); + } + + public void awaitUninterruptibly() { + try { + await(); + } catch (InterruptedException e) { + } + } + + public void awaitUninterruptibly(int millis) { + try { + await(millis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + } + } + +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java new file mode 100644 index 0000000..88f12db --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java @@ -0,0 +1,48 @@ +package com.chenshuo.muduo.example.multiplexer; + +import org.jboss.netty.buffer.ChannelBufferFactory; + +public abstract class TestCase { + protected static final ChannelBufferFactory bufferFactory = MultiplexerTest.bufferFactory; + + protected MultiplexerTest god; + protected EventQueue queue; + protected MockBackendServer backend; + + public void setOwner(MultiplexerTest god) { + this.god = god; + queue = god.getEventQueue(); + backend = god.getBackend(); + } + + public void test() { + try { + run(); + } catch (TestFailedException e) { + System.out.printf("%s FAILED: %s\n", this.getClass().getSimpleName(), e.getMessage()); + e.printStackTrace(); + return; + } catch (Exception e) { + System.out.printf("%s FATAL: %s\n", this.getClass().getSimpleName(), e.toString()); + e.printStackTrace(); + return; + } + System.out.printf("%s PASS\n", this.getClass().getSimpleName()); + } + + protected void assertEquals(Object expected, Object actual) { + if (!expected.equals(actual)) + fail("assertEquals failed"); + } + + protected void assertTrue(boolean yes) { + if (!yes) + fail("assertTrue failed"); + } + + protected void fail(String message) { + throw new TestFailedException(message); + } + + public abstract void run(); +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java new file mode 100644 index 0000000..383ea5f --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java @@ -0,0 +1,9 @@ +package com.chenshuo.muduo.example.multiplexer; + +public class TestFailedException extends RuntimeException { + private static final long serialVersionUID = 1982L; + + public TestFailedException(String message) { + super(message); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java new file mode 100644 index 0000000..5f96e21 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java @@ -0,0 +1,75 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientBackendSend extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + // step 2 + ChannelBuffer buf = backend.sendToClient(connId, "hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = backend.sendToClient(connId, "World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 255; ++i) + sb.append('H'); + + buf = backend.sendToClient(connId, sb.toString()); + + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + + // step 4 + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java new file mode 100644 index 0000000..6c71ee3 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java @@ -0,0 +1,64 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientBothSend extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + // step 2 + ChannelBuffer buf = client.send("hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = backend.sendToClient(connId, "World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 4 + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java new file mode 100644 index 0000000..cfe6eaf --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java @@ -0,0 +1,43 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.util.regex.Matcher; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientNoData extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java new file mode 100644 index 0000000..68b2ff7 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java @@ -0,0 +1,80 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientSend extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + // step 2 + ChannelBuffer buf = client.send("hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = client.send("World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 500; ++i) + sb.append('H'); + + buf = client.send(sb.toString()); + + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf.copy(0, 255), de.data); + + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf.copy(255, 245), de.data); + + // step 4 + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java new file mode 100644 index 0000000..2de3d6b --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java @@ -0,0 +1,85 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestTwoClients extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + final MockClient client1 = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId1 = Integer.parseInt(m.group(1)); + assertTrue(connId1 > 0); + client1.setId(connId1); + assertEquals("UP", m.group(2)); + + // step 2 + final MockClient client2 = god.newClient(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId2 = Integer.parseInt(m.group(1)); + assertTrue(connId2 > 0); + client2.setId(connId2); + assertEquals("UP", m.group(2)); + + ChannelBuffer buf = client1.send("hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId1, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = backend.sendToClient(connId2, "World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId2, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 4 + client1.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + assertEquals(connId1, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + + client2.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + assertEquals(connId2, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + + } +} diff --git a/examples/multiplexer/multiplexer.cc b/examples/multiplexer/multiplexer.cc new file mode 100644 index 0000000..127ba4e --- /dev/null +++ b/examples/multiplexer/multiplexer.cc @@ -0,0 +1,315 @@ +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include <queue> +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +const int kMaxConns = 10; // 65535 +const size_t kMaxPacketLen = 255; +const size_t kHeaderLen = 3; + +const uint16_t kClientPort = 3333; +const char* backendIp = "127.0.0.1"; +const uint16_t kBackendPort = 9999; + +class MultiplexServer +{ + public: + MultiplexServer(EventLoop* loop, + const InetAddress& listenAddr, + const InetAddress& backendAddr, + int numThreads) + : server_(loop, listenAddr, "MultiplexServer"), + backend_(loop, backendAddr, "MultiplexBackend"), + numThreads_(numThreads), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&MultiplexServer::onClientConnection, this, _1)); + server_.setMessageCallback( + std::bind(&MultiplexServer::onClientMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + + backend_.setConnectionCallback( + std::bind(&MultiplexServer::onBackendConnection, this, _1)); + backend_.setMessageCallback( + std::bind(&MultiplexServer::onBackendMessage, this, _1, _2, _3)); + backend_.enableRetry(); + + // loop->runEvery(10.0, std::bind(&MultiplexServer::printStatistics, this)); + + } + + void start() + { + LOG_INFO << "starting " << numThreads_ << " threads."; + backend_.connect(); + server_.start(); + } + + private: + void sendBackendPacket(int id, Buffer* buf) + { + size_t len = buf->readableBytes(); + assert(len <= kMaxPacketLen); + uint8_t header[kHeaderLen] = { + static_cast<uint8_t>(len), + static_cast<uint8_t>(id & 0xFF), + static_cast<uint8_t>((id & 0xFF00) >> 8) + }; + buf->prepend(header, kHeaderLen); + TcpConnectionPtr backendConn; + { + MutexLockGuard lock(mutex_); + backendConn = backendConn_; + } + if (backendConn) + { + backendConn->send(buf); + } + } + + void sendBackendString(int id, const string& msg) + { + assert(msg.size() <= kMaxPacketLen); + Buffer buf; + buf.append(msg); + sendBackendPacket(id, &buf); + } + + void sendBackendBuffer(int id, Buffer* buf) + { + while (buf->readableBytes() > kMaxPacketLen) + { + Buffer packet; + packet.append(buf->peek(), kMaxPacketLen); + buf->retrieve(kMaxPacketLen); + sendBackendPacket(id, &packet); + } + if (buf->readableBytes() > 0) + { + sendBackendPacket(id, buf); + } + } + + void sendToClient(Buffer* buf) + { + while (buf->readableBytes() > kHeaderLen) + { + int len = static_cast<uint8_t>(*buf->peek()); + if (buf->readableBytes() < len + kHeaderLen) + { + break; + } + else + { + int id = static_cast<uint8_t>(buf->peek()[1]); + id |= (static_cast<uint8_t>(buf->peek()[2]) << 8); + + TcpConnectionPtr clientConn; + { + MutexLockGuard lock(mutex_); + std::map<int, TcpConnectionPtr>::iterator it = clientConns_.find(id); + if (it != clientConns_.end()) + { + clientConn = it->second; + } + } + if (clientConn) + { + clientConn->send(buf->peek() + kHeaderLen, len); + } + buf->retrieve(len + kHeaderLen); + } + } + } + + void onClientConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Client " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + int id = -1; + { + MutexLockGuard lock(mutex_); + if (!availIds_.empty()) + { + id = availIds_.front(); + availIds_.pop(); + clientConns_[id] = conn; + } + } + + if (id <= 0) + { + conn->shutdown(); + } + else + { + conn->setContext(id); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS UP\r\n", id, + conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + } + } + else + { + if (!conn->getContext().empty()) + { + int id = boost::any_cast<int>(conn->getContext()); + assert(id > 0 && id <= kMaxConns); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS DOWN\r\n", + id, conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + + MutexLockGuard lock(mutex_); + if (backendConn_) + { + availIds_.push(id); + clientConns_.erase(id); + } + else + { + assert(availIds_.empty()); + assert(clientConns_.empty()); + } + } + } + } + + void onClientMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.addAndGet(len); + receivedMessages_.incrementAndGet(); + if (!conn->getContext().empty()) + { + int id = boost::any_cast<int>(conn->getContext()); + sendBackendBuffer(id, buf); + // assert(buf->readableBytes() == 0); + } + else + { + buf->retrieveAll(); + // FIXME: error handling + } + } + + void onBackendConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Backend " << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + std::vector<TcpConnectionPtr> connsToDestroy; + if (conn->connected()) + { + MutexLockGuard lock(mutex_); + backendConn_ = conn; + assert(availIds_.empty()); + for (int i = 1; i <= kMaxConns; ++i) + { + availIds_.push(i); + } + } + else + { + MutexLockGuard lock(mutex_); + backendConn_.reset(); + connsToDestroy.reserve(clientConns_.size()); + for (std::map<int, TcpConnectionPtr>::iterator it = clientConns_.begin(); + it != clientConns_.end(); + ++it) + { + connsToDestroy.push_back(it->second); + } + clientConns_.clear(); + while (!availIds_.empty()) + { + availIds_.pop(); + } + } + + for (std::vector<TcpConnectionPtr>::iterator it = connsToDestroy.begin(); + it != connsToDestroy.end(); + ++it) + { + (*it)->shutdown(); + } + } + + void onBackendMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.addAndGet(len); + receivedMessages_.incrementAndGet(); + sendToClient(buf); + } + + void printStatistics() + { + Timestamp endTime = Timestamp::now(); + int64_t newCounter = transferred_.get(); + int64_t bytes = newCounter - oldCounter_; + int64_t msgs = receivedMessages_.getAndSet(0); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n", + static_cast<double>(bytes)/time/1024/1024, + static_cast<double>(msgs)/time/1024, + static_cast<double>(bytes)/static_cast<double>(msgs)); + + oldCounter_ = newCounter; + startTime_ = endTime; + } + + TcpServer server_; + TcpClient backend_; + int numThreads_; + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; + MutexLock mutex_; + TcpConnectionPtr backendConn_ GUARDED_BY(mutex_); + std::map<int, TcpConnectionPtr> clientConns_ GUARDED_BY(mutex_); + std::queue<int> availIds_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + int numThreads = 4; + if (argc > 1) + { + backendIp = argv[1]; + } + if (argc > 2) + { + numThreads = atoi(argv[2]); + } + EventLoop loop; + InetAddress listenAddr(kClientPort); + InetAddress backendAddr(backendIp, kBackendPort); + MultiplexServer server(&loop, listenAddr, backendAddr, numThreads); + + server.start(); + + loop.loop(); +} + diff --git a/examples/multiplexer/multiplexer_simple.cc b/examples/multiplexer/multiplexer_simple.cc new file mode 100644 index 0000000..ea3f766 --- /dev/null +++ b/examples/multiplexer/multiplexer_simple.cc @@ -0,0 +1,269 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include <queue> +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +const int kMaxConns = 10; // 65535 +const size_t kMaxPacketLen = 255; +const size_t kHeaderLen = 3; + +const uint16_t kClientPort = 3333; +const char* backendIp = "127.0.0.1"; +const uint16_t kBackendPort = 9999; + +class MultiplexServer : noncopyable +{ + public: + MultiplexServer(EventLoop* loop, const InetAddress& listenAddr, const InetAddress& backendAddr) + : server_(loop, listenAddr, "MultiplexServer"), + backend_(loop, backendAddr, "MultiplexBackend") + { + server_.setConnectionCallback( + std::bind(&MultiplexServer::onClientConnection, this, _1)); + server_.setMessageCallback( + std::bind(&MultiplexServer::onClientMessage, this, _1, _2, _3)); + backend_.setConnectionCallback( + std::bind(&MultiplexServer::onBackendConnection, this, _1)); + backend_.setMessageCallback( + std::bind(&MultiplexServer::onBackendMessage, this, _1, _2, _3)); + backend_.enableRetry(); + } + + void start() + { + backend_.connect(); + server_.start(); + } + + private: + + void onClientConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Client " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + int id = -1; + if (!availIds_.empty()) + { + id = availIds_.front(); + availIds_.pop(); + clientConns_[id] = conn; + } + + if (id <= 0) + { + // no client id available + conn->shutdown(); + } + else + { + conn->setContext(id); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS UP\r\n", + id, conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + } + } + else + { + if (!conn->getContext().empty()) + { + int id = boost::any_cast<int>(conn->getContext()); + assert(id > 0 && id <= kMaxConns); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS DOWN\r\n", + id, conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + + if (backendConn_) + { + // put client id back for reusing + availIds_.push(id); + clientConns_.erase(id); + } + else + { + assert(availIds_.empty()); + assert(clientConns_.empty()); + } + } + } + } + + void sendBackendString(int id, const string& msg) + { + assert(msg.size() <= kMaxPacketLen); + Buffer buf; + buf.append(msg); + sendBackendPacket(id, &buf); + } + + void onClientMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + if (!conn->getContext().empty()) + { + int id = boost::any_cast<int>(conn->getContext()); + sendBackendBuffer(id, buf); + } + else + { + buf->retrieveAll(); + // FIXME: error handling + } + } + + void sendBackendBuffer(int id, Buffer* buf) + { + while (buf->readableBytes() > kMaxPacketLen) + { + Buffer packet; + packet.append(buf->peek(), kMaxPacketLen); + buf->retrieve(kMaxPacketLen); + sendBackendPacket(id, &packet); + } + if (buf->readableBytes() > 0) + { + sendBackendPacket(id, buf); + } + } + + void sendBackendPacket(int id, Buffer* buf) + { + size_t len = buf->readableBytes(); + LOG_DEBUG << "sendBackendPacket " << len; + assert(len <= kMaxPacketLen); + uint8_t header[kHeaderLen] = { + static_cast<uint8_t>(len), + static_cast<uint8_t>(id & 0xFF), + static_cast<uint8_t>((id & 0xFF00) >> 8) + }; + buf->prepend(header, kHeaderLen); + if (backendConn_) + { + backendConn_->send(buf); + } + } + + void onBackendConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Backend " << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + backendConn_ = conn; + assert(availIds_.empty()); + for (int i = 1; i <= kMaxConns; ++i) + { + availIds_.push(i); + } + } + else + { + backendConn_.reset(); + for (std::map<int, TcpConnectionPtr>::iterator it = clientConns_.begin(); + it != clientConns_.end(); + ++it) + { + it->second->shutdown(); + } + clientConns_.clear(); + while (!availIds_.empty()) + { + availIds_.pop(); + } + } + } + + void onBackendMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + sendToClient(buf); + } + + void sendToClient(Buffer* buf) + { + while (buf->readableBytes() > kHeaderLen) + { + int len = static_cast<uint8_t>(*buf->peek()); + if (buf->readableBytes() < len + kHeaderLen) + { + break; + } + else + { + int id = static_cast<uint8_t>(buf->peek()[1]); + id |= (static_cast<uint8_t>(buf->peek()[2]) << 8); + + if (id != 0) + { + std::map<int, TcpConnectionPtr>::iterator it = clientConns_.find(id); + if (it != clientConns_.end()) + { + it->second->send(buf->peek() + kHeaderLen, len); + } + } + else + { + string cmd(buf->peek() + kHeaderLen, len); + LOG_INFO << "Backend cmd " << cmd; + doCommand(cmd); + } + buf->retrieve(len + kHeaderLen); + } + } + } + + void doCommand(const string& cmd) + { + static const string kDisconnectCmd = "DISCONNECT "; + + if (cmd.size() > kDisconnectCmd.size() + && std::equal(kDisconnectCmd.begin(), kDisconnectCmd.end(), cmd.begin())) + { + int connId = atoi(&cmd[kDisconnectCmd.size()]); + std::map<int, TcpConnectionPtr>::iterator it = clientConns_.find(connId); + if (it != clientConns_.end()) + { + it->second->shutdown(); + } + } + } + + TcpServer server_; + TcpClient backend_; + // MutexLock mutex_; + TcpConnectionPtr backendConn_; + std::map<int, TcpConnectionPtr> clientConns_; + std::queue<int> availIds_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(kClientPort); + if (argc > 1) + { + backendIp = argv[1]; + } + InetAddress backendAddr(backendIp, kBackendPort); + MultiplexServer server(&loop, listenAddr, backendAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/discard/client.cc b/examples/netty/discard/client.cc new file mode 100644 index 0000000..3669f85 --- /dev/null +++ b/examples/netty/discard/client.cc @@ -0,0 +1,95 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class DiscardClient : noncopyable +{ + public: + DiscardClient(EventLoop* loop, const InetAddress& listenAddr, int size) + : loop_(loop), + client_(loop, listenAddr, "DiscardClient"), + message_(size, 'H') + { + client_.setConnectionCallback( + std::bind(&DiscardClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&DiscardClient::onMessage, this, _1, _2, _3)); + client_.setWriteCompleteCallback( + std::bind(&DiscardClient::onWriteComplete, this, _1)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->send(message_); + } + else + { + loop_->quit(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + buf->retrieveAll(); + } + + void onWriteComplete(const TcpConnectionPtr& conn) + { + LOG_INFO << "write complete " << message_.size(); + conn->send(message_); + } + + EventLoop* loop_; + TcpClient client_; + string message_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + EventLoop loop; + InetAddress serverAddr(argv[1], 2009); + + int size = 256; + if (argc > 2) + { + size = atoi(argv[2]); + } + + DiscardClient client(&loop, serverAddr, size); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip [msg_size]\n", argv[0]); + } +} + diff --git a/examples/netty/discard/server.cc b/examples/netty/discard/server.cc new file mode 100644 index 0000000..7b3083e --- /dev/null +++ b/examples/netty/discard/server.cc @@ -0,0 +1,96 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class DiscardServer +{ + public: + DiscardServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "DiscardServer"), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&DiscardServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&DiscardServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(3.0, std::bind(&DiscardServer::printThroughput, this)); + } + + void start() + { + LOG_INFO << "starting " << numThreads << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.add(len); + receivedMessages_.incrementAndGet(); + buf->retrieveAll(); + } + + void printThroughput() + { + Timestamp endTime = Timestamp::now(); + int64_t newCounter = transferred_.get(); + int64_t bytes = newCounter - oldCounter_; + int64_t msgs = receivedMessages_.getAndSet(0); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n", + static_cast<double>(bytes)/time/1024/1024, + static_cast<double>(msgs)/time/1024, + static_cast<double>(bytes)/static_cast<double>(msgs)); + + oldCounter_ = newCounter; + startTime_ = endTime; + } + + TcpServer server_; + + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(2009); + DiscardServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/echo/client.cc b/examples/netty/echo/client.cc new file mode 100644 index 0000000..debab4c --- /dev/null +++ b/examples/netty/echo/client.cc @@ -0,0 +1,87 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class EchoClient : noncopyable +{ + public: + EchoClient(EventLoop* loop, const InetAddress& listenAddr, int size) + : loop_(loop), + client_(loop, listenAddr, "EchoClient"), + message_(size, 'H') + { + client_.setConnectionCallback( + std::bind(&EchoClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&EchoClient::onMessage, this, _1, _2, _3)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->send(message_); + } + else + { + loop_->quit(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + conn->send(buf); + } + + EventLoop* loop_; + TcpClient client_; + string message_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + EventLoop loop; + InetAddress serverAddr(argv[1], 2007); + + int size = 256; + if (argc > 2) + { + size = atoi(argv[2]); + } + + EchoClient client(&loop, serverAddr, size); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip [msg_size]\n", argv[0]); + } +} + diff --git a/examples/netty/echo/server.cc b/examples/netty/echo/server.cc new file mode 100644 index 0000000..2c1f936 --- /dev/null +++ b/examples/netty/echo/server.cc @@ -0,0 +1,96 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class EchoServer +{ + public: + EchoServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "EchoServer"), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(3.0, std::bind(&EchoServer::printThroughput, this)); + } + + void start() + { + LOG_INFO << "starting " << numThreads << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + conn->setTcpNoDelay(true); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.addAndGet(len); + receivedMessages_.incrementAndGet(); + conn->send(buf); + } + + void printThroughput() + { + Timestamp endTime = Timestamp::now(); + int64_t newCounter = transferred_.get(); + int64_t bytes = newCounter - oldCounter_; + int64_t msgs = receivedMessages_.getAndSet(0); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n", + static_cast<double>(bytes)/time/1024/1024, + static_cast<double>(msgs)/time/1024, + static_cast<double>(bytes)/static_cast<double>(msgs)); + + oldCounter_ = newCounter; + startTime_ = endTime; + } + + TcpServer server_; + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(2007); + EchoServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/echo/server2.cc b/examples/netty/echo/server2.cc new file mode 100644 index 0000000..030943e --- /dev/null +++ b/examples/netty/echo/server2.cc @@ -0,0 +1,139 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class EchoServer +{ + public: + EchoServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "EchoServer"), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(5.0, std::bind(&EchoServer::printThroughput, this)); + } + + void start() + { + LOG_INFO << "starting " << numThreads << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + conn->setTcpNoDelay(true); + if (conn->connected()) + { + connections_.increment(); + } + else + { + connections_.decrement(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferredBytes_.addAndGet(len); + receivedMessages_.incrementAndGet(); + conn->send(buf); + } + + void printThroughput() + { + Timestamp endTime = Timestamp::now(); + double bytes = static_cast<double>(transferredBytes_.getAndSet(0)); + int msgs = receivedMessages_.getAndSet(0); + double bytesPerMsg = msgs > 0 ? bytes/msgs : 0; + double time = timeDifference(endTime, startTime_); + printf("%.3f MiB/s %.2f Kilo Msgs/s %.2f bytes per msg, ", + bytes/time/1024/1024, + static_cast<double>(msgs)/time/1000, + bytesPerMsg); + + printConnection(); + fflush(stdout); + startTime_ = endTime; + } + + void printConnection() + { + string procStatus = ProcessInfo::procStatus(); + printf("%d conn, files %d , VmSize %ld KiB, RSS %ld KiB, ", + connections_.get(), + ProcessInfo::openedFiles(), + getLong(procStatus, "VmSize:"), + getLong(procStatus, "VmRSS:")); + + string meminfo; + FileUtil::readFile("/proc/meminfo", 65536, &meminfo); + long total_kb = getLong(meminfo, "MemTotal:"); + long free_kb = getLong(meminfo, "MemFree:"); + long buffers_kb = getLong(meminfo, "Buffers:"); + long cached_kb = getLong(meminfo, "Cached:"); + printf("system memory used %ld KiB\n", + total_kb - free_kb - buffers_kb - cached_kb); + } + + long getLong(const string& procStatus, const char* key) + { + long result = 0; + size_t pos = procStatus.find(key); + if (pos != string::npos) + { + result = ::atol(procStatus.c_str() + pos + strlen(key)); + } + return result; + } + + TcpServer server_; + AtomicInt32 connections_; + AtomicInt32 receivedMessages_; + AtomicInt64 transferredBytes_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() + << ", tid = " << CurrentThread::tid() + << ", max files = " << ProcessInfo::maxOpenFiles(); + Logger::setLogLevel(Logger::WARN); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(2007); + EchoServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/uptime/uptime.cc b/examples/netty/uptime/uptime.cc new file mode 100644 index 0000000..31ceac3 --- /dev/null +++ b/examples/netty/uptime/uptime.cc @@ -0,0 +1,67 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class UptimeClient : noncopyable +{ + public: + UptimeClient(EventLoop* loop, const InetAddress& listenAddr) + : client_(loop, listenAddr, "UptimeClient") + { + client_.setConnectionCallback( + std::bind(&UptimeClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&UptimeClient::onMessage, this, _1, _2, _3)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + } + + TcpClient client_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 2) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + UptimeClient client(&loop, serverAddr); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip port\n", argv[0]); + } +} + diff --git a/examples/pingpong/bench.cc b/examples/pingpong/bench.cc new file mode 100644 index 0000000..db8214e --- /dev/null +++ b/examples/pingpong/bench.cc @@ -0,0 +1,142 @@ +// Benchmark inspired by libevent/test/bench.c +// See also: http://libev.schmorp.de/bench.html + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include <stdio.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +std::vector<int> g_pipes; +int numPipes; +int numActive; +int numWrites; +EventLoop* g_loop; +std::vector<std::unique_ptr<Channel>> g_channels; + +int g_reads, g_writes, g_fired; + +void readCallback(Timestamp, int fd, int idx) +{ + char ch; + + g_reads += static_cast<int>(::recv(fd, &ch, sizeof(ch), 0)); + if (g_writes > 0) + { + int widx = idx+1; + if (widx >= numPipes) + { + widx -= numPipes; + } + ::send(g_pipes[2 * widx + 1], "m", 1, 0); + g_writes--; + g_fired++; + } + if (g_fired == g_reads) + { + g_loop->quit(); + } +} + +std::pair<int, int> runOnce() +{ + Timestamp beforeInit(Timestamp::now()); + for (int i = 0; i < numPipes; ++i) + { + Channel& channel = *g_channels[i]; + channel.setReadCallback(std::bind(readCallback, _1, channel.fd(), i)); + channel.enableReading(); + } + + int space = numPipes / numActive; + space *= 2; + for (int i = 0; i < numActive; ++i) + { + ::send(g_pipes[i * space + 1], "m", 1, 0); + } + + g_fired = numActive; + g_reads = 0; + g_writes = numWrites; + Timestamp beforeLoop(Timestamp::now()); + g_loop->loop(); + + Timestamp end(Timestamp::now()); + + int iterTime = static_cast<int>(end.microSecondsSinceEpoch() - beforeInit.microSecondsSinceEpoch()); + int loopTime = static_cast<int>(end.microSecondsSinceEpoch() - beforeLoop.microSecondsSinceEpoch()); + return std::make_pair(iterTime, loopTime); +} + +int main(int argc, char* argv[]) +{ + numPipes = 100; + numActive = 1; + numWrites = 100; + int c; + while ((c = getopt(argc, argv, "n:a:w:")) != -1) + { + switch (c) + { + case 'n': + numPipes = atoi(optarg); + break; + case 'a': + numActive = atoi(optarg); + break; + case 'w': + numWrites = atoi(optarg); + break; + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + return 1; + } + } + + struct rlimit rl; + rl.rlim_cur = rl.rlim_max = numPipes * 2 + 50; + if (::setrlimit(RLIMIT_NOFILE, &rl) == -1) + { + perror("setrlimit"); + //return 1; // comment out this line if under valgrind + } + g_pipes.resize(2 * numPipes); + for (int i = 0; i < numPipes; ++i) + { + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, &g_pipes[i*2]) == -1) + { + perror("pipe"); + return 1; + } + } + + EventLoop loop; + g_loop = &loop; + + for (int i = 0; i < numPipes; ++i) + { + Channel* channel = new Channel(&loop, g_pipes[i*2]); + g_channels.emplace_back(channel); + } + + for (int i = 0; i < 25; ++i) + { + std::pair<int, int> t = runOnce(); + printf("%8d %8d\n", t.first, t.second); + } + + for (const auto& channel : g_channels) + { + channel->disableAll(); + channel->remove(); + } + g_channels.clear(); +} + diff --git a/examples/pingpong/client.cc b/examples/pingpong/client.cc new file mode 100644 index 0000000..cc2c61f --- /dev/null +++ b/examples/pingpong/client.cc @@ -0,0 +1,214 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class Client; + +class Session : noncopyable +{ + public: + Session(EventLoop* loop, + const InetAddress& serverAddr, + const string& name, + Client* owner) + : client_(loop, serverAddr, name), + owner_(owner), + bytesRead_(0), + bytesWritten_(0), + messagesRead_(0) + { + client_.setConnectionCallback( + std::bind(&Session::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&Session::onMessage, this, _1, _2, _3)); + } + + void start() + { + client_.connect(); + } + + void stop() + { + client_.disconnect(); + } + + int64_t bytesRead() const + { + return bytesRead_; + } + + int64_t messagesRead() const + { + return messagesRead_; + } + + private: + + void onConnection(const TcpConnectionPtr& conn); + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + ++messagesRead_; + bytesRead_ += buf->readableBytes(); + bytesWritten_ += buf->readableBytes(); + conn->send(buf); + } + + TcpClient client_; + Client* owner_; + int64_t bytesRead_; + int64_t bytesWritten_; + int64_t messagesRead_; +}; + +class Client : noncopyable +{ + public: + Client(EventLoop* loop, + const InetAddress& serverAddr, + int blockSize, + int sessionCount, + int timeout, + int threadCount) + : loop_(loop), + threadPool_(loop, "pingpong-client"), + sessionCount_(sessionCount), + timeout_(timeout) + { + loop->runAfter(timeout, std::bind(&Client::handleTimeout, this)); + if (threadCount > 1) + { + threadPool_.setThreadNum(threadCount); + } + threadPool_.start(); + + for (int i = 0; i < blockSize; ++i) + { + message_.push_back(static_cast<char>(i % 128)); + } + + for (int i = 0; i < sessionCount; ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "C%05d", i); + Session* session = new Session(threadPool_.getNextLoop(), serverAddr, buf, this); + session->start(); + sessions_.emplace_back(session); + } + } + + const string& message() const + { + return message_; + } + + void onConnect() + { + if (numConnected_.incrementAndGet() == sessionCount_) + { + LOG_WARN << "all connected"; + } + } + + void onDisconnect(const TcpConnectionPtr& conn) + { + if (numConnected_.decrementAndGet() == 0) + { + LOG_WARN << "all disconnected"; + + int64_t totalBytesRead = 0; + int64_t totalMessagesRead = 0; + for (const auto& session : sessions_) + { + totalBytesRead += session->bytesRead(); + totalMessagesRead += session->messagesRead(); + } + LOG_WARN << totalBytesRead << " total bytes read"; + LOG_WARN << totalMessagesRead << " total messages read"; + LOG_WARN << static_cast<double>(totalBytesRead) / static_cast<double>(totalMessagesRead) + << " average message size"; + LOG_WARN << static_cast<double>(totalBytesRead) / (timeout_ * 1024 * 1024) + << " MiB/s throughput"; + conn->getLoop()->queueInLoop(std::bind(&Client::quit, this)); + } + } + + private: + + void quit() + { + loop_->queueInLoop(std::bind(&EventLoop::quit, loop_)); + } + + void handleTimeout() + { + LOG_WARN << "stop"; + for (auto& session : sessions_) + { + session->stop(); + } + } + + EventLoop* loop_; + EventLoopThreadPool threadPool_; + int sessionCount_; + int timeout_; + std::vector<std::unique_ptr<Session>> sessions_; + string message_; + AtomicInt32 numConnected_; +}; + +void Session::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->send(owner_->message()); + owner_->onConnect(); + } + else + { + owner_->onDisconnect(conn); + } +} + +int main(int argc, char* argv[]) +{ + if (argc != 7) + { + fprintf(stderr, "Usage: client <host_ip> <port> <threads> <blocksize> "); + fprintf(stderr, "<sessions> <time>\n"); + } + else + { + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + Logger::setLogLevel(Logger::WARN); + + const char* ip = argv[1]; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + int threadCount = atoi(argv[3]); + int blockSize = atoi(argv[4]); + int sessionCount = atoi(argv[5]); + int timeout = atoi(argv[6]); + + EventLoop loop; + InetAddress serverAddr(ip, port); + + Client client(&loop, serverAddr, blockSize, sessionCount, timeout, threadCount); + loop.loop(); + } +} + diff --git a/examples/pingpong/server.cc b/examples/pingpong/server.cc new file mode 100644 index 0000000..53289af --- /dev/null +++ b/examples/pingpong/server.cc @@ -0,0 +1,63 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn->setTcpNoDelay(true); + } +} + +void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) +{ + conn->send(buf); +} + +int main(int argc, char* argv[]) +{ + if (argc < 4) + { + fprintf(stderr, "Usage: server <address> <port> <threads>\n"); + } + else + { + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + Logger::setLogLevel(Logger::WARN); + + const char* ip = argv[1]; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress listenAddr(ip, port); + int threadCount = atoi(argv[3]); + + EventLoop loop; + + TcpServer server(&loop, listenAddr, "PingPong"); + + server.setConnectionCallback(onConnection); + server.setMessageCallback(onMessage); + + if (threadCount > 1) + { + server.setThreadNum(threadCount); + } + + server.start(); + + loop.loop(); + } +} + diff --git a/examples/procmon/dummyload.cc b/examples/procmon/dummyload.cc new file mode 100644 index 0000000..e891182 --- /dev/null +++ b/examples/procmon/dummyload.cc @@ -0,0 +1,181 @@ +#include "muduo/base/Atomic.h" +#include "muduo/base/Condition.h" +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" +#include "muduo/base/Timestamp.h" +#include "muduo/net/EventLoop.h" + +#include <math.h> +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +int g_cycles = 0; +int g_percent = 82; +AtomicInt32 g_done; +bool g_busy = false; +MutexLock g_mutex; +Condition g_cond(g_mutex); + +double busy(int cycles) +{ + double result = 0; + for (int i = 0; i < cycles; ++i) + { + result += sqrt(i) * sqrt(i+1); + } + return result; +} + +double getSeconds(int cycles) +{ + Timestamp start = Timestamp::now(); + busy(cycles); + return timeDifference(Timestamp::now(), start); +} + +void findCycles() +{ + g_cycles = 1000; + while (getSeconds(g_cycles) < 0.001) + g_cycles = g_cycles + g_cycles / 4; // * 1.25 + printf("cycles %d\n", g_cycles); +} + +void threadFunc() +{ + while (g_done.get() == 0) + { + { + MutexLockGuard guard(g_mutex); + while (!g_busy) + g_cond.wait(); + } + busy(g_cycles); + } + printf("thread exit\n"); +} + +// this is open-loop control +void load(int percent) +{ + percent = std::max(0, percent); + percent = std::min(100, percent); + + // Bresenham's line algorithm + int err = 2*percent - 100; + int count = 0; + + for (int i = 0; i < 100; ++i) + { + bool busy = false; + if (err > 0) + { + busy = true; + err += 2*(percent - 100); + ++count; + // printf("%2d, ", i); + } + else + { + err += 2*percent; + } + + { + MutexLockGuard guard(g_mutex); + g_busy = busy; + g_cond.notifyAll(); + } + + CurrentThread::sleepUsec(10*1000); // 10 ms + } + assert(count == percent); +} + +void fixed() +{ + while (true) + { + load(g_percent); + } +} + +void cosine() +{ + while (true) + for (int i = 0; i < 200; ++i) + { + int percent = static_cast<int>((1.0 + cos(i * 3.14159 / 100)) / 2 * g_percent + 0.5); + load(percent); + } +} + +void sawtooth() +{ + while (true) + for (int i = 0; i <= 100; ++i) + { + int percent = static_cast<int>(i / 100.0 * g_percent); + load(percent); + } +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + printf("Usage: %s [fctsz] [percent] [num_threads]\n", argv[0]); + return 0; + } + + printf("pid %d\n", getpid()); + findCycles(); + + g_percent = argc > 2 ? atoi(argv[2]) : 43; + int numThreads = argc > 3 ? atoi(argv[3]) : 1; + std::vector<std::unique_ptr<Thread>> threads; + for (int i = 0; i < numThreads; ++i) + { + threads.emplace_back(new Thread(threadFunc)); + threads.back()->start(); + } + + switch (argv[1][0]) + { + case 'f': + { + fixed(); + } + break; + + case 'c': + { + cosine(); + } + break; + + case 'z': + { + sawtooth(); + } + break; + + // TODO: square and triangle waves + + default: + break; + } + + g_done.getAndSet(1); + { + MutexLockGuard guard(g_mutex); + g_busy = true; + g_cond.notifyAll(); + } + for (int i = 0; i < numThreads; ++i) + { + threads[i]->join(); + } +} diff --git a/examples/procmon/plot.cc b/examples/procmon/plot.cc new file mode 100644 index 0000000..2e62a1c --- /dev/null +++ b/examples/procmon/plot.cc @@ -0,0 +1,120 @@ +#include "examples/procmon/plot.h" +#include <algorithm> + +#include <math.h> + +#include <gd.h> +#include <gdfonts.h> + +struct Plot::MyGdFont : public gdFont {}; + +Plot::Plot(int width, int height, int totalSeconds, int samplingPeriod) + : width_(width), + height_(height), + totalSeconds_(totalSeconds), + samplingPeriod_(samplingPeriod), + image_(gdImageCreate(width_, height_)), + font_(static_cast<MyGdFont*>(gdFontGetSmall())), + fontWidth_(font_->w), + fontHeight_(font_->h), + background_(gdImageColorAllocate(image_, 255, 255, 240)), + black_(gdImageColorAllocate(image_, 0, 0, 0)), + gray_(gdImageColorAllocate(image_, 200, 200, 200)), + blue_(gdImageColorAllocate(image_, 128, 128, 255)), + kRightMargin_(3 * fontWidth_ + 5), + ratioX_(static_cast<double>(samplingPeriod_ * (width_ - kLeftMargin_ - kRightMargin_)) / totalSeconds_) +{ + // gdImageSetAntiAliased(image_, black_); +} + +Plot::~Plot() +{ + gdImageDestroy(image_); +} + +muduo::string Plot::plotCpu(const std::vector<double>& data) +{ + gdImageFilledRectangle(image_, 0, 0, width_, height_, background_); + if (data.size() > 1) + { + gdImageSetThickness(image_, 2); + double max = *std::max_element(data.begin(), data.end()); + if (max >= 10.0) + { + max = ceil(max); + } + else + { + max = std::max(0.1, ceil(max*10.0) / 10.0); + } + label(max); + + for (size_t i = 0; i < data.size()-1; ++i) + { + gdImageLine(image_, + getX(i, data.size()), + getY(data[i] / max), + getX(i+1, data.size()), + getY(data[i+1]/max), + black_); + } + } + + int total = totalSeconds_/samplingPeriod_; + gdImageSetThickness(image_, 1); + gdImageLine(image_, getX(0, total), getY(0)+2, getX(total, total), getY(0)+2, gray_); + gdImageLine(image_, getX(total, total), getY(0)+2, getX(total, total), getY(1)+2, gray_); + return toPng(); +} + +void Plot::label(double maxValue) +{ + char buf[64]; + if (maxValue >= 10.0) + snprintf(buf, sizeof buf, "%.0f", maxValue); + else + snprintf(buf, sizeof buf, "%.1f", maxValue); + + gdImageString(image_, + font_, + width_ - kRightMargin_ + 3, + kMarginY_ - 3, + reinterpret_cast<unsigned char*>(buf), + black_); + + snprintf(buf, sizeof buf, "0"); + gdImageString(image_, + font_, + width_ - kRightMargin_ + 3, + height_ - kMarginY_ - 3 - fontHeight_ / 2, + reinterpret_cast<unsigned char*>(buf), + gray_); + + snprintf(buf, sizeof buf, "-%ds", totalSeconds_); + gdImageString(image_, + font_, + kLeftMargin_, + height_ - kMarginY_ - fontHeight_, + reinterpret_cast<unsigned char*>(buf), + blue_); +} + +int Plot::getX(ssize_t i, ssize_t total) const +{ + double x = (width_ - kLeftMargin_ - kRightMargin_) + static_cast<double>(i - total) * ratioX_; + return static_cast<int>(x + 0.5) + kLeftMargin_; +} + +int Plot::getY(double value) const +{ + return static_cast<int>((1.0 - value) * (height_-2*kMarginY_) + 0.5) + kMarginY_; +} + +muduo::string Plot::toPng() +{ + int size = 0; + void* png = gdImagePngPtr(image_, &size); + muduo::string result(static_cast<char*>(png), size); + gdFree(png); + return result; +} diff --git a/examples/procmon/plot.h b/examples/procmon/plot.h new file mode 100644 index 0000000..582bec3 --- /dev/null +++ b/examples/procmon/plot.h @@ -0,0 +1,46 @@ +#include "muduo/base/noncopyable.h" +#include "muduo/base/Types.h" +#include <vector> +#include <stdlib.h> // ssize_t + +typedef struct gdImageStruct* gdImagePtr; + +class Plot : muduo::noncopyable +{ + public: + Plot(int width, int height, int totalSeconds, int samplingPeriod); + ~Plot(); + muduo::string plotCpu(const std::vector<double>& data); + + private: + muduo::string toPng(); + // pair<shared_ptr<void*>, int> toPng(); + int getX(ssize_t x, ssize_t total) const; + int getY(double value) const; + void label(double maxValue); + + // gdFont is a typedef of unnamed struct, cannot be forward declared + // wordaround suggested in http://stackoverflow.com/questions/7256436/forward-declarations-of-unnamed-struct + struct MyGdFont; + typedef struct MyGdFont* MyGdFontPtr; + + const int width_; + const int height_; + const int totalSeconds_; + const int samplingPeriod_; + gdImagePtr const image_; + MyGdFontPtr const font_; + const int fontWidth_; + const int fontHeight_; + const int background_; + const int black_; + const int gray_; + const int blue_; + + const int kRightMargin_; + static const int kLeftMargin_ = 5; + static const int kMarginY_ = 5; + + const double ratioX_; +}; + diff --git a/examples/procmon/plot_test.cc b/examples/procmon/plot_test.cc new file mode 100644 index 0000000..bfa4b90 --- /dev/null +++ b/examples/procmon/plot_test.cc @@ -0,0 +1,27 @@ +#include "examples/procmon/plot.h" +#include "muduo/base/Timestamp.h" +#include <vector> +#include <math.h> +#include <stdio.h> + +int main() +{ + std::vector<double> cpu_usage; + cpu_usage.reserve(300); + for (int i = 0; i < 300; ++i) + cpu_usage.push_back(1.0 + sin(pow(i / 30.0, 2))); + Plot plot(640, 100, 600, 2); + muduo::Timestamp start(muduo::Timestamp::now()); + const int N = 10000; + for (int i = 0; i < N; ++i) + muduo::string png = plot.plotCpu(cpu_usage); + double elapsed = timeDifference(muduo::Timestamp::now(), start); + printf("%d plots in %f seconds, %f PNG per second, %f ms per PNG\n", + N, elapsed, N / elapsed, elapsed * 1000 / N); + muduo::string png = plot.plotCpu(cpu_usage); + + FILE* fp = fopen("test.png", "wb"); + fwrite(png.data(), 1, png.size(), fp); + fclose(fp); + printf("Image saved to test.png\n"); +} diff --git a/examples/procmon/procmon.cc b/examples/procmon/procmon.cc new file mode 100644 index 0000000..7d67d2e --- /dev/null +++ b/examples/procmon/procmon.cc @@ -0,0 +1,541 @@ +#include "examples/procmon/plot.h" + +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/http/HttpRequest.h" +#include "muduo/net/http/HttpResponse.h" +#include "muduo/net/http/HttpServer.h" + +#include <boost/algorithm/string/replace.hpp> +#include <boost/circular_buffer.hpp> + +#include <sstream> +#include <type_traits> + +#include <dirent.h> +#include <stdarg.h> +#include <stdio.h> +#include <sys/stat.h> +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +using namespace muduo; +using namespace muduo::net; + +// TODO: +// - what if process exits? +// + +// Represent parsed /proc/pid/stat +struct StatData +{ + void parse(const char* startAtState, int kbPerPage) + { + // istringstream is probably not the most efficient way to parse it, + // see muduo-protorpc/examples/collect/ProcFs.cc for alternatives. + std::istringstream iss(startAtState); + + // 0 1 2 3 4 5 6 7 8 9 11 13 15 + // 3770 (cat) R 3718 3770 3718 34818 3770 4202496 214 0 0 0 0 0 0 0 20 + // 16 18 19 20 21 22 23 24 25 + // 0 1 0 298215 5750784 81 18446744073709551615 4194304 4242836 140736345340592 + // 26 + // 140736066274232 140575670169216 0 0 0 0 0 0 0 17 0 0 0 0 0 0 + + iss >> state; + iss >> ppid >> pgrp >> session >> tty_nr >> tpgid >> flags; + iss >> minflt >> cminflt >> majflt >> cmajflt; + iss >> utime >> stime >> cutime >> cstime; + iss >> priority >> nice >> num_threads >> itrealvalue >> starttime; + long vsize, rss; + iss >> vsize >> rss >> rsslim; + vsizeKb = vsize / 1024; + rssKb = rss * kbPerPage; + } + // int pid; + char state; + int ppid; + int pgrp; + int session; + int tty_nr; + int tpgid; + int flags; + + long minflt; + long cminflt; + long majflt; + long cmajflt; + + long utime; + long stime; + long cutime; + long cstime; + + long priority; + long nice; + long num_threads; + long itrealvalue; + long starttime; + + long vsizeKb; + long rssKb; + long rsslim; +}; + +static_assert(std::is_pod<StatData>::value, "StatData should be POD."); + +class Procmon : noncopyable +{ + public: + Procmon(EventLoop* loop, pid_t pid, uint16_t port, const char* procname) + : kClockTicksPerSecond_(muduo::ProcessInfo::clockTicksPerSecond()), + kbPerPage_(muduo::ProcessInfo::pageSize() / 1024), + kBootTime_(getBootTime()), + pid_(pid), + server_(loop, InetAddress(port), getName()), + procname_(procname ? procname : ProcessInfo::procname(readProcFile("stat")).as_string()), + hostname_(ProcessInfo::hostname()), + cmdline_(getCmdLine()), + ticks_(0), + cpu_usage_(600 / kPeriod_), // 10 minutes + cpu_chart_(640, 100, 600, kPeriod_), + ram_chart_(640, 100, 7200, 30) + { + { + // chdir to the same cwd of the process being monitored. + string cwd = readLink("cwd"); + if (::chdir(cwd.c_str())) + { + LOG_SYSERR << "Cannot chdir() to " << cwd; + } + } + + { + char cwd[1024]; + if (::getcwd(cwd, sizeof cwd)) + { + LOG_INFO << "Current dir: " << cwd; + } + } + memZero(&lastStatData_, sizeof lastStatData_); + server_.setHttpCallback(std::bind(&Procmon::onRequest, this, _1, _2)); + } + + void start() + { + tick(); + server_.getLoop()->runEvery(kPeriod_, std::bind(&Procmon::tick, this)); + server_.start(); + } + + private: + + string getName() const + { + char name[256]; + snprintf(name, sizeof name, "procmon-%d", pid_); + return name; + } + + void onRequest(const HttpRequest& req, HttpResponse* resp) + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/plain"); + resp->addHeader("Server", "Muduo-Procmon"); + + /* + if (!processExists(pid_)) + { + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); + return; + } + */ + + if (req.path() == "/") + { + resp->setContentType("text/html"); + fillOverview(req.query()); + resp->setBody(response_.retrieveAllAsString()); + } + else if (req.path() == "/cmdline") + { + resp->setBody(cmdline_); + } + else if (req.path() == "/cpu.png") + { + std::vector<double> cpu_usage; + for (size_t i = 0; i < cpu_usage_.size(); ++i) + cpu_usage.push_back(cpu_usage_[i].cpuUsage(kPeriod_, kClockTicksPerSecond_)); + string png = cpu_chart_.plotCpu(cpu_usage); + resp->setContentType("image/png"); + resp->setBody(png); + } + // FIXME: replace with a map + else if (req.path() == "/environ") + { + resp->setBody(getEnviron()); + } + else if (req.path() == "/io") + { + resp->setBody(readProcFile("io")); + } + else if (req.path() == "/limits") + { + resp->setBody(readProcFile("limits")); + } + else if (req.path() == "/maps") + { + resp->setBody(readProcFile("maps")); + } + // numa_maps + else if (req.path() == "/smaps") + { + resp->setBody(readProcFile("smaps")); + } + else if (req.path() == "/status") + { + resp->setBody(readProcFile("status")); + } + else if (req.path() == "/files") + { + listFiles(); + resp->setBody(response_.retrieveAllAsString()); + } + else if (req.path() == "/threads") + { + listThreads(); + resp->setBody(response_.retrieveAllAsString()); + } + else + { + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); + } + } + + void fillOverview(const string& query) + { + response_.retrieveAll(); + Timestamp now = Timestamp::now(); + appendResponse("<html><head><title>%s on %s</title>\n", + procname_.c_str(), hostname_.c_str()); + fillRefresh(query); + appendResponse("</head><body>\n"); + + string stat = readProcFile("stat"); + if (stat.empty()) + { + appendResponse("<h1>PID %d doesn't exist.</h1></body></html>", pid_); + return; + } + int pid = atoi(stat.c_str()); + assert(pid == pid_); + StringPiece procname = ProcessInfo::procname(stat); + appendResponse("<h1>%s on %s</h1>\n", + procname.as_string().c_str(), hostname_.c_str()); + response_.append("<p>Refresh <a href=\"?refresh=1\">1s</a> "); + response_.append("<a href=\"?refresh=2\">2s</a> "); + response_.append("<a href=\"?refresh=5\">5s</a> "); + response_.append("<a href=\"?refresh=15\">15s</a> "); + response_.append("<a href=\"?refresh=60\">60s</a>\n"); + response_.append("<p><a href=\"/cmdline\">Command line</a>\n"); + response_.append("<a href=\"/environ\">Environment variables</a>\n"); + response_.append("<a href=\"/threads\">Threads</a>\n"); + + appendResponse("<p>Page generated at %s (UTC)", now.toFormattedString().c_str()); + + response_.append("<p><table>"); + StatData statData; // how about use lastStatData_ ? + memZero(&statData, sizeof statData); + statData.parse(procname.end()+1, kbPerPage_); // end is ')' + + appendTableRow("PID", pid); + Timestamp started(getStartTime(statData.starttime)); // FIXME: cache it; + appendTableRow("Started at", started.toFormattedString(false /*showMicroseconds*/) + " (UTC)"); + appendTableRowFloat("Uptime (s)", timeDifference(now, started)); // FIXME: format as days+H:M:S + appendTableRow("Executable", readLink("exe")); + appendTableRow("Current dir", readLink("cwd")); + + appendTableRow("State", getState(statData.state)); + appendTableRowFloat("User time (s)", getSeconds(statData.utime)); + appendTableRowFloat("System time (s)", getSeconds(statData.stime)); + + appendTableRow("VmSize (KiB)", statData.vsizeKb); + appendTableRow("VmRSS (KiB)", statData.rssKb); + appendTableRow("Threads", statData.num_threads); + appendTableRow("CPU usage", "<img src=\"/cpu.png\" height=\"100\" witdh=\"640\">"); + + appendTableRow("Priority", statData.priority); + appendTableRow("Nice", statData.nice); + + appendTableRow("Minor page faults", statData.minflt); + appendTableRow("Major page faults", statData.majflt); + // TODO: user + + response_.append("</table>"); + response_.append("</body></html>"); + } + + void fillRefresh(const string& query) + { + size_t p = query.find("refresh="); + if (p != string::npos) + { + int seconds = atoi(query.c_str()+p+8); + if (seconds > 0) + { + appendResponse("<meta http-equiv=\"refresh\" content=\"%d\">\n", seconds); + } + } + } + + static int dirFilter(const struct dirent* d) + { + return (d->d_name[0] != '.'); + } + + static char getDirType(char d_type) + { + switch (d_type) + { + case DT_REG: return '-'; + case DT_DIR: return 'd'; + case DT_LNK: return 'l'; + default: return '?'; + } + } + + void listFiles() + { + struct dirent** namelist = NULL; + int result = ::scandir(".", &namelist, dirFilter, alphasort); + for (int i = 0; i < result; ++i) + { + struct stat stat; + if (::lstat(namelist[i]->d_name, &stat) == 0) + { + Timestamp mtime(stat.st_mtime * Timestamp::kMicroSecondsPerSecond); + int64_t size = stat.st_size; + appendResponse("%c %9" PRId64 " %s %s", getDirType(namelist[i]->d_type), size, + mtime.toFormattedString(/*showMicroseconds=*/false).c_str(), + namelist[i]->d_name); + if (namelist[i]->d_type == DT_LNK) + { + char link[1024]; + ssize_t len = ::readlink(namelist[i]->d_name, link, sizeof link - 1); + if (len > 0) + { + link[len] = '\0'; + appendResponse(" -> %s", link); + } + } + appendResponse("\n"); + } + ::free(namelist[i]); + } + ::free(namelist); + } + + void listThreads() + { + response_.retrieveAll(); + // FIXME: implement this + } + + string readProcFile(const char* basename) + { + char filename[256]; + snprintf(filename, sizeof filename, "/proc/%d/%s", pid_, basename); + string content; + FileUtil::readFile(filename, 1024*1024, &content); + return content; + } + + string readLink(const char* basename) + { + char filename[256]; + snprintf(filename, sizeof filename, "/proc/%d/%s", pid_, basename); + char link[1024]; + ssize_t len = ::readlink(filename, link, sizeof link); + string result; + if (len > 0) + { + result.assign(link, len); + } + return result; + } + + int appendResponse(const char* fmt, ...) __attribute__ ((format (printf, 2, 3))); + + void appendTableRow(const char* name, long value) + { + appendResponse("<tr><td>%s</td><td>%ld</td></tr>\n", name, value); + } + + void appendTableRowFloat(const char* name, double value) + { + appendResponse("<tr><td>%s</td><td>%.2f</td></tr>\n", name, value); + } + + void appendTableRow(const char* name, StringArg value) + { + appendResponse("<tr><td>%s</td><td>%s</td></tr>\n", name, value.c_str()); + } + + string getCmdLine() + { + return boost::replace_all_copy(readProcFile("cmdline"), string(1, '\0'), "\n\t"); + } + + string getEnviron() + { + return boost::replace_all_copy(readProcFile("environ"), string(1, '\0'), "\n"); + } + + Timestamp getStartTime(long starttime) + { + return Timestamp(Timestamp::kMicroSecondsPerSecond * kBootTime_ + + Timestamp::kMicroSecondsPerSecond * starttime / kClockTicksPerSecond_); + } + + double getSeconds(long ticks) + { + return static_cast<double>(ticks) / kClockTicksPerSecond_; + } + + void tick() + { + string stat = readProcFile("stat"); // FIXME: catch file descriptor + if (stat.empty()) + return; + StringPiece procname = ProcessInfo::procname(stat); + StatData statData; + memZero(&statData, sizeof statData); + statData.parse(procname.end()+1, kbPerPage_); // end is ')' + if (ticks_ > 0) + { + CpuTime time; + time.userTime_ = std::max(0, static_cast<int>(statData.utime - lastStatData_.utime)); + time.sysTime_ = std::max(0, static_cast<int>(statData.stime - lastStatData_.stime)); + cpu_usage_.push_back(time); + } + + lastStatData_ = statData; + ++ticks_; + } + + // + // static member functions + // + + static const char* getState(char state) + { + // One character from the string "RSDZTW" where R is running, S is sleeping in + // an interruptible wait, D is waiting in uninterruptible disk sleep, Z is zombie, + // T is traced or stopped (on a signal), and W is paging. + switch (state) + { + case 'R': + return "Running"; + case 'S': + return "Sleeping"; + case 'D': + return "Disk sleep"; + case 'Z': + return "Zombie"; + default: + return "Unknown"; + } + } + + static long getLong(const string& status, const char* key) + { + long result = 0; + size_t pos = status.find(key); + if (pos != string::npos) + { + result = ::atol(status.c_str() + pos + strlen(key)); + } + return result; + } + + static long getBootTime() + { + string stat; + FileUtil::readFile("/proc/stat", 65536, &stat); + return getLong(stat, "btime "); + } + + struct CpuTime + { + int userTime_; + int sysTime_; + double cpuUsage(double kPeriod, double kClockTicksPerSecond) const + { + return (userTime_ + sysTime_) / (kClockTicksPerSecond * kPeriod); + } + }; + + const static int kPeriod_ = 2.0; + const int kClockTicksPerSecond_; + const int kbPerPage_; + const long kBootTime_; // in Unix-time + const pid_t pid_; + HttpServer server_; + const string procname_; + const string hostname_; + const string cmdline_; + int ticks_; + StatData lastStatData_; + boost::circular_buffer<CpuTime> cpu_usage_; + Plot cpu_chart_; + Plot ram_chart_; + // scratch variables + Buffer response_; +}; + +// define outline for __attribute__ +int Procmon::appendResponse(const char* fmt, ...) +{ + char buf[1024]; + va_list args; + va_start(args, fmt); + int ret = vsnprintf(buf, sizeof buf, fmt, args); + va_end(args); + response_.append(buf); + return ret; +} + +bool processExists(pid_t pid) +{ + char filename[256]; + snprintf(filename, sizeof filename, "/proc/%d/stat", pid); + return ::access(filename, R_OK) == 0; +} + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + printf("Usage: %s pid port [name]\n", argv[0]); + return 0; + } + int pid = atoi(argv[1]); + if (!processExists(pid)) + { + printf("Process %d doesn't exist.\n", pid); + return 1; + } + + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + Procmon procmon(&loop, pid, port, argc > 3 ? argv[3] : NULL); + procmon.start(); + loop.loop(); +} diff --git a/examples/protobuf/codec/client.cc b/examples/protobuf/codec/client.cc new file mode 100644 index 0000000..1e9b9da --- /dev/null +++ b/examples/protobuf/codec/client.cc @@ -0,0 +1,121 @@ +#include "examples/protobuf/codec/dispatcher.h" +#include "examples/protobuf/codec/codec.h" +#include "examples/protobuf/codec/query.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +typedef std::shared_ptr<muduo::Empty> EmptyPtr; +typedef std::shared_ptr<muduo::Answer> AnswerPtr; + +google::protobuf::Message* messageToSend; + +class QueryClient : noncopyable +{ + public: + QueryClient(EventLoop* loop, + const InetAddress& serverAddr) + : loop_(loop), + client_(loop, serverAddr, "QueryClient"), + dispatcher_(std::bind(&QueryClient::onUnknownMessage, this, _1, _2, _3)), + codec_(std::bind(&ProtobufDispatcher::onProtobufMessage, &dispatcher_, _1, _2, _3)) + { + dispatcher_.registerMessageCallback<muduo::Answer>( + std::bind(&QueryClient::onAnswer, this, _1, _2, _3)); + dispatcher_.registerMessageCallback<muduo::Empty>( + std::bind(&QueryClient::onEmpty, this, _1, _2, _3)); + client_.setConnectionCallback( + std::bind(&QueryClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&ProtobufCodec::onMessage, &codec_, _1, _2, _3)); + } + + void connect() + { + client_.connect(); + } + + private: + + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + codec_.send(conn, *messageToSend); + } + else + { + loop_->quit(); + } + } + + void onUnknownMessage(const TcpConnectionPtr&, + const MessagePtr& message, + Timestamp) + { + LOG_INFO << "onUnknownMessage: " << message->GetTypeName(); + } + + void onAnswer(const muduo::net::TcpConnectionPtr&, + const AnswerPtr& message, + muduo::Timestamp) + { + LOG_INFO << "onAnswer:\n" << message->GetTypeName() << message->DebugString(); + } + + void onEmpty(const muduo::net::TcpConnectionPtr&, + const EmptyPtr& message, + muduo::Timestamp) + { + LOG_INFO << "onEmpty: " << message->GetTypeName(); + } + + EventLoop* loop_; + TcpClient client_; + ProtobufDispatcher dispatcher_; + ProtobufCodec codec_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 2) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + muduo::Query query; + query.set_id(1); + query.set_questioner("Chen Shuo"); + query.add_question("Running?"); + muduo::Empty empty; + messageToSend = &query; + + if (argc > 3 && argv[3][0] == 'e') + { + messageToSend = ∅ + } + + QueryClient client(&loop, serverAddr); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip port [q|e]\n", argv[0]); + } +} + diff --git a/examples/protobuf/codec/codec.cc b/examples/protobuf/codec/codec.cc new file mode 100644 index 0000000..5a47c30 --- /dev/null +++ b/examples/protobuf/codec/codec.cc @@ -0,0 +1,235 @@ +// Copyright 2011, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "examples/protobuf/codec/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" +#include "muduo/net/protorpc/google-inl.h" + +#include <google/protobuf/descriptor.h> + +#include <zlib.h> // adler32 + +using namespace muduo; +using namespace muduo::net; + +void ProtobufCodec::fillEmptyBuffer(Buffer* buf, const google::protobuf::Message& message) +{ + // buf->retrieveAll(); + assert(buf->readableBytes() == 0); + + const std::string& typeName = message.GetTypeName(); + int32_t nameLen = static_cast<int32_t>(typeName.size()+1); + buf->appendInt32(nameLen); + buf->append(typeName.c_str(), nameLen); + + // code copied from MessageLite::SerializeToArray() and MessageLite::SerializePartialToArray(). + GOOGLE_DCHECK(message.IsInitialized()) << InitializationErrorMessage("serialize", message); + + /** + * 'ByteSize()' of message is deprecated in Protocol Buffers v3.4.0 firstly. + * But, till to v3.11.0, it just getting start to be marked by '__attribute__((deprecated()))'. + * So, here, v3.9.2 is selected as maximum version using 'ByteSize()' to avoid + * potential effect for previous muduo code/projects as far as possible. + * Note: All information above just INFER from + * 1) https://github.com/protocolbuffers/protobuf/releases/tag/v3.4.0 + * 2) MACRO in file 'include/google/protobuf/port_def.inc'. + * eg. '#define PROTOBUF_DEPRECATED_MSG(msg) __attribute__((deprecated(msg)))'. + * In addition, usage of 'ToIntSize()' comes from Impl of ByteSize() in new version's Protocol Buffers. + */ + + #if GOOGLE_PROTOBUF_VERSION > 3009002 + int byte_size = google::protobuf::internal::ToIntSize(message.ByteSizeLong()); + #else + int byte_size = message.ByteSize(); + #endif + buf->ensureWritableBytes(byte_size); + + uint8_t* start = reinterpret_cast<uint8_t*>(buf->beginWrite()); + uint8_t* end = message.SerializeWithCachedSizesToArray(start); + if (end - start != byte_size) + { + #if GOOGLE_PROTOBUF_VERSION > 3009002 + ByteSizeConsistencyError(byte_size, google::protobuf::internal::ToIntSize(message.ByteSizeLong()), static_cast<int>(end - start)); + #else + ByteSizeConsistencyError(byte_size, message.ByteSize(), static_cast<int>(end - start)); + #endif + } + buf->hasWritten(byte_size); + + int32_t checkSum = static_cast<int32_t>( + ::adler32(1, + reinterpret_cast<const Bytef*>(buf->peek()), + static_cast<int>(buf->readableBytes()))); + buf->appendInt32(checkSum); + assert(buf->readableBytes() == sizeof nameLen + nameLen + byte_size + sizeof checkSum); + int32_t len = sockets::hostToNetwork32(static_cast<int32_t>(buf->readableBytes())); + buf->prepend(&len, sizeof len); +} + +// +// no more google code after this +// + +// +// FIXME: merge with RpcCodec +// + +namespace +{ + const string kNoErrorStr = "NoError"; + const string kInvalidLengthStr = "InvalidLength"; + const string kCheckSumErrorStr = "CheckSumError"; + const string kInvalidNameLenStr = "InvalidNameLen"; + const string kUnknownMessageTypeStr = "UnknownMessageType"; + const string kParseErrorStr = "ParseError"; + const string kUnknownErrorStr = "UnknownError"; +} + +const string& ProtobufCodec::errorCodeToString(ErrorCode errorCode) +{ + switch (errorCode) + { + case kNoError: + return kNoErrorStr; + case kInvalidLength: + return kInvalidLengthStr; + case kCheckSumError: + return kCheckSumErrorStr; + case kInvalidNameLen: + return kInvalidNameLenStr; + case kUnknownMessageType: + return kUnknownMessageTypeStr; + case kParseError: + return kParseErrorStr; + default: + return kUnknownErrorStr; + } +} + +void ProtobufCodec::defaultErrorCallback(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp, + ErrorCode errorCode) +{ + LOG_ERROR << "ProtobufCodec::defaultErrorCallback - " << errorCodeToString(errorCode); + if (conn && conn->connected()) + { + conn->shutdown(); + } +} + +int32_t asInt32(const char* buf) +{ + int32_t be32 = 0; + ::memcpy(&be32, buf, sizeof(be32)); + return sockets::networkToHost32(be32); +} + +void ProtobufCodec::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + while (buf->readableBytes() >= kMinMessageLen + kHeaderLen) + { + const int32_t len = buf->peekInt32(); + if (len > kMaxMessageLen || len < kMinMessageLen) + { + errorCallback_(conn, buf, receiveTime, kInvalidLength); + break; + } + else if (buf->readableBytes() >= implicit_cast<size_t>(len + kHeaderLen)) + { + ErrorCode errorCode = kNoError; + MessagePtr message = parse(buf->peek()+kHeaderLen, len, &errorCode); + if (errorCode == kNoError && message) + { + messageCallback_(conn, message, receiveTime); + buf->retrieve(kHeaderLen+len); + } + else + { + errorCallback_(conn, buf, receiveTime, errorCode); + break; + } + } + else + { + break; + } + } +} + +google::protobuf::Message* ProtobufCodec::createMessage(const std::string& typeName) +{ + google::protobuf::Message* message = NULL; + const google::protobuf::Descriptor* descriptor = + google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(typeName); + if (descriptor) + { + const google::protobuf::Message* prototype = + google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); + if (prototype) + { + message = prototype->New(); + } + } + return message; +} + +MessagePtr ProtobufCodec::parse(const char* buf, int len, ErrorCode* error) +{ + MessagePtr message; + + // check sum + int32_t expectedCheckSum = asInt32(buf + len - kHeaderLen); + int32_t checkSum = static_cast<int32_t>( + ::adler32(1, + reinterpret_cast<const Bytef*>(buf), + static_cast<int>(len - kHeaderLen))); + if (checkSum == expectedCheckSum) + { + // get message type name + int32_t nameLen = asInt32(buf); + if (nameLen >= 2 && nameLen <= len - 2*kHeaderLen) + { + std::string typeName(buf + kHeaderLen, buf + kHeaderLen + nameLen - 1); + // create message object + message.reset(createMessage(typeName)); + if (message) + { + // parse from buffer + const char* data = buf + kHeaderLen + nameLen; + int32_t dataLen = len - nameLen - 2*kHeaderLen; + if (message->ParseFromArray(data, dataLen)) + { + *error = kNoError; + } + else + { + *error = kParseError; + } + } + else + { + *error = kUnknownMessageType; + } + } + else + { + *error = kInvalidNameLen; + } + } + else + { + *error = kCheckSumError; + } + + return message; +} diff --git a/examples/protobuf/codec/codec.h b/examples/protobuf/codec/codec.h new file mode 100644 index 0000000..26fdd9b --- /dev/null +++ b/examples/protobuf/codec/codec.h @@ -0,0 +1,98 @@ +// Copyright 2011, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_EXAMPLES_PROTOBUF_CODEC_CODEC_H +#define MUDUO_EXAMPLES_PROTOBUF_CODEC_CODEC_H + +#include "muduo/net/Buffer.h" +#include "muduo/net/TcpConnection.h" + +#include <google/protobuf/message.h> + +// struct ProtobufTransportFormat __attribute__ ((__packed__)) +// { +// int32_t len; +// int32_t nameLen; +// char typeName[nameLen]; +// char protobufData[len-nameLen-8]; +// int32_t checkSum; // adler32 of nameLen, typeName and protobufData +// } + +typedef std::shared_ptr<google::protobuf::Message> MessagePtr; + +// +// FIXME: merge with RpcCodec +// +class ProtobufCodec : muduo::noncopyable +{ + public: + + enum ErrorCode + { + kNoError = 0, + kInvalidLength, + kCheckSumError, + kInvalidNameLen, + kUnknownMessageType, + kParseError, + }; + + typedef std::function<void (const muduo::net::TcpConnectionPtr&, + const MessagePtr&, + muduo::Timestamp)> ProtobufMessageCallback; + + typedef std::function<void (const muduo::net::TcpConnectionPtr&, + muduo::net::Buffer*, + muduo::Timestamp, + ErrorCode)> ErrorCallback; + + explicit ProtobufCodec(const ProtobufMessageCallback& messageCb) + : messageCallback_(messageCb), + errorCallback_(defaultErrorCallback) + { + } + + ProtobufCodec(const ProtobufMessageCallback& messageCb, const ErrorCallback& errorCb) + : messageCallback_(messageCb), + errorCallback_(errorCb) + { + } + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp receiveTime); + + void send(const muduo::net::TcpConnectionPtr& conn, + const google::protobuf::Message& message) + { + // FIXME: serialize to TcpConnection::outputBuffer() + muduo::net::Buffer buf; + fillEmptyBuffer(&buf, message); + conn->send(&buf); + } + + static const muduo::string& errorCodeToString(ErrorCode errorCode); + static void fillEmptyBuffer(muduo::net::Buffer* buf, const google::protobuf::Message& message); + static google::protobuf::Message* createMessage(const std::string& type_name); + static MessagePtr parse(const char* buf, int len, ErrorCode* errorCode); + + private: + static void defaultErrorCallback(const muduo::net::TcpConnectionPtr&, + muduo::net::Buffer*, + muduo::Timestamp, + ErrorCode); + + ProtobufMessageCallback messageCallback_; + ErrorCallback errorCallback_; + + const static int kHeaderLen = sizeof(int32_t); + const static int kMinMessageLen = 2*kHeaderLen + 2; // nameLen + typeName + checkSum + const static int kMaxMessageLen = 64*1024*1024; // same as codec_stream.h kDefaultTotalBytesLimit +}; + +#endif // MUDUO_EXAMPLES_PROTOBUF_CODEC_CODEC_H diff --git a/examples/protobuf/codec/codec_test.cc b/examples/protobuf/codec/codec_test.cc new file mode 100644 index 0000000..fab1281 --- /dev/null +++ b/examples/protobuf/codec/codec_test.cc @@ -0,0 +1,264 @@ +#include "examples/protobuf/codec/codec.h" +#include "muduo/net/Endian.h" +#include "examples/protobuf/codec/query.pb.h" + +#include <stdio.h> +#include <zlib.h> // adler32 + +using namespace muduo; +using namespace muduo::net; + +void print(const Buffer& buf) +{ + printf("encoded to %zd bytes\n", buf.readableBytes()); + for (size_t i = 0; i < buf.readableBytes(); ++i) + { + unsigned char ch = static_cast<unsigned char>(buf.peek()[i]); + + printf("%2zd: 0x%02x %c\n", i, ch, isgraph(ch) ? ch : ' '); + } +} + +void testQuery() +{ + muduo::Query query; + query.set_id(1); + query.set_questioner("Chen Shuo"); + query.add_question("Running?"); + + Buffer buf; + ProtobufCodec::fillEmptyBuffer(&buf, query); + print(buf); + + const int32_t len = buf.readInt32(); + assert(len == static_cast<int32_t>(buf.readableBytes())); + + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + MessagePtr message = ProtobufCodec::parse(buf.peek(), len, &errorCode); + assert(errorCode == ProtobufCodec::kNoError); + assert(message != NULL); + message->PrintDebugString(); + assert(message->DebugString() == query.DebugString()); + + std::shared_ptr<muduo::Query> newQuery = down_pointer_cast<muduo::Query>(message); + assert(newQuery != NULL); +} + +void testAnswer() +{ + muduo::Answer answer; + answer.set_id(1); + answer.set_questioner("Chen Shuo"); + answer.set_answerer("blog.csdn.net/Solstice"); + answer.add_solution("Jump!"); + answer.add_solution("Win!"); + + Buffer buf; + ProtobufCodec::fillEmptyBuffer(&buf, answer); + print(buf); + + const int32_t len = buf.readInt32(); + assert(len == static_cast<int32_t>(buf.readableBytes())); + + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + MessagePtr message = ProtobufCodec::parse(buf.peek(), len, &errorCode); + assert(errorCode == ProtobufCodec::kNoError); + assert(message != NULL); + message->PrintDebugString(); + assert(message->DebugString() == answer.DebugString()); + + std::shared_ptr<muduo::Answer> newAnswer = down_pointer_cast<muduo::Answer>(message); + assert(newAnswer != NULL); +} + +void testEmpty() +{ + muduo::Empty empty; + + Buffer buf; + ProtobufCodec::fillEmptyBuffer(&buf, empty); + print(buf); + + const int32_t len = buf.readInt32(); + assert(len == static_cast<int32_t>(buf.readableBytes())); + + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + MessagePtr message = ProtobufCodec::parse(buf.peek(), len, &errorCode); + assert(message != NULL); + message->PrintDebugString(); + assert(message->DebugString() == empty.DebugString()); +} + +void redoCheckSum(string& data, int len) +{ + int32_t checkSum = sockets::hostToNetwork32(static_cast<int32_t>( + ::adler32(1, + reinterpret_cast<const Bytef*>(data.c_str()), + static_cast<int>(len - 4)))); + data[len-4] = reinterpret_cast<const char*>(&checkSum)[0]; + data[len-3] = reinterpret_cast<const char*>(&checkSum)[1]; + data[len-2] = reinterpret_cast<const char*>(&checkSum)[2]; + data[len-1] = reinterpret_cast<const char*>(&checkSum)[3]; +} + +void testBadBuffer() +{ + muduo::Empty empty; + empty.set_id(43); + + Buffer buf; + ProtobufCodec::fillEmptyBuffer(&buf, empty); + // print(buf); + + const int32_t len = buf.readInt32(); + assert(len == static_cast<int32_t>(buf.readableBytes())); + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + MessagePtr message = ProtobufCodec::parse(data.c_str(), len-1, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kCheckSumError); + } + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + data[len-1]++; + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kCheckSumError); + } + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + data[0]++; + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kCheckSumError); + } + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + data[3] = 0; + redoCheckSum(data, len); + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kInvalidNameLen); + } + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + data[3] = 100; + redoCheckSum(data, len); + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kInvalidNameLen); + } + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + data[3]--; + redoCheckSum(data, len); + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kUnknownMessageType); + } + + { + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + data[4] = 'M'; + redoCheckSum(data, len); + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + assert(message == NULL); + assert(errorCode == ProtobufCodec::kUnknownMessageType); + } + + { + // FIXME: reproduce parse error + string data(buf.peek(), len); + ProtobufCodec::ErrorCode errorCode = ProtobufCodec::kNoError; + redoCheckSum(data, len); + MessagePtr message = ProtobufCodec::parse(data.c_str(), len, &errorCode); + // assert(message == NULL); + // assert(errorCode == ProtobufCodec::kParseError); + } +} + +int g_count = 0; + +void onMessage(const muduo::net::TcpConnectionPtr& conn, + const MessagePtr& message, + muduo::Timestamp receiveTime) +{ + g_count++; +} + +void testOnMessage() +{ + muduo::Query query; + query.set_id(1); + query.set_questioner("Chen Shuo"); + query.add_question("Running?"); + + Buffer buf1; + ProtobufCodec::fillEmptyBuffer(&buf1, query); + + muduo::Empty empty; + empty.set_id(43); + empty.set_id(1982); + + Buffer buf2; + ProtobufCodec::fillEmptyBuffer(&buf2, empty); + + size_t totalLen = buf1.readableBytes() + buf2.readableBytes(); + Buffer all; + all.append(buf1.peek(), buf1.readableBytes()); + all.append(buf2.peek(), buf2.readableBytes()); + assert(all.readableBytes() == totalLen); + muduo::net::TcpConnectionPtr conn; + muduo::Timestamp t; + ProtobufCodec codec(onMessage); + for (size_t len = 0; len <= totalLen; ++len) + { + Buffer input; + input.append(all.peek(), len); + + g_count = 0; + codec.onMessage(conn, &input, t); + int expected = len < buf1.readableBytes() ? 0 : 1; + if (len == totalLen) expected = 2; + assert(g_count == expected); (void) expected; + // printf("%2zd %d\n", len, g_count); + + input.append(all.peek() + len, totalLen - len); + codec.onMessage(conn, &input, t); + assert(g_count == 2); + } +} + +int main() +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + testQuery(); + puts(""); + testAnswer(); + puts(""); + testEmpty(); + puts(""); + testBadBuffer(); + puts(""); + testOnMessage(); + puts(""); + + puts("All pass!!!"); + + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/codec/dispatcher.h b/examples/protobuf/codec/dispatcher.h new file mode 100644 index 0000000..a289d82 --- /dev/null +++ b/examples/protobuf/codec/dispatcher.h @@ -0,0 +1,101 @@ +// Copyright 2011, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_EXAMPLES_PROTOBUF_CODEC_DISPATCHER_H +#define MUDUO_EXAMPLES_PROTOBUF_CODEC_DISPATCHER_H + +#include "muduo/base/noncopyable.h" +#include "muduo/net/Callbacks.h" + +#include <google/protobuf/message.h> + +#include <map> + +#include <type_traits> + +typedef std::shared_ptr<google::protobuf::Message> MessagePtr; + +class Callback : muduo::noncopyable +{ + public: + virtual ~Callback() = default; + virtual void onMessage(const muduo::net::TcpConnectionPtr&, + const MessagePtr& message, + muduo::Timestamp) const = 0; +}; + +template <typename T> +class CallbackT : public Callback +{ + static_assert(std::is_base_of<google::protobuf::Message, T>::value, + "T must be derived from gpb::Message."); + public: + typedef std::function<void (const muduo::net::TcpConnectionPtr&, + const std::shared_ptr<T>& message, + muduo::Timestamp)> ProtobufMessageTCallback; + + CallbackT(const ProtobufMessageTCallback& callback) + : callback_(callback) + { + } + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + const MessagePtr& message, + muduo::Timestamp receiveTime) const override + { + std::shared_ptr<T> concrete = muduo::down_pointer_cast<T>(message); + assert(concrete != NULL); + callback_(conn, concrete, receiveTime); + } + + private: + ProtobufMessageTCallback callback_; +}; + +class ProtobufDispatcher +{ + public: + typedef std::function<void (const muduo::net::TcpConnectionPtr&, + const MessagePtr& message, + muduo::Timestamp)> ProtobufMessageCallback; + + explicit ProtobufDispatcher(const ProtobufMessageCallback& defaultCb) + : defaultCallback_(defaultCb) + { + } + + void onProtobufMessage(const muduo::net::TcpConnectionPtr& conn, + const MessagePtr& message, + muduo::Timestamp receiveTime) const + { + CallbackMap::const_iterator it = callbacks_.find(message->GetDescriptor()); + if (it != callbacks_.end()) + { + it->second->onMessage(conn, message, receiveTime); + } + else + { + defaultCallback_(conn, message, receiveTime); + } + } + + template<typename T> + void registerMessageCallback(const typename CallbackT<T>::ProtobufMessageTCallback& callback) + { + std::shared_ptr<CallbackT<T> > pd(new CallbackT<T>(callback)); + callbacks_[T::descriptor()] = pd; + } + + private: + typedef std::map<const google::protobuf::Descriptor*, std::shared_ptr<Callback> > CallbackMap; + + CallbackMap callbacks_; + ProtobufMessageCallback defaultCallback_; +}; +#endif // MUDUO_EXAMPLES_PROTOBUF_CODEC_DISPATCHER_H + diff --git a/examples/protobuf/codec/dispatcher_lite.h b/examples/protobuf/codec/dispatcher_lite.h new file mode 100644 index 0000000..a49c06e --- /dev/null +++ b/examples/protobuf/codec/dispatcher_lite.h @@ -0,0 +1,70 @@ +// Copyright 2011, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_EXAMPLES_PROTOBUF_CODEC_DISPATCHER_LITE_H +#define MUDUO_EXAMPLES_PROTOBUF_CODEC_DISPATCHER_LITE_H + +#include "muduo/base/noncopyable.h" +#include "muduo/net/Callbacks.h" + +#include <google/protobuf/message.h> + +#include <map> + +typedef std::shared_ptr<google::protobuf::Message> MessagePtr; + +class ProtobufDispatcherLite : muduo::noncopyable +{ + public: + typedef std::function<void (const muduo::net::TcpConnectionPtr&, + const MessagePtr&, + muduo::Timestamp)> ProtobufMessageCallback; + + // ProtobufDispatcher() + // : defaultCallback_(discardProtobufMessage) + // { + // } + + explicit ProtobufDispatcherLite(const ProtobufMessageCallback& defaultCb) + : defaultCallback_(defaultCb) + { + } + + void onProtobufMessage(const muduo::net::TcpConnectionPtr& conn, + const MessagePtr& message, + muduo::Timestamp receiveTime) const + { + CallbackMap::const_iterator it = callbacks_.find(message->GetDescriptor()); + if (it != callbacks_.end()) + { + it->second(conn, message, receiveTime); + } + else + { + defaultCallback_(conn, message, receiveTime); + } + } + + void registerMessageCallback(const google::protobuf::Descriptor* desc, + const ProtobufMessageCallback& callback) + { + callbacks_[desc] = callback; + } + + private: + // static void discardProtobufMessage(const muduo::net::TcpConnectionPtr&, + // const MessagePtr&, + // muduo::Timestamp); + + typedef std::map<const google::protobuf::Descriptor*, ProtobufMessageCallback> CallbackMap; + CallbackMap callbacks_; + ProtobufMessageCallback defaultCallback_; +}; + +#endif // MUDUO_EXAMPLES_PROTOBUF_CODEC_DISPATCHER_LITE_H + diff --git a/examples/protobuf/codec/dispatcher_lite_test.cc b/examples/protobuf/codec/dispatcher_lite_test.cc new file mode 100644 index 0000000..c5f5b3a --- /dev/null +++ b/examples/protobuf/codec/dispatcher_lite_test.cc @@ -0,0 +1,55 @@ +#include "examples/protobuf/codec/dispatcher_lite.h" + +#include "examples/protobuf/codec/query.pb.h" + +#include <iostream> + +using std::cout; +using std::endl; + +void onUnknownMessageType(const muduo::net::TcpConnectionPtr&, + const MessagePtr& message, + muduo::Timestamp) +{ + cout << "onUnknownMessageType: " << message->GetTypeName() << endl; +} + +void onQuery(const muduo::net::TcpConnectionPtr&, + const MessagePtr& message, + muduo::Timestamp) +{ + cout << "onQuery: " << message->GetTypeName() << endl; + std::shared_ptr<muduo::Query> query = muduo::down_pointer_cast<muduo::Query>(message); + assert(query != NULL); +} + +void onAnswer(const muduo::net::TcpConnectionPtr&, + const MessagePtr& message, + muduo::Timestamp) +{ + cout << "onAnswer: " << message->GetTypeName() << endl; + std::shared_ptr<muduo::Answer> answer = muduo::down_pointer_cast<muduo::Answer>(message); + assert(answer != NULL); +} + +int main() +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + ProtobufDispatcherLite dispatcher(onUnknownMessageType); + dispatcher.registerMessageCallback(muduo::Query::descriptor(), onQuery); + dispatcher.registerMessageCallback(muduo::Answer::descriptor(), onAnswer); + + muduo::net::TcpConnectionPtr conn; + muduo::Timestamp t; + + std::shared_ptr<muduo::Query> query(new muduo::Query); + std::shared_ptr<muduo::Answer> answer(new muduo::Answer); + std::shared_ptr<muduo::Empty> empty(new muduo::Empty); + dispatcher.onProtobufMessage(conn, query, t); + dispatcher.onProtobufMessage(conn, answer, t); + dispatcher.onProtobufMessage(conn, empty, t); + + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/codec/dispatcher_test.cc b/examples/protobuf/codec/dispatcher_test.cc new file mode 100644 index 0000000..7643673 --- /dev/null +++ b/examples/protobuf/codec/dispatcher_test.cc @@ -0,0 +1,66 @@ +#include "examples/protobuf/codec/dispatcher.h" + +#include "examples/protobuf/codec/query.pb.h" + +#include <iostream> + +using std::cout; +using std::endl; + +typedef std::shared_ptr<muduo::Query> QueryPtr; +typedef std::shared_ptr<muduo::Answer> AnswerPtr; + +void test_down_pointer_cast() +{ + ::std::shared_ptr<google::protobuf::Message> msg(new muduo::Query); + ::std::shared_ptr<muduo::Query> query(muduo::down_pointer_cast<muduo::Query>(msg)); + assert(msg && query); + if (!query) + { + abort(); + } +} + +void onQuery(const muduo::net::TcpConnectionPtr&, + const QueryPtr& message, + muduo::Timestamp) +{ + cout << "onQuery: " << message->GetTypeName() << endl; +} + +void onAnswer(const muduo::net::TcpConnectionPtr&, + const AnswerPtr& message, + muduo::Timestamp) +{ + cout << "onAnswer: " << message->GetTypeName() << endl; +} + +void onUnknownMessageType(const muduo::net::TcpConnectionPtr&, + const MessagePtr& message, + muduo::Timestamp) +{ + cout << "onUnknownMessageType: " << message->GetTypeName() << endl; +} + +int main() +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + test_down_pointer_cast(); + + ProtobufDispatcher dispatcher(onUnknownMessageType); + dispatcher.registerMessageCallback<muduo::Query>(onQuery); + dispatcher.registerMessageCallback<muduo::Answer>(onAnswer); + + muduo::net::TcpConnectionPtr conn; + muduo::Timestamp t; + + std::shared_ptr<muduo::Query> query(new muduo::Query); + std::shared_ptr<muduo::Answer> answer(new muduo::Answer); + std::shared_ptr<muduo::Empty> empty(new muduo::Empty); + dispatcher.onProtobufMessage(conn, query, t); + dispatcher.onProtobufMessage(conn, answer, t); + dispatcher.onProtobufMessage(conn, empty, t); + + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/codec/query.proto b/examples/protobuf/codec/query.proto new file mode 100644 index 0000000..aae3cc5 --- /dev/null +++ b/examples/protobuf/codec/query.proto @@ -0,0 +1,22 @@ +package muduo; +option java_package = "muduo.codec.tests"; +option java_outer_classname = "QueryProtos"; + +message Query { + required int64 id = 1; + required string questioner = 2; + + repeated string question = 3; +} + +message Answer { + required int64 id = 1; + required string questioner = 2; + required string answerer = 3; + + repeated string solution = 4; +} + +message Empty { + optional int32 id = 1; +} diff --git a/examples/protobuf/codec/server.cc b/examples/protobuf/codec/server.cc new file mode 100644 index 0000000..6250019 --- /dev/null +++ b/examples/protobuf/codec/server.cc @@ -0,0 +1,105 @@ +#include "examples/protobuf/codec/codec.h" +#include "examples/protobuf/codec/dispatcher.h" +#include "examples/protobuf/codec/query.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +typedef std::shared_ptr<muduo::Query> QueryPtr; +typedef std::shared_ptr<muduo::Answer> AnswerPtr; + +class QueryServer : noncopyable +{ + public: + QueryServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "QueryServer"), + dispatcher_(std::bind(&QueryServer::onUnknownMessage, this, _1, _2, _3)), + codec_(std::bind(&ProtobufDispatcher::onProtobufMessage, &dispatcher_, _1, _2, _3)) + { + dispatcher_.registerMessageCallback<muduo::Query>( + std::bind(&QueryServer::onQuery, this, _1, _2, _3)); + dispatcher_.registerMessageCallback<muduo::Answer>( + std::bind(&QueryServer::onAnswer, this, _1, _2, _3)); + server_.setConnectionCallback( + std::bind(&QueryServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&ProtobufCodec::onMessage, &codec_, _1, _2, _3)); + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onUnknownMessage(const TcpConnectionPtr& conn, + const MessagePtr& message, + Timestamp) + { + LOG_INFO << "onUnknownMessage: " << message->GetTypeName(); + conn->shutdown(); + } + + void onQuery(const muduo::net::TcpConnectionPtr& conn, + const QueryPtr& message, + muduo::Timestamp) + { + LOG_INFO << "onQuery:\n" << message->GetTypeName() << message->DebugString(); + Answer answer; + answer.set_id(1); + answer.set_questioner("Chen Shuo"); + answer.set_answerer("blog.csdn.net/Solstice"); + answer.add_solution("Jump!"); + answer.add_solution("Win!"); + codec_.send(conn, answer); + + conn->shutdown(); + } + + void onAnswer(const muduo::net::TcpConnectionPtr& conn, + const AnswerPtr& message, + muduo::Timestamp) + { + LOG_INFO << "onAnswer: " << message->GetTypeName(); + conn->shutdown(); + } + + TcpServer server_; + ProtobufDispatcher dispatcher_; + ProtobufCodec codec_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress serverAddr(port); + QueryServer server(&loop, serverAddr); + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port\n", argv[0]); + } +} + diff --git a/examples/protobuf/resolver/client.cc b/examples/protobuf/resolver/client.cc new file mode 100644 index 0000000..fe44cc9 --- /dev/null +++ b/examples/protobuf/resolver/client.cc @@ -0,0 +1,117 @@ +#include "examples/protobuf/resolver/resolver.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpConnection.h" +#include "muduo/net/protorpc/RpcChannel.h" + +#include <arpa/inet.h> // inet_ntop + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class RpcClient : noncopyable +{ + public: + RpcClient(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + client_(loop, serverAddr, "RpcClient"), + channel_(new RpcChannel), + got_(0), + total_(0), + stub_(get_pointer(channel_)) + { + client_.setConnectionCallback( + std::bind(&RpcClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&RpcChannel::onMessage, get_pointer(channel_), _1, _2, _3)); + // client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + //channel_.reset(new RpcChannel(conn)); + channel_->setConnection(conn); + total_ = 4; + resolve("www.example.com"); + resolve("www.chenshuo.com"); + resolve("www.google.com"); + resolve("acme.chenshuo.org"); + } + else + { + loop_->quit(); + } + } + + void resolve(const std::string& host) + { + resolver::ResolveRequest request; + request.set_address(host); + resolver::ResolveResponse* response = new resolver::ResolveResponse; + + stub_.Resolve(NULL, &request, response, + NewCallback(this, &RpcClient::resolved, response, host)); + } + + void resolved(resolver::ResolveResponse* resp, std::string host) // pass by value for NewCallback above + { + if (resp->resolved()) + { + char buf[32]; + uint32_t ip = resp->ip(0); + inet_ntop(AF_INET, &ip, buf, sizeof buf); + + LOG_INFO << "resolved " << host << " : " << buf << "\n" + << resp->DebugString(); + } + else + { + LOG_INFO << "resolved " << host << " failed"; + } + + if (++got_ >= total_) + { + client_.disconnect(); + } + } + + EventLoop* loop_; + TcpClient client_; + RpcChannelPtr channel_; + int got_; + int total_; + resolver::ResolverService::Stub stub_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + InetAddress serverAddr(argv[1], 2053); + + RpcClient rpcClient(&loop, serverAddr); + rpcClient.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip\n", argv[0]); + } +} + diff --git a/examples/protobuf/resolver/resolver.proto b/examples/protobuf/resolver/resolver.proto new file mode 100644 index 0000000..1616473 --- /dev/null +++ b/examples/protobuf/resolver/resolver.proto @@ -0,0 +1,19 @@ +package resolver; +option cc_generic_services = true; +option java_generic_services = true; +option py_generic_services = true; + +message ResolveRequest { + required string address = 1; +} + +message ResolveResponse { + optional bool resolved = 1 [default=false]; + repeated fixed32 ip = 2; + repeated int32 port = 3; +} + +service ResolverService { + rpc Resolve (ResolveRequest) returns (ResolveResponse); +} + diff --git a/examples/protobuf/resolver/server.cc b/examples/protobuf/resolver/server.cc new file mode 100644 index 0000000..96dc574 --- /dev/null +++ b/examples/protobuf/resolver/server.cc @@ -0,0 +1,84 @@ +#include "examples/protobuf/resolver/resolver.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/protorpc/RpcServer.h" +#include "examples/cdns/Resolver.h" + +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +namespace resolver +{ + +class ResolverServiceImpl : public ResolverService +{ + public: + ResolverServiceImpl(EventLoop* loop) + : resolver_(loop, cdns::Resolver::kDNSonly) + { + } + + virtual void Resolve(::google::protobuf::RpcController* controller, + const ::resolver::ResolveRequest* request, + ::resolver::ResolveResponse* response, + ::google::protobuf::Closure* done) + { + LOG_INFO << "ResolverServiceImpl::Resolve " << request->address(); + + bool succeed = resolver_.resolve(request->address(), + std::bind(&ResolverServiceImpl::doneCallback, + this, + request->address(), + _1, + response, + done)); + if (!succeed) + { + response->set_resolved(false); + done->Run(); + } + } + + private: + + void doneCallback(const std::string& host, + const muduo::net::InetAddress& address, + ::resolver::ResolveResponse* response, + ::google::protobuf::Closure* done) + + { + LOG_INFO << "ResolverServiceImpl::doneCallback " << host; + int32_t ip = address.ipNetEndian(); + if (ip) + { + response->set_resolved(true); + response->add_ip(ip); + response->add_port(address.portNetEndian()); + } + else + { + response->set_resolved(false); + } + done->Run(); + } + + cdns::Resolver resolver_; +}; + +} // namespace resolver + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2053); + resolver::ResolverServiceImpl impl(&loop); + RpcServer server(&loop, listenAddr); + server.registerService(&impl); + server.start(); + loop.loop(); +} + diff --git a/examples/protobuf/rpc/client.cc b/examples/protobuf/rpc/client.cc new file mode 100644 index 0000000..13e1042 --- /dev/null +++ b/examples/protobuf/rpc/client.cc @@ -0,0 +1,86 @@ +#include "examples/protobuf/rpc/sudoku.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpConnection.h" +#include "muduo/net/protorpc/RpcChannel.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class RpcClient : noncopyable +{ + public: + RpcClient(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + client_(loop, serverAddr, "RpcClient"), + channel_(new RpcChannel), + stub_(get_pointer(channel_)) + { + client_.setConnectionCallback( + std::bind(&RpcClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&RpcChannel::onMessage, get_pointer(channel_), _1, _2, _3)); + // client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + //channel_.reset(new RpcChannel(conn)); + channel_->setConnection(conn); + sudoku::SudokuRequest request; + request.set_checkerboard("001010"); + sudoku::SudokuResponse* response = new sudoku::SudokuResponse; + + stub_.Solve(NULL, &request, response, NewCallback(this, &RpcClient::solved, response)); + } + else + { + loop_->quit(); + } + } + + void solved(sudoku::SudokuResponse* resp) + { + LOG_INFO << "solved:\n" << resp->DebugString(); + client_.disconnect(); + } + + EventLoop* loop_; + TcpClient client_; + RpcChannelPtr channel_; + sudoku::SudokuService::Stub stub_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + InetAddress serverAddr(argv[1], 9981); + + RpcClient rpcClient(&loop, serverAddr); + rpcClient.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip\n", argv[0]); + } + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/rpc/server.cc b/examples/protobuf/rpc/server.cc new file mode 100644 index 0000000..5b2842c --- /dev/null +++ b/examples/protobuf/rpc/server.cc @@ -0,0 +1,44 @@ +#include "examples/protobuf/rpc/sudoku.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/protorpc/RpcServer.h" + +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +namespace sudoku +{ + +class SudokuServiceImpl : public SudokuService +{ + public: + virtual void Solve(::google::protobuf::RpcController* controller, + const ::sudoku::SudokuRequest* request, + ::sudoku::SudokuResponse* response, + ::google::protobuf::Closure* done) + { + LOG_INFO << "SudokuServiceImpl::Solve"; + response->set_solved(true); + response->set_checkerboard("1234567"); + done->Run(); + } +}; + +} // namespace sudoku + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(9981); + sudoku::SudokuServiceImpl impl; + RpcServer server(&loop, listenAddr); + server.registerService(&impl); + server.start(); + loop.loop(); + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/rpc/sudoku.proto b/examples/protobuf/rpc/sudoku.proto new file mode 100644 index 0000000..a180b25 --- /dev/null +++ b/examples/protobuf/rpc/sudoku.proto @@ -0,0 +1,18 @@ +package sudoku; +option cc_generic_services = true; +option java_generic_services = true; +option py_generic_services = true; + +message SudokuRequest { + required string checkerboard = 1; +} + +message SudokuResponse { + optional bool solved = 1 [default=false]; + optional string checkerboard = 2; +} + +service SudokuService { + rpc Solve (SudokuRequest) returns (SudokuResponse); +} + diff --git a/examples/protobuf/rpcbalancer/balancer.cc b/examples/protobuf/rpcbalancer/balancer.cc new file mode 100644 index 0000000..26cb7a5 --- /dev/null +++ b/examples/protobuf/rpcbalancer/balancer.cc @@ -0,0 +1,246 @@ +#include "muduo/base/Logging.h" +#include "muduo/base/ThreadLocal.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" +#include "muduo/net/protorpc/RpcCodec.h" +#include "muduo/net/protorpc/rpc.pb.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class BackendSession : noncopyable +{ + public: + BackendSession(EventLoop* loop, const InetAddress& backendAddr, const string& name) + : loop_(loop), + client_(loop, backendAddr, name), + codec_(std::bind(&BackendSession::onRpcMessage, this, _1, _2, _3)), + nextId_(0) + { + client_.setConnectionCallback( + std::bind(&BackendSession::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&RpcCodec::onMessage, &codec_, _1, _2, _3)); + client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + // FIXME: add health check + bool send(RpcMessage& msg, const TcpConnectionPtr& clientConn) + { + loop_->assertInLoopThread(); + if (conn_) + { + uint64_t id = ++nextId_; + Request r = { msg.id(), clientConn }; + assert(outstandings_.find(id) == outstandings_.end()); + outstandings_[id] = r; + msg.set_id(id); + codec_.send(conn_, msg); + // LOG_DEBUG << "forward " << r.origId << " from " << clientConn->name() + // << " as " << id << " to " << conn_->name(); + return true; + } + else + return false; + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + loop_->assertInLoopThread(); + LOG_INFO << "Backend " + << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + conn_ = conn; + } + else + { + conn_.reset(); + // FIXME: reject pending + } + } + + void onRpcMessage(const TcpConnectionPtr&, + const RpcMessagePtr& msg, + Timestamp) + { + loop_->assertInLoopThread(); + std::map<uint64_t, Request>::iterator it = outstandings_.find(msg->id()); + if (it != outstandings_.end()) + { + uint64_t origId = it->second.origId; + TcpConnectionPtr clientConn = it->second.clientConn.lock(); + outstandings_.erase(it); + + if (clientConn) + { + // LOG_DEBUG << "send back " << origId << " of " << clientConn->name() + // << " using " << msg.id() << " from " << conn_->name(); + msg->set_id(origId); + codec_.send(clientConn, *msg); + } + } + else + { + // LOG_ERROR + } + } + + struct Request + { + uint64_t origId; + std::weak_ptr<TcpConnection> clientConn; + }; + + EventLoop* loop_; + TcpClient client_; + RpcCodec codec_; + TcpConnectionPtr conn_; + uint64_t nextId_; + std::map<uint64_t, Request> outstandings_; +}; + +class Balancer : noncopyable +{ + public: + Balancer(EventLoop* loop, + const InetAddress& listenAddr, + const string& name, + const std::vector<InetAddress>& backends) + : server_(loop, listenAddr, name), + codec_(std::bind(&Balancer::onRpcMessage, this, _1, _2, _3)), + backends_(backends) + { + server_.setThreadInitCallback( + std::bind(&Balancer::initPerThread, this, _1)); + server_.setConnectionCallback( + std::bind(&Balancer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&RpcCodec::onMessage, &codec_, _1, _2, _3)); + } + + ~Balancer() + { + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start() + { + server_.start(); + } + + private: + struct PerThread + { + size_t current; + std::vector<std::unique_ptr<BackendSession>> backends; + PerThread() : current(0) { } + }; + + void initPerThread(EventLoop* ioLoop) + { + int count = threadCount_.getAndAdd(1); + LOG_INFO << "IO thread " << count; + PerThread& t = t_backends_.value(); + t.current = count % backends_.size(); + + for (size_t i = 0; i < backends_.size(); ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "%s#%d", backends_[i].toIpPort().c_str(), count); + t.backends.emplace_back(new BackendSession(ioLoop, backends_[i], buf)); + t.backends.back()->connect(); + } + } + + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << "Client " + << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (!conn->connected()) + { + // FIXME: cancel outstanding calls, otherwise, memory leak + } + } + + void onRpcMessage(const TcpConnectionPtr& conn, + const RpcMessagePtr& msg, + Timestamp) + { + PerThread& t = t_backends_.value(); + bool succeed = false; + for (size_t i = 0; i < t.backends.size() && !succeed; ++i) + { + succeed = t.backends[t.current]->send(*msg, conn); + t.current = (t.current+1) % t.backends.size(); + } + if (!succeed) + { + // FIXME: no backend available + } + } + + TcpServer server_; + RpcCodec codec_; + std::vector<InetAddress> backends_; + AtomicInt32 threadCount_; + ThreadLocal<PerThread> t_backends_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc < 3) + { + fprintf(stderr, "Usage: %s listen_port backend_ip:port [backend_ip:port]\n", argv[0]); + } + else + { + std::vector<InetAddress> backends; + for (int i = 2; i < argc; ++i) + { + string hostport = argv[i]; + size_t colon = hostport.find(':'); + if (colon != string::npos) + { + string ip = hostport.substr(0, colon); + uint16_t port = static_cast<uint16_t>(atoi(hostport.c_str()+colon+1)); + backends.push_back(InetAddress(ip, port)); + } + else + { + fprintf(stderr, "invalid backend address %s\n", argv[i]); + return 1; + } + } + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress listenAddr(port); + + EventLoop loop; + Balancer balancer(&loop, listenAddr, "RpcBalancer", backends); + balancer.setThreadNum(4); + balancer.start(); + loop.loop(); + } + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/rpcbalancer/balancer_raw.cc b/examples/protobuf/rpcbalancer/balancer_raw.cc new file mode 100644 index 0000000..d4633b7 --- /dev/null +++ b/examples/protobuf/rpcbalancer/balancer_raw.cc @@ -0,0 +1,358 @@ +#include "muduo/base/Logging.h" +#include "muduo/base/ThreadLocal.h" +#include "muduo/net/EventLoop.h" +//#include <muduo/net/EventLoopThread.h> +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" +//#include <muduo/net/inspect/Inspector.h> +#include "muduo/net/protorpc/RpcCodec.h" +#include "muduo/net/protorpc/rpc.pb.h" + +#include <endian.h> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +struct RawMessage +{ + RawMessage(StringPiece m) + : message_(m), id_(0), loc_(NULL) + { } + + uint64_t id() const { return id_; } + void set_id(uint64_t x) { id_ = x; } + + bool parse(const string& tag) + { + const char* const body = message_.data() + ProtobufCodecLite::kHeaderLen; + const int bodylen = message_.size() - ProtobufCodecLite::kHeaderLen; + const int taglen = static_cast<int>(tag.size()); + if (ProtobufCodecLite::validateChecksum(body, bodylen) + && (memcmp(body, tag.data(), tag.size()) == 0) + && (bodylen >= taglen + 3 + 8)) + { + const char* const p = body + taglen; + uint8_t type = *(p+1); + + if (*p == 0x08 && (type == 0x01 || type == 0x02) && *(p+2) == 0x11) + { + uint64_t x = 0; + memcpy(&x, p+3, sizeof(x)); + set_id(le64toh(x)); + loc_ = p+3; + return true; + } + } + return false; + } + + void updateId() + { + uint64_t le64 = htole64(id_); + memcpy(const_cast<void*>(loc_), &le64, sizeof(le64)); + + const char* body = message_.data() + ProtobufCodecLite::kHeaderLen; + int bodylen = message_.size() - ProtobufCodecLite::kHeaderLen; + int32_t checkSum = ProtobufCodecLite::checksum(body, bodylen - ProtobufCodecLite::kChecksumLen); + int32_t be32 = sockets::hostToNetwork32(checkSum); + memcpy(const_cast<char*>(body + bodylen - ProtobufCodecLite::kChecksumLen), &be32, sizeof(be32)); + } + + StringPiece message_; + + private: + uint64_t id_; + const void* loc_; +}; + +class BackendSession : noncopyable +{ + public: + BackendSession(EventLoop* loop, const InetAddress& backendAddr, const string& name) + : loop_(loop), + client_(loop, backendAddr, name), + codec_(std::bind(&BackendSession::onRpcMessage, this, _1, _2, _3), + std::bind(&BackendSession::onRawMessage, this, _1, _2, _3)), + nextId_(0) + { + client_.setConnectionCallback( + std::bind(&BackendSession::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&RpcCodec::onMessage, &codec_, _1, _2, _3)); + client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + // FIXME: add health check + template<typename MSG> + bool send(MSG& msg, const TcpConnectionPtr& clientConn) + { + loop_->assertInLoopThread(); + if (conn_) + { + uint64_t id = ++nextId_; + Request r = { msg.id(), clientConn }; + assert(outstandings_.find(id) == outstandings_.end()); + outstandings_[id] = r; + msg.set_id(id); + sendTo(conn_, msg); + // LOG_DEBUG << "forward " << r.origId << " from " << clientConn->name() + // << " as " << id << " to " << conn_->name(); + return true; + } + else + return false; + } + + private: + void sendTo(const TcpConnectionPtr& conn, const RpcMessage& msg) + { + codec_.send(conn, msg); + } + + void sendTo(const TcpConnectionPtr& conn, RawMessage& msg) + { + msg.updateId(); + conn->send(msg.message_); + } + + void onConnection(const TcpConnectionPtr& conn) + { + loop_->assertInLoopThread(); + LOG_INFO << "Backend " + << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + conn_ = conn; + } + else + { + conn_.reset(); + // FIXME: reject pending + } + } + + void onRpcMessage(const TcpConnectionPtr&, + const RpcMessagePtr& msg, + Timestamp) + { + onMessageT(*msg); + } + + bool onRawMessage(const TcpConnectionPtr&, + StringPiece message, + Timestamp) + { + RawMessage raw(message); + if (raw.parse(codec_.tag())) + { + onMessageT(raw); + return false; + } + else + return true; // try normal rpc message callback + } + + template<typename MSG> + void onMessageT(MSG& msg) + { + loop_->assertInLoopThread(); + std::map<uint64_t, Request>::iterator it = outstandings_.find(msg.id()); + if (it != outstandings_.end()) + { + uint64_t origId = it->second.origId; + TcpConnectionPtr clientConn = it->second.clientConn.lock(); + outstandings_.erase(it); + + if (clientConn) + { + // LOG_DEBUG << "send back " << origId << " of " << clientConn->name() + // << " using " << msg.id() << " from " << conn_->name(); + msg.set_id(origId); + sendTo(clientConn, msg); + } + } + else + { + // LOG_ERROR + } + } + + struct Request + { + uint64_t origId; + std::weak_ptr<TcpConnection> clientConn; + }; + + EventLoop* loop_; + TcpClient client_; + RpcCodec codec_; + TcpConnectionPtr conn_; + uint64_t nextId_; + std::map<uint64_t, Request> outstandings_; +}; + +class Balancer : noncopyable +{ + public: + Balancer(EventLoop* loop, + const InetAddress& listenAddr, + const string& name, + const std::vector<InetAddress>& backends) + : server_(loop, listenAddr, name), + codec_(std::bind(&Balancer::onRpcMessage, this, _1, _2, _3), + std::bind(&Balancer::onRawMessage, this, _1, _2, _3)), + backends_(backends) + { + server_.setThreadInitCallback( + std::bind(&Balancer::initPerThread, this, _1)); + server_.setConnectionCallback( + std::bind(&Balancer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&RpcCodec::onMessage, &codec_, _1, _2, _3)); + } + + ~Balancer() + { + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start() + { + server_.start(); + } + + private: + struct PerThread + { + size_t current; + std::vector<std::unique_ptr<BackendSession>> backends; + PerThread() : current(0) { } + }; + + void initPerThread(EventLoop* ioLoop) + { + int count = threadCount_.getAndAdd(1); + LOG_INFO << "IO thread " << count; + PerThread& t = t_backends_.value(); + t.current = count % backends_.size(); + + for (size_t i = 0; i < backends_.size(); ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "%s#%d", backends_[i].toIpPort().c_str(), count); + t.backends.emplace_back(new BackendSession(ioLoop, backends_[i], buf)); + t.backends.back()->connect(); + } + } + + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << "Client " + << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (!conn->connected()) + { + // FIXME: cancel outstanding calls, otherwise, memory leak + } + } + + bool onRawMessage(const TcpConnectionPtr& conn, + StringPiece message, + Timestamp) + { + RawMessage raw(message); + if (raw.parse(codec_.tag())) + { + onMessageT(conn, raw); + return false; + } + else + return true; // try normal rpc message callback + } + + void onRpcMessage(const TcpConnectionPtr& conn, + const RpcMessagePtr& msg, + Timestamp) + { + onMessageT(conn, *msg); + } + + template<typename MSG> + bool onMessageT(const TcpConnectionPtr& conn, MSG& msg) + { + PerThread& t = t_backends_.value(); + bool succeed = false; + for (size_t i = 0; i < t.backends.size() && !succeed; ++i) + { + succeed = t.backends[t.current]->send(msg, conn); + t.current = (t.current+1) % t.backends.size(); + } + if (!succeed) + { + // FIXME: no backend available + } + return succeed; + } + + TcpServer server_; + RpcCodec codec_; + std::vector<InetAddress> backends_; + AtomicInt32 threadCount_; + ThreadLocal<PerThread> t_backends_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc < 3) + { + fprintf(stderr, "Usage: %s listen_port backend_ip:port [backend_ip:port]\n", argv[0]); + } + else + { + std::vector<InetAddress> backends; + for (int i = 2; i < argc; ++i) + { + string hostport = argv[i]; + size_t colon = hostport.find(':'); + if (colon != string::npos) + { + string ip = hostport.substr(0, colon); + uint16_t port = static_cast<uint16_t>(atoi(hostport.c_str()+colon+1)); + backends.push_back(InetAddress(ip, port)); + } + else + { + fprintf(stderr, "invalid backend address %s\n", argv[i]); + return 1; + } + } + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress listenAddr(port); + + // EventLoopThread inspectThread; + // new Inspector(inspectThread.startLoop(), InetAddress(8080), "rpcbalancer"); + EventLoop loop; + Balancer balancer(&loop, listenAddr, "RpcBalancer", backends); + balancer.setThreadNum(4); + balancer.start(); + loop.loop(); + } + google::protobuf::ShutdownProtobufLibrary(); +} + diff --git a/examples/protobuf/rpcbench/client.cc b/examples/protobuf/rpcbench/client.cc new file mode 100644 index 0000000..dc70323 --- /dev/null +++ b/examples/protobuf/rpcbench/client.cc @@ -0,0 +1,148 @@ +#include "examples/protobuf/rpcbench/echo.pb.h" + +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpConnection.h" +#include "muduo/net/protorpc/RpcChannel.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +static const int kRequests = 50000; + +class RpcClient : noncopyable +{ + public: + + RpcClient(EventLoop* loop, + const InetAddress& serverAddr, + CountDownLatch* allConnected, + CountDownLatch* allFinished) + : // loop_(loop), + client_(loop, serverAddr, "RpcClient"), + channel_(new RpcChannel), + stub_(get_pointer(channel_)), + allConnected_(allConnected), + allFinished_(allFinished), + count_(0) + { + client_.setConnectionCallback( + std::bind(&RpcClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&RpcChannel::onMessage, get_pointer(channel_), _1, _2, _3)); + // client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + void sendRequest() + { + echo::EchoRequest request; + request.set_payload("001010"); + echo::EchoResponse* response = new echo::EchoResponse; + stub_.Echo(NULL, &request, response, NewCallback(this, &RpcClient::replied, response)); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + //channel_.reset(new RpcChannel(conn)); + conn->setTcpNoDelay(true); + channel_->setConnection(conn); + allConnected_->countDown(); + } + } + + void replied(echo::EchoResponse* resp) + { + // LOG_INFO << "replied:\n" << resp->DebugString(); + // loop_->quit(); + ++count_; + if (count_ < kRequests) + { + sendRequest(); + } + else + { + LOG_INFO << "RpcClient " << this << " finished"; + allFinished_->countDown(); + } + } + + // EventLoop* loop_; + TcpClient client_; + RpcChannelPtr channel_; + echo::EchoService::Stub stub_; + CountDownLatch* allConnected_; + CountDownLatch* allFinished_; + int count_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + int nClients = 1; + + if (argc > 2) + { + nClients = atoi(argv[2]); + } + + int nThreads = 1; + + if (argc > 3) + { + nThreads = atoi(argv[3]); + } + + CountDownLatch allConnected(nClients); + CountDownLatch allFinished(nClients); + + EventLoop loop; + EventLoopThreadPool pool(&loop, "rpcbench-client"); + pool.setThreadNum(nThreads); + pool.start(); + InetAddress serverAddr(argv[1], 8888); + + std::vector<std::unique_ptr<RpcClient>> clients; + for (int i = 0; i < nClients; ++i) + { + clients.emplace_back(new RpcClient(pool.getNextLoop(), serverAddr, &allConnected, &allFinished)); + clients.back()->connect(); + } + allConnected.wait(); + Timestamp start(Timestamp::now()); + LOG_INFO << "all connected"; + for (int i = 0; i < nClients; ++i) + { + clients[i]->sendRequest(); + } + allFinished.wait(); + Timestamp end(Timestamp::now()); + LOG_INFO << "all finished"; + double seconds = timeDifference(end, start); + printf("%f seconds\n", seconds); + printf("%.1f calls per second\n", nClients * kRequests / seconds); + + exit(0); + } + else + { + printf("Usage: %s host_ip numClients [numThreads]\n", argv[0]); + } +} + diff --git a/examples/protobuf/rpcbench/echo.proto b/examples/protobuf/rpcbench/echo.proto new file mode 100644 index 0000000..41f7213 --- /dev/null +++ b/examples/protobuf/rpcbench/echo.proto @@ -0,0 +1,19 @@ +package echo; +//option py_generic_services = true; +option cc_generic_services = true; +option java_generic_services = true; +option java_package = "echo"; +option java_outer_classname = "EchoProto"; + +message EchoRequest { + required string payload = 1; +} + +message EchoResponse { + required string payload = 2; +} + +service EchoService { + rpc Echo (EchoRequest) returns (EchoResponse); +} + diff --git a/examples/protobuf/rpcbench/server.cc b/examples/protobuf/rpcbench/server.cc new file mode 100644 index 0000000..f73f743 --- /dev/null +++ b/examples/protobuf/rpcbench/server.cc @@ -0,0 +1,45 @@ +#include "examples/protobuf/rpcbench/echo.pb.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/protorpc/RpcServer.h" + +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +namespace echo +{ + +class EchoServiceImpl : public EchoService +{ + public: + virtual void Echo(::google::protobuf::RpcController* controller, + const ::echo::EchoRequest* request, + ::echo::EchoResponse* response, + ::google::protobuf::Closure* done) + { + //LOG_INFO << "EchoServiceImpl::Solve"; + response->set_payload(request->payload()); + done->Run(); + } +}; + +} // namespace echo + +int main(int argc, char* argv[]) +{ + int nThreads = argc > 1 ? atoi(argv[1]) : 1; + LOG_INFO << "pid = " << getpid() << " threads = " << nThreads; + EventLoop loop; + int port = argc > 2 ? atoi(argv[2]) : 8888; + InetAddress listenAddr(static_cast<uint16_t>(port)); + echo::EchoServiceImpl impl; + RpcServer server(&loop, listenAddr); + server.setThreadNum(nThreads); + server.registerService(&impl); + server.start(); + loop.loop(); +} + diff --git a/examples/roundtrip/roundtrip.cc b/examples/roundtrip/roundtrip.cc new file mode 100644 index 0000000..007f1af --- /dev/null +++ b/examples/roundtrip/roundtrip.cc @@ -0,0 +1,128 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +const size_t frameLen = 2*sizeof(int64_t); + +void serverConnectionCallback(const TcpConnectionPtr& conn) +{ + LOG_TRACE << conn->name() << " " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + conn->setTcpNoDelay(true); + } + else + { + } +} + +void serverMessageCallback(const TcpConnectionPtr& conn, + Buffer* buffer, + muduo::Timestamp receiveTime) +{ + int64_t message[2]; + while (buffer->readableBytes() >= frameLen) + { + memcpy(message, buffer->peek(), frameLen); + buffer->retrieve(frameLen); + message[1] = receiveTime.microSecondsSinceEpoch(); + conn->send(message, sizeof message); + } +} + +void runServer(uint16_t port) +{ + EventLoop loop; + TcpServer server(&loop, InetAddress(port), "ClockServer"); + server.setConnectionCallback(serverConnectionCallback); + server.setMessageCallback(serverMessageCallback); + server.start(); + loop.loop(); +} + +TcpConnectionPtr clientConnection; + +void clientConnectionCallback(const TcpConnectionPtr& conn) +{ + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + clientConnection = conn; + conn->setTcpNoDelay(true); + } + else + { + clientConnection.reset(); + } +} + +void clientMessageCallback(const TcpConnectionPtr&, + Buffer* buffer, + muduo::Timestamp receiveTime) +{ + int64_t message[2]; + while (buffer->readableBytes() >= frameLen) + { + memcpy(message, buffer->peek(), frameLen); + buffer->retrieve(frameLen); + int64_t send = message[0]; + int64_t their = message[1]; + int64_t back = receiveTime.microSecondsSinceEpoch(); + int64_t mine = (back+send)/2; + LOG_INFO << "round trip " << back - send + << " clock error " << their - mine; + } +} + +void sendMyTime() +{ + if (clientConnection) + { + int64_t message[2] = { 0, 0 }; + message[0] = Timestamp::now().microSecondsSinceEpoch(); + clientConnection->send(message, sizeof message); + } +} + +void runClient(const char* ip, uint16_t port) +{ + EventLoop loop; + TcpClient client(&loop, InetAddress(ip, port), "ClockClient"); + client.enableRetry(); + client.setConnectionCallback(clientConnectionCallback); + client.setMessageCallback(clientMessageCallback); + client.connect(); + loop.runEvery(0.2, sendMyTime); + loop.loop(); +} + +int main(int argc, char* argv[]) +{ + if (argc > 2) + { + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + if (strcmp(argv[1], "-s") == 0) + { + runServer(port); + } + else + { + runClient(argv[1], port); + } + } + else + { + printf("Usage:\n%s -s port\n%s ip port\n", argv[0], argv[0]); + } +} + diff --git a/examples/roundtrip/roundtrip_udp.cc b/examples/roundtrip/roundtrip_udp.cc new file mode 100644 index 0000000..95dcc8c --- /dev/null +++ b/examples/roundtrip/roundtrip_udp.cc @@ -0,0 +1,151 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/Socket.h" +#include "muduo/net/SocketsOps.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +const size_t frameLen = 2*sizeof(int64_t); + +int createNonblockingUDP() +{ + int sockfd = ::socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_UDP); + if (sockfd < 0) + { + LOG_SYSFATAL << "::socket"; + } + return sockfd; +} + +/////////////////////////////// Server /////////////////////////////// + +void serverReadCallback(int sockfd, muduo::Timestamp receiveTime) +{ + int64_t message[2]; + struct sockaddr peerAddr; + memZero(&peerAddr, sizeof peerAddr); + socklen_t addrLen = sizeof peerAddr; + ssize_t nr = ::recvfrom(sockfd, message, sizeof message, 0, &peerAddr, &addrLen); + + char addrStr[64]; + sockets::toIpPort(addrStr, sizeof addrStr, &peerAddr); + LOG_DEBUG << "received " << nr << " bytes from " << addrStr; + + if (nr < 0) + { + LOG_SYSERR << "::recvfrom"; + } + else if (implicit_cast<size_t>(nr) == frameLen) + { + message[1] = receiveTime.microSecondsSinceEpoch(); + ssize_t nw = ::sendto(sockfd, message, sizeof message, 0, &peerAddr, addrLen); + if (nw < 0) + { + LOG_SYSERR << "::sendto"; + } + else if (implicit_cast<size_t>(nw) != frameLen) + { + LOG_ERROR << "Expect " << frameLen << " bytes, wrote " << nw << " bytes."; + } + } + else + { + LOG_ERROR << "Expect " << frameLen << " bytes, received " << nr << " bytes."; + } +} + +void runServer(uint16_t port) +{ + Socket sock(createNonblockingUDP()); + sock.bindAddress(InetAddress(port)); + EventLoop loop; + Channel channel(&loop, sock.fd()); + channel.setReadCallback(std::bind(&serverReadCallback, sock.fd(), _1)); + channel.enableReading(); + loop.loop(); +} + +/////////////////////////////// Client /////////////////////////////// + +void clientReadCallback(int sockfd, muduo::Timestamp receiveTime) +{ + int64_t message[2]; + ssize_t nr = sockets::read(sockfd, message, sizeof message); + + if (nr < 0) + { + LOG_SYSERR << "::read"; + } + else if (implicit_cast<size_t>(nr) == frameLen) + { + int64_t send = message[0]; + int64_t their = message[1]; + int64_t back = receiveTime.microSecondsSinceEpoch(); + int64_t mine = (back+send)/2; + LOG_INFO << "round trip " << back - send + << " clock error " << their - mine; + } + else + { + LOG_ERROR << "Expect " << frameLen << " bytes, received " << nr << " bytes."; + } +} + +void sendMyTime(int sockfd) +{ + int64_t message[2] = { 0, 0 }; + message[0] = Timestamp::now().microSecondsSinceEpoch(); + ssize_t nw = sockets::write(sockfd, message, sizeof message); + if (nw < 0) + { + LOG_SYSERR << "::write"; + } + else if (implicit_cast<size_t>(nw) != frameLen) + { + LOG_ERROR << "Expect " << frameLen << " bytes, wrote " << nw << " bytes."; + } +} + +void runClient(const char* ip, uint16_t port) +{ + Socket sock(createNonblockingUDP()); + InetAddress serverAddr(ip, port); + int ret = sockets::connect(sock.fd(), serverAddr.getSockAddr()); + if (ret < 0) + { + LOG_SYSFATAL << "::connect"; + } + EventLoop loop; + Channel channel(&loop, sock.fd()); + channel.setReadCallback(std::bind(&clientReadCallback, sock.fd(), _1)); + channel.enableReading(); + loop.runEvery(0.2, std::bind(sendMyTime, sock.fd())); + loop.loop(); +} + +int main(int argc, char* argv[]) +{ + if (argc > 2) + { + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + if (strcmp(argv[1], "-s") == 0) + { + runServer(port); + } + else + { + runClient(argv[1], port); + } + } + else + { + printf("Usage:\n%s -s port\n%s ip port\n", argv[0], argv[0]); + } +} + diff --git a/examples/shorturl/shorturl.cc b/examples/shorturl/shorturl.cc new file mode 100644 index 0000000..9d8e1bd --- /dev/null +++ b/examples/shorturl/shorturl.cc @@ -0,0 +1,199 @@ +#include "muduo/net/http/HttpServer.h" +#include "muduo/net/http/HttpRequest.h" +#include "muduo/net/http/HttpResponse.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/base/Logging.h" + +#include <map> + +#include <sys/socket.h> // SO_REUSEPORT + +using namespace muduo; +using namespace muduo::net; + +extern char favicon[555]; +bool benchmark = false; + +std::map<string, string> redirections; + +void onRequest(const HttpRequest& req, HttpResponse* resp) +{ + LOG_INFO << "Headers " << req.methodString() << " " << req.path(); + if (!benchmark) + { + const std::map<string, string>& headers = req.headers(); + for (std::map<string, string>::const_iterator it = headers.begin(); + it != headers.end(); + ++it) + { + LOG_DEBUG << it->first << ": " << it->second; + } + } + + // TODO: support PUT and DELETE to create new redirections on-the-fly. + + std::map<string, string>::const_iterator it = redirections.find(req.path()); + if (it != redirections.end()) + { + resp->setStatusCode(HttpResponse::k301MovedPermanently); + resp->setStatusMessage("Moved Permanently"); + resp->addHeader("Location", it->second); + // resp->setCloseConnection(true); + } + else if (req.path() == "/") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/html"); + string now = Timestamp::now().toFormattedString(); + std::map<string, string>::const_iterator i = redirections.begin(); + string text; + for (; i != redirections.end(); ++i) + { + text.append("<ul>" + i->first + " => " + i->second + "</ul>"); + } + + resp->setBody("<html><head><title>My tiny short url service</title></head>" + "<body><h1>Known redirections</h1>" + + text + + "Now is " + now + + "</body></html>"); + } + else if (req.path() == "/favicon.ico") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("image/png"); + resp->setBody(string(favicon, sizeof favicon)); + } + else + { + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); + } +} + +int main(int argc, char* argv[]) +{ + redirections["/1"] = "http://chenshuo.com"; + redirections["/2"] = "http://blog.csdn.net/Solstice"; + + int numThreads = 0; + if (argc > 1) + { + benchmark = true; + Logger::setLogLevel(Logger::WARN); + numThreads = atoi(argv[1]); + } + +#ifdef SO_REUSEPORT + LOG_WARN << "SO_REUSEPORT"; + EventLoop loop; + EventLoopThreadPool threadPool(&loop, "shorturl"); + if (numThreads > 1) + { + threadPool.setThreadNum(numThreads); + } + else + { + numThreads = 1; + } + threadPool.start(); + + std::vector<std::unique_ptr<HttpServer>> servers; + for (int i = 0; i < numThreads; ++i) + { + servers.emplace_back(new HttpServer(threadPool.getNextLoop(), + InetAddress(8000), + "shorturl", + TcpServer::kReusePort)); + servers.back()->setHttpCallback(onRequest); + servers.back()->getLoop()->runInLoop( + std::bind(&HttpServer::start, servers.back().get())); + } + loop.loop(); +#else + LOG_WARN << "Normal"; + EventLoop loop; + HttpServer server(&loop, InetAddress(8000), "shorturl"); + server.setHttpCallback(onRequest); + server.setThreadNum(numThreads); + server.start(); + loop.loop(); +#endif +} + +char favicon[555] = { + '\x89', 'P', 'N', 'G', '\xD', '\xA', '\x1A', '\xA', + '\x0', '\x0', '\x0', '\xD', 'I', 'H', 'D', 'R', + '\x0', '\x0', '\x0', '\x10', '\x0', '\x0', '\x0', '\x10', + '\x8', '\x6', '\x0', '\x0', '\x0', '\x1F', '\xF3', '\xFF', + 'a', '\x0', '\x0', '\x0', '\x19', 't', 'E', 'X', + 't', 'S', 'o', 'f', 't', 'w', 'a', 'r', + 'e', '\x0', 'A', 'd', 'o', 'b', 'e', '\x20', + 'I', 'm', 'a', 'g', 'e', 'R', 'e', 'a', + 'd', 'y', 'q', '\xC9', 'e', '\x3C', '\x0', '\x0', + '\x1', '\xCD', 'I', 'D', 'A', 'T', 'x', '\xDA', + '\x94', '\x93', '9', 'H', '\x3', 'A', '\x14', '\x86', + '\xFF', '\x5D', 'b', '\xA7', '\x4', 'R', '\xC4', 'm', + '\x22', '\x1E', '\xA0', 'F', '\x24', '\x8', '\x16', '\x16', + 'v', '\xA', '6', '\xBA', 'J', '\x9A', '\x80', '\x8', + 'A', '\xB4', 'q', '\x85', 'X', '\x89', 'G', '\xB0', + 'I', '\xA9', 'Q', '\x24', '\xCD', '\xA6', '\x8', '\xA4', + 'H', 'c', '\x91', 'B', '\xB', '\xAF', 'V', '\xC1', + 'F', '\xB4', '\x15', '\xCF', '\x22', 'X', '\x98', '\xB', + 'T', 'H', '\x8A', 'd', '\x93', '\x8D', '\xFB', 'F', + 'g', '\xC9', '\x1A', '\x14', '\x7D', '\xF0', 'f', 'v', + 'f', '\xDF', '\x7C', '\xEF', '\xE7', 'g', 'F', '\xA8', + '\xD5', 'j', 'H', '\x24', '\x12', '\x2A', '\x0', '\x5', + '\xBF', 'G', '\xD4', '\xEF', '\xF7', '\x2F', '6', '\xEC', + '\x12', '\x20', '\x1E', '\x8F', '\xD7', '\xAA', '\xD5', '\xEA', + '\xAF', 'I', '5', 'F', '\xAA', 'T', '\x5F', '\x9F', + '\x22', 'A', '\x2A', '\x95', '\xA', '\x83', '\xE5', 'r', + '9', 'd', '\xB3', 'Y', '\x96', '\x99', 'L', '\x6', + '\xE9', 't', '\x9A', '\x25', '\x85', '\x2C', '\xCB', 'T', + '\xA7', '\xC4', 'b', '1', '\xB5', '\x5E', '\x0', '\x3', + 'h', '\x9A', '\xC6', '\x16', '\x82', '\x20', 'X', 'R', + '\x14', 'E', '6', 'S', '\x94', '\xCB', 'e', 'x', + '\xBD', '\x5E', '\xAA', 'U', 'T', '\x23', 'L', '\xC0', + '\xE0', '\xE2', '\xC1', '\x8F', '\x0', '\x9E', '\xBC', '\x9', + 'A', '\x7C', '\x3E', '\x1F', '\x83', 'D', '\x22', '\x11', + '\xD5', 'T', '\x40', '\x3F', '8', '\x80', 'w', '\xE5', + '3', '\x7', '\xB8', '\x5C', '\x2E', 'H', '\x92', '\x4', + '\x87', '\xC3', '\x81', '\x40', '\x20', '\x40', 'g', '\x98', + '\xE9', '6', '\x1A', '\xA6', 'g', '\x15', '\x4', '\xE3', + '\xD7', '\xC8', '\xBD', '\x15', '\xE1', 'i', '\xB7', 'C', + '\xAB', '\xEA', 'x', '\x2F', 'j', 'X', '\x92', '\xBB', + '\x18', '\x20', '\x9F', '\xCF', '3', '\xC3', '\xB8', '\xE9', + 'N', '\xA7', '\xD3', 'l', 'J', '\x0', 'i', '6', + '\x7C', '\x8E', '\xE1', '\xFE', 'V', '\x84', '\xE7', '\x3C', + '\x9F', 'r', '\x2B', '\x3A', 'B', '\x7B', '7', 'f', + 'w', '\xAE', '\x8E', '\xE', '\xF3', '\xBD', 'R', '\xA9', + 'd', '\x2', 'B', '\xAF', '\x85', '2', 'f', 'F', + '\xBA', '\xC', '\xD9', '\x9F', '\x1D', '\x9A', 'l', '\x22', + '\xE6', '\xC7', '\x3A', '\x2C', '\x80', '\xEF', '\xC1', '\x15', + '\x90', '\x7', '\x93', '\xA2', '\x28', '\xA0', 'S', 'j', + '\xB1', '\xB8', '\xDF', '\x29', '5', 'C', '\xE', '\x3F', + 'X', '\xFC', '\x98', '\xDA', 'y', 'j', 'P', '\x40', + '\x0', '\x87', '\xAE', '\x1B', '\x17', 'B', '\xB4', '\x3A', + '\x3F', '\xBE', 'y', '\xC7', '\xA', '\x26', '\xB6', '\xEE', + '\xD9', '\x9A', '\x60', '\x14', '\x93', '\xDB', '\x8F', '\xD', + '\xA', '\x2E', '\xE9', '\x23', '\x95', '\x29', 'X', '\x0', + '\x27', '\xEB', 'n', 'V', 'p', '\xBC', '\xD6', '\xCB', + '\xD6', 'G', '\xAB', '\x3D', 'l', '\x7D', '\xB8', '\xD2', + '\xDD', '\xA0', '\x60', '\x83', '\xBA', '\xEF', '\x5F', '\xA4', + '\xEA', '\xCC', '\x2', 'N', '\xAE', '\x5E', 'p', '\x1A', + '\xEC', '\xB3', '\x40', '9', '\xAC', '\xFE', '\xF2', '\x91', + '\x89', 'g', '\x91', '\x85', '\x21', '\xA8', '\x87', '\xB7', + 'X', '\x7E', '\x7E', '\x85', '\xBB', '\xCD', 'N', 'N', + 'b', 't', '\x40', '\xFA', '\x93', '\x89', '\xEC', '\x1E', + '\xEC', '\x86', '\x2', 'H', '\x26', '\x93', '\xD0', 'u', + '\x1D', '\x7F', '\x9', '2', '\x95', '\xBF', '\x1F', '\xDB', + '\xD7', 'c', '\x8A', '\x1A', '\xF7', '\x5C', '\xC1', '\xFF', + '\x22', 'J', '\xC3', '\x87', '\x0', '\x3', '\x0', 'K', + '\xBB', '\xF8', '\xD6', '\x2A', 'v', '\x98', 'I', '\x0', + '\x0', '\x0', '\x0', 'I', 'E', 'N', 'D', '\xAE', + 'B', '\x60', '\x82', +}; diff --git a/examples/simple/allinone/allinone.cc b/examples/simple/allinone/allinone.cc new file mode 100644 index 0000000..f4e90d9 --- /dev/null +++ b/examples/simple/allinone/allinone.cc @@ -0,0 +1,37 @@ +#include "examples/simple/chargen/chargen.h" +#include "examples/simple/daytime/daytime.h" +#include "examples/simple/discard/discard.h" +#include "examples/simple/echo/echo.h" +#include "examples/simple/time/time.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; // one loop shared by multiple servers + + ChargenServer chargenServer(&loop, InetAddress(2019)); + chargenServer.start(); + + DaytimeServer daytimeServer(&loop, InetAddress(2013)); + daytimeServer.start(); + + DiscardServer discardServer(&loop, InetAddress(2009)); + discardServer.start(); + + EchoServer echoServer(&loop, InetAddress(2007)); + echoServer.start(); + + TimeServer timeServer(&loop, InetAddress(2037)); + timeServer.start(); + + loop.loop(); +} + diff --git a/examples/simple/chargen/chargen.cc b/examples/simple/chargen/chargen.cc new file mode 100644 index 0000000..65dedb2 --- /dev/null +++ b/examples/simple/chargen/chargen.cc @@ -0,0 +1,73 @@ +#include "examples/simple/chargen/chargen.h" + +#include <stdio.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +ChargenServer::ChargenServer(EventLoop* loop, const InetAddress& listenAddr, + bool print) + : server_(loop, listenAddr, "ChargenServer"), + transferred_(0), + startTime_(Timestamp::now()) +{ + server_.setConnectionCallback( + std::bind(&ChargenServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&ChargenServer::onMessage, this, _1, _2, _3)); + server_.setWriteCompleteCallback( + std::bind(&ChargenServer::onWriteComplete, this, _1)); + if (print) { + loop->runEvery(3.0, std::bind(&ChargenServer::printThroughput, this)); + } + + string line; + for (int i = 33; i < 127; ++i) { + line.push_back(char(i)); + } + line += line; + + for (size_t i = 0; i < 127 - 33; ++i) { + message_ += line.substr(i, 72) + '\n'; + } +} + +void ChargenServer::start() { server_.start(); } + +void ChargenServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "ChargenServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) { + conn->setTcpNoDelay(true); + conn->send(message_); + } +} + +void ChargenServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " discards " << msg.size() + << " bytes received at " << time.toString(); +} + +void ChargenServer::onWriteComplete(const TcpConnectionPtr& conn) +{ + transferred_ += message_.size(); + conn->send(message_); +} + +void ChargenServer::printThroughput() +{ + Timestamp endTime = Timestamp::now(); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s\n", + static_cast<double>(transferred_) / time / 1024 / 1024); + transferred_ = 0; + startTime_ = endTime; +} diff --git a/examples/simple/chargen/chargen.h b/examples/simple/chargen/chargen.h new file mode 100644 index 0000000..3a3ebe3 --- /dev/null +++ b/examples/simple/chargen/chargen.h @@ -0,0 +1,31 @@ +#ifndef MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H +#define MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H + +#include "muduo/net/TcpServer.h" + +// RFC 864 +class ChargenServer { +public: + ChargenServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr, + bool print = false); + + void start(); + +private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, muduo::Timestamp time); + + void onWriteComplete(const muduo::net::TcpConnectionPtr& conn); + void printThroughput(); + + muduo::net::TcpServer server_; + + muduo::string message_; + int64_t transferred_; + muduo::Timestamp startTime_; +}; + +#endif // MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H diff --git a/examples/simple/chargen/main.cc b/examples/simple/chargen/main.cc new file mode 100644 index 0000000..b6f9747 --- /dev/null +++ b/examples/simple/chargen/main.cc @@ -0,0 +1,18 @@ +#include <unistd.h> + +#include "examples/simple/chargen/chargen.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2019); + ChargenServer server(&loop, listenAddr, true); + server.start(); + loop.loop(); +} diff --git a/examples/simple/chargenclient/chargenclient.cc b/examples/simple/chargenclient/chargenclient.cc new file mode 100644 index 0000000..36ec309 --- /dev/null +++ b/examples/simple/chargenclient/chargenclient.cc @@ -0,0 +1,61 @@ +#include <stdio.h> +#include <unistd.h> + +#include <utility> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +class ChargenClient : noncopyable { +public: + ChargenClient(EventLoop* loop, const InetAddress& listenAddr) + : loop_(loop), client_(loop, listenAddr, "ChargenClient") + { + client_.setConnectionCallback( + std::bind(&ChargenClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&ChargenClient::onMessage, this, _1, _2, _3)); + // client_.enableRetry(); + } + + void connect() { client_.connect(); } + +private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (!conn->connected()) loop_->quit(); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, + Timestamp receiveTime) + { + buf->retrieveAll(); + } + + EventLoop* loop_; + TcpClient client_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + EventLoop loop; + InetAddress serverAddr(argv[1], 2019); + + ChargenClient chargenClient(&loop, serverAddr); + chargenClient.connect(); + loop.loop(); + } else { + printf("Usage: %s host_ip\n", argv[0]); + } +} diff --git a/examples/simple/daytime/daytime.cc b/examples/simple/daytime/daytime.cc new file mode 100644 index 0000000..e31509a --- /dev/null +++ b/examples/simple/daytime/daytime.cc @@ -0,0 +1,44 @@ +#include "examples/simple/daytime/daytime.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +DaytimeServer::DaytimeServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "DaytimeServer") +{ + server_.setConnectionCallback( + std::bind(&DaytimeServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&DaytimeServer::onMessage, this, _1, _2, _3)); +} + +void DaytimeServer::start() +{ + server_.start(); +} + +void DaytimeServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "DaytimeServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + conn->send(Timestamp::now().toFormattedString() + "\n"); + conn->shutdown(); + } +} + +void DaytimeServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " discards " << msg.size() + << " bytes received at " << time.toString(); +} + diff --git a/examples/simple/daytime/daytime.h b/examples/simple/daytime/daytime.h new file mode 100644 index 0000000..f989eac --- /dev/null +++ b/examples/simple/daytime/daytime.h @@ -0,0 +1,25 @@ +#ifndef MUDUO_EXAMPLES_SIMPLE_DAYTIME_DAYTIME_H +#define MUDUO_EXAMPLES_SIMPLE_DAYTIME_DAYTIME_H + +#include "muduo/net/TcpServer.h" + +// RFC 867 +class DaytimeServer +{ + public: + DaytimeServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr); + + void start(); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp time); + + muduo::net::TcpServer server_; +}; + +#endif // MUDUO_EXAMPLES_SIMPLE_DAYTIME_DAYTIME_H diff --git a/examples/simple/daytime/main.cc b/examples/simple/daytime/main.cc new file mode 100644 index 0000000..309ee0d --- /dev/null +++ b/examples/simple/daytime/main.cc @@ -0,0 +1,18 @@ +#include <unistd.h> + +#include "examples/simple/daytime/daytime.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2013); + DaytimeServer server(&loop, listenAddr); + server.start(); + loop.loop(); +} diff --git a/examples/simple/discard/discard.cc b/examples/simple/discard/discard.cc new file mode 100644 index 0000000..b08e43c --- /dev/null +++ b/examples/simple/discard/discard.cc @@ -0,0 +1,38 @@ +#include "examples/simple/discard/discard.h" + +#include "muduo/base/Logging.h" + +using namespace muduo; +using namespace muduo::net; + +DiscardServer::DiscardServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "DiscardServer") +{ + server_.setConnectionCallback( + std::bind(&DiscardServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&DiscardServer::onMessage, this, _1, _2, _3)); +} + +void DiscardServer::start() +{ + server_.start(); +} + +void DiscardServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "DiscardServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); +} + +void DiscardServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " discards " << msg.size() + << " bytes received at " << time.toString(); +} + diff --git a/examples/simple/discard/discard.h b/examples/simple/discard/discard.h new file mode 100644 index 0000000..fde7ee3 --- /dev/null +++ b/examples/simple/discard/discard.h @@ -0,0 +1,25 @@ +#ifndef MUDUO_EXAMPLES_SIMPLE_DISCARD_DISCARD_H +#define MUDUO_EXAMPLES_SIMPLE_DISCARD_DISCARD_H + +#include "muduo/net/TcpServer.h" + +// RFC 863 +class DiscardServer +{ + public: + DiscardServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr); + + void start(); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp time); + + muduo::net::TcpServer server_; +}; + +#endif // MUDUO_EXAMPLES_SIMPLE_DISCARD_DISCARD_H diff --git a/examples/simple/discard/main.cc b/examples/simple/discard/main.cc new file mode 100644 index 0000000..7bce12b --- /dev/null +++ b/examples/simple/discard/main.cc @@ -0,0 +1,20 @@ +#include "examples/simple/discard/discard.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2009); + DiscardServer server(&loop, listenAddr); + server.start(); + loop.loop(); +} + diff --git a/examples/simple/echo/echo.cc b/examples/simple/echo/echo.cc new file mode 100644 index 0000000..751b683 --- /dev/null +++ b/examples/simple/echo/echo.cc @@ -0,0 +1,43 @@ +#include "examples/simple/echo/echo.h" + +#include "muduo/base/Logging.h" + +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; + +// using namespace muduo; +// using namespace muduo::net; + +EchoServer::EchoServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr) + : server_(loop, listenAddr, "EchoServer") +{ + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); +} + +void EchoServer::start() +{ + server_.start(); +} + +void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn) +{ + LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); +} + +void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp time) +{ + muduo::string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, " + << "data received at " << time.toString(); + conn->send(msg); +} + diff --git a/examples/simple/echo/echo.h b/examples/simple/echo/echo.h new file mode 100644 index 0000000..2c4d9e5 --- /dev/null +++ b/examples/simple/echo/echo.h @@ -0,0 +1,25 @@ +#ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H +#define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H + +#include "muduo/net/TcpServer.h" + +// RFC 862 +class EchoServer +{ + public: + EchoServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr); + + void start(); // calls server_.start(); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp time); + + muduo::net::TcpServer server_; +}; + +#endif // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H diff --git a/examples/simple/echo/main.cc b/examples/simple/echo/main.cc new file mode 100644 index 0000000..5a4d77f --- /dev/null +++ b/examples/simple/echo/main.cc @@ -0,0 +1,20 @@ +#include "examples/simple/echo/echo.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include <unistd.h> + +// using namespace muduo; +// using namespace muduo::net; + +int main() +{ + LOG_INFO << "pid = " << getpid(); + muduo::net::EventLoop loop; + muduo::net::InetAddress listenAddr(2007); + EchoServer server(&loop, listenAddr); + server.start(); + loop.loop(); +} + diff --git a/examples/simple/time/main.cc b/examples/simple/time/main.cc new file mode 100644 index 0000000..9ef2858 --- /dev/null +++ b/examples/simple/time/main.cc @@ -0,0 +1,18 @@ +#include <unistd.h> + +#include "examples/simple/time/time.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2037); + TimeServer server(&loop, listenAddr); + server.start(); + loop.loop(); +} diff --git a/examples/simple/time/time.cc b/examples/simple/time/time.cc new file mode 100644 index 0000000..6bc0f07 --- /dev/null +++ b/examples/simple/time/time.cc @@ -0,0 +1,40 @@ +#include "examples/simple/time/time.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" + +using namespace muduo; +using namespace muduo::net; + +TimeServer::TimeServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr) + : server_(loop, listenAddr, "TimeServer") +{ + server_.setConnectionCallback( + std::bind(&TimeServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&TimeServer::onMessage, this, _1, _2, _3)); +} + +void TimeServer::start() { server_.start(); } + +void TimeServer::onConnection(const muduo::net::TcpConnectionPtr& conn) +{ + LOG_INFO << "TimeServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) { + time_t now = ::time(NULL); + int32_t be32 = sockets::hostToNetwork32(static_cast<int32_t>(now)); + conn->send(&be32, sizeof be32); + conn->shutdown(); + } +} + +void TimeServer::onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, muduo::Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " discards " << msg.size() + << " bytes received at " << time.toString(); +} diff --git a/examples/simple/time/time.h b/examples/simple/time/time.h new file mode 100644 index 0000000..902dce2 --- /dev/null +++ b/examples/simple/time/time.h @@ -0,0 +1,23 @@ +#ifndef MUDUO_EXAMPLES_SIMPLE_TIME_TIME_H +#define MUDUO_EXAMPLES_SIMPLE_TIME_TIME_H + +#include "muduo/net/TcpServer.h" + +// RFC 868 +class TimeServer { +public: + TimeServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr); + + void start(); + +private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, muduo::Timestamp time); + + muduo::net::TcpServer server_; +}; + +#endif // MUDUO_EXAMPLES_SIMPLE_TIME_TIME_H diff --git a/examples/simple/timeclient/timeclient.cc b/examples/simple/timeclient/timeclient.cc new file mode 100644 index 0000000..c866b3a --- /dev/null +++ b/examples/simple/timeclient/timeclient.cc @@ -0,0 +1,77 @@ +#include <stdio.h> +#include <unistd.h> + +#include <utility> + +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +class TimeClient : noncopyable { +public: + TimeClient(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), client_(loop, serverAddr, "TimeClient") + { + client_.setConnectionCallback( + std::bind(&TimeClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&TimeClient::onMessage, this, _1, _2, _3)); + // client_.enableRetry(); + } + + void connect() { client_.connect(); } + +private: + EventLoop* loop_; + TcpClient client_; + + void onConnection(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (!conn->connected()) { + loop_->quit(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, + Timestamp receiveTime) + { + if (buf->readableBytes() >= sizeof(int32_t)) { + const void* data = buf->peek(); + int32_t be32 = *static_cast<const int32_t*>(data); + buf->retrieve(sizeof(int32_t)); + time_t time = sockets::networkToHost32(be32); + Timestamp ts(implicit_cast<uint64_t>(time) * + Timestamp::kMicroSecondsPerSecond); + LOG_INFO << "Server time = " << time << ", " + << ts.toFormattedString(); + } else { + LOG_INFO << conn->name() << " no enough data " + << buf->readableBytes() << " at " + << receiveTime.toFormattedString(); + } + } +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + EventLoop loop; + InetAddress serverAddr(argv[1], 2037); + + TimeClient timeClient(&loop, serverAddr); + timeClient.connect(); + loop.loop(); + } else { + printf("Usage: %s host_ip\n", argv[0]); + } +} diff --git a/examples/socks4a/balancer.cc b/examples/socks4a/balancer.cc new file mode 100644 index 0000000..1f5ac5f --- /dev/null +++ b/examples/socks4a/balancer.cc @@ -0,0 +1,91 @@ +#include "examples/socks4a/tunnel.h" + +#include "muduo/base/ThreadLocal.h" +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +std::vector<InetAddress> g_backends; +ThreadLocal<std::map<string, TunnelPtr> > t_tunnels; +MutexLock g_mutex; +size_t g_current = 0; + +void onServerConnection(const TcpConnectionPtr& conn) +{ + LOG_DEBUG << (conn->connected() ? "UP" : "DOWN"); + std::map<string, TunnelPtr>& tunnels = t_tunnels.value(); + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->stopRead(); + size_t current = 0; + { + MutexLockGuard guard(g_mutex); + current = g_current; + g_current = (g_current+1) % g_backends.size(); + } + + InetAddress backend = g_backends[current]; + TunnelPtr tunnel(new Tunnel(conn->getLoop(), backend, conn)); + tunnel->setup(); + tunnel->connect(); + + tunnels[conn->name()] = tunnel; + } + else + { + assert(tunnels.find(conn->name()) != tunnels.end()); + tunnels[conn->name()]->disconnect(); + tunnels.erase(conn->name()); + } +} + +void onServerMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) +{ + if (!conn->getContext().empty()) + { + const TcpConnectionPtr& clientConn + = boost::any_cast<const TcpConnectionPtr&>(conn->getContext()); + clientConn->send(buf); + } +} + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + fprintf(stderr, "Usage: %s listen_port backend_ip:port [backend_ip:port]\n", argv[0]); + } + else + { + for (int i = 2; i < argc; ++i) + { + string hostport = argv[i]; + size_t colon = hostport.find(':'); + if (colon != string::npos) + { + string ip = hostport.substr(0, colon); + uint16_t port = static_cast<uint16_t>(atoi(hostport.c_str()+colon+1)); + g_backends.push_back(InetAddress(ip, port)); + } + else + { + fprintf(stderr, "invalid backend address %s\n", argv[i]); + return 1; + } + } + + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress listenAddr(port); + + EventLoop loop; + TcpServer server(&loop, listenAddr, "TcpBalancer"); + server.setConnectionCallback(onServerConnection); + server.setMessageCallback(onServerMessage); + server.setThreadNum(4); + server.start(); + loop.loop(); + } +} + diff --git a/examples/socks4a/socks4a.cc b/examples/socks4a/socks4a.cc new file mode 100644 index 0000000..4890ceb --- /dev/null +++ b/examples/socks4a/socks4a.cc @@ -0,0 +1,142 @@ +#include "examples/socks4a/tunnel.h" + +#include "muduo/net/Endian.h" +#include <stdio.h> +#include <netdb.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +EventLoop* g_eventLoop; +std::map<string, TunnelPtr> g_tunnels; + +void onServerConnection(const TcpConnectionPtr& conn) +{ + LOG_DEBUG << conn->name() << (conn->connected() ? " UP" : " DOWN"); + if (conn->connected()) + { + conn->setTcpNoDelay(true); + } + else + { + std::map<string, TunnelPtr>::iterator it = g_tunnels.find(conn->name()); + if (it != g_tunnels.end()) + { + it->second->disconnect(); + g_tunnels.erase(it); + } + } +} + +void onServerMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) +{ + LOG_DEBUG << conn->name() << " " << buf->readableBytes(); + if (g_tunnels.find(conn->name()) == g_tunnels.end()) + { + if (buf->readableBytes() > 128) + { + conn->shutdown(); + } + else if (buf->readableBytes() > 8) + { + const char* begin = buf->peek() + 8; + const char* end = buf->peek() + buf->readableBytes(); + const char* where = std::find(begin, end, '\0'); + if (where != end) + { + char ver = buf->peek()[0]; + char cmd = buf->peek()[1]; + const void* port = buf->peek() + 2; + const void* ip = buf->peek() + 4; + + sockaddr_in addr; + memZero(&addr, sizeof addr); + addr.sin_family = AF_INET; + addr.sin_port = *static_cast<const in_port_t*>(port); + addr.sin_addr.s_addr = *static_cast<const uint32_t*>(ip); + + bool socks4a = sockets::networkToHost32(addr.sin_addr.s_addr) < 256; + bool okay = false; + if (socks4a) + { + const char* endOfHostName = std::find(where+1, end, '\0'); + if (endOfHostName != end) + { + string hostname = where+1; + where = endOfHostName; + LOG_INFO << "Socks4a host name " << hostname; + InetAddress tmp; + if (InetAddress::resolve(hostname, &tmp)) + { + addr.sin_addr.s_addr = tmp.ipNetEndian(); + okay = true; + } + } + else + { + return; + } + } + else + { + okay = true; + } + + InetAddress serverAddr(addr); + if (ver == 4 && cmd == 1 && okay) + { + TunnelPtr tunnel(new Tunnel(g_eventLoop, serverAddr, conn)); + tunnel->setup(); + tunnel->connect(); + g_tunnels[conn->name()] = tunnel; + buf->retrieveUntil(where+1); + char response[] = "\000\x5aUVWXYZ"; + memcpy(response+2, &addr.sin_port, 2); + memcpy(response+4, &addr.sin_addr.s_addr, 4); + conn->send(response, 8); + } + else + { + char response[] = "\000\x5bUVWXYZ"; + conn->send(response, 8); + conn->shutdown(); + } + } + } + } + else if (!conn->getContext().empty()) + { + const TcpConnectionPtr& clientConn + = boost::any_cast<const TcpConnectionPtr&>(conn->getContext()); + clientConn->send(buf); + } +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + fprintf(stderr, "Usage: %s <listen_port>\n", argv[0]); + } + else + { + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + InetAddress listenAddr(port); + + EventLoop loop; + g_eventLoop = &loop; + + TcpServer server(&loop, listenAddr, "Socks4"); + + server.setConnectionCallback(onServerConnection); + server.setMessageCallback(onServerMessage); + + server.start(); + + loop.loop(); + } +} + diff --git a/examples/socks4a/tcprelay.cc b/examples/socks4a/tcprelay.cc new file mode 100644 index 0000000..fdc60ae --- /dev/null +++ b/examples/socks4a/tcprelay.cc @@ -0,0 +1,88 @@ +#include "examples/socks4a/tunnel.h" + +#include <malloc.h> +#include <stdio.h> +#include <sys/resource.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +EventLoop* g_eventLoop; +InetAddress* g_serverAddr; +std::map<string, TunnelPtr> g_tunnels; + +void onServerConnection(const TcpConnectionPtr& conn) +{ + LOG_DEBUG << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->stopRead(); + TunnelPtr tunnel(new Tunnel(g_eventLoop, *g_serverAddr, conn)); + tunnel->setup(); + tunnel->connect(); + g_tunnels[conn->name()] = tunnel; + } + else + { + assert(g_tunnels.find(conn->name()) != g_tunnels.end()); + g_tunnels[conn->name()]->disconnect(); + g_tunnels.erase(conn->name()); + } +} + +void onServerMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) +{ + LOG_DEBUG << buf->readableBytes(); + if (!conn->getContext().empty()) + { + const TcpConnectionPtr& clientConn + = boost::any_cast<const TcpConnectionPtr&>(conn->getContext()); + clientConn->send(buf); + } +} + +void memstat() +{ + malloc_stats(); +} + +int main(int argc, char* argv[]) +{ + if (argc < 4) + { + fprintf(stderr, "Usage: %s <host_ip> <port> <listen_port>\n", argv[0]); + } + else + { + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + { + // set max virtual memory to 256MB. + size_t kOneMB = 1024*1024; + rlimit rl = { 256*kOneMB, 256*kOneMB }; + setrlimit(RLIMIT_AS, &rl); + } + const char* ip = argv[1]; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + InetAddress serverAddr(ip, port); + g_serverAddr = &serverAddr; + + uint16_t acceptPort = static_cast<uint16_t>(atoi(argv[3])); + InetAddress listenAddr(acceptPort); + + EventLoop loop; + g_eventLoop = &loop; + loop.runEvery(3, memstat); + + TcpServer server(&loop, listenAddr, "TcpRelay"); + + server.setConnectionCallback(onServerConnection); + server.setMessageCallback(onServerMessage); + + server.start(); + + loop.loop(); + } +} + diff --git a/examples/socks4a/tunnel.h b/examples/socks4a/tunnel.h new file mode 100644 index 0000000..636671f --- /dev/null +++ b/examples/socks4a/tunnel.h @@ -0,0 +1,195 @@ +#ifndef MUDUO_EXAMPLES_SOCKS4A_TUNNEL_H +#define MUDUO_EXAMPLES_SOCKS4A_TUNNEL_H + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +class Tunnel : public std::enable_shared_from_this<Tunnel>, + muduo::noncopyable +{ + public: + Tunnel(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& serverAddr, + const muduo::net::TcpConnectionPtr& serverConn) + : client_(loop, serverAddr, serverConn->name()), + serverConn_(serverConn) + { + LOG_INFO << "Tunnel " << serverConn->peerAddress().toIpPort() + << " <-> " << serverAddr.toIpPort(); + } + + ~Tunnel() + { + LOG_INFO << "~Tunnel"; + } + + void setup() + { + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + + client_.setConnectionCallback( + std::bind(&Tunnel::onClientConnection, shared_from_this(), _1)); + client_.setMessageCallback( + std::bind(&Tunnel::onClientMessage, shared_from_this(), _1, _2, _3)); + serverConn_->setHighWaterMarkCallback( + std::bind(&Tunnel::onHighWaterMarkWeak, + std::weak_ptr<Tunnel>(shared_from_this()), kServer, _1, _2), + 1024*1024); + } + + void connect() + { + client_.connect(); + } + + void disconnect() + { + client_.disconnect(); + // serverConn_.reset(); + } + + private: + void teardown() + { + client_.setConnectionCallback(muduo::net::defaultConnectionCallback); + client_.setMessageCallback(muduo::net::defaultMessageCallback); + if (serverConn_) + { + serverConn_->setContext(boost::any()); + serverConn_->shutdown(); + } + clientConn_.reset(); + } + + void onClientConnection(const muduo::net::TcpConnectionPtr& conn) + { + using std::placeholders::_1; + using std::placeholders::_2; + + LOG_DEBUG << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->setHighWaterMarkCallback( + std::bind(&Tunnel::onHighWaterMarkWeak, + std::weak_ptr<Tunnel>(shared_from_this()), kClient, _1, _2), + 1024*1024); + serverConn_->setContext(conn); + serverConn_->startRead(); + clientConn_ = conn; + if (serverConn_->inputBuffer()->readableBytes() > 0) + { + conn->send(serverConn_->inputBuffer()); + } + } + else + { + teardown(); + } + } + + void onClientMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp) + { + LOG_DEBUG << conn->name() << " " << buf->readableBytes(); + if (serverConn_) + { + serverConn_->send(buf); + } + else + { + buf->retrieveAll(); + abort(); + } + } + + enum ServerClient + { + kServer, kClient + }; + + void onHighWaterMark(ServerClient which, + const muduo::net::TcpConnectionPtr& conn, + size_t bytesToSent) + { + using std::placeholders::_1; + + LOG_INFO << (which == kServer ? "server" : "client") + << " onHighWaterMark " << conn->name() + << " bytes " << bytesToSent; + + if (which == kServer) + { + if (serverConn_->outputBuffer()->readableBytes() > 0) + { + clientConn_->stopRead(); + serverConn_->setWriteCompleteCallback( + std::bind(&Tunnel::onWriteCompleteWeak, + std::weak_ptr<Tunnel>(shared_from_this()), kServer, _1)); + } + } + else + { + if (clientConn_->outputBuffer()->readableBytes() > 0) + { + serverConn_->stopRead(); + clientConn_->setWriteCompleteCallback( + std::bind(&Tunnel::onWriteCompleteWeak, + std::weak_ptr<Tunnel>(shared_from_this()), kClient, _1)); + } + } + } + + static void onHighWaterMarkWeak(const std::weak_ptr<Tunnel>& wkTunnel, + ServerClient which, + const muduo::net::TcpConnectionPtr& conn, + size_t bytesToSent) + { + std::shared_ptr<Tunnel> tunnel = wkTunnel.lock(); + if (tunnel) + { + tunnel->onHighWaterMark(which, conn, bytesToSent); + } + } + + void onWriteComplete(ServerClient which, const muduo::net::TcpConnectionPtr& conn) + { + LOG_INFO << (which == kServer ? "server" : "client") + << " onWriteComplete " << conn->name(); + if (which == kServer) + { + clientConn_->startRead(); + serverConn_->setWriteCompleteCallback(muduo::net::WriteCompleteCallback()); + } + else + { + serverConn_->startRead(); + clientConn_->setWriteCompleteCallback(muduo::net::WriteCompleteCallback()); + } + } + + static void onWriteCompleteWeak(const std::weak_ptr<Tunnel>& wkTunnel, + ServerClient which, + const muduo::net::TcpConnectionPtr& conn) + { + std::shared_ptr<Tunnel> tunnel = wkTunnel.lock(); + if (tunnel) + { + tunnel->onWriteComplete(which, conn); + } + } + + private: + muduo::net::TcpClient client_; + muduo::net::TcpConnectionPtr serverConn_; + muduo::net::TcpConnectionPtr clientConn_; +}; +typedef std::shared_ptr<Tunnel> TunnelPtr; + +#endif // MUDUO_EXAMPLES_SOCKS4A_TUNNEL_H diff --git a/examples/sudoku/batch.cc b/examples/sudoku/batch.cc new file mode 100644 index 0000000..4d38c89 --- /dev/null +++ b/examples/sudoku/batch.cc @@ -0,0 +1,235 @@ +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" + +#include <fstream> + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +bool verify(const string& result) +{ + return true; +} + +void runLocal(std::istream& in) +{ + Timestamp start(Timestamp::now()); + std::string line; + int count = 0; + //int succeed = 0; + while (getline(in, line)) + { + if (line.size() == implicit_cast<size_t>(kCells)) + { + ++count; + if (verify(solveSudoku(line))) + { + //++succeed; + } + } + } + double elapsed = timeDifference(Timestamp::now(), start); + printf("%.3f sec, %.3f us per sudoku.\n", elapsed, 1000 * 1000 * elapsed / count); +} + +typedef std::vector<string> Input; +typedef std::shared_ptr<Input> InputPtr; + +InputPtr readInput(std::istream& in) +{ + InputPtr input(new Input); + std::string line; + while (getline(in, line)) + { + if (line.size() == implicit_cast<size_t>(kCells)) + { + input->push_back(line.c_str()); + } + } + return input; +} + +typedef std::function<void(const string&, double, int)> DoneCallback; + +class SudokuClient : noncopyable +{ + public: + SudokuClient(EventLoop* loop, + const InetAddress& serverAddr, + const InputPtr& input, + const string& name, + const DoneCallback& cb + ) + : name_(name), + client_(loop, serverAddr, name_), + input_(input), + cb_(cb), + count_(0) + { + client_.setConnectionCallback( + std::bind(&SudokuClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&SudokuClient::onMessage, this, _1, _2, _3)); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + LOG_INFO << name_ << " connected"; + start_ = Timestamp::now(); + for (size_t i = 0; i < input_->size(); ++i) + { + LogStream buf; + buf << i+1 << ":" << (*input_)[i] << "\r\n"; + conn->send(buf.buffer().data(), buf.buffer().length()); + } + LOG_INFO << name_ << " sent requests"; + } + else + { + LOG_INFO << name_ << " disconnected"; + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + //LOG_DEBUG << buf->retrieveAllAsString(); + + size_t len = buf->readableBytes(); + while (len >= kCells + 2) + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + string response(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + ++count_; + if (!verify(response)) + { + LOG_ERROR << "Bad response:" << response; + conn->shutdown(); + break; + } + } + else if (len > 100) // id + ":" + kCells + "\r\n" + { + LOG_ERROR << "Line is too long!"; + conn->shutdown(); + break; + } + else + { + break; + } + } + + if (count_ == static_cast<int>(input_->size())) + { + LOG_INFO << name_ << " done."; + double elapsed = timeDifference(Timestamp::now(), start_); + cb_(name_, elapsed, count_); + conn->shutdown(); + } + } + + string name_; + TcpClient client_; + InputPtr input_; + DoneCallback cb_; + int count_; + Timestamp start_; +}; + +Timestamp g_start; +int g_connections; +int g_finished; +EventLoop* g_loop; + +void done(const string& name, double elapsed, int count) +{ + LOG_INFO << name << " " << elapsed << " seconds " + << Fmt("%.3f", 1000 * 1000 * elapsed / count) + << " us per request."; + ++g_finished; + if (g_finished == g_connections) + { + g_loop->runAfter(1.0, std::bind(&EventLoop::quit, g_loop)); + double total = timeDifference(Timestamp::now(), g_start); + LOG_INFO << "total " << total << " seconds, " + << (total/g_connections) << " seconds per client"; + } +} + +void runClient(std::istream& in, const InetAddress& serverAddr, int conn) +{ + InputPtr input(readInput(in)); + EventLoop loop; + g_loop = &loop; + g_connections = conn; + + g_start = Timestamp::now(); + std::vector<std::unique_ptr<SudokuClient>> clients; + for (int i = 0; i < conn; ++i) + { + Fmt f("client-%03d", i+1); + string name(f.data(), f.length()); + clients.emplace_back(new SudokuClient(&loop, serverAddr, input, name, done)); + clients.back()->connect(); + } + + loop.loop(); +} + +int main(int argc, char* argv[]) +{ + int conn = 1; + InetAddress serverAddr("127.0.0.1", 9981); + const char* input = NULL; + bool local = true; + switch (argc) + { + case 4: + conn = atoi(argv[3]); + // FALL THROUGH + case 3: + serverAddr = InetAddress(argv[2], 9981); + local = false; + // FALL THROUGH + case 2: + input = argv[1]; + break; + default: + printf("Usage: %s input server_ip [connections]\n", argv[0]); + return 0; + } + + std::ifstream in(input); + if (in) + { + if (local) + { + runLocal(in); + } + else + { + runClient(in, serverAddr, conn); + } + } + else + { + printf("Cannot open %s\n", input); + } +} diff --git a/examples/sudoku/loadtest.cc b/examples/sudoku/loadtest.cc new file mode 100644 index 0000000..37f8c1a --- /dev/null +++ b/examples/sudoku/loadtest.cc @@ -0,0 +1,248 @@ +#include <stdio.h> + +#include <fstream> +#include <numeric> +#include <unordered_map> + +#include "examples/sudoku/percentile.h" +#include "examples/sudoku/sudoku.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +typedef std::vector<string> Input; +typedef std::shared_ptr<const Input> InputPtr; + +InputPtr readInput(std::istream& in) +{ + std::shared_ptr<Input> input(new Input); + std::string line; + while (getline(in, line)) { + if (line.size() == implicit_cast<size_t>(kCells)) { + input->push_back(line.c_str()); + } + } + return input; +} + +class SudokuClient : noncopyable { +public: + SudokuClient(EventLoop* loop, const InetAddress& serverAddr, + const InputPtr& input, const string& name, bool nodelay) + : name_(name), + tcpNoDelay_(nodelay), + client_(loop, serverAddr, name_), + input_(input), + count_(0) + { + client_.setConnectionCallback( + std::bind(&SudokuClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&SudokuClient::onMessage, this, _1, _2, _3)); + } + + void connect() { client_.connect(); } + + void send(int n) + { + assert(n > 0); + if (!conn_) return; + + Timestamp now(Timestamp::now()); + for (int i = 0; i < n; ++i) { + char buf[256]; + const string& req = (*input_)[count_ % input_->size()]; + int len = snprintf(buf, sizeof buf, "%s-%08d:%s\r\n", name_.c_str(), + count_, req.c_str()); + requests_.append(buf, len); + sendTime_[count_] = now; + ++count_; + } + + conn_->send(&requests_); + } + + void report(std::vector<int>* latency, int* infly) + { + latency->insert(latency->end(), latencies_.begin(), latencies_.end()); + latencies_.clear(); + *infly += static_cast<int>(sendTime_.size()); + } + +private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) { + LOG_INFO << name_ << " connected"; + if (tcpNoDelay_) conn->setTcpNoDelay(true); + conn_ = conn; + } else { + LOG_INFO << name_ << " disconnected"; + conn_.reset(); + // FIXME: exit + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, + Timestamp recvTime) + { + size_t len = buf->readableBytes(); + while (len >= kCells + 2) { + const char* crlf = buf->findCRLF(); + if (crlf) { + string response(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + if (!verify(response, recvTime)) { + LOG_ERROR << "Bad response:" << response; + conn->shutdown(); + break; + } + } else if (len > 100) // id + ":" + kCells + "\r\n" + { + LOG_ERROR << "Line is too long!"; + conn->shutdown(); + break; + } else { + break; + } + } + } + + bool verify(const string& response, Timestamp recvTime) + { + size_t colon = response.find(':'); + if (colon != string::npos) { + size_t dash = response.find('-'); + if (dash != string::npos && dash < colon) { + int id = atoi(response.c_str() + dash + 1); + std::unordered_map<int, Timestamp>::iterator sendTime = + sendTime_.find(id); + if (sendTime != sendTime_.end()) { + int64_t latency_us = + recvTime.microSecondsSinceEpoch() - + sendTime->second.microSecondsSinceEpoch(); + latencies_.push_back(static_cast<int>(latency_us)); + sendTime_.erase(sendTime); + } else { + LOG_ERROR << "Unknown id " << id << " of " << name_; + } + } + } + // FIXME + return true; + } + + const string name_; + const bool tcpNoDelay_; + TcpClient client_; + TcpConnectionPtr conn_; + Buffer requests_; + const InputPtr input_; + int count_; + std::unordered_map<int, Timestamp> sendTime_; + std::vector<int> latencies_; +}; + +class SudokuLoadtest : noncopyable { +public: + SudokuLoadtest() : count_(0), ticks_(0), sofar_(0) {} + + void runClient(const InputPtr& input, const InetAddress& serverAddr, + int rps, int conn, bool nodelay) + { + EventLoop loop; + + for (int i = 0; i < conn; ++i) { + Fmt f("c%04d", i + 1); + string name(f.data(), f.length()); + clients_.emplace_back( + new SudokuClient(&loop, serverAddr, input, name, nodelay)); + clients_.back()->connect(); + } + + loop.runEvery(1.0 / kHz, std::bind(&SudokuLoadtest::tick, this, rps)); + loop.runEvery(1.0, std::bind(&SudokuLoadtest::tock, this)); + loop.loop(); + } + +private: + void tick(int rps) + { + ++ticks_; + int64_t reqs = rps * ticks_ / kHz - sofar_; + sofar_ += reqs; + + if (reqs > 0) { + for (const auto& client : clients_) { + client->send(static_cast<int>(reqs)); + } + } + } + + void tock() + { + std::vector<int> latencies; + int infly = 0; + for (const auto& client : clients_) { + client->report(&latencies, &infly); + } + + Percentile p(latencies, infly); + LOG_INFO << p.report(); + char buf[64]; + snprintf(buf, sizeof buf, "r%04d", count_); + p.save(latencies, buf); + ++count_; + } + + std::vector<std::unique_ptr<SudokuClient>> clients_; + int count_; + int64_t ticks_; + int64_t sofar_; + static const int kHz = 100; +}; + +int main(int argc, char* argv[]) +{ + int conn = 1; + int rps = 100; + bool nodelay = false; + InetAddress serverAddr("127.0.0.1", 9981); + switch (argc) { + case 6: + nodelay = string(argv[5]) == "-n"; + // FALL THROUGH + case 5: + conn = atoi(argv[4]); + // FALL THROUGH + case 4: + rps = atoi(argv[3]); + // FALL THROUGH + case 3: + serverAddr = InetAddress(argv[2], 9981); + // FALL THROUGH + case 2: + break; + default: + printf( + "Usage: %s input server_ip [requests_per_second] [connections] " + "[-n]\n", + argv[0]); + return 0; + } + + std::ifstream in(argv[1]); + if (in) { + InputPtr input(readInput(in)); + printf("%zd requests from %s\n", input->size(), argv[1]); + SudokuLoadtest test; + test.runClient(input, serverAddr, rps, conn, nodelay); + } else { + printf("Cannot open %s\n", argv[1]); + } +} diff --git a/examples/sudoku/percentile.h b/examples/sudoku/percentile.h new file mode 100644 index 0000000..185b2c0 --- /dev/null +++ b/examples/sudoku/percentile.h @@ -0,0 +1,82 @@ + +// this is not a standalone header file + +class Percentile { +public: + Percentile(std::vector<int>& latencies, int infly) + { + stat << "recv " << muduo::Fmt("%6zd", latencies.size()) << " in-fly " + << infly; + + if (!latencies.empty()) { + std::sort(latencies.begin(), latencies.end()); + int min = latencies.front(); + int max = latencies.back(); + int sum = std::accumulate(latencies.begin(), latencies.end(), 0); + int mean = sum / static_cast<int>(latencies.size()); + int median = getPercentile(latencies, 50); + int p90 = getPercentile(latencies, 90); + int p99 = getPercentile(latencies, 99); + stat << " min " << min << " max " << max << " avg " << mean + << " median " << median << " p90 " << p90 << " p99 " << p99; + } + } + + const muduo::LogStream::Buffer& report() const { return stat.buffer(); } + + void save(const std::vector<int>& latencies, muduo::StringArg name) const + { + if (latencies.empty()) return; + muduo::FileUtil::AppendFile f(name); + f.append("# ", 2); + f.append(stat.buffer().data(), stat.buffer().length()); + f.append("\n", 1); + + const int kInterval = 5; // 5 us per bucket + int low = latencies.front() / kInterval * kInterval; + int count = 0; + int sum = 0; + const double total = static_cast<double>(latencies.size()); + char buf[64]; +#ifndef NDEBUG + for (size_t i = 0; i < latencies.size(); ++i) { + int n = snprintf(buf, sizeof buf, "# %d\n", latencies[i]); + f.append(buf, n); + } +#endif + // FIXME: improve this O(N) algorithm, maybe use lower_bound(). + for (size_t i = 0; i < latencies.size(); ++i) { + if (latencies[i] < low + kInterval) + ++count; + else { + sum += count; + int n = snprintf(buf, sizeof buf, "%4d %5d %5.2f\n", low, count, + 100 * sum / total); + f.append(buf, n); + low = latencies[i] / kInterval * kInterval; + assert(latencies[i] < low + kInterval); + count = 1; + } + } + sum += count; + assert(sum == total); + int n = snprintf(buf, sizeof buf, "%4d %5d %5.1f\n", low, count, + 100 * sum / total); + f.append(buf, n); + } + +private: + static int getPercentile(const std::vector<int>& latencies, int percent) + { + // The Nearest Rank method + assert(!latencies.empty()); + size_t idx = 0; + if (percent > 0) { + idx = (latencies.size() * percent + 99) / 100 - 1; + assert(idx < latencies.size()); + } + return latencies[idx]; + } + + muduo::LogStream stat; +}; diff --git a/examples/sudoku/pipeline.cc b/examples/sudoku/pipeline.cc new file mode 100644 index 0000000..13276e2 --- /dev/null +++ b/examples/sudoku/pipeline.cc @@ -0,0 +1,224 @@ +#include <stdio.h> + +#include <fstream> +#include <numeric> +#include <unordered_map> + +#include "examples/sudoku/percentile.h" +#include "examples/sudoku/sudoku.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +typedef std::vector<string> Input; +typedef std::shared_ptr<const Input> InputPtr; + +class SudokuClient : noncopyable { +public: + SudokuClient(EventLoop* loop, const InetAddress& serverAddr, + const InputPtr& input, const string& name, int pipelines, + bool nodelay) + : name_(name), + pipelines_(pipelines), + tcpNoDelay_(nodelay), + client_(loop, serverAddr, name_), + input_(input), + count_(0) + { + client_.setConnectionCallback( + std::bind(&SudokuClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&SudokuClient::onMessage, this, _1, _2, _3)); + } + + void connect() { client_.connect(); } + + void report(std::vector<int>* latency, int* infly) + { + latency->insert(latency->end(), latencies_.begin(), latencies_.end()); + latencies_.clear(); + *infly += static_cast<int>(sendTime_.size()); + } + +private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) { + LOG_INFO << name_ << " connected"; + if (tcpNoDelay_) conn->setTcpNoDelay(true); + conn_ = conn; + send(pipelines_); + } else { + LOG_INFO << name_ << " disconnected"; + conn_.reset(); + // FIXME: exit + } + } + + void send(int n) + { + Timestamp now(Timestamp::now()); + Buffer requests; + for (int i = 0; i < n; ++i) { + char buf[256]; + const string& req = (*input_)[count_ % input_->size()]; + int len = snprintf(buf, sizeof buf, "%s-%08d:%s\r\n", name_.c_str(), + count_, req.c_str()); + requests.append(buf, len); + sendTime_[count_] = now; + ++count_; + } + + conn_->send(&requests); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, + Timestamp recvTime) + { + size_t len = buf->readableBytes(); + while (len >= kCells + 2) { + const char* crlf = buf->findCRLF(); + if (crlf) { + string response(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + if (verify(response, recvTime)) { + send(1); + } else { + LOG_ERROR << "Bad response:" << response; + conn->shutdown(); + break; + } + } else if (len > 100) // id + ":" + kCells + "\r\n" + { + LOG_ERROR << "Line is too long!"; + conn->shutdown(); + break; + } else { + break; + } + } + } + + bool verify(const string& response, Timestamp recvTime) + { + size_t colon = response.find(':'); + if (colon != string::npos) { + size_t dash = response.find('-'); + if (dash != string::npos && dash < colon) { + int id = atoi(response.c_str() + dash + 1); + std::unordered_map<int, Timestamp>::iterator sendTime = + sendTime_.find(id); + if (sendTime != sendTime_.end()) { + int64_t latency_us = + recvTime.microSecondsSinceEpoch() - + sendTime->second.microSecondsSinceEpoch(); + latencies_.push_back(static_cast<int>(latency_us)); + sendTime_.erase(sendTime); + } else { + LOG_ERROR << "Unknown id " << id << " of " << name_; + } + } + } + // FIXME + return true; + } + + const string name_; + const int pipelines_; + const bool tcpNoDelay_; + TcpClient client_; + TcpConnectionPtr conn_; + const InputPtr input_; + int count_; + std::unordered_map<int, Timestamp> sendTime_; + std::vector<int> latencies_; +}; + +void report(const std::vector<std::unique_ptr<SudokuClient>>& clients) +{ + static int count = 0; + + std::vector<int> latencies; + int infly = 0; + for (const auto& client : clients) { + client->report(&latencies, &infly); + } + + Percentile p(latencies, infly); + LOG_INFO << p.report(); + char buf[64]; + snprintf(buf, sizeof buf, "p%04d", count); + p.save(latencies, buf); + ++count; +} + +InputPtr readInput(std::istream& in) +{ + std::shared_ptr<Input> input(new Input); + std::string line; + while (getline(in, line)) { + if (line.size() == implicit_cast<size_t>(kCells)) { + input->push_back(line.c_str()); + } + } + return input; +} + +void runClient(const InputPtr& input, const InetAddress& serverAddr, int conn, + int pipelines, bool nodelay) +{ + EventLoop loop; + std::vector<std::unique_ptr<SudokuClient>> clients; + for (int i = 0; i < conn; ++i) { + Fmt f("c%04d", i + 1); + string name(f.data(), f.length()); + clients.emplace_back(new SudokuClient(&loop, serverAddr, input, name, + pipelines, nodelay)); + clients.back()->connect(); + } + + loop.runEvery(1.0, std::bind(report, std::ref(clients))); + loop.loop(); +} + +int main(int argc, char* argv[]) +{ + int conn = 1; + int pipelines = 1; + bool nodelay = false; + InetAddress serverAddr("127.0.0.1", 9981); + switch (argc) { + case 6: + nodelay = string(argv[5]) == "-n"; + // FALL THROUGH + case 5: + pipelines = atoi(argv[4]); + // FALL THROUGH + case 4: + conn = atoi(argv[3]); + // FALL THROUGH + case 3: + serverAddr = InetAddress(argv[2], 9981); + // FALL THROUGH + case 2: + break; + default: + printf("Usage: %s input server_ip [connections] [pipelines] [-n]\n", + argv[0]); + return 0; + } + + std::ifstream in(argv[1]); + if (in) { + InputPtr input(readInput(in)); + printf("%zd requests from %s\n", input->size(), argv[1]); + runClient(input, serverAddr, conn, pipelines, nodelay); + } else { + printf("Cannot open %s\n", argv[1]); + } +} diff --git a/examples/sudoku/server_basic.cc b/examples/sudoku/server_basic.cc new file mode 100644 index 0000000..c973392 --- /dev/null +++ b/examples/sudoku/server_basic.cc @@ -0,0 +1,128 @@ +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpServer.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class SudokuServer +{ + public: + SudokuServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "SudokuServer"), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&SudokuServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&SudokuServer::onMessage, this, _1, _2, _3)); + } + + void start() + { + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + LOG_DEBUG << conn->name(); + size_t len = buf->readableBytes(); + while (len >= kCells + 2) + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + string request(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + if (!processRequest(conn, request)) + { + conn->send("Bad Request!\r\n"); + conn->shutdown(); + break; + } + } + else if (len > 100) // id + ":" + kCells + "\r\n" + { + conn->send("Id too long!\r\n"); + conn->shutdown(); + break; + } + else + { + break; + } + } + } + + bool processRequest(const TcpConnectionPtr& conn, const string& request) + { + string id; + string puzzle; + bool goodRequest = true; + + string::const_iterator colon = find(request.begin(), request.end(), ':'); + if (colon != request.end()) + { + id.assign(request.begin(), colon); + puzzle.assign(colon+1, request.end()); + } + else + { + puzzle = request; + } + + if (puzzle.size() == implicit_cast<size_t>(kCells)) + { + LOG_DEBUG << conn->name(); + string result = solveSudoku(puzzle); + if (id.empty()) + { + conn->send(result+"\r\n"); + } + else + { + conn->send(id+":"+result+"\r\n"); + } + } + else + { + goodRequest = false; + } + return goodRequest; + } + + TcpServer server_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + EventLoop loop; + InetAddress listenAddr(9981); + SudokuServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/sudoku/server_hybrid.cc b/examples/sudoku/server_hybrid.cc new file mode 100644 index 0000000..a971cb6 --- /dev/null +++ b/examples/sudoku/server_hybrid.cc @@ -0,0 +1,195 @@ +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/base/ThreadPool.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpServer.h" +#include "muduo/net/inspect/Inspector.h" + +#include <boost/circular_buffer.hpp> + +//#include <stdio.h> +//#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +#include "examples/sudoku/stat.h" + +class SudokuServer : noncopyable +{ + public: + SudokuServer(EventLoop* loop, + const InetAddress& listenAddr, + int numEventLoops, + int numThreads, + bool nodelay) + : server_(loop, listenAddr, "SudokuServer"), + threadPool_(), + numThreads_(numThreads), + tcpNoDelay_(nodelay), + startTime_(Timestamp::now()), + stat_(threadPool_), + inspectThread_(), + inspector_(inspectThread_.startLoop(), InetAddress(9982), "sudoku-solver") + { + LOG_INFO << "Use " << numEventLoops << " IO threads."; + LOG_INFO << "TCP no delay " << nodelay; + + server_.setConnectionCallback( + std::bind(&SudokuServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&SudokuServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numEventLoops); + + inspector_.add("sudoku", "stats", std::bind(&SudokuStat::report, &stat_), + "statistics of sudoku solver"); + inspector_.add("sudoku", "reset", std::bind(&SudokuStat::reset, &stat_), + "reset statistics of sudoku solver"); + } + + void start() + { + LOG_INFO << "Starting " << numThreads_ << " computing threads."; + threadPool_.start(numThreads_); + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected() && tcpNoDelay_) + conn->setTcpNoDelay(true); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) + { + LOG_DEBUG << conn->name(); + size_t len = buf->readableBytes(); + while (len >= kCells + 2) + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + string request(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + stat_.recordRequest(); + if (!processRequest(conn, request, receiveTime)) + { + conn->send("Bad Request!\r\n"); + conn->shutdown(); + stat_.recordBadRequest(); + break; + } + } + else if (len > 100) // id + ":" + kCells + "\r\n" + { + conn->send("Id too long!\r\n"); + conn->shutdown(); + stat_.recordBadRequest(); + break; + } + else + { + break; + } + } + } + + struct Request + { + string id; + string puzzle; + Timestamp receiveTime; + }; + + bool processRequest(const TcpConnectionPtr& conn, const string& request, Timestamp receiveTime) + { + Request req; + req.receiveTime = receiveTime; + + string::const_iterator colon = find(request.begin(), request.end(), ':'); + if (colon != request.end()) + { + req.id.assign(request.begin(), colon); + req.puzzle.assign(colon+1, request.end()); + } + else + { + // when using thread pool, an id must be provided in the request. + if (numThreads_ > 1) + return false; + req.puzzle = request; + } + + if (req.puzzle.size() == implicit_cast<size_t>(kCells)) + { + threadPool_.run(std::bind(&SudokuServer::solve, this, conn, req)); + return true; + } + return false; + } + + void solve(const TcpConnectionPtr& conn, const Request& req) + { + LOG_DEBUG << conn->name(); + string result = solveSudoku(req.puzzle); + if (req.id.empty()) + { + conn->send(result + "\r\n"); + } + else + { + conn->send(req.id + ":" + result + "\r\n"); + } + stat_.recordResponse(Timestamp::now(), req.receiveTime, result != kNoSolution); + } + + TcpServer server_; + ThreadPool threadPool_; + const int numThreads_; + const bool tcpNoDelay_; + const Timestamp startTime_; + + SudokuStat stat_; + EventLoopThread inspectThread_; + Inspector inspector_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << argv[0] << " [number of IO threads] [number of worker threads] [-n]"; + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + int numEventLoops = 0; + int numThreads = 0; + bool nodelay = false; + if (argc > 1) + { + numEventLoops = atoi(argv[1]); + } + if (argc > 2) + { + numThreads = atoi(argv[2]); + } + if (argc > 3 && string(argv[3]) == "-n") + { + nodelay = true; + } + + EventLoop loop; + InetAddress listenAddr(9981); + SudokuServer server(&loop, listenAddr, numEventLoops, numThreads, nodelay); + + server.start(); + + loop.loop(); +} + diff --git a/examples/sudoku/server_multiloop.cc b/examples/sudoku/server_multiloop.cc new file mode 100644 index 0000000..dc53274 --- /dev/null +++ b/examples/sudoku/server_multiloop.cc @@ -0,0 +1,137 @@ +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpServer.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class SudokuServer +{ + public: + SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads) + : server_(loop, listenAddr, "SudokuServer"), + numThreads_(numThreads), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&SudokuServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&SudokuServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + } + + void start() + { + LOG_INFO << "starting " << numThreads_ << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + LOG_DEBUG << conn->name(); + size_t len = buf->readableBytes(); + while (len >= kCells + 2) + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + string request(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + if (!processRequest(conn, request)) + { + conn->send("Bad Request!\r\n"); + conn->shutdown(); + break; + } + } + else if (len > 100) // id + ":" + kCells + "\r\n" + { + conn->send("Id too long!\r\n"); + conn->shutdown(); + break; + } + else + { + break; + } + } + } + + bool processRequest(const TcpConnectionPtr& conn, const string& request) + { + string id; + string puzzle; + bool goodRequest = true; + + string::const_iterator colon = find(request.begin(), request.end(), ':'); + if (colon != request.end()) + { + id.assign(request.begin(), colon); + puzzle.assign(colon+1, request.end()); + } + else + { + puzzle = request; + } + + if (puzzle.size() == implicit_cast<size_t>(kCells)) + { + LOG_DEBUG << conn->name(); + string result = solveSudoku(puzzle); + if (id.empty()) + { + conn->send(result+"\r\n"); + } + else + { + conn->send(id+":"+result+"\r\n"); + } + } + else + { + goodRequest = false; + } + return goodRequest; + } + + TcpServer server_; + int numThreads_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + int numThreads = 0; + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(9981); + SudokuServer server(&loop, listenAddr, numThreads); + + server.start(); + + loop.loop(); +} + diff --git a/examples/sudoku/server_prod.cc b/examples/sudoku/server_prod.cc new file mode 100644 index 0000000..b1e77e6 --- /dev/null +++ b/examples/sudoku/server_prod.cc @@ -0,0 +1,246 @@ +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/base/ThreadPool.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpServer.h" +#include "muduo/net/inspect/Inspector.h" + +#include <boost/circular_buffer.hpp> + +//#include <stdio.h> +//#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +#include "examples/sudoku/stat.h" + +class SudokuServer : noncopyable +{ + public: + SudokuServer(EventLoop* loop, + const InetAddress& listenAddr, + int numEventLoops, + int numThreads, + bool nodelay) + : server_(loop, listenAddr, "SudokuServer"), + threadPool_(), + numThreads_(numThreads), + tcpNoDelay_(nodelay), + startTime_(Timestamp::now()), + stat_(threadPool_), + inspectThread_(), + inspector_(inspectThread_.startLoop(), InetAddress(9982), "sudoku-solver") + { + LOG_INFO << "Use " << numEventLoops << " IO threads."; + LOG_INFO << "TCP no delay " << nodelay; + + server_.setConnectionCallback( + std::bind(&SudokuServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&SudokuServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numEventLoops); + + inspector_.add("sudoku", "stats", std::bind(&SudokuStat::report, &stat_), + "statistics of sudoku solver"); + inspector_.add("sudoku", "reset", std::bind(&SudokuStat::reset, &stat_), + "reset statistics of sudoku solver"); + } + + void start() + { + LOG_INFO << "Starting " << numThreads_ << " computing threads."; + threadPool_.start(numThreads_); + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + if (tcpNoDelay_) + conn->setTcpNoDelay(true); + conn->setHighWaterMarkCallback( + std::bind(&SudokuServer::highWaterMark, this, _1, _2), 5 * 1024 * 1024); + bool throttle = false; + conn->setContext(throttle); + } + } + + void highWaterMark(const TcpConnectionPtr& conn, size_t tosend) + { + LOG_WARN << conn->name() << " high water mark " << tosend; + if (tosend < 10 * 1024 * 1024) + { + conn->setHighWaterMarkCallback( + std::bind(&SudokuServer::highWaterMark, this, _1, _2), 10 * 1024 * 1024); + conn->setWriteCompleteCallback(std::bind(&SudokuServer::writeComplete, this, _1)); + bool throttle = true; + conn->setContext(throttle); + } + else + { + conn->send("Bad Request!\r\n"); + conn->shutdown(); // FIXME: forceClose() ? + stat_.recordBadRequest(); + } + } + + void writeComplete(const TcpConnectionPtr& conn) + { + LOG_INFO << conn->name() << " write complete"; + conn->setHighWaterMarkCallback( + std::bind(&SudokuServer::highWaterMark, this, _1, _2), 5 * 1024 * 1024); + conn->setWriteCompleteCallback(WriteCompleteCallback()); + bool throttle = false; + conn->setContext(throttle); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) + { + size_t len = buf->readableBytes(); + while (len >= kCells + 2) + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + string request(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + stat_.recordRequest(); + if (!processRequest(conn, request, receiveTime)) + { + conn->send("Bad Request!\r\n"); + conn->shutdown(); + stat_.recordBadRequest(); + break; + } + } + else if (len > 100) // id + ":" + kCells + "\r\n" + { + conn->send("Id too long!\r\n"); + conn->shutdown(); + stat_.recordBadRequest(); + break; + } + else + { + break; + } + } + } + + struct Request + { + string id; + string puzzle; + Timestamp receiveTime; + }; + + bool processRequest(const TcpConnectionPtr& conn, const string& request, Timestamp receiveTime) + { + Request req; + req.receiveTime = receiveTime; + + string::const_iterator colon = find(request.begin(), request.end(), ':'); + if (colon != request.end()) + { + req.id.assign(request.begin(), colon); + req.puzzle.assign(colon+1, request.end()); + } + else + { + // when using thread pool, an id must be provided in the request. + if (numThreads_ > 1) + return false; + req.puzzle = request; + } + + if (req.puzzle.size() == implicit_cast<size_t>(kCells)) + { + bool throttle = boost::any_cast<bool>(conn->getContext()); + if (threadPool_.queueSize() < 1000 * 1000 && !throttle) + { + threadPool_.run(std::bind(&SudokuServer::solve, this, conn, req)); + } + else + { + if (req.id.empty()) + { + conn->send("ServerTooBusy\r\n"); + } + else + { + conn->send(req.id + ":ServerTooBusy\r\n"); + } + stat_.recordDroppedRequest(); + } + return true; + } + return false; + } + + void solve(const TcpConnectionPtr& conn, const Request& req) + { + LOG_DEBUG << conn->name(); + string result = solveSudoku(req.puzzle); + if (req.id.empty()) + { + conn->send(result + "\r\n"); + } + else + { + conn->send(req.id + ":" + result + "\r\n"); + } + stat_.recordResponse(Timestamp::now(), req.receiveTime, result != kNoSolution); + } + + TcpServer server_; + ThreadPool threadPool_; + const int numThreads_; + const bool tcpNoDelay_; + const Timestamp startTime_; + + SudokuStat stat_; + EventLoopThread inspectThread_; + Inspector inspector_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << argv[0] << " [number of IO threads] [number of worker threads] [-n]"; + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + int numEventLoops = 0; + int numThreads = 0; + bool nodelay = false; + if (argc > 1) + { + numEventLoops = atoi(argv[1]); + } + if (argc > 2) + { + numThreads = atoi(argv[2]); + } + if (argc > 3 && string(argv[3]) == "-n") + { + nodelay = true; + } + + EventLoop loop; + InetAddress listenAddr(9981); + SudokuServer server(&loop, listenAddr, numEventLoops, numThreads, nodelay); + + server.start(); + + loop.loop(); +} + diff --git a/examples/sudoku/server_threadpool.cc b/examples/sudoku/server_threadpool.cc new file mode 100644 index 0000000..b70d35e --- /dev/null +++ b/examples/sudoku/server_threadpool.cc @@ -0,0 +1,146 @@ +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/base/ThreadPool.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpServer.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +class SudokuServer +{ + public: + SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads) + : server_(loop, listenAddr, "SudokuServer"), + numThreads_(numThreads), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&SudokuServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&SudokuServer::onMessage, this, _1, _2, _3)); + } + + void start() + { + LOG_INFO << "starting " << numThreads_ << " threads."; + threadPool_.start(numThreads_); + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + LOG_DEBUG << conn->name(); + size_t len = buf->readableBytes(); + while (len >= kCells + 2) + { + const char* crlf = buf->findCRLF(); + if (crlf) + { + string request(buf->peek(), crlf); + buf->retrieveUntil(crlf + 2); + len = buf->readableBytes(); + if (!processRequest(conn, request)) + { + conn->send("Bad Request!\r\n"); + conn->shutdown(); + break; + } + } + else if (len > 100) // id + ":" + kCells + "\r\n" + { + conn->send("Id too long!\r\n"); + conn->shutdown(); + break; + } + else + { + break; + } + } + } + + bool processRequest(const TcpConnectionPtr& conn, const string& request) + { + string id; + string puzzle; + bool goodRequest = true; + + string::const_iterator colon = find(request.begin(), request.end(), ':'); + if (colon != request.end()) + { + id.assign(request.begin(), colon); + puzzle.assign(colon+1, request.end()); + } + else + { + puzzle = request; + } + + if (puzzle.size() == implicit_cast<size_t>(kCells)) + { + threadPool_.run(std::bind(&solve, conn, puzzle, id)); + } + else + { + goodRequest = false; + } + return goodRequest; + } + + static void solve(const TcpConnectionPtr& conn, + const string& puzzle, + const string& id) + { + LOG_DEBUG << conn->name(); + string result = solveSudoku(puzzle); + if (id.empty()) + { + conn->send(result+"\r\n"); + } + else + { + conn->send(id+":"+result+"\r\n"); + } + } + + TcpServer server_; + ThreadPool threadPool_; + int numThreads_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + int numThreads = 0; + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(9981); + SudokuServer server(&loop, listenAddr, numThreads); + + server.start(); + + loop.loop(); +} + diff --git a/examples/sudoku/stat.h b/examples/sudoku/stat.h new file mode 100644 index 0000000..821f14a --- /dev/null +++ b/examples/sudoku/stat.h @@ -0,0 +1,179 @@ +// This is not a standalone header + +class SudokuStat : muduo::noncopyable { +public: + SudokuStat(const ThreadPool& pool) + : pool_(pool), + lastSecond_(0), + requests_(kSeconds), + latencies_(kSeconds), + totalRequests_(0), + totalResponses_(0), + totalSolved_(0), + badRequests_(0), + droppedRequests_(0), + totalLatency_(0), + badLatency_(0) + { + } + + string report() const + { + LogStream result; + size_t queueSize = pool_.queueSize(); + result << "task_queue_size " << queueSize << '\n'; + + { + MutexLockGuard lock(mutex_); + result << "total_requests " << totalRequests_ << '\n'; + result << "total_responses " << totalResponses_ << '\n'; + result << "total_solved " << totalSolved_ << '\n'; + result << "bad_requests " << badRequests_ << '\n'; + result << "dropped_requests " << droppedRequests_ << '\n'; + result << "latency_sum_us " << totalLatency_ << '\n'; + if (badLatency_ > 0) { + result << "bad_latency" << badLatency_ << '\n'; + } + + result << "last_second " << lastSecond_ << '\n'; + int64_t requests = 0; + result << "requests_per_second"; + for (size_t i = 0; i < requests_.size(); ++i) { + requests += requests_[i]; + result << ' ' << requests_[i]; + } + result << '\n'; + result << "requests_60s " << requests << '\n'; + + int64_t latency = 0; + result << "latency_sum_us_per_second"; + for (size_t i = 0; i < latencies_.size(); ++i) { + latency += latencies_[i]; + result << ' ' << latencies_[i]; + } + result << '\n'; + result << "latency_sum_us_60s " << latency << '\n'; + int64_t latencyAvg60s = requests == 0 ? 0 : latency / requests; + result << "latency_us_60s " << latencyAvg60s << '\n'; + int64_t latencyAvg = + totalResponses_ == 0 ? 0 : totalLatency_ / totalResponses_; + result << "latency_us_avg " << latencyAvg << '\n'; + } + return result.buffer().toString(); + } + + string reset() + { + { + MutexLockGuard lock(mutex_); + lastSecond_ = 0; + requests_.clear(); + latencies_.clear(); + totalRequests_ = 0; + totalResponses_ = 0; + totalSolved_ = 0; + badRequests_ = 0; + totalLatency_ = 0; + badLatency_ = 0; + } + return "reset done."; + } + + void recordResponse(Timestamp now, Timestamp receive, bool solved) + { + const time_t second = now.secondsSinceEpoch(); + const int64_t elapsed_us = + now.microSecondsSinceEpoch() - receive.microSecondsSinceEpoch(); + MutexLockGuard lock(mutex_); + assert(requests_.size() == latencies_.size()); + ++totalResponses_; + if (solved) ++totalSolved_; + if (elapsed_us < 0) { + ++badLatency_; + return; + } + totalLatency_ += elapsed_us; + + const time_t firstSecond = + lastSecond_ - static_cast<ssize_t>(requests_.size()) + 1; + if (lastSecond_ == second) { + // the most common case + ++requests_.back(); + latencies_.back() += elapsed_us; + } else if (lastSecond_ + 1 == second || lastSecond_ == 0) { + // next second + lastSecond_ = second; + requests_.push_back(0); + latencies_.push_back(0); + ++requests_.back(); + latencies_.back() += elapsed_us; + } else if (second > lastSecond_) { + // jump ahead + if (second < lastSecond_ + kSeconds) { + // eg. lastSecond_ == 100, second < 160 + while (lastSecond_ < second) { + requests_.push_back(0); + latencies_.push_back(0); + ++lastSecond_; + } + } else { + // eg. lastSecond_ == 100, second >= 160 + requests_.clear(); + latencies_.clear(); + lastSecond_ = second; + requests_.push_back(0); + latencies_.push_back(0); + } + ++requests_.back(); + latencies_.back() += elapsed_us; + } else if (second >= firstSecond) { + // jump backwards + // eg. lastSecond_ = 150, size = 10, second > 140 + // FIXME: if second > lastSecond_ - kSeconds, push_front() + + size_t idx = second - firstSecond; + assert(idx < requests_.size()); + ++requests_[idx]; + latencies_[idx] += elapsed_us; + } else { + assert(second < firstSecond); + // discard + // eg. lastSecond_ = 150, size = 10, second <= 140 + } + assert(requests_.size() == latencies_.size()); + } + + void recordRequest() + { + MutexLockGuard lock(mutex_); + ++totalRequests_; + } + + void recordBadRequest() + { + MutexLockGuard lock(mutex_); + ++badRequests_; + } + + void recordDroppedRequest() + { + MutexLockGuard lock(mutex_); + ++droppedRequests_; + } + +private: + const ThreadPool& pool_; // only for ThreadPool::queueSize() + mutable MutexLock mutex_; + // invariant: + // 0. requests_.size() == latencies_.size() + // 1. if lastSecond_ > 0, requests_.back() is for that second + // 2. requests_.front() is for second (last second - size() + 1) + time_t lastSecond_; + boost::circular_buffer<int64_t> requests_; + boost::circular_buffer<int64_t> latencies_; + int64_t totalRequests_, totalResponses_, totalSolved_, badRequests_, + droppedRequests_, totalLatency_, badLatency_; + // FIXME int128_t for totalLatency_; + + static const int kSeconds = 60; +}; diff --git a/examples/sudoku/stat_unittest.cc b/examples/sudoku/stat_unittest.cc new file mode 100644 index 0000000..724d545 --- /dev/null +++ b/examples/sudoku/stat_unittest.cc @@ -0,0 +1,134 @@ +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/base/ThreadPool.h" + +#include <boost/circular_buffer.hpp> +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +using namespace muduo; + +#include "examples/sudoku/stat.h" + +#include <stdio.h> + +BOOST_AUTO_TEST_CASE(testSudokuStatSameSecond) +{ + ThreadPool p; + SudokuStat s(p); + + for (int i = 0; i < 100; ++i) + { + time_t start = 1234567890; + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = Timestamp::fromUnixTime(start, i); + s.recordResponse(send, recv, i % 3 != 0); + } + printf("same second:\n%s\n", s.report().c_str()); +} + +BOOST_AUTO_TEST_CASE(testSudokuStatNextSecond) +{ + ThreadPool p; + SudokuStat s(p); + + time_t start = 1234567890; + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = addTime(recv, 0.002); + for (int i = 0; i < 10000; ++i) + { + s.recordResponse(send, recv, true); + recv = addTime(send, 0.01); + send = addTime(recv, 0.02); + } + printf("next second:\n%s\n", s.report().c_str()); +} + +BOOST_AUTO_TEST_CASE(testSudokuStatFuzz) +{ + ThreadPool p; + SudokuStat s(p); + + time_t start = 1234567890; + srand(static_cast<unsigned>(time(NULL))); + for (int i = 0; i < 10000; ++i) + { + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = Timestamp::fromUnixTime(start, 200); + s.recordResponse(send, recv, true); + int jump = (rand() % 200) - 100; + // printf("%4d ", jump); + start += jump; + } +} + +BOOST_AUTO_TEST_CASE(testSudokuStatJumpAhead5) +{ + ThreadPool p; + SudokuStat s(p); + + time_t start = 1234567890; + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = Timestamp::fromUnixTime(start, 200); + s.recordResponse(send, recv, true); + + recv = addTime(recv, 4); + send = addTime(send, 5); + s.recordResponse(send, recv, true); + printf("jump ahead 5 seconds:\n%s\n", s.report().c_str()); +} + +BOOST_AUTO_TEST_CASE(testSudokuStatJumpAhead59) +{ + ThreadPool p; + SudokuStat s(p); + + time_t start = 1234567890; + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = Timestamp::fromUnixTime(start, 200); + s.recordResponse(send, recv, true); + + recv = addTime(recv, 55); + send = addTime(send, 59); + s.recordResponse(send, recv, true); + printf("jump ahead 59 seconds:\n%s\n", s.report().c_str()); +} + +BOOST_AUTO_TEST_CASE(testSudokuStatJumpAhead60) +{ + ThreadPool p; + SudokuStat s(p); + + time_t start = 1234567890; + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = Timestamp::fromUnixTime(start, 200); + s.recordResponse(send, recv, true); + + recv = addTime(recv, 58); + send = addTime(send, 60); + s.recordResponse(send, recv, true); + printf("jump ahead 60 seconds:\n%s\n", s.report().c_str()); +} + +BOOST_AUTO_TEST_CASE(testSudokuStatJumpBack3) +{ + ThreadPool p; + SudokuStat s(p); + + time_t start = 1234567890; + Timestamp recv = Timestamp::fromUnixTime(start, 0); + Timestamp send = Timestamp::fromUnixTime(start, 200); + s.recordResponse(send, recv, true); + + recv = addTime(recv, 9); + send = addTime(send, 10); + s.recordResponse(send, recv, true); + + recv = addTime(recv, -4); + send = addTime(send, -3); + s.recordResponse(send, recv, true); + + printf("jump back 3 seconds:\n%s\n", s.report().c_str()); +} + diff --git a/examples/sudoku/sudoku.cc b/examples/sudoku/sudoku.cc new file mode 100644 index 0000000..ce2b979 --- /dev/null +++ b/examples/sudoku/sudoku.cc @@ -0,0 +1,292 @@ +#include "examples/sudoku/sudoku.h" + +#include <vector> +#include <assert.h> +#include <string.h> + +using namespace muduo; + +// Dancing links algorithm by Donald E. Knuth +// www-cs-faculty.stanford.edu/~uno/papers/dancing-color.ps.gz + +struct Node; +typedef Node Column; +struct Node +{ + Node* left; + Node* right; + Node* up; + Node* down; + Column* col; + int name; + int size; +}; + +const int kMaxNodes = 1 + 81*4 + 9*9*9*4; +// const int kMaxColumns = 400; +const int kRow = 100, kCol = 200, kBox = 300; +extern const char kNoSolution[] = "NoSolution"; + +class SudokuSolver +{ + public: + SudokuSolver(int board[kCells]) + : inout_(board), + cur_node_(0) + { + stack_.reserve(100); + + root_ = new_column(); + root_->left = root_->right = root_; + memZero(columns_, sizeof(columns_)); + + bool rows[kCells][10] = { {false} }; + bool cols[kCells][10] = { {false} }; + bool boxes[kCells][10] = { {false} }; + + for (int i = 0; i < kCells; ++i) { + int row = i / 9; + int col = i % 9; + int box = row/3*3 + col/3; + int val = inout_[i]; + rows[row][val] = true; + cols[col][val] = true; + boxes[box][val] = true; + } + + for (int i = 0; i < kCells; ++i) { + if (inout_[i] == 0) { + append_column(i); + } + } + + for (int i = 0; i < 9; ++i) { + for (int v = 1; v < 10; ++v) { + if (!rows[i][v]) + append_column(get_row_col(i, v)); + if (!cols[i][v]) + append_column(get_col_col(i, v)); + if (!boxes[i][v]) + append_column(get_box_col(i, v)); + } + } + + for (int i = 0; i < kCells; ++i) { + if (inout_[i] == 0) { + int row = i / 9; + int col = i % 9; + int box = row/3*3 + col/3; + //int val = inout[i]; + for (int v = 1; v < 10; ++v) { + if (!(rows[row][v] || cols[col][v] || boxes[box][v])) { + Node* n0 = new_row(i); + Node* nr = new_row(get_row_col(row, v)); + Node* nc = new_row(get_col_col(col, v)); + Node* nb = new_row(get_box_col(box, v)); + put_left(n0, nr); + put_left(n0, nc); + put_left(n0, nb); + } + } + } + } + } + + bool solve() + { + if (root_->left == root_) { + for (size_t i = 0; i < stack_.size(); ++i) { + Node* n = stack_[i]; + int cell = -1; + int val = -1; + while (cell == -1 || val == -1) { + if (n->name < 100) + cell = n->name; + else + val = n->name % 10; + n = n->right; + } + + //assert(cell != -1 && val != -1); + inout_[cell] = val; + } + return true; + } + + Column* const col = get_min_column(); + cover(col); + for (Node* row = col->down; row != col; row = row->down) { + stack_.push_back(row); + for (Node* j = row->right; j != row; j = j->right) { + cover(j->col); + } + if (solve()) { + return true; + } + stack_.pop_back(); + for (Node* j = row->left; j != row; j = j->left) { + uncover(j->col); + } + } + uncover(col); + return false; + } + + private: + + Column* root_; + int* inout_; + Column* columns_[400]; + std::vector<Node*> stack_; + Node nodes_[kMaxNodes]; + int cur_node_; + + Column* new_column(int n = 0) + { + assert(cur_node_ < kMaxNodes); + Column* c = &nodes_[cur_node_++]; + memZero(c, sizeof(Column)); + c->left = c; + c->right = c; + c->up = c; + c->down = c; + c->col = c; + c->name = n; + return c; + } + + void append_column(int n) + { + assert(columns_[n] == NULL); + + Column* c = new_column(n); + put_left(root_, c); + columns_[n] = c; + } + + Node* new_row(int col) + { + assert(columns_[col] != NULL); + assert(cur_node_ < kMaxNodes); + + Node* r = &nodes_[cur_node_++]; + + //Node* r = new Node; + memZero(r, sizeof(Node)); + r->left = r; + r->right = r; + r->up = r; + r->down = r; + r->name = col; + r->col = columns_[col]; + put_up(r->col, r); + return r; + } + + int get_row_col(int row, int val) + { + return kRow+row*10+val; + } + + int get_col_col(int col, int val) + { + return kCol+col*10+val; + } + + int get_box_col(int box, int val) + { + return kBox+box*10+val; + } + + Column* get_min_column() + { + Column* c = root_->right; + int min_size = c->size; + if (min_size > 1) { + for (Column* cc = c->right; cc != root_; cc = cc->right) { + if (min_size > cc->size) { + c = cc; + min_size = cc->size; + if (min_size <= 1) + break; + } + } + } + return c; + } + + void cover(Column* c) + { + c->right->left = c->left; + c->left->right = c->right; + for (Node* row = c->down; row != c; row = row->down) { + for (Node* j = row->right; j != row; j = j->right) { + j->down->up = j->up; + j->up->down = j->down; + j->col->size--; + } + } + } + + void uncover(Column* c) + { + for (Node* row = c->up; row != c; row = row->up) { + for (Node* j = row->left; j != row; j = j->left) { + j->col->size++; + j->down->up = j; + j->up->down = j; + } + } + c->right->left = c; + c->left->right = c; + } + + void put_left(Column* old, Column* nnew) + { + nnew->left = old->left; + nnew->right = old; + old->left->right = nnew; + old->left = nnew; + } + + void put_up(Column* old, Node* nnew) + { + nnew->up = old->up; + nnew->down = old; + old->up->down = nnew; + old->up = nnew; + old->size++; + nnew->col = old; + } +}; + +string solveSudoku(const StringPiece& puzzle) +{ + assert(puzzle.size() == kCells); + + string result = kNoSolution; + + int board[kCells] = { 0 }; + bool valid = true; + for (int i = 0; i < kCells; ++i) + { + board[i] = puzzle[i] - '0'; + valid = valid && (0 <= board[i] && board[i] <= 9); + } + + if (valid) + { + SudokuSolver s(board); + if (s.solve()) + { + result.clear(); + result.resize(kCells); + for (int i = 0; i < kCells; ++i) + { + result[i] = static_cast<char>(board[i] + '0'); + } + } + } + return result; +} + diff --git a/examples/sudoku/sudoku.h b/examples/sudoku/sudoku.h new file mode 100644 index 0000000..f1431d5 --- /dev/null +++ b/examples/sudoku/sudoku.h @@ -0,0 +1,12 @@ +#ifndef MUDUO_EXAMPLES_SUDOKU_SUDOKU_H +#define MUDUO_EXAMPLES_SUDOKU_SUDOKU_H + + +#include "muduo/base/Types.h" +#include "muduo/base/StringPiece.h" + +muduo::string solveSudoku(const muduo::StringPiece& puzzle); +const int kCells = 81; +extern const char kNoSolution[]; + +#endif // MUDUO_EXAMPLES_SUDOKU_SUDOKU_H diff --git a/examples/twisted/finger/README b/examples/twisted/finger/README new file mode 100644 index 0000000..1160033 --- /dev/null +++ b/examples/twisted/finger/README @@ -0,0 +1,12 @@ +The Finger protocol, RFC 1288. + +"Finger is based on the Transmission Control Protocol, using TCP port +79 decimal (117 octal). The local host opens a TCP connection to a +remote host on the Finger port. An RUIP becomes available on the +remote end of the connection to process the request. The local host +sends the RUIP a one line query based upon the Finger query +specification, and waits for the RUIP to respond. The RUIP receives +and processes the query, returns an answer, then initiates the close +of the connection. The local host receives the answer and the close +signal, then proceeds closing its end of the connection." + diff --git a/examples/twisted/finger/finger01.cc b/examples/twisted/finger/finger01.cc new file mode 100644 index 0000000..0c02e86 --- /dev/null +++ b/examples/twisted/finger/finger01.cc @@ -0,0 +1,10 @@ +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + EventLoop loop; + loop.loop(); +} diff --git a/examples/twisted/finger/finger02.cc b/examples/twisted/finger/finger02.cc new file mode 100644 index 0000000..8f8de52 --- /dev/null +++ b/examples/twisted/finger/finger02.cc @@ -0,0 +1,13 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + EventLoop loop; + TcpServer server(&loop, InetAddress(1079), "Finger"); + server.start(); + loop.loop(); +} diff --git a/examples/twisted/finger/finger03.cc b/examples/twisted/finger/finger03.cc new file mode 100644 index 0000000..292116e --- /dev/null +++ b/examples/twisted/finger/finger03.cc @@ -0,0 +1,22 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn->shutdown(); + } +} + +int main() +{ + EventLoop loop; + TcpServer server(&loop, InetAddress(1079), "Finger"); + server.setConnectionCallback(onConnection); + server.start(); + loop.loop(); +} diff --git a/examples/twisted/finger/finger04.cc b/examples/twisted/finger/finger04.cc new file mode 100644 index 0000000..4b4509f --- /dev/null +++ b/examples/twisted/finger/finger04.cc @@ -0,0 +1,24 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + if (buf->findCRLF()) + { + conn->shutdown(); + } +} + +int main() +{ + EventLoop loop; + TcpServer server(&loop, InetAddress(1079), "Finger"); + server.setMessageCallback(onMessage); + server.start(); + loop.loop(); +} diff --git a/examples/twisted/finger/finger05.cc b/examples/twisted/finger/finger05.cc new file mode 100644 index 0000000..762ba85 --- /dev/null +++ b/examples/twisted/finger/finger05.cc @@ -0,0 +1,25 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + if (buf->findCRLF()) + { + conn->send("No such user\r\n"); + conn->shutdown(); + } +} + +int main() +{ + EventLoop loop; + TcpServer server(&loop, InetAddress(1079), "Finger"); + server.setMessageCallback(onMessage); + server.start(); + loop.loop(); +} diff --git a/examples/twisted/finger/finger06.cc b/examples/twisted/finger/finger06.cc new file mode 100644 index 0000000..17d7d06 --- /dev/null +++ b/examples/twisted/finger/finger06.cc @@ -0,0 +1,44 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <map> + +using namespace muduo; +using namespace muduo::net; + +typedef std::map<string, string> UserMap; +UserMap users; + +string getUser(const string& user) +{ + string result = "No such user"; + UserMap::iterator it = users.find(user); + if (it != users.end()) + { + result = it->second; + } + return result; +} + +void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + const char* crlf = buf->findCRLF(); + if (crlf) + { + string user(buf->peek(), crlf); + conn->send(getUser(user) + "\r\n"); + buf->retrieveUntil(crlf + 2); + conn->shutdown(); + } +} + +int main() +{ + EventLoop loop; + TcpServer server(&loop, InetAddress(1079), "Finger"); + server.setMessageCallback(onMessage); + server.start(); + loop.loop(); +} diff --git a/examples/twisted/finger/finger07.cc b/examples/twisted/finger/finger07.cc new file mode 100644 index 0000000..2159909 --- /dev/null +++ b/examples/twisted/finger/finger07.cc @@ -0,0 +1,45 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include <map> + +using namespace muduo; +using namespace muduo::net; + +typedef std::map<string, string> UserMap; +UserMap users; + +string getUser(const string& user) +{ + string result = "No such user"; + UserMap::iterator it = users.find(user); + if (it != users.end()) + { + result = it->second; + } + return result; +} + +void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + const char* crlf = buf->findCRLF(); + if (crlf) + { + string user(buf->peek(), crlf); + conn->send(getUser(user) + "\r\n"); + buf->retrieveUntil(crlf + 2); + conn->shutdown(); + } +} + +int main() +{ + users["schen"] = "Happy and well"; + EventLoop loop; + TcpServer server(&loop, InetAddress(1079), "Finger"); + server.setMessageCallback(onMessage); + server.start(); + loop.loop(); +} diff --git a/examples/wordcount/README b/examples/wordcount/README new file mode 100644 index 0000000..f4937c7 --- /dev/null +++ b/examples/wordcount/README @@ -0,0 +1,19 @@ +A distributed word counting example. + +A hasher shards <word,count> to multiple receivers by hash(word). + +A receiver collects <word,count> from multiple hashers +and writes the result to disk. + +Example run with 3 hashers and 4 receivers: +1. run 4 receivers on 4 machines, namely ip1:port1, ip2:port2, ip3:port3, ip4:port4. + a. on ip1, bin/wordcount_receiver port1 3 + b. on ip2, bin/wordcount_receiver port2 3 + c. on ip3, bin/wordcount_receiver port3 3 + d. on ip4, bin/wordcount_receiver port4 3 +2. run 3 hashers on 3 machines. + a. on ip1, bin/wordcount_hasher 'ip1:port1,ip2:port2,ip3:port3,ip4:port4' input1 + b. on ip2, bin/wordcount_hasher 'ip1:port1,ip2:port2,ip3:port3,ip4:port4' input2 + c. on ip3, bin/wordcount_hasher 'ip1:port1,ip2:port2,ip3:port3,ip4:port4' input3 input4 +3. wait all hashers and receivers exit. + diff --git a/examples/wordcount/gen.py b/examples/wordcount/gen.py new file mode 100644 index 0000000..3b93dcd --- /dev/null +++ b/examples/wordcount/gen.py @@ -0,0 +1,14 @@ +#!/usr/bin/python + +import random + +words = 1000000 +word_len = 5 +alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-' + +output = open('random_words', 'w') +for x in xrange(words): + arr = [random.choice(alphabet) for i in range(word_len)] + word = ''.join(arr) + output.write(word) + output.write('\n') diff --git a/examples/wordcount/hash.h b/examples/wordcount/hash.h new file mode 100644 index 0000000..895f3a2 --- /dev/null +++ b/examples/wordcount/hash.h @@ -0,0 +1,8 @@ +#ifndef MUDUO_EXAMPLES_WORDCOUNT_HASH_H +#define MUDUO_EXAMPLES_WORDCOUNT_HASH_H + +#include <unordered_map> + +typedef std::unordered_map<muduo::string, int64_t> WordCountMap; + +#endif // MUDUO_EXAMPLES_WORDCOUNT_HASH_H diff --git a/examples/wordcount/hasher.cc b/examples/wordcount/hasher.cc new file mode 100644 index 0000000..c56bb0c --- /dev/null +++ b/examples/wordcount/hasher.cc @@ -0,0 +1,236 @@ +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/TcpClient.h" + +#include <boost/tokenizer.hpp> + +#include "examples/wordcount/hash.h" + +#include <fstream> +#include <iostream> + +#include <stdio.h> +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +using namespace muduo; +using namespace muduo::net; + +size_t g_batchSize = 65536; +const size_t kMaxHashSize = 10 * 1000 * 1000; + +class SendThrottler : muduo::noncopyable +{ + public: + SendThrottler(EventLoop* loop, const InetAddress& addr) + : client_(loop, addr, "Sender"), + connectLatch_(1), + disconnectLatch_(1), + cond_(mutex_), + congestion_(false) + { + LOG_INFO << "SendThrottler [" << addr.toIpPort() << "]"; + client_.setConnectionCallback( + std::bind(&SendThrottler::onConnection, this, _1)); + } + + void connect() + { + client_.connect(); + connectLatch_.wait(); + } + + void disconnect() + { + if (buffer_.readableBytes() > 0) + { + LOG_DEBUG << "send " << buffer_.readableBytes() << " bytes"; + conn_->send(&buffer_); + } + conn_->shutdown(); + disconnectLatch_.wait(); + } + + void send(const string& word, int64_t count) + { + buffer_.append(word); + // FIXME: use LogStream + char buf[64]; + snprintf(buf, sizeof buf, "\t%" PRId64 "\r\n", count); + buffer_.append(buf); + if (buffer_.readableBytes() >= g_batchSize) + { + throttle(); + LOG_TRACE << "send " << buffer_.readableBytes(); + conn_->send(&buffer_); + } + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + if (conn->connected()) + { + conn->setHighWaterMarkCallback( + std::bind(&SendThrottler::onHighWaterMark, this), 1024*1024); + conn->setWriteCompleteCallback( + std::bind(&SendThrottler::onWriteComplete, this)); + + conn_ = conn; + connectLatch_.countDown(); + } + else + { + conn_.reset(); + disconnectLatch_.countDown(); + } + } + + void onHighWaterMark() + { + MutexLockGuard lock(mutex_); + congestion_ = true; + } + + void onWriteComplete() + { + MutexLockGuard lock(mutex_); + bool oldCong = congestion_; + congestion_ = false; + if (oldCong) + { + cond_.notify(); + } + } + + void throttle() + { + MutexLockGuard lock(mutex_); + while (congestion_) + { + LOG_DEBUG << "wait "; + cond_.wait(); + } + } + + TcpClient client_; + TcpConnectionPtr conn_; + CountDownLatch connectLatch_; + CountDownLatch disconnectLatch_; + Buffer buffer_; + + MutexLock mutex_; + Condition cond_; + bool congestion_; +}; + +class WordCountSender : muduo::noncopyable +{ + public: + explicit WordCountSender(const std::string& receivers); + + void connectAll() + { + for (size_t i = 0; i < buckets_.size(); ++i) + { + buckets_[i]->connect(); + } + LOG_INFO << "All connected"; + } + + void disconnectAll() + { + for (size_t i = 0; i < buckets_.size(); ++i) + { + buckets_[i]->disconnect(); + } + LOG_INFO << "All disconnected"; + } + + void processFile(const char* filename); + + private: + EventLoopThread loopThread_; + EventLoop* loop_; + std::vector<std::unique_ptr<SendThrottler>> buckets_; +}; + +WordCountSender::WordCountSender(const std::string& receivers) + : loop_(loopThread_.startLoop()) +{ + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(", "); + tokenizer tokens(receivers, sep); + for (tokenizer::iterator tok_iter = tokens.begin(); + tok_iter != tokens.end(); ++tok_iter) + { + std::string ipport = *tok_iter; + size_t colon = ipport.find(':'); + if (colon != std::string::npos) + { + uint16_t port = static_cast<uint16_t>(atoi(&ipport[colon+1])); + InetAddress addr(ipport.substr(0, colon), port); + buckets_.emplace_back(new SendThrottler(loop_, addr)); + } + else + { + assert(0 && "Invalid address"); + } + } +} + +void WordCountSender::processFile(const char* filename) +{ + LOG_INFO << "processFile " << filename; + WordCountMap wordcounts; + // FIXME: use mmap to read file + std::ifstream in(filename); + string word; + // FIXME: make local hash optional. + std::hash<string> hash; + while (in) + { + wordcounts.clear(); + while (in >> word) + { + wordcounts[word] += 1; + if (wordcounts.size() > kMaxHashSize) + { + break; + } + } + + LOG_INFO << "send " << wordcounts.size() << " records"; + for (WordCountMap::iterator it = wordcounts.begin(); + it != wordcounts.end(); ++it) + { + size_t idx = hash(it->first) % buckets_.size(); + buckets_[idx]->send(it->first, it->second); + } + } +} + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + printf("Usage: %s addresses_of_receivers input_file1 [input_file2]* \n", argv[0]); + printf("Example: %s 'ip1:port1,ip2:port2,ip3:port3' input_file1 input_file2 \n", argv[0]); + } + else + { + const char* batchSize = ::getenv("BATCH_SIZE"); + if (batchSize) + { + g_batchSize = atoi(batchSize); + } + WordCountSender sender(argv[1]); + sender.connectAll(); + for (int i = 2; i < argc; ++i) + { + sender.processFile(argv[i]); + } + sender.disconnectAll(); + } +} diff --git a/examples/wordcount/receiver.cc b/examples/wordcount/receiver.cc new file mode 100644 index 0000000..5a119c0 --- /dev/null +++ b/examples/wordcount/receiver.cc @@ -0,0 +1,107 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include "examples/wordcount/hash.h" + +#include <fstream> + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +class WordCountReceiver : muduo::noncopyable +{ + public: + WordCountReceiver(EventLoop* loop, const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "WordCountReceiver"), + senders_(0) + { + server_.setConnectionCallback( + std::bind(&WordCountReceiver::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&WordCountReceiver::onMessage, this, _1, _2, _3)); + } + + void start(int senders) + { + LOG_INFO << "start " << senders << " senders"; + senders_ = senders; + wordcounts_.clear(); + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_DEBUG << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (!conn->connected()) + { + if (--senders_ == 0) + { + output(); + loop_->quit(); + } + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + const char* crlf = NULL; + while ( (crlf = buf->findCRLF()) != NULL) + { + // string request(buf->peek(), crlf); + // printf("%s\n", request.c_str()); + const char* tab = std::find(buf->peek(), crlf, '\t'); + if (tab != crlf) + { + string word(buf->peek(), tab); + int64_t cnt = atoll(tab); + wordcounts_[word] += cnt; + } + else + { + LOG_ERROR << "Wrong format, no tab found"; + conn->shutdown(); + } + buf->retrieveUntil(crlf + 2); + } + } + + void output() + { + LOG_INFO << "Writing shard"; + std::ofstream out("shard"); + for (WordCountMap::iterator it = wordcounts_.begin(); + it != wordcounts_.end(); ++it) + { + out << it->first << '\t' << it->second << '\n'; + } + } + + EventLoop* loop_; + TcpServer server_; + int senders_; + WordCountMap wordcounts_; +}; + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + printf("Usage: %s listen_port number_of_senders\n", argv[0]); + } + else + { + EventLoop loop; + int port = atoi(argv[1]); + InetAddress addr(static_cast<uint16_t>(port)); + WordCountReceiver receiver(&loop, addr); + receiver.start(atoi(argv[2])); + loop.loop(); + } +} diff --git a/examples/wordcount/slowsink.py b/examples/wordcount/slowsink.py new file mode 100644 index 0000000..1867c47 --- /dev/null +++ b/examples/wordcount/slowsink.py @@ -0,0 +1,62 @@ +#!/usr/bin/python + +import os, socket, sys, time + +host = '' +port = 2007 + +if len(sys.argv) > 1: + mps = float(sys.argv[1]) +else: + mps = 1.0 +bps = mps * 1000000 +BUFSIZE = int(bps/10) # sleep 100ms at full speed + +print "Mbytes/s =", mps + +if len(sys.argv) > 3: + host = sys.argv[2] + port = int(sys.argv[3]) + print "connecting to %s:%d" % (host, port) +else: + print "listening on port", port + +if host: + client_socket = socket.create_connection((host, port)) + print "connected to", client_socket.getpeername() +else: + listen_address = ("", port) + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(listen_address) + server_socket.listen(5) + + (client_socket, client_address) = server_socket.accept() + print "got connection from", client_address + +start = time.time() +total_size = 0 +dot = bps + +while True: + data = client_socket.recv(BUFSIZE) + if data: + size = len(data) + total_size += size + if total_size >= dot: + dot += bps + sys.stdout.write('.') + sys.stdout.flush() + time.sleep(size / bps) + else: + print "\ndisconnect" + client_socket.close() + break + +end = time.time() +elapsed = end - start +print "elapsed seconds %.3f" % elapsed +print "total bytes", total_size +print "throughput bytes/s %.2f" % (total_size / elapsed) +print "throughput Mbytes/s %.3f" % (total_size / elapsed / 1000000) + diff --git a/examples/zeromq/README b/examples/zeromq/README new file mode 100644 index 0000000..28e4aa0 --- /dev/null +++ b/examples/zeromq/README @@ -0,0 +1,3 @@ + +local_lat.cc : echo server with LengthHeaderCodec +remote_lat.cc : echo client with LengthHeaderCodec diff --git a/examples/zeromq/local_lat.cc b/examples/zeromq/local_lat.cc new file mode 100644 index 0000000..bf81cd6 --- /dev/null +++ b/examples/zeromq/local_lat.cc @@ -0,0 +1,64 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" +#include "examples/asio/chat/codec.h" + +#include <stdio.h> +#include <unistd.h> + +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; +using muduo::get_pointer; + +bool g_tcpNoDelay = false; + +void onConnection(const muduo::net::TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn->setTcpNoDelay(g_tcpNoDelay); + } +} + +void onStringMessage(LengthHeaderCodec* codec, + const muduo::net::TcpConnectionPtr& conn, + const muduo::string& message, + muduo::Timestamp) +{ + codec->send(get_pointer(conn), message); +} + +int main(int argc, char* argv[]) +{ + if (argc > 1) + { + uint16_t port = static_cast<uint16_t>(atoi(argv[1])); + g_tcpNoDelay = argc > 2 ? atoi(argv[2]) : false; + int threadCount = argc > 3 ? atoi(argv[3]) : 0; + + LOG_INFO << "pid = " << getpid() << ", listen port = " << port; + // muduo::Logger::setLogLevel(muduo::Logger::WARN); + muduo::net::EventLoop loop; + muduo::net::InetAddress listenAddr(port); + muduo::net::TcpServer server(&loop, listenAddr, "PingPong"); + LengthHeaderCodec codec(std::bind(onStringMessage, &codec, _1, _2, _3)); + + server.setConnectionCallback(onConnection); + server.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec, _1, _2, _3)); + + if (threadCount > 1) + { + server.setThreadNum(threadCount); + } + + server.start(); + + loop.loop(); + } + else + { + fprintf(stderr, "Usage: %s listen_port [tcp_no_delay [threads]]\n", argv[0]); + } +} diff --git a/examples/zeromq/remote_lat.cc b/examples/zeromq/remote_lat.cc new file mode 100644 index 0000000..708f919 --- /dev/null +++ b/examples/zeromq/remote_lat.cc @@ -0,0 +1,96 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" +#include "examples/asio/chat/codec.h" + +#include <stdio.h> + +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; +using muduo::get_pointer; + +bool g_tcpNoDelay = false; +int g_msgSize = 0; +int g_totalMsgs = 0; +int g_msgCount = 0; +muduo::string g_message; +muduo::Timestamp g_start; + +void onConnection(LengthHeaderCodec* codec, const muduo::net::TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + LOG_INFO << "connected"; + g_start = muduo::Timestamp::now(); + conn->setTcpNoDelay(g_tcpNoDelay); + codec->send(get_pointer(conn), g_message); + } + else + { + LOG_INFO << "disconnected"; + muduo::net::EventLoop::getEventLoopOfCurrentThread()->quit(); + } +} + +void onStringMessage(LengthHeaderCodec* codec, + const muduo::net::TcpConnectionPtr& conn, + const muduo::string& message, + muduo::Timestamp) +{ + if (message.size() != static_cast<size_t>(g_msgSize)) + { + abort(); + } + + ++g_msgCount; + + if (g_msgCount < g_totalMsgs) + { + codec->send(get_pointer(conn), message); + } + else + { + muduo::Timestamp end = muduo::Timestamp::now(); + LOG_INFO << "done"; + double elapsed = timeDifference(end, g_start); + LOG_INFO << g_msgSize << " message bytes"; + LOG_INFO << g_msgCount << " round-trips"; + LOG_INFO << elapsed << " seconds"; + LOG_INFO << muduo::Fmt("%.3f", g_msgCount / elapsed) << " round-trips per second"; + LOG_INFO << muduo::Fmt("%.3f", (1000000 * elapsed / g_msgCount / 2)) + << " latency [us]"; + LOG_INFO << muduo::Fmt("%.3f", (g_msgSize * g_msgCount / elapsed / 1024 / 1024)) + << " band width [MiB/s]"; + conn->shutdown(); + } +} + +int main(int argc, char* argv[]) +{ + if (argc > 3) + { + const char* ip = argv[1]; + uint16_t port = static_cast<uint16_t>(atoi(argv[2])); + g_msgSize = atoi(argv[3]); + g_message.assign(g_msgSize, 'H'); + g_totalMsgs = argc > 4 ? atoi(argv[4]) : 10000; + g_tcpNoDelay = argc > 5 ? atoi(argv[5]) : false; + + muduo::net::EventLoop loop; + muduo::net::InetAddress serverAddr(ip, port); + muduo::net::TcpClient client(&loop, serverAddr, "Client"); + LengthHeaderCodec codec(std::bind(onStringMessage, &codec, _1, _2, _3)); + client.setConnectionCallback( + std::bind(onConnection, &codec, _1)); + client.setMessageCallback( + std::bind(&LengthHeaderCodec::onMessage, &codec, _1, _2, _3)); + client.connect(); + loop.loop(); + } + else + { + fprintf(stderr, "Usage: %s server_ip server_port msg_size", argv[0]); + fprintf(stderr, " [msg_count [tcp_no_delay]]\n"); + } +} diff --git a/muduo/base/AsyncLogging.cc b/muduo/base/AsyncLogging.cc new file mode 100644 index 0000000..7cf42a6 --- /dev/null +++ b/muduo/base/AsyncLogging.cc @@ -0,0 +1,124 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/AsyncLogging.h" + +#include <stdio.h> + +#include "muduo/base/LogFile.h" +#include "muduo/base/Timestamp.h" + +using namespace muduo; + +AsyncLogging::AsyncLogging(const string& basename, off_t rollSize, + int flushInterval) + : flushInterval_(flushInterval), + running_(false), + basename_(basename), + rollSize_(rollSize), + thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"), + latch_(1), + mutex_(), + cond_(mutex_), + currentBuffer_(new Buffer), + nextBuffer_(new Buffer), + buffers_() +{ + currentBuffer_->bzero(); + nextBuffer_->bzero(); + buffers_.reserve(16); +} + +void AsyncLogging::append(const char* logline, int len) +{ + muduo::MutexLockGuard lock(mutex_); + if (currentBuffer_->avail() > len) { + currentBuffer_->append(logline, len); + } else { + buffers_.push_back(std::move(currentBuffer_)); + + if (nextBuffer_) { + currentBuffer_ = std::move(nextBuffer_); + } else { + currentBuffer_.reset(new Buffer); // Rarely happens + } + currentBuffer_->append(logline, len); + cond_.notify(); + } +} + +void AsyncLogging::threadFunc() +{ + assert(running_ == true); + latch_.countDown(); + LogFile output(basename_, rollSize_, false); + BufferPtr newBuffer1(new Buffer); + BufferPtr newBuffer2(new Buffer); + newBuffer1->bzero(); + newBuffer2->bzero(); + BufferVector buffersToWrite; + buffersToWrite.reserve(16); + while (running_) { + assert(newBuffer1 && newBuffer1->length() == 0); + assert(newBuffer2 && newBuffer2->length() == 0); + assert(buffersToWrite.empty()); + + { + muduo::MutexLockGuard lock(mutex_); + if (buffers_.empty()) // unusual usage! + { + cond_.waitForSeconds(flushInterval_); + } + buffers_.push_back(std::move(currentBuffer_)); + currentBuffer_ = std::move(newBuffer1); + buffersToWrite.swap(buffers_); + if (!nextBuffer_) { + nextBuffer_ = std::move(newBuffer2); + } + } + + assert(!buffersToWrite.empty()); + + if (buffersToWrite.size() > 25) { + char buf[256]; + snprintf(buf, sizeof buf, + "Dropped log messages at %s, %zd larger buffers\n", + Timestamp::now().toFormattedString().c_str(), + buffersToWrite.size() - 2); + fputs(buf, stderr); + output.append(buf, static_cast<int>(strlen(buf))); + buffersToWrite.erase(buffersToWrite.begin() + 2, + buffersToWrite.end()); + } + + for (const auto& buffer : buffersToWrite) { + // FIXME: use unbuffered stdio FILE ? or use ::writev ? + output.append(buffer->data(), buffer->length()); + } + + if (buffersToWrite.size() > 2) { + // drop non-bzero-ed buffers, avoid trashing + buffersToWrite.resize(2); + } + + if (!newBuffer1) { + assert(!buffersToWrite.empty()); + newBuffer1 = std::move(buffersToWrite.back()); + buffersToWrite.pop_back(); + newBuffer1->reset(); + } + + if (!newBuffer2) { + assert(!buffersToWrite.empty()); + newBuffer2 = std::move(buffersToWrite.back()); + buffersToWrite.pop_back(); + newBuffer2->reset(); + } + + buffersToWrite.clear(); + output.flush(); + } + output.flush(); +} diff --git a/muduo/base/AsyncLogging.h b/muduo/base/AsyncLogging.h new file mode 100644 index 0000000..b28cb95 --- /dev/null +++ b/muduo/base/AsyncLogging.h @@ -0,0 +1,70 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_ASYNCLOGGING_H +#define MUDUO_BASE_ASYNCLOGGING_H + +#include <atomic> +#include <vector> + +#include "muduo/base/BlockingQueue.h" +#include "muduo/base/BoundedBlockingQueue.h" +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/LogStream.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" + +namespace muduo { + +class AsyncLogging : noncopyable { +public: + AsyncLogging(const string& basename, off_t rollSize, int flushInterval = 3); + + ~AsyncLogging() + { + if (running_) { + stop(); + } + } + + void append(const char* logline, int len); + + void start() + { + running_ = true; + thread_.start(); + latch_.wait(); + } + + void stop() NO_THREAD_SAFETY_ANALYSIS + { + running_ = false; + cond_.notify(); + thread_.join(); + } + +private: + void threadFunc(); + + typedef muduo::detail::FixedBuffer<muduo::detail::kLargeBuffer> Buffer; + typedef std::vector<std::unique_ptr<Buffer>> BufferVector; + typedef BufferVector::value_type BufferPtr; + + const int flushInterval_; + std::atomic<bool> running_; + const string basename_; + const off_t rollSize_; + muduo::Thread thread_; + muduo::CountDownLatch latch_; + muduo::MutexLock mutex_; + muduo::Condition cond_ GUARDED_BY(mutex_); + BufferPtr currentBuffer_ GUARDED_BY(mutex_); + BufferPtr nextBuffer_ GUARDED_BY(mutex_); + BufferVector buffers_ GUARDED_BY(mutex_); +}; + +} // namespace muduo + +#endif // MUDUO_BASE_ASYNCLOGGING_H diff --git a/muduo/base/Atomic.h b/muduo/base/Atomic.h new file mode 100644 index 0000000..bc78f49 --- /dev/null +++ b/muduo/base/Atomic.h @@ -0,0 +1,74 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_ATOMIC_H +#define MUDUO_BASE_ATOMIC_H + +#include <stdint.h> + +#include "muduo/base/noncopyable.h" + +namespace muduo { + +namespace detail { +template <typename T> +class AtomicIntegerT : noncopyable { +public: + AtomicIntegerT() : value_(0) {} + + // uncomment if you need copying and assignment + // + // AtomicIntegerT(const AtomicIntegerT& that) + // : value_(that.get()) + // {} + // + // AtomicIntegerT& operator=(const AtomicIntegerT& that) + // { + // getAndSet(that.get()); + // return *this; + // } + + T get() + { + // in gcc >= 4.7: __atomic_load_n(&value_, __ATOMIC_SEQ_CST) + return __sync_val_compare_and_swap(&value_, 0, 0); + } + + T getAndAdd(T x) + { + // in gcc >= 4.7: __atomic_fetch_add(&value_, x, __ATOMIC_SEQ_CST) + return __sync_fetch_and_add(&value_, x); + } + + T addAndGet(T x) { return getAndAdd(x) + x; } + + T incrementAndGet() { return addAndGet(1); } + + T decrementAndGet() { return addAndGet(-1); } + + void add(T x) { getAndAdd(x); } + + void increment() { incrementAndGet(); } + + void decrement() { decrementAndGet(); } + + T getAndSet(T newValue) + { + // in gcc >= 4.7: __atomic_exchange_n(&value_, newValue, + // __ATOMIC_SEQ_CST) + return __sync_lock_test_and_set(&value_, newValue); + } + +private: + volatile T value_; +}; +} // namespace detail + +typedef detail::AtomicIntegerT<int32_t> AtomicInt32; +typedef detail::AtomicIntegerT<int64_t> AtomicInt64; + +} // namespace muduo + +#endif // MUDUO_BASE_ATOMIC_H diff --git a/muduo/base/BUILD.bazel b/muduo/base/BUILD.bazel new file mode 100644 index 0000000..48b598f --- /dev/null +++ b/muduo/base/BUILD.bazel @@ -0,0 +1,23 @@ +cc_library( + name = "base", + srcs = [ + "AsyncLogging.cc", + "Condition.cc", + "CountDownLatch.cc", + "CurrentThread.cc", + "Date.cc", + "Exception.cc", + "FileUtil.cc", + "LogFile.cc", + "LogStream.cc", + "Logging.cc", + "ProcessInfo.cc", + "Thread.cc", + "ThreadPool.cc", + "TimeZone.cc", + "Timestamp.cc", + ], + hdrs = glob(["*.h"]), + linkopts = ["-pthread"], + visibility = ["//visibility:public"], +) diff --git a/muduo/base/BlockingQueue.h b/muduo/base/BlockingQueue.h new file mode 100644 index 0000000..407c012 --- /dev/null +++ b/muduo/base/BlockingQueue.h @@ -0,0 +1,72 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_BLOCKINGQUEUE_H +#define MUDUO_BASE_BLOCKINGQUEUE_H + +#include "muduo/base/Condition.h" +#include "muduo/base/Mutex.h" + +#include <deque> +#include <assert.h> + +namespace muduo +{ + +template<typename T> +class BlockingQueue : noncopyable +{ + public: + BlockingQueue() + : mutex_(), + notEmpty_(mutex_), + queue_() + { + } + + void put(const T& x) + { + MutexLockGuard lock(mutex_); + queue_.push_back(x); + notEmpty_.notify(); // wait morphing saves us + // http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/ + } + + void put(T&& x) + { + MutexLockGuard lock(mutex_); + queue_.push_back(std::move(x)); + notEmpty_.notify(); + } + + T take() + { + MutexLockGuard lock(mutex_); + // always use a while-loop, due to spurious wakeup + while (queue_.empty()) + { + notEmpty_.wait(); + } + assert(!queue_.empty()); + T front(std::move(queue_.front())); + queue_.pop_front(); + return front; + } + + size_t size() const + { + MutexLockGuard lock(mutex_); + return queue_.size(); + } + + private: + mutable MutexLock mutex_; + Condition notEmpty_ GUARDED_BY(mutex_); + std::deque<T> queue_ GUARDED_BY(mutex_); +}; + +} // namespace muduo + +#endif // MUDUO_BASE_BLOCKINGQUEUE_H diff --git a/muduo/base/BoundedBlockingQueue.h b/muduo/base/BoundedBlockingQueue.h new file mode 100644 index 0000000..9cbf17a --- /dev/null +++ b/muduo/base/BoundedBlockingQueue.h @@ -0,0 +1,94 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H +#define MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H + +#include <assert.h> + +#include <boost/circular_buffer.hpp> + +#include "muduo/base/Condition.h" +#include "muduo/base/Mutex.h" + +namespace muduo { + +template <typename T> +class BoundedBlockingQueue : noncopyable { +public: + explicit BoundedBlockingQueue(int maxSize) + : mutex_(), notEmpty_(mutex_), notFull_(mutex_), queue_(maxSize) + { + } + + void put(const T& x) + { + MutexLockGuard lock(mutex_); + while (queue_.full()) { + notFull_.wait(); + } + assert(!queue_.full()); + queue_.push_back(x); + notEmpty_.notify(); + } + + void put(T&& x) + { + MutexLockGuard lock(mutex_); + while (queue_.full()) { + notFull_.wait(); + } + assert(!queue_.full()); + queue_.push_back(std::move(x)); + notEmpty_.notify(); + } + + T take() + { + MutexLockGuard lock(mutex_); + while (queue_.empty()) { + notEmpty_.wait(); + } + assert(!queue_.empty()); + T front(std::move(queue_.front())); + queue_.pop_front(); + notFull_.notify(); + return front; + } + + bool empty() const + { + MutexLockGuard lock(mutex_); + return queue_.empty(); + } + + bool full() const + { + MutexLockGuard lock(mutex_); + return queue_.full(); + } + + size_t size() const + { + MutexLockGuard lock(mutex_); + return queue_.size(); + } + + size_t capacity() const + { + MutexLockGuard lock(mutex_); + return queue_.capacity(); + } + +private: + mutable MutexLock mutex_; + Condition notEmpty_ GUARDED_BY(mutex_); + Condition notFull_ GUARDED_BY(mutex_); + boost::circular_buffer<T> queue_ GUARDED_BY(mutex_); +}; + +} // namespace muduo + +#endif // MUDUO_BASE_BOUNDEDBLOCKINGQUEUE_H diff --git a/muduo/base/Condition.cc b/muduo/base/Condition.cc new file mode 100644 index 0000000..9f8678b --- /dev/null +++ b/muduo/base/Condition.cc @@ -0,0 +1,28 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/Condition.h" + +#include <errno.h> + +// returns true if time out, false otherwise. +bool muduo::Condition::waitForSeconds(double seconds) +{ + struct timespec abstime; + // FIXME: use CLOCK_MONOTONIC or CLOCK_MONOTONIC_RAW to prevent time rewind. + clock_gettime(CLOCK_REALTIME, &abstime); + + const int64_t kNanoSecondsPerSecond = 1000000000; + int64_t nanoseconds = static_cast<int64_t>(seconds * kNanoSecondsPerSecond); + + abstime.tv_sec += static_cast<time_t>((abstime.tv_nsec + nanoseconds) / + kNanoSecondsPerSecond); + abstime.tv_nsec = static_cast<long>((abstime.tv_nsec + nanoseconds) % + kNanoSecondsPerSecond); + + MutexLock::UnassignGuard ug(mutex_); + return ETIMEDOUT == + pthread_cond_timedwait(&pcond_, mutex_.getPthreadMutex(), &abstime); +} diff --git a/muduo/base/Condition.h b/muduo/base/Condition.h new file mode 100644 index 0000000..f5b1797 --- /dev/null +++ b/muduo/base/Condition.h @@ -0,0 +1,44 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_CONDITION_H +#define MUDUO_BASE_CONDITION_H + +#include <pthread.h> + +#include "muduo/base/Mutex.h" + +namespace muduo { + +class Condition : noncopyable { +public: + explicit Condition(MutexLock& mutex) : mutex_(mutex) + { + MCHECK(pthread_cond_init(&pcond_, NULL)); + } + + ~Condition() { MCHECK(pthread_cond_destroy(&pcond_)); } + + void wait() + { + MutexLock::UnassignGuard ug(mutex_); + MCHECK(pthread_cond_wait(&pcond_, mutex_.getPthreadMutex())); + } + + // returns true if time out, false otherwise. + bool waitForSeconds(double seconds); + + void notify() { MCHECK(pthread_cond_signal(&pcond_)); } + + void notifyAll() { MCHECK(pthread_cond_broadcast(&pcond_)); } + +private: + MutexLock& mutex_; + pthread_cond_t pcond_; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_CONDITION_H diff --git a/muduo/base/CountDownLatch.cc b/muduo/base/CountDownLatch.cc new file mode 100644 index 0000000..44c1844 --- /dev/null +++ b/muduo/base/CountDownLatch.cc @@ -0,0 +1,36 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/CountDownLatch.h" + +using namespace muduo; + +CountDownLatch::CountDownLatch(int count) + : mutex_(), condition_(mutex_), count_(count) +{ +} + +void CountDownLatch::wait() +{ + MutexLockGuard lock(mutex_); + while (count_ > 0) { + condition_.wait(); + } +} + +void CountDownLatch::countDown() +{ + MutexLockGuard lock(mutex_); + --count_; + if (count_ == 0) { + condition_.notifyAll(); + } +} + +int CountDownLatch::getCount() const +{ + MutexLockGuard lock(mutex_); + return count_; +} diff --git a/muduo/base/CountDownLatch.h b/muduo/base/CountDownLatch.h new file mode 100644 index 0000000..2286ceb --- /dev/null +++ b/muduo/base/CountDownLatch.h @@ -0,0 +1,31 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_COUNTDOWNLATCH_H +#define MUDUO_BASE_COUNTDOWNLATCH_H + +#include "muduo/base/Condition.h" +#include "muduo/base/Mutex.h" + +namespace muduo { + +class CountDownLatch : noncopyable { +public: + explicit CountDownLatch(int count); + + void wait(); + + void countDown(); + + int getCount() const; + +private: + mutable MutexLock mutex_; + Condition condition_ GUARDED_BY(mutex_); + int count_ GUARDED_BY(mutex_); +}; + +} // namespace muduo +#endif // MUDUO_BASE_COUNTDOWNLATCH_H diff --git a/muduo/base/CurrentThread.cc b/muduo/base/CurrentThread.cc new file mode 100644 index 0000000..b648d92 --- /dev/null +++ b/muduo/base/CurrentThread.cc @@ -0,0 +1,73 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/CurrentThread.h" + +#include <cxxabi.h> +#include <execinfo.h> +#include <stdlib.h> + +namespace muduo { +namespace CurrentThread { +__thread int t_cachedTid = 0; +__thread char t_tidString[32]; +__thread int t_tidStringLength = 6; +__thread const char* t_threadName = "unknown"; +static_assert(std::is_same<int, pid_t>::value, "pid_t should be int"); + +string stackTrace(bool demangle) +{ + string stack; + const int max_frames = 200; + void* frame[max_frames]; + int nptrs = ::backtrace(frame, max_frames); + char** strings = ::backtrace_symbols(frame, nptrs); + if (strings) { + size_t len = 256; + char* demangled = + demangle ? static_cast<char*>(::malloc(len)) : nullptr; + for (int i = 1; i < nptrs; + ++i) // skipping the 0-th, which is this function + { + if (demangle) { + // https://panthema.net/2008/0901-stacktrace-demangled/ + // bin/exception_test(_ZN3Bar4testEv+0x79) [0x401909] + char* left_par = nullptr; + char* plus = nullptr; + for (char* p = strings[i]; *p; ++p) { + if (*p == '(') + left_par = p; + else if (*p == '+') + plus = p; + } + + if (left_par && plus) { + *plus = '\0'; + int status = 0; + char* ret = abi::__cxa_demangle(left_par + 1, demangled, + &len, &status); + *plus = '+'; + if (status == 0) { + demangled = ret; // ret could be realloc() + stack.append(strings[i], left_par + 1); + stack.append(demangled); + stack.append(plus); + stack.push_back('\n'); + continue; + } + } + } + // Fallback to mangled names + stack.append(strings[i]); + stack.push_back('\n'); + } + free(demangled); + free(strings); + } + return stack; +} + +} // namespace CurrentThread +} // namespace muduo diff --git a/muduo/base/CurrentThread.h b/muduo/base/CurrentThread.h new file mode 100644 index 0000000..61685af --- /dev/null +++ b/muduo/base/CurrentThread.h @@ -0,0 +1,51 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_CURRENTTHREAD_H +#define MUDUO_BASE_CURRENTTHREAD_H + +#include "muduo/base/Types.h" + +namespace muduo { +namespace CurrentThread { +// internal +extern __thread int t_cachedTid; +extern __thread char t_tidString[32]; +extern __thread int t_tidStringLength; +extern __thread const char* t_threadName; + +// cacheTid 用于获取当前线程的 ID 并存储到 t_cachedTid +void cacheTid(); + +inline int tid() +{ + // __builtin_expect 这个应该是一个优化的语句 + if (__builtin_expect(t_cachedTid == 0, 0)) { + cacheTid(); + } + return t_cachedTid; +} + +inline const char* tidString() // for logging +{ + return t_tidString; +} + +inline int tidStringLength() // for logging +{ + return t_tidStringLength; +} + +inline const char* name() { return t_threadName; } + +bool isMainThread(); + +void sleepUsec(int64_t usec); // for testing + +string stackTrace(bool demangle); +} // namespace CurrentThread +} // namespace muduo + +#endif // MUDUO_BASE_CURRENTTHREAD_H diff --git a/muduo/base/Date.cc b/muduo/base/Date.cc new file mode 100644 index 0000000..8e1152c --- /dev/null +++ b/muduo/base/Date.cc @@ -0,0 +1,72 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/Date.h" + +#include <stdio.h> // snprintf +#include <time.h> // struct tm + +namespace muduo { +namespace detail { + +char require_32_bit_integer_at_least[sizeof(int) >= sizeof(int32_t) ? 1 : -1]; + +// algorithm and explanation see: +// http://www.faqs.org/faqs/calendars/faq/part2/ +// http://blog.csdn.net/Solstice + +int getJulianDayNumber(int year, int month, int day) +{ + (void)require_32_bit_integer_at_least; // no warning please + int a = (14 - month) / 12; + int y = year + 4800 - a; + int m = month + 12 * a - 3; + return day + (153 * m + 2) / 5 + y * 365 + y / 4 - y / 100 + y / 400 - + 32045; +} + +struct Date::YearMonthDay getYearMonthDay(int julianDayNumber) +{ + int a = julianDayNumber + 32044; + int b = (4 * a + 3) / 146097; + int c = a - ((b * 146097) / 4); + int d = (4 * c + 3) / 1461; + int e = c - ((1461 * d) / 4); + int m = (5 * e + 2) / 153; + Date::YearMonthDay ymd; + ymd.day = e - ((153 * m + 2) / 5) + 1; + ymd.month = m + 3 - 12 * (m / 10); + ymd.year = b * 100 + d - 4800 + (m / 10); + return ymd; +} +} // namespace detail +const int Date::kJulianDayOf1970_01_01 = detail::getJulianDayNumber(1970, 1, 1); +} // namespace muduo + +using namespace muduo; +using namespace muduo::detail; + +Date::Date(int y, int m, int d) : julianDayNumber_(getJulianDayNumber(y, m, d)) +{ +} + +Date::Date(const struct tm& t) + : julianDayNumber_( + getJulianDayNumber(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday)) +{ +} + +string Date::toIsoString() const +{ + char buf[32]; + YearMonthDay ymd(yearMonthDay()); + snprintf(buf, sizeof buf, "%4d-%02d-%02d", ymd.year, ymd.month, ymd.day); + return buf; +} + +Date::YearMonthDay Date::yearMonthDay() const +{ + return getYearMonthDay(julianDayNumber_); +} diff --git a/muduo/base/Date.h b/muduo/base/Date.h new file mode 100644 index 0000000..2fe3723 --- /dev/null +++ b/muduo/base/Date.h @@ -0,0 +1,100 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_DATE_H +#define MUDUO_BASE_DATE_H + +#include "muduo/base/Types.h" +#include "muduo/base/copyable.h" + +struct tm; + +namespace muduo { + +/// +/// Date in Gregorian calendar. +/// +/// This class is immutable. +/// It's recommended to pass it by value, since it's passed in register on x64. +/// +class Date : public muduo::copyable +// public boost::less_than_comparable<Date>, +// public boost::equality_comparable<Date> +{ +public: + struct YearMonthDay { + int year; // [1900..2500] + int month; // [1..12] + int day; // [1..31] + }; + + static const int kDaysPerWeek = 7; + static const int kJulianDayOf1970_01_01; + + /// + /// Constucts an invalid Date. + /// + Date() : julianDayNumber_(0) {} + + /// + /// Constucts a yyyy-mm-dd Date. + /// + /// 1 <= month <= 12 + Date(int year, int month, int day); + + /// + /// Constucts a Date from Julian Day Number. + /// + explicit Date(int julianDayNum) : julianDayNumber_(julianDayNum) {} + + /// + /// Constucts a Date from struct tm + /// + explicit Date(const struct tm&); + + // default copy/assignment/dtor are Okay + + void swap(Date& that) + { + std::swap(julianDayNumber_, that.julianDayNumber_); + } + + bool valid() const { return julianDayNumber_ > 0; } + + /// + /// Converts to yyyy-mm-dd format. + /// + string toIsoString() const; + + struct YearMonthDay yearMonthDay() const; + + int year() const { return yearMonthDay().year; } + + int month() const { return yearMonthDay().month; } + + int day() const { return yearMonthDay().day; } + + // [0, 1, ..., 6] => [Sunday, Monday, ..., Saturday ] + int weekDay() const { return (julianDayNumber_ + 1) % kDaysPerWeek; } + + int julianDayNumber() const { return julianDayNumber_; } + +private: + int julianDayNumber_; +}; + +inline bool operator<(Date x, Date y) +{ + return x.julianDayNumber() < y.julianDayNumber(); +} + +inline bool operator==(Date x, Date y) +{ + return x.julianDayNumber() == y.julianDayNumber(); +} + +} // namespace muduo + +#endif // MUDUO_BASE_DATE_H diff --git a/muduo/base/Exception.cc b/muduo/base/Exception.cc new file mode 100644 index 0000000..6e2afbe --- /dev/null +++ b/muduo/base/Exception.cc @@ -0,0 +1,18 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/Exception.h" +#include "muduo/base/CurrentThread.h" + +namespace muduo +{ + +Exception::Exception(string msg) + : message_(std::move(msg)), + stack_(CurrentThread::stackTrace(/*demangle=*/false)) +{ +} + +} // namespace muduo diff --git a/muduo/base/Exception.h b/muduo/base/Exception.h new file mode 100644 index 0000000..27dcce1 --- /dev/null +++ b/muduo/base/Exception.h @@ -0,0 +1,33 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_EXCEPTION_H +#define MUDUO_BASE_EXCEPTION_H + +#include <exception> + +#include "muduo/base/Types.h" + +namespace muduo { + +class Exception : public std::exception { +public: + Exception(string what); + ~Exception() noexcept override = default; + + // default copy-ctor and operator= are okay. + + const char* what() const noexcept override { return message_.c_str(); } + + const char* stackTrace() const noexcept { return stack_.c_str(); } + +private: + string message_; + string stack_; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_EXCEPTION_H diff --git a/muduo/base/FileUtil.cc b/muduo/base/FileUtil.cc new file mode 100644 index 0000000..0c7b4c1 --- /dev/null +++ b/muduo/base/FileUtil.cc @@ -0,0 +1,148 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/FileUtil.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "muduo/base/Logging.h" + +using namespace muduo; + +FileUtil::AppendFile::AppendFile(StringArg filename) + : fp_(::fopen(filename.c_str(), "ae")), // 'e' for O_CLOEXEC + writtenBytes_(0) +{ + assert(fp_); + ::setbuffer(fp_, buffer_, sizeof buffer_); + // posix_fadvise POSIX_FADV_DONTNEED ? +} + +FileUtil::AppendFile::~AppendFile() { ::fclose(fp_); } + +void FileUtil::AppendFile::append(const char* logline, const size_t len) +{ + size_t n = write(logline, len); + size_t remain = len - n; + while (remain > 0) { + size_t x = write(logline + n, remain); + if (x == 0) { + int err = ferror(fp_); + if (err) { + fprintf(stderr, "AppendFile::append() failed %s\n", + strerror_tl(err)); + } + break; + } + n += x; + remain = len - n; // remain -= x + } + + writtenBytes_ += len; +} + +void FileUtil::AppendFile::flush() { ::fflush(fp_); } + +size_t FileUtil::AppendFile::write(const char* logline, size_t len) +{ + // #undef fwrite_unlocked + return ::fwrite_unlocked(logline, 1, len, fp_); +} + +FileUtil::ReadSmallFile::ReadSmallFile(StringArg filename) + : fd_(::open(filename.c_str(), O_RDONLY | O_CLOEXEC)), err_(0) +{ + buf_[0] = '\0'; + if (fd_ < 0) { + err_ = errno; + } +} + +FileUtil::ReadSmallFile::~ReadSmallFile() +{ + if (fd_ >= 0) { + ::close(fd_); // FIXME: check EINTR + } +} + +// return errno +template <typename String> +int FileUtil::ReadSmallFile::readToString(int maxSize, String* content, + int64_t* fileSize, + int64_t* modifyTime, + int64_t* createTime) +{ + static_assert(sizeof(off_t) == 8, "_FILE_OFFSET_BITS = 64"); + assert(content != NULL); + int err = err_; + if (fd_ >= 0) { + content->clear(); + + if (fileSize) { + struct stat statbuf; + if (::fstat(fd_, &statbuf) == 0) { + if (S_ISREG(statbuf.st_mode)) { + *fileSize = statbuf.st_size; + content->reserve(static_cast<int>( + std::min(implicit_cast<int64_t>(maxSize), *fileSize))); + } else if (S_ISDIR(statbuf.st_mode)) { + err = EISDIR; + } + if (modifyTime) { + *modifyTime = statbuf.st_mtime; + } + if (createTime) { + *createTime = statbuf.st_ctime; + } + } else { + err = errno; + } + } + + while (content->size() < implicit_cast<size_t>(maxSize)) { + size_t toRead = std::min( + implicit_cast<size_t>(maxSize) - content->size(), sizeof(buf_)); + ssize_t n = ::read(fd_, buf_, toRead); + if (n > 0) { + content->append(buf_, n); + } else { + if (n < 0) { + err = errno; + } + break; + } + } + } + return err; +} + +int FileUtil::ReadSmallFile::readToBuffer(int* size) +{ + int err = err_; + if (fd_ >= 0) { + ssize_t n = ::pread(fd_, buf_, sizeof(buf_) - 1, 0); + if (n >= 0) { + if (size) { + *size = static_cast<int>(n); + } + buf_[n] = '\0'; + } else { + err = errno; + } + } + return err; +} + +template int FileUtil::readFile(StringArg filename, int maxSize, + string* content, int64_t*, int64_t*, int64_t*); + +template int FileUtil::ReadSmallFile::readToString(int maxSize, string* content, + int64_t*, int64_t*, + int64_t*); diff --git a/muduo/base/FileUtil.h b/muduo/base/FileUtil.h new file mode 100644 index 0000000..81b7eda --- /dev/null +++ b/muduo/base/FileUtil.h @@ -0,0 +1,79 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_BASE_FILEUTIL_H +#define MUDUO_BASE_FILEUTIL_H + +#include <sys/types.h> // for off_t + +#include "muduo/base/StringPiece.h" +#include "muduo/base/noncopyable.h" + +namespace muduo { +namespace FileUtil { + +// read small file < 64KB +class ReadSmallFile : noncopyable { +public: + ReadSmallFile(StringArg filename); + ~ReadSmallFile(); + + // return errno + template <typename String> + int readToString(int maxSize, String* content, int64_t* fileSize, + int64_t* modifyTime, int64_t* createTime); + + /// Read at maxium kBufferSize into buf_ + // return errno + int readToBuffer(int* size); + + const char* buffer() const { return buf_; } + + static const int kBufferSize = 64 * 1024; + +private: + int fd_; + int err_; + char buf_[kBufferSize]; +}; + +// read the file content, returns errno if error happens. +template <typename String> +int readFile(StringArg filename, int maxSize, String* content, + int64_t* fileSize = NULL, int64_t* modifyTime = NULL, + int64_t* createTime = NULL) +{ + ReadSmallFile file(filename); + return file.readToString(maxSize, content, fileSize, modifyTime, + createTime); +} + +// not thread safe +class AppendFile : noncopyable { +public: + explicit AppendFile(StringArg filename); + + ~AppendFile(); + + void append(const char* logline, size_t len); + + void flush(); + + off_t writtenBytes() const { return writtenBytes_; } + +private: + size_t write(const char* logline, size_t len); + + FILE* fp_; + char buffer_[64 * 1024]; + off_t writtenBytes_; +}; + +} // namespace FileUtil +} // namespace muduo + +#endif // MUDUO_BASE_FILEUTIL_H diff --git a/muduo/base/GzipFile.h b/muduo/base/GzipFile.h new file mode 100644 index 0000000..f2f777b --- /dev/null +++ b/muduo/base/GzipFile.h @@ -0,0 +1,84 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#pragma once + +#include <zlib.h> + +#include "muduo/base/StringPiece.h" +#include "muduo/base/noncopyable.h" + +namespace muduo { + +class GzipFile : noncopyable { +public: + GzipFile(GzipFile&& rhs) noexcept : file_(rhs.file_) { rhs.file_ = NULL; } + + ~GzipFile() + { + if (file_) { + ::gzclose(file_); + } + } + + GzipFile& operator=(GzipFile&& rhs) noexcept + { + swap(rhs); + return *this; + } + + bool valid() const { return file_ != NULL; } + void swap(GzipFile& rhs) { std::swap(file_, rhs.file_); } +#if ZLIB_VERNUM >= 0x1240 + bool setBuffer(int size) { return ::gzbuffer(file_, size) == 0; } +#endif + + // return the number of uncompressed bytes actually read, 0 for eof, -1 for + // error + int read(void* buf, int len) { return ::gzread(file_, buf, len); } + + // return the number of uncompressed bytes actually written + int write(StringPiece buf) + { + return ::gzwrite(file_, buf.data(), buf.size()); + } + + // number of uncompressed bytes + off_t tell() const { return ::gztell(file_); } + +#if ZLIB_VERNUM >= 0x1240 + // number of compressed bytes + off_t offset() const { return ::gzoffset(file_); } +#endif + + // int flush(int f) { return ::gzflush(file_, f); } + + static GzipFile openForRead(StringArg filename) + { + return GzipFile(::gzopen(filename.c_str(), "rbe")); + } + + static GzipFile openForAppend(StringArg filename) + { + return GzipFile(::gzopen(filename.c_str(), "abe")); + } + + static GzipFile openForWriteExclusive(StringArg filename) + { + return GzipFile(::gzopen(filename.c_str(), "wbxe")); + } + + static GzipFile openForWriteTruncate(StringArg filename) + { + return GzipFile(::gzopen(filename.c_str(), "wbe")); + } + +private: + explicit GzipFile(gzFile file) : file_(file) {} + + gzFile file_; +}; + +} // namespace muduo diff --git a/muduo/base/LogFile.cc b/muduo/base/LogFile.cc new file mode 100644 index 0000000..e618662 --- /dev/null +++ b/muduo/base/LogFile.cc @@ -0,0 +1,115 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/LogFile.h" + +#include <assert.h> +#include <stdio.h> +#include <time.h> + +#include "muduo/base/FileUtil.h" +#include "muduo/base/ProcessInfo.h" + +using namespace muduo; + +LogFile::LogFile(const string& basename, off_t rollSize, bool threadSafe, + int flushInterval, int checkEveryN) + : basename_(basename), + rollSize_(rollSize), + flushInterval_(flushInterval), + checkEveryN_(checkEveryN), + count_(0), + mutex_(threadSafe ? new MutexLock : NULL), + startOfPeriod_(0), + lastRoll_(0), + lastFlush_(0) +{ + assert(basename.find('/') == string::npos); + rollFile(); +} + +LogFile::~LogFile() = default; + +void LogFile::append(const char* logline, int len) +{ + if (mutex_) { + MutexLockGuard lock(*mutex_); + append_unlocked(logline, len); + } else { + append_unlocked(logline, len); + } +} + +void LogFile::flush() +{ + if (mutex_) { + MutexLockGuard lock(*mutex_); + file_->flush(); + } else { + file_->flush(); + } +} + +void LogFile::append_unlocked(const char* logline, int len) +{ + file_->append(logline, len); + + if (file_->writtenBytes() > rollSize_) { + rollFile(); + } else { + ++count_; + if (count_ >= checkEveryN_) { + count_ = 0; + time_t now = ::time(NULL); + time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_; + if (thisPeriod_ != startOfPeriod_) { + rollFile(); + } else if (now - lastFlush_ > flushInterval_) { + lastFlush_ = now; + file_->flush(); + } + } + } +} + +bool LogFile::rollFile() +{ + time_t now = 0; + string filename = getLogFileName(basename_, &now); + time_t start = now / kRollPerSeconds_ * kRollPerSeconds_; + + if (now > lastRoll_) { + lastRoll_ = now; + lastFlush_ = now; + startOfPeriod_ = start; + file_.reset(new FileUtil::AppendFile(filename)); + return true; + } + return false; +} + +string LogFile::getLogFileName(const string& basename, time_t* now) +{ + string filename; + filename.reserve(basename.size() + 64); + filename = basename; + + char timebuf[32]; + struct tm tm; + *now = time(NULL); + gmtime_r(now, &tm); // FIXME: localtime_r ? + strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm); + filename += timebuf; + + filename += ProcessInfo::hostname(); + + char pidbuf[32]; + snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid()); + filename += pidbuf; + + filename += ".log"; + + return filename; +} diff --git a/muduo/base/LogFile.h b/muduo/base/LogFile.h new file mode 100644 index 0000000..7c45da9 --- /dev/null +++ b/muduo/base/LogFile.h @@ -0,0 +1,52 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_LOGFILE_H +#define MUDUO_BASE_LOGFILE_H + +#include <memory> + +#include "muduo/base/Mutex.h" +#include "muduo/base/Types.h" + +namespace muduo { + +namespace FileUtil { +class AppendFile; +} + +class LogFile : noncopyable { +public: + LogFile(const string& basename, off_t rollSize, bool threadSafe = true, + int flushInterval = 3, int checkEveryN = 1024); + ~LogFile(); + + void append(const char* logline, int len); + void flush(); + bool rollFile(); + +private: + void append_unlocked(const char* logline, int len); + + static string getLogFileName(const string& basename, time_t* now); + + const string basename_; + const off_t rollSize_; + const int flushInterval_; + const int checkEveryN_; + + int count_; + + std::unique_ptr<MutexLock> mutex_; + time_t startOfPeriod_; + time_t lastRoll_; + time_t lastFlush_; + std::unique_ptr<FileUtil::AppendFile> file_; + + const static int kRollPerSeconds_ = 60 * 60 * 24; +}; + +} // namespace muduo +#endif // MUDUO_BASE_LOGFILE_H diff --git a/muduo/base/LogStream.cc b/muduo/base/LogStream.cc new file mode 100644 index 0000000..6bd5b13 --- /dev/null +++ b/muduo/base/LogStream.cc @@ -0,0 +1,337 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/LogStream.h" + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <algorithm> +#include <limits> +#include <type_traits> + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include <inttypes.h> + +using namespace muduo; +using namespace muduo::detail; + +// TODO: better itoa. +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wtautological-compare" +#else +#pragma GCC diagnostic ignored "-Wtype-limits" +#endif + +namespace muduo { +namespace detail { + +const char digits[] = "9876543210123456789"; +const char* zero = digits + 9; +static_assert(sizeof(digits) == 20, "wrong number of digits"); + +const char digitsHex[] = "0123456789ABCDEF"; +static_assert(sizeof digitsHex == 17, "wrong number of digitsHex"); + +// Efficient Integer to String Conversions, by Matthew Wilson. +template <typename T> +size_t convert(char buf[], T value) +{ + T i = value; + char* p = buf; + + do { + int lsd = static_cast<int>(i % 10); + i /= 10; + *p++ = zero[lsd]; + } while (i != 0); + + if (value < 0) { + *p++ = '-'; + } + *p = '\0'; + std::reverse(buf, p); + + return p - buf; +} + +size_t convertHex(char buf[], uintptr_t value) +{ + uintptr_t i = value; + char* p = buf; + + do { + int lsd = static_cast<int>(i % 16); + i /= 16; + *p++ = digitsHex[lsd]; + } while (i != 0); + + *p = '\0'; + std::reverse(buf, p); + + return p - buf; +} + +template class FixedBuffer<kSmallBuffer>; +template class FixedBuffer<kLargeBuffer>; + +} // namespace detail + +/* + Format a number with 5 characters, including SI units. + [0, 999] + [1.00k, 999k] + [1.00M, 999M] + [1.00G, 999G] + [1.00T, 999T] + [1.00P, 999P] + [1.00E, inf) +*/ +std::string formatSI(int64_t s) +{ + double n = static_cast<double>(s); + char buf[64]; + if (s < 1000) + snprintf(buf, sizeof(buf), "%" PRId64, s); + else if (s < 9995) + snprintf(buf, sizeof(buf), "%.2fk", n / 1e3); + else if (s < 99950) + snprintf(buf, sizeof(buf), "%.1fk", n / 1e3); + else if (s < 999500) + snprintf(buf, sizeof(buf), "%.0fk", n / 1e3); + else if (s < 9995000) + snprintf(buf, sizeof(buf), "%.2fM", n / 1e6); + else if (s < 99950000) + snprintf(buf, sizeof(buf), "%.1fM", n / 1e6); + else if (s < 999500000) + snprintf(buf, sizeof(buf), "%.0fM", n / 1e6); + else if (s < 9995000000) + snprintf(buf, sizeof(buf), "%.2fG", n / 1e9); + else if (s < 99950000000) + snprintf(buf, sizeof(buf), "%.1fG", n / 1e9); + else if (s < 999500000000) + snprintf(buf, sizeof(buf), "%.0fG", n / 1e9); + else if (s < 9995000000000) + snprintf(buf, sizeof(buf), "%.2fT", n / 1e12); + else if (s < 99950000000000) + snprintf(buf, sizeof(buf), "%.1fT", n / 1e12); + else if (s < 999500000000000) + snprintf(buf, sizeof(buf), "%.0fT", n / 1e12); + else if (s < 9995000000000000) + snprintf(buf, sizeof(buf), "%.2fP", n / 1e15); + else if (s < 99950000000000000) + snprintf(buf, sizeof(buf), "%.1fP", n / 1e15); + else if (s < 999500000000000000) + snprintf(buf, sizeof(buf), "%.0fP", n / 1e15); + else + snprintf(buf, sizeof(buf), "%.2fE", n / 1e18); + return buf; +} + +/* + [0, 1023] + [1.00Ki, 9.99Ki] + [10.0Ki, 99.9Ki] + [ 100Ki, 1023Ki] + [1.00Mi, 9.99Mi] +*/ +std::string formatIEC(int64_t s) +{ + double n = static_cast<double>(s); + char buf[64]; + const double Ki = 1024.0; + const double Mi = Ki * 1024.0; + const double Gi = Mi * 1024.0; + const double Ti = Gi * 1024.0; + const double Pi = Ti * 1024.0; + const double Ei = Pi * 1024.0; + + if (n < Ki) + snprintf(buf, sizeof buf, "%" PRId64, s); + else if (n < Ki * 9.995) + snprintf(buf, sizeof buf, "%.2fKi", n / Ki); + else if (n < Ki * 99.95) + snprintf(buf, sizeof buf, "%.1fKi", n / Ki); + else if (n < Ki * 1023.5) + snprintf(buf, sizeof buf, "%.0fKi", n / Ki); + + else if (n < Mi * 9.995) + snprintf(buf, sizeof buf, "%.2fMi", n / Mi); + else if (n < Mi * 99.95) + snprintf(buf, sizeof buf, "%.1fMi", n / Mi); + else if (n < Mi * 1023.5) + snprintf(buf, sizeof buf, "%.0fMi", n / Mi); + + else if (n < Gi * 9.995) + snprintf(buf, sizeof buf, "%.2fGi", n / Gi); + else if (n < Gi * 99.95) + snprintf(buf, sizeof buf, "%.1fGi", n / Gi); + else if (n < Gi * 1023.5) + snprintf(buf, sizeof buf, "%.0fGi", n / Gi); + + else if (n < Ti * 9.995) + snprintf(buf, sizeof buf, "%.2fTi", n / Ti); + else if (n < Ti * 99.95) + snprintf(buf, sizeof buf, "%.1fTi", n / Ti); + else if (n < Ti * 1023.5) + snprintf(buf, sizeof buf, "%.0fTi", n / Ti); + + else if (n < Pi * 9.995) + snprintf(buf, sizeof buf, "%.2fPi", n / Pi); + else if (n < Pi * 99.95) + snprintf(buf, sizeof buf, "%.1fPi", n / Pi); + else if (n < Pi * 1023.5) + snprintf(buf, sizeof buf, "%.0fPi", n / Pi); + + else if (n < Ei * 9.995) + snprintf(buf, sizeof buf, "%.2fEi", n / Ei); + else + snprintf(buf, sizeof buf, "%.1fEi", n / Ei); + return buf; +} + +} // namespace muduo + +template <int SIZE> +const char* FixedBuffer<SIZE>::debugString() +{ + *cur_ = '\0'; + return data_; +} + +template <int SIZE> +void FixedBuffer<SIZE>::cookieStart() +{ +} + +template <int SIZE> +void FixedBuffer<SIZE>::cookieEnd() +{ +} + +void LogStream::staticCheck() +{ + static_assert(kMaxNumericSize - 10 > std::numeric_limits<double>::digits10, + "kMaxNumericSize is large enough"); + static_assert( + kMaxNumericSize - 10 > std::numeric_limits<long double>::digits10, + "kMaxNumericSize is large enough"); + static_assert(kMaxNumericSize - 10 > std::numeric_limits<long>::digits10, + "kMaxNumericSize is large enough"); + static_assert( + kMaxNumericSize - 10 > std::numeric_limits<long long>::digits10, + "kMaxNumericSize is large enough"); +} + +template <typename T> +void LogStream::formatInteger(T v) +{ + if (buffer_.avail() >= kMaxNumericSize) { + size_t len = convert(buffer_.current(), v); + buffer_.add(len); + } +} + +LogStream& LogStream::operator<<(short v) +{ + *this << static_cast<int>(v); + return *this; +} + +LogStream& LogStream::operator<<(unsigned short v) +{ + *this << static_cast<unsigned int>(v); + return *this; +} + +LogStream& LogStream::operator<<(int v) +{ + formatInteger(v); + return *this; +} + +LogStream& LogStream::operator<<(unsigned int v) +{ + formatInteger(v); + return *this; +} + +LogStream& LogStream::operator<<(long v) +{ + formatInteger(v); + return *this; +} + +LogStream& LogStream::operator<<(unsigned long v) +{ + formatInteger(v); + return *this; +} + +LogStream& LogStream::operator<<(long long v) +{ + formatInteger(v); + return *this; +} + +LogStream& LogStream::operator<<(unsigned long long v) +{ + formatInteger(v); + return *this; +} + +LogStream& LogStream::operator<<(const void* p) +{ + uintptr_t v = reinterpret_cast<uintptr_t>(p); + if (buffer_.avail() >= kMaxNumericSize) { + char* buf = buffer_.current(); + buf[0] = '0'; + buf[1] = 'x'; + size_t len = convertHex(buf + 2, v); + buffer_.add(len + 2); + } + return *this; +} + +// FIXME: replace this with Grisu3 by Florian Loitsch. +LogStream& LogStream::operator<<(double v) +{ + if (buffer_.avail() >= kMaxNumericSize) { + int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12g", v); + buffer_.add(len); + } + return *this; +} + +template <typename T> +Fmt::Fmt(const char* fmt, T val) +{ + static_assert(std::is_arithmetic<T>::value == true, + "Must be arithmetic type"); + + length_ = snprintf(buf_, sizeof buf_, fmt, val); + assert(static_cast<size_t>(length_) < sizeof buf_); +} + +// Explicit instantiations + +template Fmt::Fmt(const char* fmt, char); + +template Fmt::Fmt(const char* fmt, short); +template Fmt::Fmt(const char* fmt, unsigned short); +template Fmt::Fmt(const char* fmt, int); +template Fmt::Fmt(const char* fmt, unsigned int); +template Fmt::Fmt(const char* fmt, long); +template Fmt::Fmt(const char* fmt, unsigned long); +template Fmt::Fmt(const char* fmt, long long); +template Fmt::Fmt(const char* fmt, unsigned long long); + +template Fmt::Fmt(const char* fmt, float); +template Fmt::Fmt(const char* fmt, double); diff --git a/muduo/base/LogStream.h b/muduo/base/LogStream.h new file mode 100644 index 0000000..c5b85c5 --- /dev/null +++ b/muduo/base/LogStream.h @@ -0,0 +1,190 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_LOGSTREAM_H +#define MUDUO_BASE_LOGSTREAM_H + +#include <assert.h> +#include <string.h> // memcpy + +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" +#include "muduo/base/noncopyable.h" + +namespace muduo { + +namespace detail { + +const int kSmallBuffer = 4000; +const int kLargeBuffer = 4000 * 1000; + +template <int SIZE> +class FixedBuffer : noncopyable { +public: + FixedBuffer() : cur_(data_) { setCookie(cookieStart); } + + ~FixedBuffer() { setCookie(cookieEnd); } + + void append(const char* /*restrict*/ buf, size_t len) + { + // FIXME: append partially + if (implicit_cast<size_t>(avail()) > len) { + memcpy(cur_, buf, len); + cur_ += len; + } + } + + const char* data() const { return data_; } + int length() const { return static_cast<int>(cur_ - data_); } + + // write to data_ directly + char* current() { return cur_; } + int avail() const { return static_cast<int>(end() - cur_); } + void add(size_t len) { cur_ += len; } + + void reset() { cur_ = data_; } + void bzero() { memZero(data_, sizeof data_); } + + // for used by GDB + const char* debugString(); + void setCookie(void (*cookie)()) { cookie_ = cookie; } + // for used by unit test + string toString() const { return string(data_, length()); } + StringPiece toStringPiece() const { return StringPiece(data_, length()); } + +private: + const char* end() const { return data_ + sizeof data_; } + // Must be outline function for cookies. + static void cookieStart(); + static void cookieEnd(); + + void (*cookie_)(); + char data_[SIZE]; + char* cur_; +}; + +} // namespace detail + +class LogStream : noncopyable { + typedef LogStream self; + +public: + typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer; + + self& operator<<(bool v) + { + buffer_.append(v ? "1" : "0", 1); + return *this; + } + + self& operator<<(short); + self& operator<<(unsigned short); + self& operator<<(int); + self& operator<<(unsigned int); + self& operator<<(long); + self& operator<<(unsigned long); + self& operator<<(long long); + self& operator<<(unsigned long long); + + self& operator<<(const void*); + + self& operator<<(float v) + { + *this << static_cast<double>(v); + return *this; + } + self& operator<<(double); + // self& operator<<(long double); + + self& operator<<(char v) + { + buffer_.append(&v, 1); + return *this; + } + + // self& operator<<(signed char); + // self& operator<<(unsigned char); + + self& operator<<(const char* str) + { + if (str) { + buffer_.append(str, strlen(str)); + } else { + buffer_.append("(null)", 6); + } + return *this; + } + + self& operator<<(const unsigned char* str) + { + return operator<<(reinterpret_cast<const char*>(str)); + } + + self& operator<<(const string& v) + { + buffer_.append(v.c_str(), v.size()); + return *this; + } + + self& operator<<(const StringPiece& v) + { + buffer_.append(v.data(), v.size()); + return *this; + } + + self& operator<<(const Buffer& v) + { + *this << v.toStringPiece(); + return *this; + } + + void append(const char* data, int len) { buffer_.append(data, len); } + const Buffer& buffer() const { return buffer_; } + void resetBuffer() { buffer_.reset(); } + +private: + void staticCheck(); + + template <typename T> + void formatInteger(T); + + Buffer buffer_; + + static const int kMaxNumericSize = 48; +}; + +class Fmt // : noncopyable +{ +public: + template <typename T> + Fmt(const char* fmt, T val); + + const char* data() const { return buf_; } + int length() const { return length_; } + +private: + char buf_[32]; + int length_; +}; + +inline LogStream& operator<<(LogStream& s, const Fmt& fmt) +{ + s.append(fmt.data(), fmt.length()); + return s; +} + +// Format quantity n in SI units (k, M, G, T, P, E). +// The returned string is atmost 5 characters long. +// Requires n >= 0 +string formatSI(int64_t n); + +// Format quantity n in IEC (binary) units (Ki, Mi, Gi, Ti, Pi, Ei). +// The returned string is atmost 6 characters long. +// Requires n >= 0 +string formatIEC(int64_t n); + +} // namespace muduo + +#endif // MUDUO_BASE_LOGSTREAM_H diff --git a/muduo/base/Logging.cc b/muduo/base/Logging.cc new file mode 100644 index 0000000..c09a8ce --- /dev/null +++ b/muduo/base/Logging.cc @@ -0,0 +1,227 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/Logging.h" + +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Timestamp.h" +#include "muduo/base/TimeZone.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <sstream> + +namespace muduo +{ + +/* +class LoggerImpl +{ + public: + typedef Logger::LogLevel LogLevel; + LoggerImpl(LogLevel level, int old_errno, const char* file, int line); + void finish(); + + Timestamp time_; + LogStream stream_; + LogLevel level_; + int line_; + const char* fullname_; + const char* basename_; +}; +*/ + +__thread char t_errnobuf[512]; +__thread char t_time[64]; +__thread time_t t_lastSecond; + +const char* strerror_tl(int savedErrno) +{ + return strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf); +} + +Logger::LogLevel initLogLevel() +{ + if (::getenv("MUDUO_LOG_TRACE")) + return Logger::TRACE; + else if (::getenv("MUDUO_LOG_DEBUG")) + return Logger::DEBUG; + else + return Logger::INFO; +} + +Logger::LogLevel g_logLevel = initLogLevel(); + +const char* LogLevelName[Logger::NUM_LOG_LEVELS] = +{ + "TRACE ", + "DEBUG ", + "INFO ", + "WARN ", + "ERROR ", + "FATAL ", +}; + +// helper class for known string length at compile time +class T +{ + public: + T(const char* str, unsigned len) + :str_(str), + len_(len) + { + assert(strlen(str) == len_); + } + + const char* str_; + const unsigned len_; +}; + +inline LogStream& operator<<(LogStream& s, T v) +{ + s.append(v.str_, v.len_); + return s; +} + +inline LogStream& operator<<(LogStream& s, const Logger::SourceFile& v) +{ + s.append(v.data_, v.size_); + return s; +} + +void defaultOutput(const char* msg, int len) +{ + size_t n = fwrite(msg, 1, len, stdout); + //FIXME check n + (void)n; +} + +void defaultFlush() +{ + fflush(stdout); +} + +Logger::OutputFunc g_output = defaultOutput; +Logger::FlushFunc g_flush = defaultFlush; +TimeZone g_logTimeZone; + +} // namespace muduo + +using namespace muduo; + +Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line) + : time_(Timestamp::now()), + stream_(), + level_(level), + line_(line), + basename_(file) +{ + formatTime(); + CurrentThread::tid(); + stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength()); + stream_ << T(LogLevelName[level], 6); + if (savedErrno != 0) + { + stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") "; + } +} + +void Logger::Impl::formatTime() +{ + int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch(); + time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / Timestamp::kMicroSecondsPerSecond); + int microseconds = static_cast<int>(microSecondsSinceEpoch % Timestamp::kMicroSecondsPerSecond); + if (seconds != t_lastSecond) + { + t_lastSecond = seconds; + struct tm tm_time; + if (g_logTimeZone.valid()) + { + tm_time = g_logTimeZone.toLocalTime(seconds); + } + else + { + ::gmtime_r(&seconds, &tm_time); // FIXME TimeZone::fromUtcTime + } + + int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d", + tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday, + tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); + assert(len == 17); (void)len; + } + + if (g_logTimeZone.valid()) + { + Fmt us(".%06d ", microseconds); + assert(us.length() == 8); + stream_ << T(t_time, 17) << T(us.data(), 8); + } + else + { + Fmt us(".%06dZ ", microseconds); + assert(us.length() == 9); + stream_ << T(t_time, 17) << T(us.data(), 9); + } +} + +void Logger::Impl::finish() +{ + stream_ << " - " << basename_ << ':' << line_ << '\n'; +} + +Logger::Logger(SourceFile file, int line) + : impl_(INFO, 0, file, line) +{ +} + +Logger::Logger(SourceFile file, int line, LogLevel level, const char* func) + : impl_(level, 0, file, line) +{ + impl_.stream_ << func << ' '; +} + +Logger::Logger(SourceFile file, int line, LogLevel level) + : impl_(level, 0, file, line) +{ +} + +Logger::Logger(SourceFile file, int line, bool toAbort) + : impl_(toAbort?FATAL:ERROR, errno, file, line) +{ +} + +Logger::~Logger() +{ + impl_.finish(); + const LogStream::Buffer& buf(stream().buffer()); + g_output(buf.data(), buf.length()); + if (impl_.level_ == FATAL) + { + g_flush(); + abort(); + } +} + +void Logger::setLogLevel(Logger::LogLevel level) +{ + g_logLevel = level; +} + +void Logger::setOutput(OutputFunc out) +{ + g_output = out; +} + +void Logger::setFlush(FlushFunc flush) +{ + g_flush = flush; +} + +void Logger::setTimeZone(const TimeZone& tz) +{ + g_logTimeZone = tz; +} diff --git a/muduo/base/Logging.h b/muduo/base/Logging.h new file mode 100644 index 0000000..be56689 --- /dev/null +++ b/muduo/base/Logging.h @@ -0,0 +1,159 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_LOGGING_H +#define MUDUO_BASE_LOGGING_H + +#include "muduo/base/LogStream.h" +#include "muduo/base/Timestamp.h" + +namespace muduo +{ + +class TimeZone; + +class Logger +{ + public: + enum LogLevel + { + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL, + NUM_LOG_LEVELS, + }; + + // compile time calculation of basename of source file + class SourceFile + { + public: + template<int N> + SourceFile(const char (&arr)[N]) + : data_(arr), + size_(N-1) + { + const char* slash = strrchr(data_, '/'); // builtin function + if (slash) + { + data_ = slash + 1; + size_ -= static_cast<int>(data_ - arr); + } + } + + explicit SourceFile(const char* filename) + : data_(filename) + { + const char* slash = strrchr(filename, '/'); + if (slash) + { + data_ = slash + 1; + } + size_ = static_cast<int>(strlen(data_)); + } + + const char* data_; + int size_; + }; + + Logger(SourceFile file, int line); + Logger(SourceFile file, int line, LogLevel level); + Logger(SourceFile file, int line, LogLevel level, const char* func); + Logger(SourceFile file, int line, bool toAbort); + ~Logger(); + + LogStream& stream() { return impl_.stream_; } + + static LogLevel logLevel(); + static void setLogLevel(LogLevel level); + + typedef void (*OutputFunc)(const char* msg, int len); + typedef void (*FlushFunc)(); + static void setOutput(OutputFunc); + static void setFlush(FlushFunc); + static void setTimeZone(const TimeZone& tz); + + private: + +class Impl +{ + public: + typedef Logger::LogLevel LogLevel; + Impl(LogLevel level, int old_errno, const SourceFile& file, int line); + void formatTime(); + void finish(); + + Timestamp time_; + LogStream stream_; + LogLevel level_; + int line_; + SourceFile basename_; +}; + + Impl impl_; + +}; + +extern Logger::LogLevel g_logLevel; + +inline Logger::LogLevel Logger::logLevel() +{ + return g_logLevel; +} + +// +// CAUTION: do not write: +// +// if (good) +// LOG_INFO << "Good news"; +// else +// LOG_WARN << "Bad news"; +// +// this expends to +// +// if (good) +// if (logging_INFO) +// logInfoStream << "Good news"; +// else +// logWarnStream << "Bad news"; +// +#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \ + muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream() +#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \ + muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream() +#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \ + muduo::Logger(__FILE__, __LINE__).stream() +#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream() +#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream() +#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream() +#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream() +#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream() + +const char* strerror_tl(int savedErrno); + +// Taken from glog/logging.h +// +// Check that the input is non NULL. This very useful in constructor +// initializer lists. + +#define CHECK_NOTNULL(val) \ + ::muduo::CheckNotNull(__FILE__, __LINE__, "'" #val "' Must be non NULL", (val)) + +// A small helper for CHECK_NOTNULL(). +template <typename T> +T* CheckNotNull(Logger::SourceFile file, int line, const char *names, T* ptr) +{ + if (ptr == NULL) + { + Logger(file, line, Logger::FATAL).stream() << names; + } + return ptr; +} + +} // namespace muduo + +#endif // MUDUO_BASE_LOGGING_H diff --git a/muduo/base/Mutex.h b/muduo/base/Mutex.h new file mode 100644 index 0000000..3f3ec74 --- /dev/null +++ b/muduo/base/Mutex.h @@ -0,0 +1,207 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_MUTEX_H +#define MUDUO_BASE_MUTEX_H + +#include <assert.h> +#include <pthread.h> + +#include "muduo/base/CurrentThread.h" +#include "muduo/base/noncopyable.h" + +// Thread safety annotations { +// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(__clang__) && (!defined(SWIG)) +#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define ACQUIRED_BEFORE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define ACQUIRED_AFTER(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define REQUIRES_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define RELEASE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define RELEASE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define ASSERT_SHARED_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define NO_THREAD_SAFETY_ANALYSIS \ + THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + +// End of thread safety annotations } + +#ifdef CHECK_PTHREAD_RETURN_VALUE + +#ifdef NDEBUG +__BEGIN_DECLS +extern void __assert_perror_fail(int errnum, const char* file, + unsigned int line, + const char* function) noexcept + __attribute__((__noreturn__)); +__END_DECLS +#endif + +#define MCHECK(ret) \ + ({ \ + __typeof__(ret) errnum = (ret); \ + if (__builtin_expect(errnum != 0, 0)) \ + __assert_perror_fail(errnum, __FILE__, __LINE__, __func__); \ + }) + +#else // CHECK_PTHREAD_RETURN_VALUE + +#define MCHECK(ret) \ + ({ \ + __typeof__(ret) errnum = (ret); \ + assert(errnum == 0); \ + (void)errnum; \ + }) + +#endif // CHECK_PTHREAD_RETURN_VALUE + +namespace muduo { + +// Use as data member of a class, eg. +// +// class Foo +// { +// public: +// int size() const; +// +// private: +// mutable MutexLock mutex_; +// std::vector<int> data_ GUARDED_BY(mutex_); +// }; +class CAPABILITY("mutex") MutexLock : noncopyable { +public: + MutexLock() : holder_(0) { MCHECK(pthread_mutex_init(&mutex_, NULL)); } + + ~MutexLock() + { + assert(holder_ == 0); + MCHECK(pthread_mutex_destroy(&mutex_)); + } + + // must be called when locked, i.e. for assertion + bool isLockedByThisThread() const + { + return holder_ == CurrentThread::tid(); + } + + void assertLocked() const ASSERT_CAPABILITY(this) + { + assert(isLockedByThisThread()); + } + + // internal usage + + void lock() ACQUIRE() + { + MCHECK(pthread_mutex_lock(&mutex_)); + assignHolder(); + } + + void unlock() RELEASE() + { + unassignHolder(); + MCHECK(pthread_mutex_unlock(&mutex_)); + } + + pthread_mutex_t* getPthreadMutex() /* non-const */ { return &mutex_; } + +private: + friend class Condition; + + class UnassignGuard : noncopyable { + public: + explicit UnassignGuard(MutexLock& owner) : owner_(owner) + { + owner_.unassignHolder(); + } + + ~UnassignGuard() { owner_.assignHolder(); } + + private: + MutexLock& owner_; + }; + + void unassignHolder() { holder_ = 0; } + + void assignHolder() { holder_ = CurrentThread::tid(); } + + pthread_mutex_t mutex_; + pid_t holder_; +}; + +// Use as a stack variable, eg. +// int Foo::size() const +// { +// MutexLockGuard lock(mutex_); +// return data_.size(); +// } +class SCOPED_CAPABILITY MutexLockGuard : noncopyable { +public: + explicit MutexLockGuard(MutexLock& mutex) ACQUIRE(mutex) : mutex_(mutex) + { + mutex_.lock(); + } + + ~MutexLockGuard() RELEASE() { mutex_.unlock(); } + +private: + MutexLock& mutex_; +}; + +} // namespace muduo + +// Prevent misuse like: +// MutexLockGuard(mutex_); +// A tempory object doesn't hold the lock for long! +#define MutexLockGuard(x) error "Missing guard object name" + +#endif // MUDUO_BASE_MUTEX_H diff --git a/muduo/base/ProcessInfo.cc b/muduo/base/ProcessInfo.cc new file mode 100644 index 0000000..cfa156c --- /dev/null +++ b/muduo/base/ProcessInfo.cc @@ -0,0 +1,210 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/ProcessInfo.h" + +#include <assert.h> +#include <dirent.h> +#include <pwd.h> +#include <stdio.h> // snprintf +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/times.h> +#include <unistd.h> + +#include <algorithm> + +#include "muduo/base/CurrentThread.h" +#include "muduo/base/FileUtil.h" + +namespace muduo { +namespace detail { +__thread int t_numOpenedFiles = 0; +int fdDirFilter(const struct dirent* d) +{ + if (::isdigit(d->d_name[0])) { + ++t_numOpenedFiles; + } + return 0; +} + +__thread std::vector<pid_t>* t_pids = NULL; +int taskDirFilter(const struct dirent* d) +{ + if (::isdigit(d->d_name[0])) { + t_pids->push_back(atoi(d->d_name)); + } + return 0; +} + +int scanDir(const char* dirpath, int (*filter)(const struct dirent*)) +{ + struct dirent** namelist = NULL; + int result = ::scandir(dirpath, &namelist, filter, alphasort); + assert(namelist == NULL); + return result; +} + +Timestamp g_startTime = Timestamp::now(); +// assume those won't change during the life time of a process. +int g_clockTicks = static_cast<int>(::sysconf(_SC_CLK_TCK)); +int g_pageSize = static_cast<int>(::sysconf(_SC_PAGE_SIZE)); +} // namespace detail +} // namespace muduo + +using namespace muduo; +using namespace muduo::detail; + +pid_t ProcessInfo::pid() { return ::getpid(); } + +string ProcessInfo::pidString() +{ + char buf[32]; + snprintf(buf, sizeof buf, "%d", pid()); + return buf; +} + +uid_t ProcessInfo::uid() { return ::getuid(); } + +string ProcessInfo::username() +{ + struct passwd pwd; + struct passwd* result = NULL; + char buf[8192]; + const char* name = "unknownuser"; + + getpwuid_r(uid(), &pwd, buf, sizeof buf, &result); + if (result) { + name = pwd.pw_name; + } + return name; +} + +uid_t ProcessInfo::euid() { return ::geteuid(); } + +Timestamp ProcessInfo::startTime() { return g_startTime; } + +int ProcessInfo::clockTicksPerSecond() { return g_clockTicks; } + +int ProcessInfo::pageSize() { return g_pageSize; } + +bool ProcessInfo::isDebugBuild() +{ +#ifdef NDEBUG + return false; +#else + return true; +#endif +} + +string ProcessInfo::hostname() +{ + // HOST_NAME_MAX 64 + // _POSIX_HOST_NAME_MAX 255 + char buf[256]; + if (::gethostname(buf, sizeof buf) == 0) { + buf[sizeof(buf) - 1] = '\0'; + return buf; + } else { + return "unknownhost"; + } +} + +string ProcessInfo::procname() { return procname(procStat()).as_string(); } + +StringPiece ProcessInfo::procname(const string& stat) +{ + StringPiece name; + size_t lp = stat.find('('); + size_t rp = stat.rfind(')'); + if (lp != string::npos && rp != string::npos && lp < rp) { + name.set(stat.data() + lp + 1, static_cast<int>(rp - lp - 1)); + } + return name; +} + +string ProcessInfo::procStatus() +{ + string result; + FileUtil::readFile("/proc/self/status", 65536, &result); + return result; +} + +string ProcessInfo::procStat() +{ + string result; + FileUtil::readFile("/proc/self/stat", 65536, &result); + return result; +} + +string ProcessInfo::threadStat() +{ + char buf[64]; + snprintf(buf, sizeof buf, "/proc/self/task/%d/stat", CurrentThread::tid()); + string result; + FileUtil::readFile(buf, 65536, &result); + return result; +} + +string ProcessInfo::exePath() +{ + string result; + char buf[1024]; + ssize_t n = ::readlink("/proc/self/exe", buf, sizeof buf); + if (n > 0) { + result.assign(buf, n); + } + return result; +} + +int ProcessInfo::openedFiles() +{ + t_numOpenedFiles = 0; + scanDir("/proc/self/fd", fdDirFilter); + return t_numOpenedFiles; +} + +int ProcessInfo::maxOpenFiles() +{ + struct rlimit rl; + if (::getrlimit(RLIMIT_NOFILE, &rl)) { + return openedFiles(); + } else { + return static_cast<int>(rl.rlim_cur); + } +} + +ProcessInfo::CpuTime ProcessInfo::cpuTime() +{ + ProcessInfo::CpuTime t; + struct tms tms; + if (::times(&tms) >= 0) { + const double hz = static_cast<double>(clockTicksPerSecond()); + t.userSeconds = static_cast<double>(tms.tms_utime) / hz; + t.systemSeconds = static_cast<double>(tms.tms_stime) / hz; + } + return t; +} + +int ProcessInfo::numThreads() +{ + int result = 0; + string status = procStatus(); + size_t pos = status.find("Threads:"); + if (pos != string::npos) { + result = ::atoi(status.c_str() + pos + 8); + } + return result; +} + +std::vector<pid_t> ProcessInfo::threads() +{ + std::vector<pid_t> result; + t_pids = &result; + scanDir("/proc/self/task", taskDirFilter); + t_pids = NULL; + std::sort(result.begin(), result.end()); + return result; +} diff --git a/muduo/base/ProcessInfo.h b/muduo/base/ProcessInfo.h new file mode 100644 index 0000000..d93d2fb --- /dev/null +++ b/muduo/base/ProcessInfo.h @@ -0,0 +1,67 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_BASE_PROCESSINFO_H +#define MUDUO_BASE_PROCESSINFO_H + +#include <sys/types.h> + +#include <vector> + +#include "muduo/base/StringPiece.h" +#include "muduo/base/Timestamp.h" +#include "muduo/base/Types.h" + +namespace muduo { + +namespace ProcessInfo { +pid_t pid(); +string pidString(); +uid_t uid(); +string username(); +uid_t euid(); +Timestamp startTime(); +int clockTicksPerSecond(); +int pageSize(); +bool isDebugBuild(); // constexpr + +string hostname(); +string procname(); +StringPiece procname(const string& stat); + +/// read /proc/self/status +string procStatus(); + +/// read /proc/self/stat +string procStat(); + +/// read /proc/self/task/tid/stat +string threadStat(); + +/// readlink /proc/self/exe +string exePath(); + +int openedFiles(); +int maxOpenFiles(); + +struct CpuTime { + double userSeconds; + double systemSeconds; + + CpuTime() : userSeconds(0.0), systemSeconds(0.0) {} + + double total() const { return userSeconds + systemSeconds; } +}; +CpuTime cpuTime(); + +int numThreads(); +std::vector<pid_t> threads(); +} // namespace ProcessInfo + +} // namespace muduo + +#endif // MUDUO_BASE_PROCESSINFO_H diff --git a/muduo/base/Singleton.h b/muduo/base/Singleton.h new file mode 100644 index 0000000..7189671 --- /dev/null +++ b/muduo/base/Singleton.h @@ -0,0 +1,75 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_SINGLETON_H +#define MUDUO_BASE_SINGLETON_H + +#include <assert.h> +#include <pthread.h> +#include <stdlib.h> // atexit + +#include "muduo/base/noncopyable.h" + +namespace muduo { + +namespace detail { +// This doesn't detect inherited member functions! +// http://stackoverflow.com/questions/1966362/sfinae-to-check-for-inherited-member-functions +template <typename T> +struct has_no_destroy { + template <typename C> + static char test(decltype(&C::no_destroy)); + template <typename C> + static int32_t test(...); + const static bool value = sizeof(test<T>(0)) == 1; +}; +} // namespace detail + +template <typename T> +class Singleton : noncopyable { +public: + Singleton() = delete; + ~Singleton() = delete; + + static T& instance() + { + pthread_once(&ponce_, &Singleton::init); + assert(value_ != NULL); + return *value_; + } + +private: + static void init() + { + value_ = new T(); + if (!detail::has_no_destroy<T>::value) { + ::atexit(destroy); + } + } + + static void destroy() + { + typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; + T_must_be_complete_type dummy; + (void)dummy; + + delete value_; + value_ = NULL; + } + +private: + static pthread_once_t ponce_; + static T* value_; +}; + +template <typename T> +pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT; + +template <typename T> +T* Singleton<T>::value_ = NULL; + +} // namespace muduo + +#endif // MUDUO_BASE_SINGLETON_H diff --git a/muduo/base/StringPiece.h b/muduo/base/StringPiece.h new file mode 100644 index 0000000..fb9b756 --- /dev/null +++ b/muduo/base/StringPiece.h @@ -0,0 +1,200 @@ +// Taken from PCRE pcre_stringpiece.h +// +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat +// +// A string like object that points into another piece of memory. +// Useful for providing an interface that allows clients to easily +// pass in either a "const char*" or a "string". +// +// Arghh! I wish C++ literals were automatically of type "string". + +#ifndef MUDUO_BASE_STRINGPIECE_H +#define MUDUO_BASE_STRINGPIECE_H + +#include <string.h> + +#include <iosfwd> // for ostream forward-declaration + +#include "muduo/base/Types.h" + +namespace muduo { + +// For passing C-style string argument to a function. +class StringArg // copyable +{ +public: + StringArg(const char* str) : str_(str) {} + + StringArg(const string& str) : str_(str.c_str()) {} + + const char* c_str() const { return str_; } + +private: + const char* str_; +}; + +class StringPiece { +private: + const char* ptr_; + int length_; + +public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + StringPiece() : ptr_(NULL), length_(0) {} + StringPiece(const char* str) + : ptr_(str), length_(static_cast<int>(strlen(ptr_))) + { + } + StringPiece(const unsigned char* str) + : ptr_(reinterpret_cast<const char*>(str)), + length_(static_cast<int>(strlen(ptr_))) + { + } + StringPiece(const string& str) + : ptr_(str.data()), length_(static_cast<int>(str.size())) + { + } + StringPiece(const char* offset, int len) : ptr_(offset), length_(len) {} + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. Use "as_string().c_str()" if you really need to do + // this. Or better yet, change your routine so it does not rely on NUL + // termination. + const char* data() const { return ptr_; } + int size() const { return length_; } + bool empty() const { return length_ == 0; } + const char* begin() const { return ptr_; } + const char* end() const { return ptr_ + length_; } + + void clear() + { + ptr_ = NULL; + length_ = 0; + } + void set(const char* buffer, int len) + { + ptr_ = buffer; + length_ = len; + } + void set(const char* str) + { + ptr_ = str; + length_ = static_cast<int>(strlen(str)); + } + void set(const void* buffer, int len) + { + ptr_ = reinterpret_cast<const char*>(buffer); + length_ = len; + } + + char operator[](int i) const { return ptr_[i]; } + + void remove_prefix(int n) + { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(int n) { length_ -= n; } + + bool operator==(const StringPiece& x) const + { + return ((length_ == x.length_) && (memcmp(ptr_, x.ptr_, length_) == 0)); + } + bool operator!=(const StringPiece& x) const { return !(*this == x); } + +#define STRINGPIECE_BINARY_PREDICATE(cmp, auxcmp) \ + bool operator cmp(const StringPiece& x) const \ + { \ + int r = \ + memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \ + return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_))); \ + } + STRINGPIECE_BINARY_PREDICATE(<, <); + STRINGPIECE_BINARY_PREDICATE(<=, <); + STRINGPIECE_BINARY_PREDICATE(>=, >); + STRINGPIECE_BINARY_PREDICATE(>, >); +#undef STRINGPIECE_BINARY_PREDICATE + + int compare(const StringPiece& x) const + { + int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); + if (r == 0) { + if (length_ < x.length_) + r = -1; + else if (length_ > x.length_) + r = +1; + } + return r; + } + + string as_string() const { return string(data(), size()); } + + void CopyToString(string* target) const { target->assign(ptr_, length_); } + + // Does "this" start with "x" + bool starts_with(const StringPiece& x) const + { + return ((length_ >= x.length_) && + (memcmp(ptr_, x.ptr_, x.length_) == 0)); + } +}; + +} // namespace muduo + +// ------------------------------------------------------------------ +// Functions used to create STL containers that use StringPiece +// Remember that a StringPiece's lifetime had better be less than +// that of the underlying string or char*. If it is not, then you +// cannot safely store a StringPiece into an STL container +// ------------------------------------------------------------------ + +#ifdef HAVE_TYPE_TRAITS +// This makes vector<StringPiece> really fast for some STL implementations +template <> +struct __type_traits<muduo::StringPiece> { + typedef __true_type has_trivial_default_constructor; + typedef __true_type has_trivial_copy_constructor; + typedef __true_type has_trivial_assignment_operator; + typedef __true_type has_trivial_destructor; + typedef __true_type is_POD_type; +}; +#endif + +// allow StringPiece to be logged +std::ostream& operator<<(std::ostream& o, const muduo::StringPiece& piece); + +#endif // MUDUO_BASE_STRINGPIECE_H diff --git a/muduo/base/Thread.cc b/muduo/base/Thread.cc new file mode 100644 index 0000000..92d42cf --- /dev/null +++ b/muduo/base/Thread.cc @@ -0,0 +1,198 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/Thread.h" + +#include <errno.h> +#include <linux/unistd.h> +#include <stdio.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <type_traits> + +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Exception.h" +#include "muduo/base/Logging.h" + +namespace muduo { +namespace detail { + +pid_t gettid() +{ + /* + ::syscall(SYS_gettid) 是 Linux 系统调用的一部分,用于获取当前线程的线程 + ID(TID,Thread ID)。在 Linux 中,每个线程都有一个唯一的 TID。 + 具体而言,syscall + 是一个系统调用的包装函数,它允许用户空间程序直接调用内核中的系统调用。SYS_gettid + 是一个宏,表示获取线程 ID 的系统调用号。 因此,::syscall(SYS_gettid) + 的作用是在运行时调用 Linux 内核的 gettid 系统调用,返回当前线程的线程 ID。 + 注意: + 使用 syscall 需要小心,因为它是直接调用内核的接口,不提供 C + 库的包装和错误处理。如果可能的话,最好使用更高级别的接口或者库。 SYS_gettid + 系统调用在不同的 Linux 发行版和内核版本中可能有所不同。通常,可以在 + <sys/syscall.h> 头文件中找到对应的宏定义。 要在 C++ 程序中使用 + ::syscall,可能需要包含一些相关的头文件,如 <sys/syscall.h>。 + + pid_t 是线程ID类型。 + */ + return static_cast<pid_t>(::syscall(SYS_gettid)); +} + +void afterFork() +{ + muduo::CurrentThread::t_cachedTid = 0; + muduo::CurrentThread::t_threadName = "main"; + CurrentThread::tid(); + // no need to call pthread_atfork(NULL, NULL, &afterFork); +} + +class ThreadNameInitializer { +public: + ThreadNameInitializer() + { + muduo::CurrentThread::t_threadName = "main"; + CurrentThread::tid(); + pthread_atfork(NULL, NULL, &afterFork); + } +}; + +ThreadNameInitializer init; + +struct ThreadData { + typedef muduo::Thread::ThreadFunc ThreadFunc; + ThreadFunc func_; + string name_; + pid_t* tid_; + CountDownLatch* latch_; + + ThreadData(ThreadFunc func, const string& name, pid_t* tid, + CountDownLatch* latch) + : func_(std::move(func)), name_(name), tid_(tid), latch_(latch) + { + } + + void runInThread() + { + *tid_ = muduo::CurrentThread::tid(); + tid_ = NULL; + latch_->countDown(); + latch_ = NULL; + + muduo::CurrentThread::t_threadName = + name_.empty() ? "muduoThread" : name_.c_str(); + ::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName); + try { + func_(); + muduo::CurrentThread::t_threadName = "finished"; + } catch (const Exception& ex) { + muduo::CurrentThread::t_threadName = "crashed"; + fprintf(stderr, "exception caught in Thread %s\n", name_.c_str()); + fprintf(stderr, "reason: %s\n", ex.what()); + fprintf(stderr, "stack trace: %s\n", ex.stackTrace()); + abort(); + } catch (const std::exception& ex) { + muduo::CurrentThread::t_threadName = "crashed"; + fprintf(stderr, "exception caught in Thread %s\n", name_.c_str()); + fprintf(stderr, "reason: %s\n", ex.what()); + abort(); + } catch (...) { + muduo::CurrentThread::t_threadName = "crashed"; + fprintf(stderr, "unknown exception caught in Thread %s\n", + name_.c_str()); + throw; // rethrow + } + } +}; + +void* startThread(void* obj) +{ + ThreadData* data = static_cast<ThreadData*>(obj); + data->runInThread(); + delete data; + return NULL; +} + +} // namespace detail + +void CurrentThread::cacheTid() +{ + if (t_cachedTid == 0) { + t_cachedTid = detail::gettid(); + t_tidStringLength = + snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid); + } +} + +bool CurrentThread::isMainThread() { return tid() == ::getpid(); } + +void CurrentThread::sleepUsec(int64_t usec) +{ + struct timespec ts = {0, 0}; + ts.tv_sec = static_cast<time_t>(usec / Timestamp::kMicroSecondsPerSecond); + ts.tv_nsec = + static_cast<long>(usec % Timestamp::kMicroSecondsPerSecond * 1000); + ::nanosleep(&ts, NULL); +} + +AtomicInt32 Thread::numCreated_; + +Thread::Thread(ThreadFunc func, const string& n) + : started_(false), + joined_(false), + pthreadId_(0), + tid_(0), + func_(std::move(func)), + name_(n), + latch_(1) +{ + setDefaultName(); +} + +Thread::~Thread() +{ + if (started_ && !joined_) { + pthread_detach(pthreadId_); + } +} + +void Thread::setDefaultName() +{ + int num = numCreated_.incrementAndGet(); + if (name_.empty()) { + char buf[32]; + snprintf(buf, sizeof buf, "Thread%d", num); + name_ = buf; + } +} + +void Thread::start() +{ + assert(!started_); + started_ = true; + // FIXME: move(func_) + detail::ThreadData* data = + new detail::ThreadData(func_, name_, &tid_, &latch_); + if (pthread_create(&pthreadId_, NULL, &detail::startThread, data)) { + started_ = false; + delete data; // or no delete? + LOG_SYSFATAL << "Failed in pthread_create"; + } else { + latch_.wait(); + assert(tid_ > 0); + } +} + +int Thread::join() +{ + assert(started_); + assert(!joined_); + joined_ = true; + return pthread_join(pthreadId_, NULL); +} + +} // namespace muduo diff --git a/muduo/base/Thread.h b/muduo/base/Thread.h new file mode 100644 index 0000000..943915f --- /dev/null +++ b/muduo/base/Thread.h @@ -0,0 +1,53 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_THREAD_H +#define MUDUO_BASE_THREAD_H + +#include <pthread.h> + +#include <functional> +#include <memory> + +#include "muduo/base/Atomic.h" +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Types.h" + +namespace muduo { + +class Thread : noncopyable { +public: + typedef std::function<void()> ThreadFunc; + + explicit Thread(ThreadFunc, const string& name = string()); + // FIXME: make it movable in C++11 + ~Thread(); + + void start(); + int join(); // return pthread_join() + + bool started() const { return started_; } + // pthread_t pthreadId() const { return pthreadId_; } + pid_t tid() const { return tid_; } + const string& name() const { return name_; } + + static int numCreated() { return numCreated_.get(); } + +private: + void setDefaultName(); + + bool started_; + bool joined_; + pthread_t pthreadId_; + pid_t tid_; + ThreadFunc func_; + string name_; + CountDownLatch latch_; + + static AtomicInt32 numCreated_; +}; + +} // namespace muduo +#endif // MUDUO_BASE_THREAD_H diff --git a/muduo/base/ThreadLocal.h b/muduo/base/ThreadLocal.h new file mode 100644 index 0000000..b8283bc --- /dev/null +++ b/muduo/base/ThreadLocal.h @@ -0,0 +1,59 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_THREADLOCAL_H +#define MUDUO_BASE_THREADLOCAL_H + +#include "muduo/base/Mutex.h" +#include "muduo/base/noncopyable.h" + +#include <pthread.h> + +namespace muduo +{ + +template<typename T> +class ThreadLocal : noncopyable +{ + public: + ThreadLocal() + { + MCHECK(pthread_key_create(&pkey_, &ThreadLocal::destructor)); + } + + ~ThreadLocal() + { + MCHECK(pthread_key_delete(pkey_)); + } + + T& value() + { + T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_)); + if (!perThreadValue) + { + T* newObj = new T(); + MCHECK(pthread_setspecific(pkey_, newObj)); + perThreadValue = newObj; + } + return *perThreadValue; + } + + private: + + static void destructor(void *x) + { + T* obj = static_cast<T*>(x); + typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; + T_must_be_complete_type dummy; (void) dummy; + delete obj; + } + + private: + pthread_key_t pkey_; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_THREADLOCAL_H diff --git a/muduo/base/ThreadLocalSingleton.h b/muduo/base/ThreadLocalSingleton.h new file mode 100644 index 0000000..0520bec --- /dev/null +++ b/muduo/base/ThreadLocalSingleton.h @@ -0,0 +1,73 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_THREADLOCALSINGLETON_H +#define MUDUO_BASE_THREADLOCALSINGLETON_H + +#include <assert.h> +#include <pthread.h> + +#include "muduo/base/noncopyable.h" + +namespace muduo { + +template <typename T> +class ThreadLocalSingleton : noncopyable { +public: + ThreadLocalSingleton() = delete; + ~ThreadLocalSingleton() = delete; + + static T& instance() + { + if (!t_value_) { + t_value_ = new T(); + deleter_.set(t_value_); + } + return *t_value_; + } + + static T* pointer() { return t_value_; } + +private: + static void destructor(void* obj) + { + assert(obj == t_value_); + typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; + T_must_be_complete_type dummy; + (void)dummy; + delete t_value_; + t_value_ = 0; + } + + class Deleter { + public: + Deleter() + { + pthread_key_create(&pkey_, &ThreadLocalSingleton::destructor); + } + + ~Deleter() { pthread_key_delete(pkey_); } + + void set(T* newObj) + { + assert(pthread_getspecific(pkey_) == NULL); + pthread_setspecific(pkey_, newObj); + } + + pthread_key_t pkey_; + }; + + static __thread T* t_value_; + static Deleter deleter_; +}; + +template <typename T> +__thread T* ThreadLocalSingleton<T>::t_value_ = 0; + +template <typename T> +typename ThreadLocalSingleton<T>::Deleter ThreadLocalSingleton<T>::deleter_; + +} // namespace muduo +#endif // MUDUO_BASE_THREADLOCALSINGLETON_H diff --git a/muduo/base/ThreadPool.cc b/muduo/base/ThreadPool.cc new file mode 100644 index 0000000..0bcbf18 --- /dev/null +++ b/muduo/base/ThreadPool.cc @@ -0,0 +1,133 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/ThreadPool.h" + +#include <assert.h> +#include <stdio.h> + +#include "muduo/base/Exception.h" + +using namespace muduo; + +ThreadPool::ThreadPool(const string& nameArg) + : mutex_(), + notEmpty_(mutex_), + notFull_(mutex_), + name_(nameArg), + maxQueueSize_(0), + running_(false) +{ +} + +ThreadPool::~ThreadPool() +{ + if (running_) { + stop(); + } +} + +void ThreadPool::start(int numThreads) +{ + assert(threads_.empty()); + running_ = true; + threads_.reserve(numThreads); + for (int i = 0; i < numThreads; ++i) { + char id[32]; + snprintf(id, sizeof id, "%d", i + 1); + threads_.emplace_back(new muduo::Thread( + std::bind(&ThreadPool::runInThread, this), name_ + id)); + threads_[i]->start(); + } + if (numThreads == 0 && threadInitCallback_) { + threadInitCallback_(); + } +} + +void ThreadPool::stop() +{ + { + MutexLockGuard lock(mutex_); + running_ = false; + notEmpty_.notifyAll(); + } + for (auto& thr : threads_) { + thr->join(); + } +} + +size_t ThreadPool::queueSize() const +{ + MutexLockGuard lock(mutex_); + return queue_.size(); +} + +void ThreadPool::run(Task task) +{ + if (threads_.empty()) { + task(); + } else { + MutexLockGuard lock(mutex_); + while (isFull()) { + notFull_.wait(); + } + assert(!isFull()); + + queue_.push_back(std::move(task)); + notEmpty_.notify(); + } +} + +ThreadPool::Task ThreadPool::take() +{ + MutexLockGuard lock(mutex_); + // always use a while-loop, due to spurious wakeup + while (queue_.empty() && running_) { + notEmpty_.wait(); + } + Task task; + if (!queue_.empty()) { + task = queue_.front(); + queue_.pop_front(); + if (maxQueueSize_ > 0) { + notFull_.notify(); + } + } + return task; +} + +bool ThreadPool::isFull() const +{ + mutex_.assertLocked(); + return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_; +} + +void ThreadPool::runInThread() +{ + try { + if (threadInitCallback_) { + threadInitCallback_(); + } + while (running_) { + Task task(take()); + if (task) { + task(); + } + } + } catch (const Exception& ex) { + fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str()); + fprintf(stderr, "reason: %s\n", ex.what()); + fprintf(stderr, "stack trace: %s\n", ex.stackTrace()); + abort(); + } catch (const std::exception& ex) { + fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str()); + fprintf(stderr, "reason: %s\n", ex.what()); + abort(); + } catch (...) { + fprintf(stderr, "unknown exception caught in ThreadPool %s\n", + name_.c_str()); + throw; // rethrow + } +} diff --git a/muduo/base/ThreadPool.h b/muduo/base/ThreadPool.h new file mode 100644 index 0000000..d85c933 --- /dev/null +++ b/muduo/base/ThreadPool.h @@ -0,0 +1,62 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_THREADPOOL_H +#define MUDUO_BASE_THREADPOOL_H + +#include <deque> +#include <vector> + +#include "muduo/base/Condition.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" +#include "muduo/base/Types.h" + +namespace muduo { + +class ThreadPool : noncopyable { +public: + typedef std::function<void()> Task; + + explicit ThreadPool(const string& nameArg = string("ThreadPool")); + ~ThreadPool(); + + // Must be called before start(). + void setMaxQueueSize(int maxSize) { maxQueueSize_ = maxSize; } + void setThreadInitCallback(const Task& cb) { threadInitCallback_ = cb; } + + void start(int numThreads); + void stop(); + + const string& name() const { return name_; } + + size_t queueSize() const; + + // Could block if maxQueueSize > 0 + // There is no move-only version of std::function in C++ as of C++14. + // So we don't need to overload a const& and an && versions + // as we do in (Bounded)BlockingQueue. + // https://stackoverflow.com/a/25408989 + void run(Task f); + +private: + bool isFull() const REQUIRES(mutex_); + void runInThread(); + Task take(); + + mutable MutexLock mutex_; + Condition notEmpty_ GUARDED_BY(mutex_); + Condition notFull_ GUARDED_BY(mutex_); + string name_; + Task threadInitCallback_; + std::vector<std::unique_ptr<muduo::Thread>> threads_; + std::deque<Task> queue_ GUARDED_BY(mutex_); + size_t maxQueueSize_; + bool running_; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_THREADPOOL_H diff --git a/muduo/base/TimeZone.cc b/muduo/base/TimeZone.cc new file mode 100644 index 0000000..e7b7abc --- /dev/null +++ b/muduo/base/TimeZone.cc @@ -0,0 +1,321 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/TimeZone.h" + +#include <assert.h> + +#include <algorithm> +#include <stdexcept> +#include <string> +#include <vector> + +#include "muduo/base/Date.h" +#include "muduo/base/noncopyable.h" +// #define _BSD_SOURCE +#include <endian.h> +#include <stdint.h> +#include <stdio.h> + +namespace muduo { +namespace detail { + +struct Transition { + time_t gmttime; + time_t localtime; + int localtimeIdx; + + Transition(time_t t, time_t l, int localIdx) + : gmttime(t), localtime(l), localtimeIdx(localIdx) + { + } +}; + +struct Comp { + bool compareGmt; + + Comp(bool gmt) : compareGmt(gmt) {} + + bool operator()(const Transition& lhs, const Transition& rhs) const + { + if (compareGmt) + return lhs.gmttime < rhs.gmttime; + else + return lhs.localtime < rhs.localtime; + } + + bool equal(const Transition& lhs, const Transition& rhs) const + { + if (compareGmt) + return lhs.gmttime == rhs.gmttime; + else + return lhs.localtime == rhs.localtime; + } +}; + +struct Localtime { + time_t gmtOffset; + bool isDst; + int arrbIdx; + + Localtime(time_t offset, bool dst, int arrb) + : gmtOffset(offset), isDst(dst), arrbIdx(arrb) + { + } +}; + +inline void fillHMS(unsigned seconds, struct tm* utc) +{ + utc->tm_sec = seconds % 60; + unsigned minutes = seconds / 60; + utc->tm_min = minutes % 60; + utc->tm_hour = minutes / 60; +} + +} // namespace detail +const int kSecondsPerDay = 24 * 60 * 60; +} // namespace muduo + +using namespace muduo; +using namespace std; + +struct TimeZone::Data { + vector<detail::Transition> transitions; + vector<detail::Localtime> localtimes; + vector<string> names; + string abbreviation; +}; + +namespace muduo { +namespace detail { + +class File : noncopyable { +public: + File(const char* file) : fp_(::fopen(file, "rb")) {} + + ~File() + { + if (fp_) { + ::fclose(fp_); + } + } + + bool valid() const { return fp_; } + + string readBytes(int n) + { + char buf[n]; + ssize_t nr = ::fread(buf, 1, n, fp_); + if (nr != n) throw logic_error("no enough data"); + return string(buf, n); + } + + int32_t readInt32() + { + int32_t x = 0; + ssize_t nr = ::fread(&x, 1, sizeof(int32_t), fp_); + if (nr != sizeof(int32_t)) throw logic_error("bad int32_t data"); + return be32toh(x); + } + + uint8_t readUInt8() + { + uint8_t x = 0; + ssize_t nr = ::fread(&x, 1, sizeof(uint8_t), fp_); + if (nr != sizeof(uint8_t)) throw logic_error("bad uint8_t data"); + return x; + } + +private: + FILE* fp_; +}; + +bool readTimeZoneFile(const char* zonefile, struct TimeZone::Data* data) +{ + File f(zonefile); + if (f.valid()) { + try { + string head = f.readBytes(4); + if (head != "TZif") throw logic_error("bad head"); + string version = f.readBytes(1); + f.readBytes(15); + + int32_t isgmtcnt = f.readInt32(); + int32_t isstdcnt = f.readInt32(); + int32_t leapcnt = f.readInt32(); + int32_t timecnt = f.readInt32(); + int32_t typecnt = f.readInt32(); + int32_t charcnt = f.readInt32(); + + vector<int32_t> trans; + vector<int> localtimes; + trans.reserve(timecnt); + for (int i = 0; i < timecnt; ++i) { + trans.push_back(f.readInt32()); + } + + for (int i = 0; i < timecnt; ++i) { + uint8_t local = f.readUInt8(); + localtimes.push_back(local); + } + + for (int i = 0; i < typecnt; ++i) { + int32_t gmtoff = f.readInt32(); + uint8_t isdst = f.readUInt8(); + uint8_t abbrind = f.readUInt8(); + + data->localtimes.push_back(Localtime(gmtoff, isdst, abbrind)); + } + + for (int i = 0; i < timecnt; ++i) { + int localIdx = localtimes[i]; + time_t localtime = + trans[i] + data->localtimes[localIdx].gmtOffset; + data->transitions.push_back( + Transition(trans[i], localtime, localIdx)); + } + + data->abbreviation = f.readBytes(charcnt); + + // leapcnt + for (int i = 0; i < leapcnt; ++i) { + // int32_t leaptime = f.readInt32(); + // int32_t cumleap = f.readInt32(); + } + // FIXME + (void)isstdcnt; + (void)isgmtcnt; + } catch (logic_error& e) { + fprintf(stderr, "%s\n", e.what()); + } + } + return true; +} + +const Localtime* findLocaltime(const TimeZone::Data& data, Transition sentry, + Comp comp) +{ + const Localtime* local = NULL; + + if (data.transitions.empty() || comp(sentry, data.transitions.front())) { + // FIXME: should be first non dst time zone + local = &data.localtimes.front(); + } else { + vector<Transition>::const_iterator transI = lower_bound( + data.transitions.begin(), data.transitions.end(), sentry, comp); + if (transI != data.transitions.end()) { + if (!comp.equal(sentry, *transI)) { + assert(transI != data.transitions.begin()); + --transI; + } + local = &data.localtimes[transI->localtimeIdx]; + } else { + // FIXME: use TZ-env + local = &data.localtimes[data.transitions.back().localtimeIdx]; + } + } + + return local; +} + +} // namespace detail +} // namespace muduo + +TimeZone::TimeZone(const char* zonefile) : data_(new TimeZone::Data) +{ + if (!detail::readTimeZoneFile(zonefile, data_.get())) { + data_.reset(); + } +} + +TimeZone::TimeZone(int eastOfUtc, const char* name) : data_(new TimeZone::Data) +{ + data_->localtimes.push_back(detail::Localtime(eastOfUtc, false, 0)); + data_->abbreviation = name; +} + +struct tm TimeZone::toLocalTime(time_t seconds) const +{ + struct tm localTime; + memZero(&localTime, sizeof(localTime)); + assert(data_ != NULL); + const Data& data(*data_); + + detail::Transition sentry(seconds, 0, 0); + const detail::Localtime* local = + findLocaltime(data, sentry, detail::Comp(true)); + + if (local) { + time_t localSeconds = seconds + local->gmtOffset; + ::gmtime_r(&localSeconds, &localTime); // FIXME: fromUtcTime + localTime.tm_isdst = local->isDst; + localTime.tm_gmtoff = local->gmtOffset; + localTime.tm_zone = &data.abbreviation[local->arrbIdx]; + } + + return localTime; +} + +time_t TimeZone::fromLocalTime(const struct tm& localTm) const +{ + assert(data_ != NULL); + const Data& data(*data_); + + struct tm tmp = localTm; + time_t seconds = ::timegm(&tmp); // FIXME: toUtcTime + detail::Transition sentry(0, seconds, 0); + const detail::Localtime* local = + findLocaltime(data, sentry, detail::Comp(false)); + if (localTm.tm_isdst) { + struct tm tryTm = toLocalTime(seconds - local->gmtOffset); + if (!tryTm.tm_isdst && tryTm.tm_hour == localTm.tm_hour && + tryTm.tm_min == localTm.tm_min) { + // FIXME: HACK + seconds -= 3600; + } + } + return seconds - local->gmtOffset; +} + +struct tm TimeZone::toUtcTime(time_t secondsSinceEpoch, bool yday) +{ + struct tm utc; + memZero(&utc, sizeof(utc)); + utc.tm_zone = "GMT"; + int seconds = static_cast<int>(secondsSinceEpoch % kSecondsPerDay); + int days = static_cast<int>(secondsSinceEpoch / kSecondsPerDay); + if (seconds < 0) { + seconds += kSecondsPerDay; + --days; + } + detail::fillHMS(seconds, &utc); + Date date(days + Date::kJulianDayOf1970_01_01); + Date::YearMonthDay ymd = date.yearMonthDay(); + utc.tm_year = ymd.year - 1900; + utc.tm_mon = ymd.month - 1; + utc.tm_mday = ymd.day; + utc.tm_wday = date.weekDay(); + + if (yday) { + Date startOfYear(ymd.year, 1, 1); + utc.tm_yday = date.julianDayNumber() - startOfYear.julianDayNumber(); + } + return utc; +} + +time_t TimeZone::fromUtcTime(const struct tm& utc) +{ + return fromUtcTime(utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday, + utc.tm_hour, utc.tm_min, utc.tm_sec); +} + +time_t TimeZone::fromUtcTime(int year, int month, int day, int hour, int minute, + int seconds) +{ + Date date(year, month, day); + int secondsInDay = hour * 3600 + minute * 60 + seconds; + time_t days = date.julianDayNumber() - Date::kJulianDayOf1970_01_01; + return days * kSecondsPerDay + secondsInDay; +} diff --git a/muduo/base/TimeZone.h b/muduo/base/TimeZone.h new file mode 100644 index 0000000..cef1250 --- /dev/null +++ b/muduo/base/TimeZone.h @@ -0,0 +1,51 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_TIMEZONE_H +#define MUDUO_BASE_TIMEZONE_H + +#include <time.h> + +#include <memory> + +#include "muduo/base/copyable.h" + +namespace muduo { + +// TimeZone for 1970~2030 +class TimeZone : public muduo::copyable { +public: + explicit TimeZone(const char* zonefile); + TimeZone(int eastOfUtc, const char* tzname); // a fixed timezone + TimeZone() = default; // an invalid timezone + + // default copy ctor/assignment/dtor are Okay. + + bool valid() const + { + // 'explicit operator bool() const' in C++11 + return static_cast<bool>(data_); + } + + struct tm toLocalTime(time_t secondsSinceEpoch) const; + time_t fromLocalTime(const struct tm&) const; + + // gmtime(3) + static struct tm toUtcTime(time_t secondsSinceEpoch, bool yday = false); + // timegm(3) + static time_t fromUtcTime(const struct tm&); + // year in [1900..2500], month in [1..12], day in [1..31] + static time_t fromUtcTime(int year, int month, int day, int hour, + int minute, int seconds); + + struct Data; + +private: + std::shared_ptr<Data> data_; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_TIMEZONE_H diff --git a/muduo/base/Timestamp.cc b/muduo/base/Timestamp.cc new file mode 100644 index 0000000..038ba82 --- /dev/null +++ b/muduo/base/Timestamp.cc @@ -0,0 +1,60 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/base/Timestamp.h" + +#include <stdio.h> +#include <sys/time.h> + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include <inttypes.h> + +using namespace muduo; + +static_assert(sizeof(Timestamp) == sizeof(int64_t), + "Timestamp is same size as int64_t"); + +string Timestamp::toString() const +{ + char buf[32] = {0}; + int64_t seconds = microSecondsSinceEpoch_ / kMicroSecondsPerSecond; + int64_t microseconds = microSecondsSinceEpoch_ % kMicroSecondsPerSecond; + snprintf(buf, sizeof(buf), "%" PRId64 ".%06" PRId64 "", seconds, + microseconds); + return buf; +} + +string Timestamp::toFormattedString(bool showMicroseconds) const +{ + char buf[64] = {0}; + time_t seconds = + static_cast<time_t>(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); + struct tm tm_time; + gmtime_r(&seconds, &tm_time); + + if (showMicroseconds) { + int microseconds = + static_cast<int>(microSecondsSinceEpoch_ % kMicroSecondsPerSecond); + snprintf(buf, sizeof(buf), "%4d%02d%02d %02d:%02d:%02d.%06d", + tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday, + tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, microseconds); + } else { + snprintf(buf, sizeof(buf), "%4d%02d%02d %02d:%02d:%02d", + tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday, + tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); + } + return buf; +} + +Timestamp Timestamp::now() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + int64_t seconds = tv.tv_sec; + return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec); +} diff --git a/muduo/base/Timestamp.h b/muduo/base/Timestamp.h new file mode 100644 index 0000000..363d3a8 --- /dev/null +++ b/muduo/base/Timestamp.h @@ -0,0 +1,117 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_TIMESTAMP_H +#define MUDUO_BASE_TIMESTAMP_H + +#include <boost/operators.hpp> + +#include "muduo/base/Types.h" +#include "muduo/base/copyable.h" + +namespace muduo { + +/// +/// Time stamp in UTC, in microseconds resolution. +/// +/// This class is immutable. +/// It's recommended to pass it by value, since it's passed in register on x64. +/// +class Timestamp : public muduo::copyable, + public boost::equality_comparable<Timestamp>, + public boost::less_than_comparable<Timestamp> { +public: + /// + /// Constucts an invalid Timestamp. + /// + Timestamp() : microSecondsSinceEpoch_(0) {} + + /// + /// Constucts a Timestamp at specific time + /// + /// @param microSecondsSinceEpoch + explicit Timestamp(int64_t microSecondsSinceEpochArg) + : microSecondsSinceEpoch_(microSecondsSinceEpochArg) + { + } + + void swap(Timestamp& that) + { + std::swap(microSecondsSinceEpoch_, that.microSecondsSinceEpoch_); + } + + // default copy/assignment/dtor are Okay + + string toString() const; + string toFormattedString(bool showMicroseconds = true) const; + + bool valid() const { return microSecondsSinceEpoch_ > 0; } + + // for internal usage. + int64_t microSecondsSinceEpoch() const { return microSecondsSinceEpoch_; } + time_t secondsSinceEpoch() const + { + return static_cast<time_t>(microSecondsSinceEpoch_ / + kMicroSecondsPerSecond); + } + + /// + /// Get time of now. + /// + static Timestamp now(); + static Timestamp invalid() { return Timestamp(); } + + static Timestamp fromUnixTime(time_t t) { return fromUnixTime(t, 0); } + + static Timestamp fromUnixTime(time_t t, int microseconds) + { + return Timestamp(static_cast<int64_t>(t) * kMicroSecondsPerSecond + + microseconds); + } + + static const int kMicroSecondsPerSecond = 1000 * 1000; + +private: + int64_t microSecondsSinceEpoch_; +}; + +inline bool operator<(Timestamp lhs, Timestamp rhs) +{ + return lhs.microSecondsSinceEpoch() < rhs.microSecondsSinceEpoch(); +} + +inline bool operator==(Timestamp lhs, Timestamp rhs) +{ + return lhs.microSecondsSinceEpoch() == rhs.microSecondsSinceEpoch(); +} + +/// +/// Gets time difference of two timestamps, result in seconds. +/// +/// @param high, low +/// @return (high-low) in seconds +/// @c double has 52-bit precision, enough for one-microsecond +/// resolution for next 100 years. +inline double timeDifference(Timestamp high, Timestamp low) +{ + int64_t diff = high.microSecondsSinceEpoch() - low.microSecondsSinceEpoch(); + return static_cast<double>(diff) / Timestamp::kMicroSecondsPerSecond; +} + +/// +/// Add @c seconds to given timestamp. +/// +/// @return timestamp+seconds as Timestamp +/// +inline Timestamp addTime(Timestamp timestamp, double seconds) +{ + int64_t delta = + static_cast<int64_t>(seconds * Timestamp::kMicroSecondsPerSecond); + return Timestamp(timestamp.microSecondsSinceEpoch() + delta); +} + +} // namespace muduo + +#endif // MUDUO_BASE_TIMESTAMP_H diff --git a/muduo/base/Types.h b/muduo/base/Types.h new file mode 100644 index 0000000..ad94756 --- /dev/null +++ b/muduo/base/Types.h @@ -0,0 +1,123 @@ +#ifndef MUDUO_BASE_TYPES_H +#define MUDUO_BASE_TYPES_H + +#include <stdint.h> +#include <string.h> // memset +#include <string> + +#ifndef NDEBUG +#include <assert.h> +#endif + +/// +/// The most common stuffs. +/// +namespace muduo +{ + +using std::string; + +inline void memZero(void* p, size_t n) +{ + memset(p, 0, n); +} + +// Taken from google-protobuf stubs/common.h +// +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) and others +// +// Contains basic types and utilities used by the rest of the library. + +// +// Use implicit_cast as a safe version of static_cast or const_cast +// for upcasting in the type hierarchy (i.e. casting a pointer to Foo +// to a pointer to SuperclassOfFoo or casting a pointer to Foo to +// a const pointer to Foo). +// When you use implicit_cast, the compiler checks that the cast is safe. +// Such explicit implicit_casts are necessary in surprisingly many +// situations where C++ demands an exact type match instead of an +// argument type convertable to a target type. +// +// The From type can be inferred, so the preferred syntax for using +// implicit_cast is the same as for static_cast etc.: +// +// implicit_cast<ToType>(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +template<typename To, typename From> +inline To implicit_cast(From const &f) +{ + return f; +} + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use implicit_cast<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. + +template<typename To, typename From> // use like this: down_cast<T*>(foo); +inline To down_cast(From* f) // so we only accept pointers +{ + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) + { + implicit_cast<From*, To>(0); + } + +#if !defined(NDEBUG) && !defined(GOOGLE_PROTOBUF_NO_RTTI) + assert(f == NULL || dynamic_cast<To>(f) != NULL); // RTTI: debug mode only! +#endif + return static_cast<To>(f); +} + +} // namespace muduo + +#endif // MUDUO_BASE_TYPES_H diff --git a/muduo/base/WeakCallback.h b/muduo/base/WeakCallback.h new file mode 100644 index 0000000..a5093d7 --- /dev/null +++ b/muduo/base/WeakCallback.h @@ -0,0 +1,61 @@ +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef MUDUO_BASE_WEAKCALLBACK_H +#define MUDUO_BASE_WEAKCALLBACK_H + +#include <functional> +#include <memory> + +namespace muduo { + +// A barely usable WeakCallback + +template <typename CLASS, typename... ARGS> +class WeakCallback { +public: + WeakCallback(const std::weak_ptr<CLASS>& object, + const std::function<void(CLASS*, ARGS...)>& function) + : object_(object), function_(function) + { + } + + // Default dtor, copy ctor and assignment are okay + + void operator()(ARGS&&... args) const + { + std::shared_ptr<CLASS> ptr(object_.lock()); + if (ptr) { + function_(ptr.get(), std::forward<ARGS>(args)...); + } + // else + // { + // LOG_TRACE << "expired"; + // } + } + +private: + std::weak_ptr<CLASS> object_; + std::function<void(CLASS*, ARGS...)> function_; +}; + +template <typename CLASS, typename... ARGS> +WeakCallback<CLASS, ARGS...> makeWeakCallback( + const std::shared_ptr<CLASS>& object, void (CLASS::*function)(ARGS...)) +{ + return WeakCallback<CLASS, ARGS...>(object, function); +} + +template <typename CLASS, typename... ARGS> +WeakCallback<CLASS, ARGS...> makeWeakCallback( + const std::shared_ptr<CLASS>& object, + void (CLASS::*function)(ARGS...) const) +{ + return WeakCallback<CLASS, ARGS...>(object, function); +} + +} // namespace muduo + +#endif // MUDUO_BASE_WEAKCALLBACK_H diff --git a/muduo/base/copyable.h b/muduo/base/copyable.h new file mode 100644 index 0000000..ef505f4 --- /dev/null +++ b/muduo/base/copyable.h @@ -0,0 +1,17 @@ +#ifndef MUDUO_BASE_COPYABLE_H +#define MUDUO_BASE_COPYABLE_H + +namespace muduo { + +/// A tag class emphasises the objects are copyable. +/// The empty base class optimization applies. +/// Any derived class of copyable should be a value type. +class copyable { +protected: + copyable() = default; + ~copyable() = default; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_COPYABLE_H diff --git a/muduo/base/noncopyable.h b/muduo/base/noncopyable.h new file mode 100644 index 0000000..010c8d1 --- /dev/null +++ b/muduo/base/noncopyable.h @@ -0,0 +1,18 @@ +#ifndef MUDUO_BASE_NONCOPYABLE_H +#define MUDUO_BASE_NONCOPYABLE_H + +namespace muduo { + +class noncopyable { +public: + noncopyable(const noncopyable&) = delete; + void operator=(const noncopyable&) = delete; + +protected: + noncopyable() = default; + ~noncopyable() = default; +}; + +} // namespace muduo + +#endif // MUDUO_BASE_NONCOPYABLE_H diff --git a/muduo/base/tests/AsyncLogging_test.cc b/muduo/base/tests/AsyncLogging_test.cc new file mode 100644 index 0000000..ad6fde4 --- /dev/null +++ b/muduo/base/tests/AsyncLogging_test.cc @@ -0,0 +1,64 @@ +#include "muduo/base/AsyncLogging.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Timestamp.h" + +#include <stdio.h> +#include <sys/resource.h> +#include <unistd.h> + +off_t kRollSize = 500*1000*1000; + +muduo::AsyncLogging* g_asyncLog = NULL; + +void asyncOutput(const char* msg, int len) +{ + g_asyncLog->append(msg, len); +} + +void bench(bool longLog) +{ + muduo::Logger::setOutput(asyncOutput); + + int cnt = 0; + const int kBatch = 1000; + muduo::string empty = " "; + muduo::string longStr(3000, 'X'); + longStr += " "; + + for (int t = 0; t < 30; ++t) + { + muduo::Timestamp start = muduo::Timestamp::now(); + for (int i = 0; i < kBatch; ++i) + { + LOG_INFO << "Hello 0123456789" << " abcdefghijklmnopqrstuvwxyz " + << (longLog ? longStr : empty) + << cnt; + ++cnt; + } + muduo::Timestamp end = muduo::Timestamp::now(); + printf("%f\n", timeDifference(end, start)*1000000/kBatch); + struct timespec ts = { 0, 500*1000*1000 }; + nanosleep(&ts, NULL); + } +} + +int main(int argc, char* argv[]) +{ + { + // set max virtual memory to 2GB. + size_t kOneGB = 1000*1024*1024; + rlimit rl = { 2*kOneGB, 2*kOneGB }; + setrlimit(RLIMIT_AS, &rl); + } + + printf("pid = %d\n", getpid()); + + char name[256] = { '\0' }; + strncpy(name, argv[0], sizeof name - 1); + muduo::AsyncLogging log(::basename(name), kRollSize); + log.start(); + g_asyncLog = &log; + + bool longLog = argc > 1; + bench(longLog); +} diff --git a/muduo/base/tests/Atomic_unittest.cc b/muduo/base/tests/Atomic_unittest.cc new file mode 100644 index 0000000..11d6107 --- /dev/null +++ b/muduo/base/tests/Atomic_unittest.cc @@ -0,0 +1,38 @@ +#include "muduo/base/Atomic.h" +#include <assert.h> + +int main() +{ + { + muduo::AtomicInt64 a0; + assert(a0.get() == 0); + assert(a0.getAndAdd(1) == 0); + assert(a0.get() == 1); + assert(a0.addAndGet(2) == 3); + assert(a0.get() == 3); + assert(a0.incrementAndGet() == 4); + assert(a0.get() == 4); + a0.increment(); + assert(a0.get() == 5); + assert(a0.addAndGet(-3) == 2); + assert(a0.getAndSet(100) == 2); + assert(a0.get() == 100); + } + + { + muduo::AtomicInt32 a1; + assert(a1.get() == 0); + assert(a1.getAndAdd(1) == 0); + assert(a1.get() == 1); + assert(a1.addAndGet(2) == 3); + assert(a1.get() == 3); + assert(a1.incrementAndGet() == 4); + assert(a1.get() == 4); + a1.increment(); + assert(a1.get() == 5); + assert(a1.addAndGet(-3) == 2); + assert(a1.getAndSet(100) == 2); + assert(a1.get() == 100); + } +} + diff --git a/muduo/base/tests/BlockingQueue_bench.cc b/muduo/base/tests/BlockingQueue_bench.cc new file mode 100644 index 0000000..bd65bfa --- /dev/null +++ b/muduo/base/tests/BlockingQueue_bench.cc @@ -0,0 +1,106 @@ +#include "muduo/base/BlockingQueue.h" +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Thread.h" +#include "muduo/base/Timestamp.h" + +#include <map> +#include <string> +#include <vector> +#include <stdio.h> +#include <unistd.h> + +class Bench +{ + public: + Bench(int numThreads) + : latch_(numThreads) + { + threads_.reserve(numThreads); + for (int i = 0; i < numThreads; ++i) + { + char name[32]; + snprintf(name, sizeof name, "work thread %d", i); + threads_.emplace_back(new muduo::Thread( + std::bind(&Bench::threadFunc, this), muduo::string(name))); + } + for (auto& thr : threads_) + { + thr->start(); + } + } + + void run(int times) + { + printf("waiting for count down latch\n"); + latch_.wait(); + printf("all threads started\n"); + for (int i = 0; i < times; ++i) + { + muduo::Timestamp now(muduo::Timestamp::now()); + queue_.put(now); + usleep(1000); + } + } + + void joinAll() + { + for (size_t i = 0; i < threads_.size(); ++i) + { + queue_.put(muduo::Timestamp::invalid()); + } + + for (auto& thr : threads_) + { + thr->join(); + } + } + + private: + + void threadFunc() + { + printf("tid=%d, %s started\n", + muduo::CurrentThread::tid(), + muduo::CurrentThread::name()); + + std::map<int, int> delays; + latch_.countDown(); + bool running = true; + while (running) + { + muduo::Timestamp t(queue_.take()); + muduo::Timestamp now(muduo::Timestamp::now()); + if (t.valid()) + { + int delay = static_cast<int>(timeDifference(now, t) * 1000000); + // printf("tid=%d, latency = %d us\n", + // muduo::CurrentThread::tid(), delay); + ++delays[delay]; + } + running = t.valid(); + } + + printf("tid=%d, %s stopped\n", + muduo::CurrentThread::tid(), + muduo::CurrentThread::name()); + for (const auto& delay : delays) + { + printf("tid = %d, delay = %d, count = %d\n", + muduo::CurrentThread::tid(), + delay.first, delay.second); + } + } + + muduo::BlockingQueue<muduo::Timestamp> queue_; + muduo::CountDownLatch latch_; + std::vector<std::unique_ptr<muduo::Thread>> threads_; +}; + +int main(int argc, char* argv[]) +{ + int threads = argc > 1 ? atoi(argv[1]) : 1; + + Bench t(threads); + t.run(10000); + t.joinAll(); +} diff --git a/muduo/base/tests/BlockingQueue_test.cc b/muduo/base/tests/BlockingQueue_test.cc new file mode 100644 index 0000000..413fbd2 --- /dev/null +++ b/muduo/base/tests/BlockingQueue_test.cc @@ -0,0 +1,106 @@ +#include "muduo/base/BlockingQueue.h" +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Thread.h" + +#include <memory> +#include <string> +#include <vector> +#include <stdio.h> +#include <unistd.h> + +class Test +{ + public: + Test(int numThreads) + : latch_(numThreads) + { + for (int i = 0; i < numThreads; ++i) + { + char name[32]; + snprintf(name, sizeof name, "work thread %d", i); + threads_.emplace_back(new muduo::Thread( + std::bind(&Test::threadFunc, this), muduo::string(name))); + } + for (auto& thr : threads_) + { + thr->start(); + } + } + + void run(int times) + { + printf("waiting for count down latch\n"); + latch_.wait(); + printf("all threads started\n"); + for (int i = 0; i < times; ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "hello %d", i); + queue_.put(buf); + printf("tid=%d, put data = %s, size = %zd\n", muduo::CurrentThread::tid(), buf, queue_.size()); + } + } + + void joinAll() + { + for (size_t i = 0; i < threads_.size(); ++i) + { + queue_.put("stop"); + } + + for (auto& thr : threads_) + { + thr->join(); + } + } + + private: + + void threadFunc() + { + printf("tid=%d, %s started\n", + muduo::CurrentThread::tid(), + muduo::CurrentThread::name()); + + latch_.countDown(); + bool running = true; + while (running) + { + std::string d(queue_.take()); + printf("tid=%d, get data = %s, size = %zd\n", muduo::CurrentThread::tid(), d.c_str(), queue_.size()); + running = (d != "stop"); + } + + printf("tid=%d, %s stopped\n", + muduo::CurrentThread::tid(), + muduo::CurrentThread::name()); + } + + muduo::BlockingQueue<std::string> queue_; + muduo::CountDownLatch latch_; + std::vector<std::unique_ptr<muduo::Thread>> threads_; +}; + +void testMove() +{ + muduo::BlockingQueue<std::unique_ptr<int>> queue; + queue.put(std::unique_ptr<int>(new int(42))); + std::unique_ptr<int> x = queue.take(); + printf("took %d\n", *x); + *x = 123; + queue.put(std::move(x)); + std::unique_ptr<int> y = queue.take(); + printf("took %d\n", *y); +} + +int main() +{ + printf("pid=%d, tid=%d\n", ::getpid(), muduo::CurrentThread::tid()); + Test t(5); + t.run(100); + t.joinAll(); + + testMove(); + + printf("number of created threads %d\n", muduo::Thread::numCreated()); +} diff --git a/muduo/base/tests/BoundedBlockingQueue_test.cc b/muduo/base/tests/BoundedBlockingQueue_test.cc new file mode 100644 index 0000000..49f57fb --- /dev/null +++ b/muduo/base/tests/BoundedBlockingQueue_test.cc @@ -0,0 +1,110 @@ +#include "muduo/base/BoundedBlockingQueue.h" +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Thread.h" + +#include <string> +#include <vector> + +#include <stdio.h> +#include <unistd.h> + +class Test +{ + public: + Test(int numThreads) + : queue_(20), + latch_(numThreads) + { + threads_.reserve(numThreads); + for (int i = 0; i < numThreads; ++i) + { + char name[32]; + snprintf(name, sizeof name, "work thread %d", i); + threads_.emplace_back(new muduo::Thread( + std::bind(&Test::threadFunc, this), muduo::string(name))); + } + for (auto& thr : threads_) + { + thr->start(); + } + } + + void run(int times) + { + printf("waiting for count down latch\n"); + latch_.wait(); + printf("all threads started\n"); + for (int i = 0; i < times; ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "hello %d", i); + queue_.put(buf); + printf("tid=%d, put data = %s, size = %zd\n", muduo::CurrentThread::tid(), buf, queue_.size()); + } + } + + void joinAll() + { + for (size_t i = 0; i < threads_.size(); ++i) + { + queue_.put("stop"); + } + + for (auto& thr : threads_) + { + thr->join(); + } + } + + private: + + void threadFunc() + { + printf("tid=%d, %s started\n", + muduo::CurrentThread::tid(), + muduo::CurrentThread::name()); + + latch_.countDown(); + bool running = true; + while (running) + { + std::string d(queue_.take()); + printf("tid=%d, get data = %s, size = %zd\n", muduo::CurrentThread::tid(), d.c_str(), queue_.size()); + running = (d != "stop"); + } + + printf("tid=%d, %s stopped\n", + muduo::CurrentThread::tid(), + muduo::CurrentThread::name()); + } + + muduo::BoundedBlockingQueue<std::string> queue_; + muduo::CountDownLatch latch_; + std::vector<std::unique_ptr<muduo::Thread>> threads_; +}; + +void testMove() +{ +#if BOOST_VERSION >= 105500L + muduo::BoundedBlockingQueue<std::unique_ptr<int>> queue(10); + queue.put(std::unique_ptr<int>(new int(42))); + std::unique_ptr<int> x = queue.take(); + printf("took %d\n", *x); + *x = 123; + queue.put(std::move(x)); + std::unique_ptr<int> y; + y = queue.take(); + printf("took %d\n", *y); +#endif +} + +int main() +{ + printf("pid=%d, tid=%d\n", ::getpid(), muduo::CurrentThread::tid()); + testMove(); + Test t(5); + t.run(100); + t.joinAll(); + + printf("number of created threads %d\n", muduo::Thread::numCreated()); +} diff --git a/muduo/base/tests/Date_unittest.cc b/muduo/base/tests/Date_unittest.cc new file mode 100644 index 0000000..bf2d386 --- /dev/null +++ b/muduo/base/tests/Date_unittest.cc @@ -0,0 +1,88 @@ +#include "muduo/base/Date.h" +#include <assert.h> +#include <stdio.h> + +using muduo::Date; + +const int kMonthsOfYear = 12; + +int isLeapYear(int year) +{ + if (year % 400 == 0) + return 1; + else if (year % 100 == 0) + return 0; + else if (year % 4 == 0) + return 1; + else + return 0; +} + +int daysOfMonth(int year, int month) +{ + static int days[2][kMonthsOfYear+1] = + { + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + }; + return days[isLeapYear(year)][month]; +} + +void passByConstReference(const Date& x) +{ + printf("%s\n", x.toIsoString().c_str()); +} + +void passByValue(Date x) +{ + printf("%s\n", x.toIsoString().c_str()); +} + +int main() +{ + time_t now = time(NULL); + struct tm t1 = *gmtime(&now); + struct tm t2 = *localtime(&now); + Date someDay(2008, 9, 10); + printf("%s\n", someDay.toIsoString().c_str()); + passByValue(someDay); + passByConstReference(someDay); + Date todayUtc(t1); + printf("%s\n", todayUtc.toIsoString().c_str()); + Date todayLocal(t2); + printf("%s\n", todayLocal.toIsoString().c_str()); + + int julianDayNumber = 2415021; + int weekDay = 1; // Monday + + for (int year = 1900; year < 2500; ++year) + { + assert(Date(year, 3, 1).julianDayNumber() - Date(year, 2, 29).julianDayNumber() + == isLeapYear(year)); + for (int month = 1; month <= kMonthsOfYear; ++month) + { + for (int day = 1; day <= daysOfMonth(year, month); ++day) + { + Date d(year, month, day); + // printf("%s %d\n", d.toString().c_str(), d.weekDay()); + assert(year == d.year()); + assert(month == d.month()); + assert(day == d.day()); + assert(weekDay == d.weekDay()); + assert(julianDayNumber == d.julianDayNumber()); + + Date d2(julianDayNumber); + assert(year == d2.year()); + assert(month == d2.month()); + assert(day == d2.day()); + assert(weekDay == d2.weekDay()); + assert(julianDayNumber == d2.julianDayNumber()); + + ++julianDayNumber; + weekDay = (weekDay+1) % 7; + } + } + } + printf("All passed.\n"); +} + diff --git a/muduo/base/tests/Exception_test.cc b/muduo/base/tests/Exception_test.cc new file mode 100644 index 0000000..7fd8004 --- /dev/null +++ b/muduo/base/tests/Exception_test.cc @@ -0,0 +1,51 @@ +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Exception.h" +#include <functional> +#include <vector> +#include <stdio.h> + +class Bar +{ + public: + void test(std::vector<std::string> names = {}) + { + printf("Stack:\n%s\n", muduo::CurrentThread::stackTrace(true).c_str()); + [] { + printf("Stack inside lambda:\n%s\n", muduo::CurrentThread::stackTrace(true).c_str()); + }(); + std::function<void()> func([] { + printf("Stack inside std::function:\n%s\n", muduo::CurrentThread::stackTrace(true).c_str()); + }); + func(); + + func = std::bind(&Bar::callback, this); + func(); + + throw muduo::Exception("oops"); + } + + private: + void callback() + { + printf("Stack inside std::bind:\n%s\n", muduo::CurrentThread::stackTrace(true).c_str()); + } +}; + +void foo() +{ + Bar b; + b.test(); +} + +int main() +{ + try + { + foo(); + } + catch (const muduo::Exception& ex) + { + printf("reason: %s\n", ex.what()); + printf("stack trace:\n%s\n", ex.stackTrace()); + } +} diff --git a/muduo/base/tests/FileUtil_test.cc b/muduo/base/tests/FileUtil_test.cc new file mode 100644 index 0000000..9abe299 --- /dev/null +++ b/muduo/base/tests/FileUtil_test.cc @@ -0,0 +1,30 @@ +#include "muduo/base/FileUtil.h" + +#include <stdio.h> +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +using namespace muduo; + +int main() +{ + string result; + int64_t size = 0; + int err = FileUtil::readFile("/proc/self", 1024, &result, &size); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/proc/self", 1024, &result, NULL); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/proc/self/cmdline", 1024, &result, &size); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/dev/null", 1024, &result, &size); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/dev/zero", 1024, &result, &size); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/notexist", 1024, &result, &size); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/dev/zero", 102400, &result, &size); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); + err = FileUtil::readFile("/dev/zero", 102400, &result, NULL); + printf("%d %zd %" PRIu64 "\n", err, result.size(), size); +} + diff --git a/muduo/base/tests/Fork_test.cc b/muduo/base/tests/Fork_test.cc new file mode 100644 index 0000000..6a3cba6 --- /dev/null +++ b/muduo/base/tests/Fork_test.cc @@ -0,0 +1,46 @@ +#include "muduo/base/CurrentThread.h" + +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +namespace +{ +__thread int x = 0; +} + +void print() +{ + printf("pid=%d tid=%d x=%d\n", getpid(), muduo::CurrentThread::tid(), x); +} + +int main() +{ + printf("parent %d\n", getpid()); + print(); + x = 1; + print(); + pid_t p = fork(); + + if (p == 0) + { + printf("chlid %d\n", getpid()); + // child + print(); + x = 2; + print(); + + if (fork() == 0) + { + printf("grandchlid %d\n", getpid()); + print(); + x = 3; + print(); + } + } + else + { + // parent + print(); + } +} diff --git a/muduo/base/tests/GzipFile_test.cc b/muduo/base/tests/GzipFile_test.cc new file mode 100644 index 0000000..d3f9cc3 --- /dev/null +++ b/muduo/base/tests/GzipFile_test.cc @@ -0,0 +1,54 @@ +#include "muduo/base/GzipFile.h" + +#include "muduo/base/Logging.h" + +int main() +{ + const char* filename = "/tmp/gzipfile_test.gz"; + ::unlink(filename); + const char data[] = "123456789012345678901234567890123456789012345678901234567890\n"; + { + muduo::GzipFile writer = muduo::GzipFile::openForAppend(filename); + if (writer.valid()) + { + LOG_INFO << "tell " << writer.tell(); + LOG_INFO << "wrote " << writer.write(data); + LOG_INFO << "tell " << writer.tell(); + } + } + + { + printf("testing reader\n"); + muduo::GzipFile reader = muduo::GzipFile::openForRead(filename); + if (reader.valid()) + { + char buf[256]; + LOG_INFO << "tell " << reader.tell(); + int nr = reader.read(buf, sizeof buf); + printf("read %d\n", nr); + if (nr >= 0) + { + buf[nr] = '\0'; + printf("data %s", buf); + } + LOG_INFO << "tell " << reader.tell(); + if (strncmp(buf, data, strlen(data)) != 0) + { + printf("failed!!!\n"); + abort(); + } + else + { + printf("PASSED\n"); + } + } + } + + { + muduo::GzipFile writer = muduo::GzipFile::openForWriteExclusive(filename); + if (writer.valid() || errno != EEXIST) + { + printf("FAILED\n"); + } + } +} diff --git a/muduo/base/tests/LogFile_test.cc b/muduo/base/tests/LogFile_test.cc new file mode 100644 index 0000000..a0b753a --- /dev/null +++ b/muduo/base/tests/LogFile_test.cc @@ -0,0 +1,34 @@ +#include "muduo/base/LogFile.h" +#include "muduo/base/Logging.h" + +#include <unistd.h> + +std::unique_ptr<muduo::LogFile> g_logFile; + +void outputFunc(const char* msg, int len) +{ + g_logFile->append(msg, len); +} + +void flushFunc() +{ + g_logFile->flush(); +} + +int main(int argc, char* argv[]) +{ + char name[256] = { '\0' }; + strncpy(name, argv[0], sizeof name - 1); + g_logFile.reset(new muduo::LogFile(::basename(name), 200*1000)); + muduo::Logger::setOutput(outputFunc); + muduo::Logger::setFlush(flushFunc); + + muduo::string line = "1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ "; + + for (int i = 0; i < 10000; ++i) + { + LOG_INFO << line << i; + + usleep(1000); + } +} diff --git a/muduo/base/tests/LogStream_bench.cc b/muduo/base/tests/LogStream_bench.cc new file mode 100644 index 0000000..c91637a --- /dev/null +++ b/muduo/base/tests/LogStream_bench.cc @@ -0,0 +1,82 @@ +#include "muduo/base/LogStream.h" +#include "muduo/base/Timestamp.h" + +#include <sstream> +#include <stdio.h> +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +using namespace muduo; + +const size_t N = 1000000; + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +template<typename T> +void benchPrintf(const char* fmt) +{ + char buf[32]; + Timestamp start(Timestamp::now()); + for (size_t i = 0; i < N; ++i) + snprintf(buf, sizeof buf, fmt, (T)(i)); + Timestamp end(Timestamp::now()); + + printf("benchPrintf %f\n", timeDifference(end, start)); +} + +template<typename T> +void benchStringStream() +{ + Timestamp start(Timestamp::now()); + std::ostringstream os; + + for (size_t i = 0; i < N; ++i) + { + os << (T)(i); + os.seekp(0, std::ios_base::beg); + } + Timestamp end(Timestamp::now()); + + printf("benchStringStream %f\n", timeDifference(end, start)); +} + +template<typename T> +void benchLogStream() +{ + Timestamp start(Timestamp::now()); + LogStream os; + for (size_t i = 0; i < N; ++i) + { + os << (T)(i); + os.resetBuffer(); + } + Timestamp end(Timestamp::now()); + + printf("benchLogStream %f\n", timeDifference(end, start)); +} + +int main() +{ + benchPrintf<int>("%d"); + + puts("int"); + benchPrintf<int>("%d"); + benchStringStream<int>(); + benchLogStream<int>(); + + puts("double"); + benchPrintf<double>("%.12g"); + benchStringStream<double>(); + benchLogStream<double>(); + + puts("int64_t"); + benchPrintf<int64_t>("%" PRId64); + benchStringStream<int64_t>(); + benchLogStream<int64_t>(); + + puts("void*"); + benchPrintf<void*>("%p"); + benchStringStream<void*>(); + benchLogStream<void*>(); + +} diff --git a/muduo/base/tests/LogStream_test.cc b/muduo/base/tests/LogStream_test.cc new file mode 100644 index 0000000..b6070e1 --- /dev/null +++ b/muduo/base/tests/LogStream_test.cc @@ -0,0 +1,286 @@ +#include "muduo/base/LogStream.h" + +#include <limits> +#include <stdint.h> + +//#define BOOST_TEST_MODULE LogStreamTest +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +using muduo::string; + +BOOST_AUTO_TEST_CASE(testLogStreamBooleans) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + BOOST_CHECK_EQUAL(buf.toString(), string("")); + os << true; + BOOST_CHECK_EQUAL(buf.toString(), string("1")); + os << '\n'; + BOOST_CHECK_EQUAL(buf.toString(), string("1\n")); + os << false; + BOOST_CHECK_EQUAL(buf.toString(), string("1\n0")); +} + +BOOST_AUTO_TEST_CASE(testLogStreamIntegers) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + BOOST_CHECK_EQUAL(buf.toString(), string("")); + os << 1; + BOOST_CHECK_EQUAL(buf.toString(), string("1")); + os << 0; + BOOST_CHECK_EQUAL(buf.toString(), string("10")); + os << -1; + BOOST_CHECK_EQUAL(buf.toString(), string("10-1")); + os.resetBuffer(); + + os << 0 << " " << 123 << 'x' << 0x64; + BOOST_CHECK_EQUAL(buf.toString(), string("0 123x100")); +} + +BOOST_AUTO_TEST_CASE(testLogStreamIntegerLimits) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + os << -2147483647; + BOOST_CHECK_EQUAL(buf.toString(), string("-2147483647")); + os << static_cast<int>(-2147483647 - 1); + BOOST_CHECK_EQUAL(buf.toString(), string("-2147483647-2147483648")); + os << ' '; + os << 2147483647; + BOOST_CHECK_EQUAL(buf.toString(), string("-2147483647-2147483648 2147483647")); + os.resetBuffer(); + + os << std::numeric_limits<int16_t>::min(); + BOOST_CHECK_EQUAL(buf.toString(), string("-32768")); + os.resetBuffer(); + + os << std::numeric_limits<int16_t>::max(); + BOOST_CHECK_EQUAL(buf.toString(), string("32767")); + os.resetBuffer(); + + os << std::numeric_limits<uint16_t>::min(); + BOOST_CHECK_EQUAL(buf.toString(), string("0")); + os.resetBuffer(); + + os << std::numeric_limits<uint16_t>::max(); + BOOST_CHECK_EQUAL(buf.toString(), string("65535")); + os.resetBuffer(); + + os << std::numeric_limits<int32_t>::min(); + BOOST_CHECK_EQUAL(buf.toString(), string("-2147483648")); + os.resetBuffer(); + + os << std::numeric_limits<int32_t>::max(); + BOOST_CHECK_EQUAL(buf.toString(), string("2147483647")); + os.resetBuffer(); + + os << std::numeric_limits<uint32_t>::min(); + BOOST_CHECK_EQUAL(buf.toString(), string("0")); + os.resetBuffer(); + + os << std::numeric_limits<uint32_t>::max(); + BOOST_CHECK_EQUAL(buf.toString(), string("4294967295")); + os.resetBuffer(); + + os << std::numeric_limits<int64_t>::min(); + BOOST_CHECK_EQUAL(buf.toString(), string("-9223372036854775808")); + os.resetBuffer(); + + os << std::numeric_limits<int64_t>::max(); + BOOST_CHECK_EQUAL(buf.toString(), string("9223372036854775807")); + os.resetBuffer(); + + os << std::numeric_limits<uint64_t>::min(); + BOOST_CHECK_EQUAL(buf.toString(), string("0")); + os.resetBuffer(); + + os << std::numeric_limits<uint64_t>::max(); + BOOST_CHECK_EQUAL(buf.toString(), string("18446744073709551615")); + os.resetBuffer(); + + int16_t a = 0; + int32_t b = 0; + int64_t c = 0; + os << a; + os << b; + os << c; + BOOST_CHECK_EQUAL(buf.toString(), string("000")); +} + +BOOST_AUTO_TEST_CASE(testLogStreamFloats) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + + os << 0.0; + BOOST_CHECK_EQUAL(buf.toString(), string("0")); + os.resetBuffer(); + + os << 1.0; + BOOST_CHECK_EQUAL(buf.toString(), string("1")); + os.resetBuffer(); + + os << 0.1; + BOOST_CHECK_EQUAL(buf.toString(), string("0.1")); + os.resetBuffer(); + + os << 0.05; + BOOST_CHECK_EQUAL(buf.toString(), string("0.05")); + os.resetBuffer(); + + os << 0.15; + BOOST_CHECK_EQUAL(buf.toString(), string("0.15")); + os.resetBuffer(); + + double a = 0.1; + os << a; + BOOST_CHECK_EQUAL(buf.toString(), string("0.1")); + os.resetBuffer(); + + double b = 0.05; + os << b; + BOOST_CHECK_EQUAL(buf.toString(), string("0.05")); + os.resetBuffer(); + + double c = 0.15; + os << c; + BOOST_CHECK_EQUAL(buf.toString(), string("0.15")); + os.resetBuffer(); + + os << a+b; + BOOST_CHECK_EQUAL(buf.toString(), string("0.15")); + os.resetBuffer(); + + BOOST_CHECK(a+b != c); + + os << 1.23456789; + BOOST_CHECK_EQUAL(buf.toString(), string("1.23456789")); + os.resetBuffer(); + + os << 1.234567; + BOOST_CHECK_EQUAL(buf.toString(), string("1.234567")); + os.resetBuffer(); + + os << -123.456; + BOOST_CHECK_EQUAL(buf.toString(), string("-123.456")); + os.resetBuffer(); +} + +BOOST_AUTO_TEST_CASE(testLogStreamVoid) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + + os << static_cast<void*>(0); + BOOST_CHECK_EQUAL(buf.toString(), string("0x0")); + os.resetBuffer(); + + os << reinterpret_cast<void*>(8888); + BOOST_CHECK_EQUAL(buf.toString(), string("0x22B8")); + os.resetBuffer(); +} + +BOOST_AUTO_TEST_CASE(testLogStreamStrings) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + + os << "Hello "; + BOOST_CHECK_EQUAL(buf.toString(), string("Hello ")); + + string chenshuo = "Shuo Chen"; + os << chenshuo; + BOOST_CHECK_EQUAL(buf.toString(), string("Hello Shuo Chen")); +} + +BOOST_AUTO_TEST_CASE(testLogStreamFmts) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + + os << muduo::Fmt("%4d", 1); + BOOST_CHECK_EQUAL(buf.toString(), string(" 1")); + os.resetBuffer(); + + os << muduo::Fmt("%4.2f", 1.2); + BOOST_CHECK_EQUAL(buf.toString(), string("1.20")); + os.resetBuffer(); + + os << muduo::Fmt("%4.2f", 1.2) << muduo::Fmt("%4d", 43); + BOOST_CHECK_EQUAL(buf.toString(), string("1.20 43")); + os.resetBuffer(); +} + +BOOST_AUTO_TEST_CASE(testLogStreamLong) +{ + muduo::LogStream os; + const muduo::LogStream::Buffer& buf = os.buffer(); + for (int i = 0; i < 399; ++i) + { + os << "123456789 "; + BOOST_CHECK_EQUAL(buf.length(), 10*(i+1)); + BOOST_CHECK_EQUAL(buf.avail(), 4000 - 10*(i+1)); + } + + os << "abcdefghi "; + BOOST_CHECK_EQUAL(buf.length(), 3990); + BOOST_CHECK_EQUAL(buf.avail(), 10); + + os << "abcdefghi"; + BOOST_CHECK_EQUAL(buf.length(), 3999); + BOOST_CHECK_EQUAL(buf.avail(), 1); +} + +BOOST_AUTO_TEST_CASE(testFormatSI) +{ + BOOST_CHECK_EQUAL(muduo::formatSI(0), string("0")); + BOOST_CHECK_EQUAL(muduo::formatSI(999), string("999")); + BOOST_CHECK_EQUAL(muduo::formatSI(1000), string("1.00k")); + BOOST_CHECK_EQUAL(muduo::formatSI(9990), string("9.99k")); + BOOST_CHECK_EQUAL(muduo::formatSI(9994), string("9.99k")); + BOOST_CHECK_EQUAL(muduo::formatSI(9995), string("10.0k")); + BOOST_CHECK_EQUAL(muduo::formatSI(10000), string("10.0k")); + BOOST_CHECK_EQUAL(muduo::formatSI(10049), string("10.0k")); + BOOST_CHECK_EQUAL(muduo::formatSI(10050), string("10.1k")); + BOOST_CHECK_EQUAL(muduo::formatSI(99900), string("99.9k")); + BOOST_CHECK_EQUAL(muduo::formatSI(99949), string("99.9k")); + BOOST_CHECK_EQUAL(muduo::formatSI(99950), string("100k")); + BOOST_CHECK_EQUAL(muduo::formatSI(100499), string("100k")); + // FIXME: + // BOOST_CHECK_EQUAL(muduo::formatSI(100500), string("101k")); + BOOST_CHECK_EQUAL(muduo::formatSI(100501), string("101k")); + BOOST_CHECK_EQUAL(muduo::formatSI(999499), string("999k")); + BOOST_CHECK_EQUAL(muduo::formatSI(999500), string("1.00M")); + BOOST_CHECK_EQUAL(muduo::formatSI(1004999), string("1.00M")); + // BOOST_CHECK_EQUAL(muduo::formatSI(1005000), string("1.01M")); + BOOST_CHECK_EQUAL(muduo::formatSI(1005001), string("1.01M")); + BOOST_CHECK_EQUAL(muduo::formatSI(INT64_MAX), string("9.22E")); +} + +BOOST_AUTO_TEST_CASE(testFormatIEC) +{ + BOOST_CHECK_EQUAL(muduo::formatIEC(0), string("0")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1023), string("1023")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1024), string("1.00Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10234), string("9.99Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10235), string("10.0Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10240), string("10.0Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10291), string("10.0Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10292), string("10.1Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(102348), string("99.9Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(102349), string("100Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(102912), string("100Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(102913), string("101Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1022976), string("999Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1047552), string("1023Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1047961), string("1023Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1048063), string("1023Ki")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1048064), string("1.00Mi")); + BOOST_CHECK_EQUAL(muduo::formatIEC(1048576), string("1.00Mi")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10480517), string("9.99Mi")); + BOOST_CHECK_EQUAL(muduo::formatIEC(10480518), string("10.0Mi")); + BOOST_CHECK_EQUAL(muduo::formatIEC(INT64_MAX), string("8.00Ei")); +} diff --git a/muduo/base/tests/Logging_test.cc b/muduo/base/tests/Logging_test.cc new file mode 100644 index 0000000..3de5e86 --- /dev/null +++ b/muduo/base/tests/Logging_test.cc @@ -0,0 +1,122 @@ +#include "muduo/base/Logging.h" +#include "muduo/base/LogFile.h" +#include "muduo/base/ThreadPool.h" +#include "muduo/base/TimeZone.h" + +#include <stdio.h> +#include <unistd.h> + +int g_total; +FILE* g_file; +std::unique_ptr<muduo::LogFile> g_logFile; + +void dummyOutput(const char* msg, int len) +{ + g_total += len; + if (g_file) + { + fwrite(msg, 1, len, g_file); + } + else if (g_logFile) + { + g_logFile->append(msg, len); + } +} + +void bench(const char* type) +{ + muduo::Logger::setOutput(dummyOutput); + muduo::Timestamp start(muduo::Timestamp::now()); + g_total = 0; + + int n = 1000*1000; + const bool kLongLog = false; + muduo::string empty = " "; + muduo::string longStr(3000, 'X'); + longStr += " "; + for (int i = 0; i < n; ++i) + { + LOG_INFO << "Hello 0123456789" << " abcdefghijklmnopqrstuvwxyz" + << (kLongLog ? longStr : empty) + << i; + } + muduo::Timestamp end(muduo::Timestamp::now()); + double seconds = timeDifference(end, start); + printf("%12s: %f seconds, %d bytes, %10.2f msg/s, %.2f MiB/s\n", + type, seconds, g_total, n / seconds, g_total / seconds / (1024 * 1024)); +} + +void logInThread() +{ + LOG_INFO << "logInThread"; + usleep(1000); +} + +int main() +{ + getppid(); // for ltrace and strace + + muduo::ThreadPool pool("pool"); + pool.start(5); + pool.run(logInThread); + pool.run(logInThread); + pool.run(logInThread); + pool.run(logInThread); + pool.run(logInThread); + + LOG_TRACE << "trace"; + LOG_DEBUG << "debug"; + LOG_INFO << "Hello"; + LOG_WARN << "World"; + LOG_ERROR << "Error"; + LOG_INFO << sizeof(muduo::Logger); + LOG_INFO << sizeof(muduo::LogStream); + LOG_INFO << sizeof(muduo::Fmt); + LOG_INFO << sizeof(muduo::LogStream::Buffer); + + sleep(1); + bench("nop"); + + char buffer[64*1024]; + + g_file = fopen("/dev/null", "w"); + setbuffer(g_file, buffer, sizeof buffer); + bench("/dev/null"); + fclose(g_file); + + g_file = fopen("/tmp/log", "w"); + setbuffer(g_file, buffer, sizeof buffer); + bench("/tmp/log"); + fclose(g_file); + + g_file = NULL; + g_logFile.reset(new muduo::LogFile("test_log_st", 500*1000*1000, false)); + bench("test_log_st"); + + g_logFile.reset(new muduo::LogFile("test_log_mt", 500*1000*1000, true)); + bench("test_log_mt"); + g_logFile.reset(); + + { + g_file = stdout; + sleep(1); + muduo::TimeZone beijing(8*3600, "CST"); + muduo::Logger::setTimeZone(beijing); + LOG_TRACE << "trace CST"; + LOG_DEBUG << "debug CST"; + LOG_INFO << "Hello CST"; + LOG_WARN << "World CST"; + LOG_ERROR << "Error CST"; + + sleep(1); + muduo::TimeZone newyork("/usr/share/zoneinfo/America/New_York"); + muduo::Logger::setTimeZone(newyork); + LOG_TRACE << "trace NYT"; + LOG_DEBUG << "debug NYT"; + LOG_INFO << "Hello NYT"; + LOG_WARN << "World NYT"; + LOG_ERROR << "Error NYT"; + g_file = NULL; + } + bench("timezone nop"); +} diff --git a/muduo/base/tests/Mutex_test.cc b/muduo/base/tests/Mutex_test.cc new file mode 100644 index 0000000..0c22d71 --- /dev/null +++ b/muduo/base/tests/Mutex_test.cc @@ -0,0 +1,82 @@ +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" +#include "muduo/base/Timestamp.h" + +#include <vector> +#include <stdio.h> + +using namespace muduo; +using namespace std; + +MutexLock g_mutex; +vector<int> g_vec; +const int kCount = 10*1000*1000; + +void threadFunc() +{ + for (int i = 0; i < kCount; ++i) + { + MutexLockGuard lock(g_mutex); + g_vec.push_back(i); + } +} + +int foo() __attribute__ ((noinline)); + +int g_count = 0; +int foo() +{ + MutexLockGuard lock(g_mutex); + if (!g_mutex.isLockedByThisThread()) + { + printf("FAIL\n"); + return -1; + } + + ++g_count; + return 0; +} + +int main() +{ + MCHECK(foo()); + if (g_count != 1) + { + printf("MCHECK calls twice.\n"); + abort(); + } + + const int kMaxThreads = 8; + g_vec.reserve(kMaxThreads * kCount); + + Timestamp start(Timestamp::now()); + for (int i = 0; i < kCount; ++i) + { + g_vec.push_back(i); + } + + printf("single thread without lock %f\n", timeDifference(Timestamp::now(), start)); + + start = Timestamp::now(); + threadFunc(); + printf("single thread with lock %f\n", timeDifference(Timestamp::now(), start)); + + for (int nthreads = 1; nthreads < kMaxThreads; ++nthreads) + { + std::vector<std::unique_ptr<Thread>> threads; + g_vec.clear(); + start = Timestamp::now(); + for (int i = 0; i < nthreads; ++i) + { + threads.emplace_back(new Thread(&threadFunc)); + threads.back()->start(); + } + for (int i = 0; i < nthreads; ++i) + { + threads[i]->join(); + } + printf("%d thread(s) with lock %f\n", nthreads, timeDifference(Timestamp::now(), start)); + } +} + diff --git a/muduo/base/tests/ProcessInfo_test.cc b/muduo/base/tests/ProcessInfo_test.cc new file mode 100644 index 0000000..6e30bad --- /dev/null +++ b/muduo/base/tests/ProcessInfo_test.cc @@ -0,0 +1,17 @@ +#include "muduo/base/ProcessInfo.h" +#include <stdio.h> +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +int main() +{ + printf("pid = %d\n", muduo::ProcessInfo::pid()); + printf("uid = %d\n", muduo::ProcessInfo::uid()); + printf("euid = %d\n", muduo::ProcessInfo::euid()); + printf("start time = %s\n", muduo::ProcessInfo::startTime().toFormattedString().c_str()); + printf("hostname = %s\n", muduo::ProcessInfo::hostname().c_str()); + printf("opened files = %d\n", muduo::ProcessInfo::openedFiles()); + printf("threads = %zd\n", muduo::ProcessInfo::threads().size()); + printf("num threads = %d\n", muduo::ProcessInfo::numThreads()); + printf("status = %s\n", muduo::ProcessInfo::procStatus().c_str()); +} diff --git a/muduo/base/tests/SingletonThreadLocal_test.cc b/muduo/base/tests/SingletonThreadLocal_test.cc new file mode 100644 index 0000000..29edf6c --- /dev/null +++ b/muduo/base/tests/SingletonThreadLocal_test.cc @@ -0,0 +1,58 @@ +#include "muduo/base/Singleton.h" +#include "muduo/base/CurrentThread.h" +#include "muduo/base/ThreadLocal.h" +#include "muduo/base/Thread.h" + +#include <stdio.h> +#include <unistd.h> + +class Test : muduo::noncopyable +{ + public: + Test() + { + printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this); + } + + ~Test() + { + printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str()); + } + + const muduo::string& name() const { return name_; } + void setName(const muduo::string& n) { name_ = n; } + + private: + muduo::string name_; +}; + +#define STL muduo::Singleton<muduo::ThreadLocal<Test> >::instance().value() + +void print() +{ + printf("tid=%d, %p name=%s\n", + muduo::CurrentThread::tid(), + &STL, + STL.name().c_str()); +} + +void threadFunc(const char* changeTo) +{ + print(); + STL.setName(changeTo); + sleep(1); + print(); +} + +int main() +{ + STL.setName("main one"); + muduo::Thread t1(std::bind(threadFunc, "thread1")); + muduo::Thread t2(std::bind(threadFunc, "thread2")); + t1.start(); + t2.start(); + t1.join(); + print(); + t2.join(); + pthread_exit(0); +} diff --git a/muduo/base/tests/Singleton_test.cc b/muduo/base/tests/Singleton_test.cc new file mode 100644 index 0000000..124d7f7 --- /dev/null +++ b/muduo/base/tests/Singleton_test.cc @@ -0,0 +1,65 @@ +#include "muduo/base/Singleton.h" +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Thread.h" + +#include <stdio.h> + +class Test : muduo::noncopyable +{ + public: + Test() + { + printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this); + } + + ~Test() + { + printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str()); + } + + const muduo::string& name() const { return name_; } + void setName(const muduo::string& n) { name_ = n; } + + private: + muduo::string name_; +}; + +class TestNoDestroy : muduo::noncopyable +{ + public: + // Tag member for Singleton<T> + void no_destroy(); + + TestNoDestroy() + { + printf("tid=%d, constructing TestNoDestroy %p\n", muduo::CurrentThread::tid(), this); + } + + ~TestNoDestroy() + { + printf("tid=%d, destructing TestNoDestroy %p\n", muduo::CurrentThread::tid(), this); + } +}; + +void threadFunc() +{ + printf("tid=%d, %p name=%s\n", + muduo::CurrentThread::tid(), + &muduo::Singleton<Test>::instance(), + muduo::Singleton<Test>::instance().name().c_str()); + muduo::Singleton<Test>::instance().setName("only one, changed"); +} + +int main() +{ + muduo::Singleton<Test>::instance().setName("only one"); + muduo::Thread t1(threadFunc); + t1.start(); + t1.join(); + printf("tid=%d, %p name=%s\n", + muduo::CurrentThread::tid(), + &muduo::Singleton<Test>::instance(), + muduo::Singleton<Test>::instance().name().c_str()); + muduo::Singleton<TestNoDestroy>::instance(); + printf("with valgrind, you should see %zd-byte memory leak.\n", sizeof(TestNoDestroy)); +} diff --git a/muduo/base/tests/ThreadLocalSingleton_test.cc b/muduo/base/tests/ThreadLocalSingleton_test.cc new file mode 100644 index 0000000..efe485a --- /dev/null +++ b/muduo/base/tests/ThreadLocalSingleton_test.cc @@ -0,0 +1,58 @@ +#include "muduo/base/ThreadLocalSingleton.h" +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Thread.h" + +#include <stdio.h> + +class Test : muduo::noncopyable +{ + public: + Test() + { + printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this); + } + + ~Test() + { + printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str()); + } + + const muduo::string& name() const { return name_; } + void setName(const muduo::string& n) { name_ = n; } + + private: + muduo::string name_; +}; + +void threadFunc(const char* changeTo) +{ + printf("tid=%d, %p name=%s\n", + muduo::CurrentThread::tid(), + &muduo::ThreadLocalSingleton<Test>::instance(), + muduo::ThreadLocalSingleton<Test>::instance().name().c_str()); + muduo::ThreadLocalSingleton<Test>::instance().setName(changeTo); + printf("tid=%d, %p name=%s\n", + muduo::CurrentThread::tid(), + &muduo::ThreadLocalSingleton<Test>::instance(), + muduo::ThreadLocalSingleton<Test>::instance().name().c_str()); + + // no need to manually delete it + // muduo::ThreadLocalSingleton<Test>::destroy(); +} + +int main() +{ + muduo::ThreadLocalSingleton<Test>::instance().setName("main one"); + muduo::Thread t1(std::bind(threadFunc, "thread1")); + muduo::Thread t2(std::bind(threadFunc, "thread2")); + t1.start(); + t2.start(); + t1.join(); + printf("tid=%d, %p name=%s\n", + muduo::CurrentThread::tid(), + &muduo::ThreadLocalSingleton<Test>::instance(), + muduo::ThreadLocalSingleton<Test>::instance().name().c_str()); + t2.join(); + + pthread_exit(0); +} diff --git a/muduo/base/tests/ThreadLocal_test.cc b/muduo/base/tests/ThreadLocal_test.cc new file mode 100644 index 0000000..a5b89cf --- /dev/null +++ b/muduo/base/tests/ThreadLocal_test.cc @@ -0,0 +1,61 @@ +#include "muduo/base/ThreadLocal.h" +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Thread.h" + +#include <stdio.h> + +class Test : muduo::noncopyable +{ + public: + Test() + { + printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this); + } + + ~Test() + { + printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str()); + } + + const muduo::string& name() const { return name_; } + void setName(const muduo::string& n) { name_ = n; } + + private: + muduo::string name_; +}; + +muduo::ThreadLocal<Test> testObj1; +muduo::ThreadLocal<Test> testObj2; + +void print() +{ + printf("tid=%d, obj1 %p name=%s\n", + muduo::CurrentThread::tid(), + &testObj1.value(), + testObj1.value().name().c_str()); + printf("tid=%d, obj2 %p name=%s\n", + muduo::CurrentThread::tid(), + &testObj2.value(), + testObj2.value().name().c_str()); +} + +void threadFunc() +{ + print(); + testObj1.value().setName("changed 1"); + testObj2.value().setName("changed 42"); + print(); +} + +int main() +{ + testObj1.value().setName("main one"); + print(); + muduo::Thread t1(threadFunc); + t1.start(); + t1.join(); + testObj2.value().setName("main two"); + print(); + + pthread_exit(0); +} diff --git a/muduo/base/tests/ThreadPool_test.cc b/muduo/base/tests/ThreadPool_test.cc new file mode 100644 index 0000000..ee16286 --- /dev/null +++ b/muduo/base/tests/ThreadPool_test.cc @@ -0,0 +1,64 @@ +#include "muduo/base/ThreadPool.h" +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Logging.h" + +#include <stdio.h> +#include <unistd.h> // usleep + +void print() +{ + printf("tid=%d\n", muduo::CurrentThread::tid()); +} + +void printString(const std::string& str) +{ + LOG_INFO << str; + usleep(100*1000); +} + +void test(int maxSize) +{ + LOG_WARN << "Test ThreadPool with max queue size = " << maxSize; + muduo::ThreadPool pool("MainThreadPool"); + pool.setMaxQueueSize(maxSize); + pool.start(5); + + LOG_WARN << "Adding"; + pool.run(print); + pool.run(print); + for (int i = 0; i < 100; ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "task %d", i); + pool.run(std::bind(printString, std::string(buf))); + } + LOG_WARN << "Done"; + + muduo::CountDownLatch latch(1); + pool.run(std::bind(&muduo::CountDownLatch::countDown, &latch)); + latch.wait(); + pool.stop(); +} + +/* + * Wish we could do this in the future. +void testMove() +{ + muduo::ThreadPool pool; + pool.start(2); + + std::unique_ptr<int> x(new int(42)); + pool.run([y = std::move(x)]{ printf("%d: %d\n", muduo::CurrentThread::tid(), *y); }); + pool.stop(); +} +*/ + +int main() +{ + test(0); + test(1); + test(5); + test(10); + test(50); +} diff --git a/muduo/base/tests/Thread_bench.cc b/muduo/base/tests/Thread_bench.cc new file mode 100644 index 0000000..ae3d49c --- /dev/null +++ b/muduo/base/tests/Thread_bench.cc @@ -0,0 +1,86 @@ +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" +#include "muduo/base/Timestamp.h" + +#include <map> +#include <string> +#include <stdio.h> +#include <sys/wait.h> +#include <unistd.h> + +muduo::MutexLock g_mutex; +std::map<int, int> g_delays; + +void threadFunc() +{ + //printf("tid=%d\n", muduo::CurrentThread::tid()); +} + +void threadFunc2(muduo::Timestamp start) +{ + muduo::Timestamp now(muduo::Timestamp::now()); + int delay = static_cast<int>(timeDifference(now, start) * 1000000); + muduo::MutexLockGuard lock(g_mutex); + ++g_delays[delay]; +} + +void forkBench() +{ + sleep(10); + muduo::Timestamp start(muduo::Timestamp::now()); + int kProcesses = 10*1000; + + for (int i = 0; i < kProcesses; ++i) + { + pid_t child = fork(); + if (child == 0) + { + exit(0); + } + else + { + waitpid(child, NULL, 0); + } + } + + double timeUsed = timeDifference(muduo::Timestamp::now(), start); + printf("process creation time used %f us\n", timeUsed*1000000/kProcesses); + printf("number of created processes %d\n", kProcesses); +} + +int main(int argc, char* argv[]) +{ + printf("pid=%d, tid=%d\n", ::getpid(), muduo::CurrentThread::tid()); + muduo::Timestamp start(muduo::Timestamp::now()); + + int kThreads = 100*1000; + for (int i = 0; i < kThreads; ++i) + { + muduo::Thread t1(threadFunc); + t1.start(); + t1.join(); + } + + double timeUsed = timeDifference(muduo::Timestamp::now(), start); + printf("thread creation time %f us\n", timeUsed*1000000/kThreads); + printf("number of created threads %d\n", muduo::Thread::numCreated()); + + for (int i = 0; i < kThreads; ++i) + { + muduo::Timestamp now(muduo::Timestamp::now()); + muduo::Thread t2(std::bind(threadFunc2, now)); + t2.start(); + t2.join(); + } + { + muduo::MutexLockGuard lock(g_mutex); + for (const auto& delay : g_delays) + { + printf("delay = %d, count = %d\n", + delay.first, delay.second); + } + } + + forkBench(); +} diff --git a/muduo/base/tests/Thread_test.cc b/muduo/base/tests/Thread_test.cc new file mode 100644 index 0000000..d6e78b9 --- /dev/null +++ b/muduo/base/tests/Thread_test.cc @@ -0,0 +1,91 @@ +#include "muduo/base/Thread.h" +#include "muduo/base/CurrentThread.h" + +#include <string> +#include <stdio.h> +#include <unistd.h> + +void mysleep(int seconds) +{ + timespec t = { seconds, 0 }; + nanosleep(&t, NULL); +} + +void threadFunc() +{ + printf("tid=%d\n", muduo::CurrentThread::tid()); +} + +void threadFunc2(int x) +{ + printf("tid=%d, x=%d\n", muduo::CurrentThread::tid(), x); +} + +void threadFunc3() +{ + printf("tid=%d\n", muduo::CurrentThread::tid()); + mysleep(1); +} + +class Foo +{ + public: + explicit Foo(double x) + : x_(x) + { + } + + void memberFunc() + { + printf("tid=%d, Foo::x_=%f\n", muduo::CurrentThread::tid(), x_); + } + + void memberFunc2(const std::string& text) + { + printf("tid=%d, Foo::x_=%f, text=%s\n", muduo::CurrentThread::tid(), x_, text.c_str()); + } + + private: + double x_; +}; + +int main() +{ + printf("pid=%d, tid=%d\n", ::getpid(), muduo::CurrentThread::tid()); + + muduo::Thread t1(threadFunc); + t1.start(); + printf("t1.tid=%d\n", t1.tid()); + t1.join(); + + muduo::Thread t2(std::bind(threadFunc2, 42), + "thread for free function with argument"); + t2.start(); + printf("t2.tid=%d\n", t2.tid()); + t2.join(); + + Foo foo(87.53); + muduo::Thread t3(std::bind(&Foo::memberFunc, &foo), + "thread for member function without argument"); + t3.start(); + t3.join(); + + muduo::Thread t4(std::bind(&Foo::memberFunc2, std::ref(foo), std::string("Shuo Chen"))); + t4.start(); + t4.join(); + + { + muduo::Thread t5(threadFunc3); + t5.start(); + // t5 may destruct eariler than thread creation. + } + mysleep(2); + { + muduo::Thread t6(threadFunc3); + t6.start(); + mysleep(2); + // t6 destruct later than thread creation. + } + sleep(2); + printf("number of created threads %d\n", muduo::Thread::numCreated()); +} diff --git a/muduo/base/tests/TimeZone_unittest.cc b/muduo/base/tests/TimeZone_unittest.cc new file mode 100644 index 0000000..5d1de82 --- /dev/null +++ b/muduo/base/tests/TimeZone_unittest.cc @@ -0,0 +1,276 @@ +#include "muduo/base/TimeZone.h" +#include "muduo/base/Types.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +using muduo::TimeZone; + +struct tm getTm(int year, int month, int day, + int hour, int minute, int seconds) +{ + struct tm gmt; + muduo::memZero(&gmt, sizeof gmt); + gmt.tm_year = year - 1900; + gmt.tm_mon = month - 1; + gmt.tm_mday = day; + gmt.tm_hour = hour; + gmt.tm_min = minute; + gmt.tm_sec = seconds; + return gmt; +} + +struct tm getTm(const char* str) +{ + struct tm gmt; + muduo::memZero(&gmt, sizeof gmt); + strptime(str, "%F %T", &gmt); + return gmt; +} + +time_t getGmt(int year, int month, int day, + int hour, int minute, int seconds) +{ + struct tm gmt = getTm(year, month, day, hour, minute, seconds); + return timegm(&gmt); +} + +time_t getGmt(const char* str) +{ + struct tm gmt = getTm(str); + return timegm(&gmt); +} + +struct TestCase +{ + const char* gmt; + const char* local; + bool isdst; +}; + +void test(const TimeZone& tz, TestCase tc) +{ + time_t gmt = getGmt(tc.gmt); + + { + struct tm local = tz.toLocalTime(gmt); + char buf[256]; + strftime(buf, sizeof buf, "%F %T%z(%Z)", &local); + + if (strcmp(buf, tc.local) != 0 || tc.isdst != local.tm_isdst) + { + printf("WRONG: "); + } + printf("%s -> %s\n", tc.gmt, buf); + } + + { + struct tm local = getTm(tc.local); + local.tm_isdst = tc.isdst; + time_t result = tz.fromLocalTime(local); + if (result != gmt) + { + struct tm local2 = tz.toLocalTime(result); + char buf[256]; + strftime(buf, sizeof buf, "%F %T%z(%Z)", &local2); + + printf("WRONG fromLocalTime: %ld %ld %s\n", + static_cast<long>(gmt), static_cast<long>(result), buf); + } + } +} + +void testNewYork() +{ + TimeZone tz("/usr/share/zoneinfo/America/New_York"); + TestCase cases[] = + { + + { "2006-03-07 00:00:00", "2006-03-06 19:00:00-0500(EST)", false }, + { "2006-04-02 06:59:59", "2006-04-02 01:59:59-0500(EST)", false }, + { "2006-04-02 07:00:00", "2006-04-02 03:00:00-0400(EDT)", true }, + { "2006-05-01 00:00:00", "2006-04-30 20:00:00-0400(EDT)", true }, + { "2006-05-02 01:00:00", "2006-05-01 21:00:00-0400(EDT)", true }, + { "2006-10-21 05:00:00", "2006-10-21 01:00:00-0400(EDT)", true }, + { "2006-10-29 05:59:59", "2006-10-29 01:59:59-0400(EDT)", true }, + { "2006-10-29 06:00:00", "2006-10-29 01:00:00-0500(EST)", false }, + { "2006-10-29 06:59:59", "2006-10-29 01:59:59-0500(EST)", false }, + { "2006-12-31 06:00:00", "2006-12-31 01:00:00-0500(EST)", false }, + { "2007-01-01 00:00:00", "2006-12-31 19:00:00-0500(EST)", false }, + + { "2007-03-07 00:00:00", "2007-03-06 19:00:00-0500(EST)", false }, + { "2007-03-11 06:59:59", "2007-03-11 01:59:59-0500(EST)", false }, + { "2007-03-11 07:00:00", "2007-03-11 03:00:00-0400(EDT)", true }, + { "2007-05-01 00:00:00", "2007-04-30 20:00:00-0400(EDT)", true }, + { "2007-05-02 01:00:00", "2007-05-01 21:00:00-0400(EDT)", true }, + { "2007-10-31 05:00:00", "2007-10-31 01:00:00-0400(EDT)", true }, + { "2007-11-04 05:59:59", "2007-11-04 01:59:59-0400(EDT)", true }, + { "2007-11-04 06:00:00", "2007-11-04 01:00:00-0500(EST)", false }, + { "2007-11-04 06:59:59", "2007-11-04 01:59:59-0500(EST)", false }, + { "2007-12-31 06:00:00", "2007-12-31 01:00:00-0500(EST)", false }, + { "2008-01-01 00:00:00", "2007-12-31 19:00:00-0500(EST)", false }, + + { "2009-03-07 00:00:00", "2009-03-06 19:00:00-0500(EST)", false }, + { "2009-03-08 06:59:59", "2009-03-08 01:59:59-0500(EST)", false }, + { "2009-03-08 07:00:00", "2009-03-08 03:00:00-0400(EDT)", true }, + { "2009-05-01 00:00:00", "2009-04-30 20:00:00-0400(EDT)", true }, + { "2009-05-02 01:00:00", "2009-05-01 21:00:00-0400(EDT)", true }, + { "2009-10-31 05:00:00", "2009-10-31 01:00:00-0400(EDT)", true }, + { "2009-11-01 05:59:59", "2009-11-01 01:59:59-0400(EDT)", true }, + { "2009-11-01 06:00:00", "2009-11-01 01:00:00-0500(EST)", false }, + { "2009-11-01 06:59:59", "2009-11-01 01:59:59-0500(EST)", false }, + { "2009-12-31 06:00:00", "2009-12-31 01:00:00-0500(EST)", false }, + { "2010-01-01 00:00:00", "2009-12-31 19:00:00-0500(EST)", false }, + + { "2010-03-13 00:00:00", "2010-03-12 19:00:00-0500(EST)", false }, + { "2010-03-14 06:59:59", "2010-03-14 01:59:59-0500(EST)", false }, + { "2010-03-14 07:00:00", "2010-03-14 03:00:00-0400(EDT)", true }, + { "2010-05-01 00:00:00", "2010-04-30 20:00:00-0400(EDT)", true }, + { "2010-05-02 01:00:00", "2010-05-01 21:00:00-0400(EDT)", true }, + { "2010-11-06 05:00:00", "2010-11-06 01:00:00-0400(EDT)", true }, + { "2010-11-07 05:59:59", "2010-11-07 01:59:59-0400(EDT)", true }, + { "2010-11-07 06:00:00", "2010-11-07 01:00:00-0500(EST)", false }, + { "2010-11-07 06:59:59", "2010-11-07 01:59:59-0500(EST)", false }, + { "2010-12-31 06:00:00", "2010-12-31 01:00:00-0500(EST)", false }, + { "2011-01-01 00:00:00", "2010-12-31 19:00:00-0500(EST)", false }, + + { "2011-03-01 00:00:00", "2011-02-28 19:00:00-0500(EST)", false }, + { "2011-03-13 06:59:59", "2011-03-13 01:59:59-0500(EST)", false }, + { "2011-03-13 07:00:00", "2011-03-13 03:00:00-0400(EDT)", true }, + { "2011-05-01 00:00:00", "2011-04-30 20:00:00-0400(EDT)", true }, + { "2011-05-02 01:00:00", "2011-05-01 21:00:00-0400(EDT)", true }, + { "2011-11-06 05:59:59", "2011-11-06 01:59:59-0400(EDT)", true }, + { "2011-11-06 06:00:00", "2011-11-06 01:00:00-0500(EST)", false }, + { "2011-11-06 06:59:59", "2011-11-06 01:59:59-0500(EST)", false }, + { "2011-12-31 06:00:00", "2011-12-31 01:00:00-0500(EST)", false }, + { "2012-01-01 00:00:00", "2011-12-31 19:00:00-0500(EST)", false }, + + }; + + for (const auto& c : cases) + { + test(tz, c); + } +} + +void testLondon() +{ + TimeZone tz("/usr/share/zoneinfo/Europe/London"); + TestCase cases[] = + { + + { "2011-03-26 00:00:00", "2011-03-26 00:00:00+0000(GMT)", false }, + { "2011-03-27 00:59:59", "2011-03-27 00:59:59+0000(GMT)", false }, + { "2011-03-27 01:00:00", "2011-03-27 02:00:00+0100(BST)", true }, + { "2011-10-30 00:59:59", "2011-10-30 01:59:59+0100(BST)", true }, + { "2011-10-30 01:00:00", "2011-10-30 01:00:00+0000(GMT)", false }, + { "2012-10-30 01:59:59", "2012-10-30 01:59:59+0000(GMT)", false }, + { "2011-12-31 22:00:00", "2011-12-31 22:00:00+0000(GMT)", false }, + { "2012-01-01 00:00:00", "2012-01-01 00:00:00+0000(GMT)", false }, + + { "2012-03-24 00:00:00", "2012-03-24 00:00:00+0000(GMT)", false }, + { "2012-03-25 00:59:59", "2012-03-25 00:59:59+0000(GMT)", false }, + { "2012-03-25 01:00:00", "2012-03-25 02:00:00+0100(BST)", true }, + { "2012-10-28 00:59:59", "2012-10-28 01:59:59+0100(BST)", true }, + { "2012-10-28 01:00:00", "2012-10-28 01:00:00+0000(GMT)", false }, + { "2012-10-28 01:59:59", "2012-10-28 01:59:59+0000(GMT)", false }, + { "2012-12-31 22:00:00", "2012-12-31 22:00:00+0000(GMT)", false }, + { "2013-01-01 00:00:00", "2013-01-01 00:00:00+0000(GMT)", false }, + + }; + + for (const auto& c : cases) + { + test(tz, c); + } +} + +void testHongKong() +{ + TimeZone tz("/usr/share/zoneinfo/Asia/Hong_Kong"); + TestCase cases[] = + { + + { "2011-04-03 00:00:00", "2011-04-03 08:00:00+0800(HKT)", false}, + + }; + + for (const auto& c : cases) + { + test(tz, c); + } +} + +void testSydney() +{ + TimeZone tz("/usr/share/zoneinfo/Australia/Sydney"); + TestCase cases[] = + { + + { "2011-01-01 00:00:00", "2011-01-01 11:00:00+1100(EST)", true }, + { "2011-04-02 15:59:59", "2011-04-03 02:59:59+1100(EST)", true }, + { "2011-04-02 16:00:00", "2011-04-03 02:00:00+1000(EST)", false }, + { "2011-04-02 16:59:59", "2011-04-03 02:59:59+1000(EST)", false }, + { "2011-05-02 01:00:00", "2011-05-02 11:00:00+1000(EST)", false }, + { "2011-10-01 15:59:59", "2011-10-02 01:59:59+1000(EST)", false }, + { "2011-10-01 16:00:00", "2011-10-02 03:00:00+1100(EST)", true }, + { "2011-12-31 22:00:00", "2012-01-01 09:00:00+1100(EST)", true }, + + }; + + for (const auto& c : cases) + { + test(tz, c); + } +} + +void testUtc() +{ + const int kRange = 100*1000*1000; + for (time_t t = -kRange; t <= kRange; t += 11) + { + struct tm* t1 = gmtime(&t); + struct tm t2 = TimeZone::toUtcTime(t, true); + char buf1[80], buf2[80]; + strftime(buf1, sizeof buf1, "%F %T %u %j", t1); + strftime(buf2, sizeof buf2, "%F %T %u %j", &t2); + if (strcmp(buf1, buf2) != 0) + { + printf("'%s' != '%s'\n", buf1, buf2); + assert(0); + } + time_t t3 = TimeZone::fromUtcTime(t2); + if (t != t3) + { + printf("%ld != %ld\n", static_cast<long>(t), static_cast<long>(t3)); + assert(0); + } + } +} + +void testFixedTimezone() +{ + TimeZone tz(8*3600, "CST"); + TestCase cases[] = + { + + { "2014-04-03 00:00:00", "2014-04-03 08:00:00+0800(CST)", false}, + + }; + + for (const auto& c : cases) + { + test(tz, c); + } +} + +int main() +{ + testNewYork(); + testLondon(); + testSydney(); + testHongKong(); + testFixedTimezone(); + testUtc(); +} diff --git a/muduo/base/tests/Timestamp_unittest.cc b/muduo/base/tests/Timestamp_unittest.cc new file mode 100644 index 0000000..5e27592 --- /dev/null +++ b/muduo/base/tests/Timestamp_unittest.cc @@ -0,0 +1,66 @@ +#include "muduo/base/Timestamp.h" +#include <vector> +#include <stdio.h> + +using muduo::Timestamp; + +void passByConstReference(const Timestamp& x) +{ + printf("%s\n", x.toString().c_str()); +} + +void passByValue(Timestamp x) +{ + printf("%s\n", x.toString().c_str()); +} + +void benchmark() +{ + const int kNumber = 1000*1000; + + std::vector<Timestamp> stamps; + stamps.reserve(kNumber); + for (int i = 0; i < kNumber; ++i) + { + stamps.push_back(Timestamp::now()); + } + printf("%s\n", stamps.front().toString().c_str()); + printf("%s\n", stamps.back().toString().c_str()); + printf("%f\n", timeDifference(stamps.back(), stamps.front())); + + int increments[100] = { 0 }; + int64_t start = stamps.front().microSecondsSinceEpoch(); + for (int i = 1; i < kNumber; ++i) + { + int64_t next = stamps[i].microSecondsSinceEpoch(); + int64_t inc = next - start; + start = next; + if (inc < 0) + { + printf("reverse!\n"); + } + else if (inc < 100) + { + ++increments[inc]; + } + else + { + printf("big gap %d\n", static_cast<int>(inc)); + } + } + + for (int i = 0; i < 100; ++i) + { + printf("%2d: %d\n", i, increments[i]); + } +} + +int main() +{ + Timestamp now(Timestamp::now()); + printf("%s\n", now.toString().c_str()); + passByValue(now); + passByConstReference(now); + benchmark(); +} + diff --git a/muduo/net/Acceptor.cc b/muduo/net/Acceptor.cc new file mode 100644 index 0000000..520e20d --- /dev/null +++ b/muduo/net/Acceptor.cc @@ -0,0 +1,100 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/Acceptor.h" + +#include <errno.h> +#include <fcntl.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/SocketsOps.h" +// #include <sys/types.h> +// #include <sys/stat.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, + bool reuseport) + : loop_(loop), + acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())), + acceptChannel_(loop, acceptSocket_.fd()), + listenning_(false), + idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC)) +{ + // 其中任何一个步骤出错都会造成程序终止,因此这里看不到错误处理。 + assert(idleFd_ >= 0); + acceptSocket_.setReuseAddr(true); + acceptSocket_.setReusePort(reuseport); + acceptSocket_.bindAddress(listenAddr); + acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); +} + +Acceptor::~Acceptor() +{ + acceptChannel_.disableAll(); + acceptChannel_.remove(); + ::close(idleFd_); +} + +void Acceptor::listen() +{ + loop_->assertInLoopThread(); + listenning_ = true; + acceptSocket_.listen(); + acceptChannel_.enableReading(); +} + +/* + Acceptor::handleRead() 的策略很简单,每次 accept(2) 一个 socket。另外还 + 有两种实现策略,一是每次循环 accept(2),直至没有新的连接到达;二是每次尝 + 试 accept(2) N 个新连接,N 的值一般是 10。后面这两种做法适合短连接服务,而 + muduo 是为长连接服务优化的,因此这里用了最简单的办法。这三种策略的对比见 + 论文《accept()able Strategies for Improving Web Server Performance》3。 +*/ +void Acceptor::handleRead() +{ + loop_->assertInLoopThread(); + InetAddress peerAddr; + // FIXME loop until no more + +/* + 注意这里的实现没有考虑文件描述符耗尽的情况,muduo 的处理办法 + 简单概述为: +*/ + int connfd = acceptSocket_.accept(&peerAddr); + if (connfd >= 0) { + + /* + 这种传递 int 句柄的做法不够理想,在 + C++11 中可以先创建 Socket 对象,再用移动语义把 Socket 对象 std::move() 给回调 + 函数,确保资源的安全释放。 + */ + // string hostport = peerAddr.toIpPort(); + // LOG_TRACE << "Accepts of " << hostport; + if (newConnectionCallback_) { + newConnectionCallback_(connfd, peerAddr); + } else { + sockets::close(connfd); + } + } else { + LOG_SYSERR << "in Acceptor::handleRead"; + // Read the section named "The special problem of + // accept()ing when you can't" in libev's doc. + // By Marc Lehmann, author of libev. + if (errno == EMFILE) { + ::close(idleFd_); + idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL); + ::close(idleFd_); + idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC); + } + } +} diff --git a/muduo/net/Acceptor.h b/muduo/net/Acceptor.h new file mode 100644 index 0000000..45ead43 --- /dev/null +++ b/muduo/net/Acceptor.h @@ -0,0 +1,69 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_ACCEPTOR_H +#define MUDUO_NET_ACCEPTOR_H + +#include <functional> + +#include "muduo/net/Channel.h" +#include "muduo/net/Socket.h" + +namespace muduo { +namespace net { + +class EventLoop; +class InetAddress; + + +/* + Acceptor class,用于 accept(2) 新 TCP 连接,并通过回调通知使用者。 + 它是内部 class,供 TcpServer 使用,生命期由后者控制。 +*/ +/// +/// Acceptor of incoming TCP connections. +/// +class Acceptor : noncopyable { +public: + typedef std::function<void(int sockfd, const InetAddress&)> + NewConnectionCallback; + + Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport); + ~Acceptor(); + + void setNewConnectionCallback(const NewConnectionCallback& cb) + { + newConnectionCallback_ = cb; + } + + bool listenning() const { return listenning_; } + void listen(); + +private: + void handleRead(); + +/* + Socket 是一个 RAII handle, + 封装了 socket 文件描述符的生命期。Acceptor 的 socket 是 listening socket,即 + server socket。Channel 用于观察此 socket 上的 readable 事件,并回调 Acceptor:: + handleRead(),后者会调用 accept(2) 来接受新连接,并回调用户 callback。 +*/ + EventLoop* loop_; + Socket acceptSocket_; + Channel acceptChannel_; + NewConnectionCallback newConnectionCallback_; + bool listenning_; + int idleFd_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_ACCEPTOR_H diff --git a/muduo/net/BUILD.bazel b/muduo/net/BUILD.bazel new file mode 100644 index 0000000..33d78d1 --- /dev/null +++ b/muduo/net/BUILD.bazel @@ -0,0 +1,51 @@ +cc_library( + name = "net", + srcs = [ + "Acceptor.cc", + "Buffer.cc", + "Channel.cc", + "Connector.cc", + "EventLoop.cc", + "EventLoopThread.cc", + "EventLoopThreadPool.cc", + "InetAddress.cc", + "Poller.cc", + "Socket.cc", + "SocketsOps.cc", + "TcpClient.cc", + "TcpConnection.cc", + "TcpServer.cc", + "Timer.cc", + "TimerQueue.cc", + "poller/DefaultPoller.cc", + "poller/EPollPoller.cc", + "poller/PollPoller.cc", + ], + hdrs = [ + "Acceptor.h", + "Buffer.h", + "Callbacks.h", + "Channel.h", + "Connector.h", + "Endian.h", + "EventLoop.h", + "EventLoopThread.h", + "EventLoopThreadPool.h", + "InetAddress.h", + "Poller.h", + "Socket.h", + "SocketsOps.h", + "TcpClient.h", + "TcpConnection.h", + "TcpServer.h", + "Timer.h", + "TimerId.h", + "TimerQueue.h", + "poller/EPollPoller.h", + "poller/PollPoller.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//muduo/base", + ], +) diff --git a/muduo/net/Buffer.cc b/muduo/net/Buffer.cc new file mode 100644 index 0000000..bac05eb --- /dev/null +++ b/muduo/net/Buffer.cc @@ -0,0 +1,78 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/Buffer.h" + +#include <errno.h> +#include <sys/uio.h> + +#include "muduo/net/SocketsOps.h" + +using namespace muduo; +using namespace muduo::net; + +const char Buffer::kCRLF[] = "\r\n"; + +const size_t Buffer::kCheapPrepend; +const size_t Buffer::kInitialSize; + +/* + Level Trigger(水平触发)和Edge Trigger(边缘触发)是与事件驱动编程模型相关的两种触发方式,通常用于描述事件在非阻塞 I/O 模型中的处理方式。 + 这两种触发方式主要用于描述事件发生时系统如何通知应用程序以及应用程序如何响应事件。 + + Level Trigger(水平触发): + 当事件发生并且仍然处于活动状态时,系统会不断地通知应用程序,直到事件处理完成或被标记为非活动状态。 + 水平触发适用于需要持续关注某个事件状态变化的情况。应用程序需要不断检查事件状态,直到事件不再活动。 + 对于 I/O 事件,水平触发意味着当文件描述符上有数据可读或可写时,系统会不断通知应用程序直到所有数据被读取或写入。 + + Edge Trigger(边缘触发): + 当事件状态发生变化时(例如从无数据到有数据,或从有数据到无数据),系统会通知应用程序一次。即只在状态发生变化时触发一次,而不会持续通知。 + 边缘触发适用于只关心事件状态变化瞬间的情况。应用程序需要在每次事件触发时立即处理,而不是持续关注事件状态。 + 对于 I/O 事件,边缘触发意味着只有在文件描述符状态变化时(例如从不可读到可读,或从不可写到可写),系统会通知应用程序一次。 + 在较低层次的操作系统和网络编程中,通常通过系统调用如 select、poll、epoll(在Linux中)等来实现水平触发。 + 而边缘触发通常需要更高级别的接口,例如 epoll 中的 EPOLLET 标志。 + + 选择使用水平触发还是边缘触发通常取决于应用程序的需求。水平触发允许应用程序以更简单的方式处理事件,但可能导致处理效率低下。 + 边缘触发则更适用于一些对事件状态变化非常敏感的场景,但相对来说可能更复杂一些。 + + Buffer::readFd() 只调用一次 read(2),而没有反复调用 read(2) 直到其 + 返回 EAGAIN。首先,这么做是正确的,因为 muduo 采用 level trigger,这么做不会 + 丢失数据或消息。其次,对追求低延迟的程序来说,这么做是高效的,因为每次读数 + 据只需要一次系统调用。再次,这样做照顾了多个连接的公平性,不会因为某个连接 + 上数据量过大而影响其他连接处理消息。 +*/ + +ssize_t Buffer::readFd(int fd, int* savedErrno) +{ + // saved an ioctl()/FIONREAD call to tell how much to read + char extrabuf[65536]; + struct iovec vec[2]; + const size_t writable = writableBytes(); + vec[0].iov_base = begin() + writerIndex_; + vec[0].iov_len = writable; + vec[1].iov_base = extrabuf; + vec[1].iov_len = sizeof extrabuf; + // when there is enough space in this buffer, don't read into extrabuf. + // when extrabuf is used, we read 128k-1 bytes at most. + const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; + const ssize_t n = sockets::readv(fd, vec, iovcnt); + if (n < 0) { + *savedErrno = errno; + } else if (implicit_cast<size_t>(n) <= writable) { + writerIndex_ += n; + } else { + writerIndex_ = buffer_.size(); + append(extrabuf, n - writable); + } + // if (n == writable + sizeof extrabuf) + // { + // goto line_30; + // } + return n; +} diff --git a/muduo/net/Buffer.h b/muduo/net/Buffer.h new file mode 100644 index 0000000..d4ad9a6 --- /dev/null +++ b/muduo/net/Buffer.h @@ -0,0 +1,374 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_BUFFER_H +#define MUDUO_NET_BUFFER_H + +#include <assert.h> +#include <string.h> + +#include <algorithm> +#include <vector> + +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" +#include "muduo/base/copyable.h" +#include "muduo/net/Endian.h" +// #include <unistd.h> // ssize_t + +namespace muduo { +namespace net { + +/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer +/// +/// @code +/// +-------------------+------------------+------------------+ +/// | prependable bytes | readable bytes | writable bytes | +/// | | (CONTENT) | | +/// +-------------------+------------------+------------------+ +/// | | | | +/// 0 <= readerIndex <= writerIndex <= size +/// @endcode +class Buffer : public muduo::copyable { +public: + static const size_t kCheapPrepend = 8; + static const size_t kInitialSize = 1024; + + explicit Buffer(size_t initialSize = kInitialSize) + : buffer_(kCheapPrepend + initialSize), + readerIndex_(kCheapPrepend), + writerIndex_(kCheapPrepend) + { + assert(readableBytes() == 0); + assert(writableBytes() == initialSize); + assert(prependableBytes() == kCheapPrepend); + } + + // implicit copy-ctor, move-ctor, dtor and assignment are fine + // NOTE: implicit move-ctor is added in g++ 4.6 + + void swap(Buffer& rhs) noexcept + { + buffer_.swap(rhs.buffer_); + std::swap(readerIndex_, rhs.readerIndex_); + std::swap(writerIndex_, rhs.writerIndex_); + } + + size_t readableBytes() const { return writerIndex_ - readerIndex_; } + + size_t writableBytes() const { return buffer_.size() - writerIndex_; } + + size_t prependableBytes() const { return readerIndex_; } + + const char* peek() const { return begin() + readerIndex_; } + + const char* findCRLF() const + { + // FIXME: replace with memmem()? + const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF + 2); + return crlf == beginWrite() ? NULL : crlf; + } + + const char* findCRLF(const char* start) const + { + assert(peek() <= start); + assert(start <= beginWrite()); + // FIXME: replace with memmem()? + const char* crlf = std::search(start, beginWrite(), kCRLF, kCRLF + 2); + return crlf == beginWrite() ? NULL : crlf; + } + + const char* findEOL() const + { + const void* eol = memchr(peek(), '\n', readableBytes()); + return static_cast<const char*>(eol); + } + + const char* findEOL(const char* start) const + { + assert(peek() <= start); + assert(start <= beginWrite()); + const void* eol = memchr(start, '\n', beginWrite() - start); + return static_cast<const char*>(eol); + } + + // retrieve returns void, to prevent + // string str(retrieve(readableBytes()), readableBytes()); + // the evaluation of two functions are unspecified + void retrieve(size_t len) + { + assert(len <= readableBytes()); + if (len < readableBytes()) { + readerIndex_ += len; + } else { + retrieveAll(); + } + } + + void retrieveUntil(const char* end) + { + assert(peek() <= end); + assert(end <= beginWrite()); + retrieve(end - peek()); + } + + void retrieveInt64() { retrieve(sizeof(int64_t)); } + + void retrieveInt32() { retrieve(sizeof(int32_t)); } + + void retrieveInt16() { retrieve(sizeof(int16_t)); } + + void retrieveInt8() { retrieve(sizeof(int8_t)); } + + void retrieveAll() + { + readerIndex_ = kCheapPrepend; + writerIndex_ = kCheapPrepend; + } + + string retrieveAllAsString() { return retrieveAsString(readableBytes()); } + + string retrieveAsString(size_t len) + { + assert(len <= readableBytes()); + string result(peek(), len); + retrieve(len); + return result; + } + + StringPiece toStringPiece() const + { + return StringPiece(peek(), static_cast<int>(readableBytes())); + } + + void append(const StringPiece& str) { append(str.data(), str.size()); } + + void append(const char* /*restrict*/ data, size_t len) + { + ensureWritableBytes(len); + std::copy(data, data + len, beginWrite()); + hasWritten(len); + } + + void append(const void* /*restrict*/ data, size_t len) + { + append(static_cast<const char*>(data), len); + } + + void ensureWritableBytes(size_t len) + { + if (writableBytes() < len) { + makeSpace(len); + } + assert(writableBytes() >= len); + } + + char* beginWrite() { return begin() + writerIndex_; } + + const char* beginWrite() const { return begin() + writerIndex_; } + + void hasWritten(size_t len) + { + assert(len <= writableBytes()); + writerIndex_ += len; + } + + void unwrite(size_t len) + { + assert(len <= readableBytes()); + writerIndex_ -= len; + } + + /// + /// Append int64_t using network endian + /// + void appendInt64(int64_t x) + { + int64_t be64 = sockets::hostToNetwork64(x); + append(&be64, sizeof be64); + } + + /// + /// Append int32_t using network endian + /// + void appendInt32(int32_t x) + { + int32_t be32 = sockets::hostToNetwork32(x); + append(&be32, sizeof be32); + } + + void appendInt16(int16_t x) + { + int16_t be16 = sockets::hostToNetwork16(x); + append(&be16, sizeof be16); + } + + void appendInt8(int8_t x) { append(&x, sizeof x); } + + /// + /// Read int64_t from network endian + /// + /// Require: buf->readableBytes() >= sizeof(int32_t) + int64_t readInt64() + { + int64_t result = peekInt64(); + retrieveInt64(); + return result; + } + + /// + /// Read int32_t from network endian + /// + /// Require: buf->readableBytes() >= sizeof(int32_t) + int32_t readInt32() + { + int32_t result = peekInt32(); + retrieveInt32(); + return result; + } + + int16_t readInt16() + { + int16_t result = peekInt16(); + retrieveInt16(); + return result; + } + + int8_t readInt8() + { + int8_t result = peekInt8(); + retrieveInt8(); + return result; + } + + /// + /// Peek int64_t from network endian + /// + /// Require: buf->readableBytes() >= sizeof(int64_t) + int64_t peekInt64() const + { + assert(readableBytes() >= sizeof(int64_t)); + int64_t be64 = 0; + ::memcpy(&be64, peek(), sizeof be64); + return sockets::networkToHost64(be64); + } + + /// + /// Peek int32_t from network endian + /// + /// Require: buf->readableBytes() >= sizeof(int32_t) + int32_t peekInt32() const + { + assert(readableBytes() >= sizeof(int32_t)); + int32_t be32 = 0; + ::memcpy(&be32, peek(), sizeof be32); + return sockets::networkToHost32(be32); + } + + int16_t peekInt16() const + { + assert(readableBytes() >= sizeof(int16_t)); + int16_t be16 = 0; + ::memcpy(&be16, peek(), sizeof be16); + return sockets::networkToHost16(be16); + } + + int8_t peekInt8() const + { + assert(readableBytes() >= sizeof(int8_t)); + int8_t x = *peek(); + return x; + } + + /// + /// Prepend int64_t using network endian + /// + void prependInt64(int64_t x) + { + int64_t be64 = sockets::hostToNetwork64(x); + prepend(&be64, sizeof be64); + } + + /// + /// Prepend int32_t using network endian + /// + void prependInt32(int32_t x) + { + int32_t be32 = sockets::hostToNetwork32(x); + prepend(&be32, sizeof be32); + } + + void prependInt16(int16_t x) + { + int16_t be16 = sockets::hostToNetwork16(x); + prepend(&be16, sizeof be16); + } + + void prependInt8(int8_t x) { prepend(&x, sizeof x); } + + void prepend(const void* /*restrict*/ data, size_t len) + { + assert(len <= prependableBytes()); + readerIndex_ -= len; + const char* d = static_cast<const char*>(data); + std::copy(d, d + len, begin() + readerIndex_); + } + + void shrink(size_t reserve) + { + // FIXME: use vector::shrink_to_fit() in C++ 11 if possible. + Buffer other; + other.ensureWritableBytes(readableBytes() + reserve); + other.append(toStringPiece()); + swap(other); + } + + size_t internalCapacity() const { return buffer_.capacity(); } + + /// Read data directly into buffer. + /// + /// It may implement with readv(2) + /// @return result of read(2), @c errno is saved + ssize_t readFd(int fd, int* savedErrno); + +private: + char* begin() { return &*buffer_.begin(); } + + const char* begin() const { return &*buffer_.begin(); } + + void makeSpace(size_t len) + { + if (writableBytes() + prependableBytes() < len + kCheapPrepend) { + // FIXME: move readable data + buffer_.resize(writerIndex_ + len); + } else { + // move readable data to the front, make space inside buffer + assert(kCheapPrepend < readerIndex_); + size_t readable = readableBytes(); + std::copy(begin() + readerIndex_, begin() + writerIndex_, + begin() + kCheapPrepend); + readerIndex_ = kCheapPrepend; + writerIndex_ = readerIndex_ + readable; + assert(readable == readableBytes()); + } + } + +private: + std::vector<char> buffer_; + size_t readerIndex_; + size_t writerIndex_; + + static const char kCRLF[]; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_BUFFER_H diff --git a/muduo/net/Callbacks.h b/muduo/net/Callbacks.h new file mode 100644 index 0000000..a0c3ffd --- /dev/null +++ b/muduo/net/Callbacks.h @@ -0,0 +1,84 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_CALLBACKS_H +#define MUDUO_NET_CALLBACKS_H + +#include <functional> +#include <memory> + +#include "muduo/base/Timestamp.h" + +namespace muduo { + +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; + +// should really belong to base/Types.h, but <memory> is not included there. + +template <typename T> +inline T* get_pointer(const std::shared_ptr<T>& ptr) +{ + return ptr.get(); +} + +template <typename T> +inline T* get_pointer(const std::unique_ptr<T>& ptr) +{ + return ptr.get(); +} + +// Adapted from google-protobuf stubs/common.h +// see License in muduo/base/Types.h +template <typename To, typename From> +inline ::std::shared_ptr<To> down_pointer_cast(const ::std::shared_ptr<From>& f) +{ + if (false) { + implicit_cast<From*, To*>(0); + } + +#ifndef NDEBUG + assert(f == NULL || dynamic_cast<To*>(get_pointer(f)) != NULL); +#endif + return ::std::static_pointer_cast<To>(f); +} + +namespace net { + +// All client visible callbacks go here. + +class Buffer; +class TcpConnection; + +/* + TcpServer持有目前存活的 TcpConnection 的 shared_ptr(定义为 TcpConnectionPtr), + 因为 TcpConnection 对象的生命期是模糊的,用户也可以持有 TcpConnectionPtr。 +*/ +typedef std::shared_ptr<TcpConnection> TcpConnectionPtr; +typedef std::function<void()> TimerCallback; +typedef std::function<void(const TcpConnectionPtr&)> ConnectionCallback; +typedef std::function<void(const TcpConnectionPtr&)> CloseCallback; +typedef std::function<void(const TcpConnectionPtr&)> WriteCompleteCallback; +typedef std::function<void(const TcpConnectionPtr&, size_t)> + HighWaterMarkCallback; + +// the data has been read to (buf, len) +typedef std::function<void(const TcpConnectionPtr&, Buffer*, Timestamp)> + MessageCallback; + +void defaultConnectionCallback(const TcpConnectionPtr& conn); +void defaultMessageCallback(const TcpConnectionPtr& conn, Buffer* buffer, + Timestamp receiveTime); + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_CALLBACKS_H diff --git a/muduo/net/Channel.cc b/muduo/net/Channel.cc new file mode 100644 index 0000000..8ed5690 --- /dev/null +++ b/muduo/net/Channel.cc @@ -0,0 +1,167 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/Channel.h" + +#include <poll.h> + +#include <sstream> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +const int Channel::kNoneEvent = 0; +const int Channel::kReadEvent = POLLIN | POLLPRI; +const int Channel::kWriteEvent = POLLOUT; + +Channel::Channel(EventLoop* loop, int fd__) + : loop_(loop), + fd_(fd__), + events_(0), + revents_(0), + index_(-1), + logHup_(true), + tied_(false), + eventHandling_(false), + addedToLoop_(false) +{ +} + +Channel::~Channel() +{ + assert(!eventHandling_); + assert(!addedToLoop_); + if (loop_->isInLoopThread()) { + assert(!loop_->hasChannel(this)); + } +} + +void Channel::tie(const std::shared_ptr<void>& obj) +{ + tie_ = obj; + tied_ = true; +} + +void Channel::update() +{ + addedToLoop_ = true; + loop_->updateChannel(this); +} + +void Channel::remove() +{ + assert(isNoneEvent()); + addedToLoop_ = false; + loop_->removeChannel(this); +} + +/* + Channel::handleEvent() 是 Channel 的核心,它由 EventLoop::loop() 调用,它 + 的功能是根据 revents_ 的值分别调用不同的用户回调。 +*/ +void Channel::handleEvent(Timestamp receiveTime) +{ + std::shared_ptr<void> guard; + if (tied_) { + guard = tie_.lock(); + if (guard) { + handleEventWithGuard(receiveTime); + } + } else { + handleEventWithGuard(receiveTime); + } +} + +/* + 简单介绍一下:POLLOUT、POLLPRI 、POLLRDHUP 、POLLIN + + 这些是用于 Linux 中的 poll 系统调用中的事件标志位,它们用于指定对文件描述符进行轮询时感兴趣的事件。以下是对它们的简单介绍: + + POLLOUT: + 意义:表示文件描述符是否可写。如果设置了该标志,表示你对文件描述符上的写操作感兴趣。 + 示例用途:当你希望知道是否可以往某个套接字(socket)写入数据时,可以设置 POLLOUT 标志进行轮询。 + + POLLPRI: + 意义:表示有紧急数据可读。这通常用于带外(out-of-band)数据,例如带外数据的套接字上的紧急指针被设置。 + 示例用途:在带外数据到达时,你可能会设置 POLLPRI 标志进行轮询。 + + POLLRDHUP: + 意义:表示对端关闭连接,或者半关闭的状态。如果设置了该标志,表示你对文件描述符的读操作感兴趣,并且对端关闭了连接(或者半关闭)。 + 示例用途:在网络编程中,你可能希望在对端关闭连接时进行一些清理工作,可以设置 POLLRDHUP 标志进行轮询。 + + POLLIN: + 意义:表示文件描述符是否可读。如果设置了该标志,表示你对文件描述符上的读操作感兴趣。 + 示例用途:当你希望知道是否可以从某个套接字(socket)读取数据时,可以设置 POLLIN 标志进行轮询。 + 这些标志位可通过位掩码进行组合,以满足对多个事件的关注。在 poll 系统调用的使用中,你会将关注的事件标志设置到 struct pollfd 结构体 + 的 events 字段中,然后调用 poll 函数进行轮询。 + + 这段代码根据 revents_ 的值,调用了不同的用户注册的回调函数,以处理不同的事件。 + 这是一个典型的事件驱动编程的实现,通过设置不同的回调函数,用户可以在发生特定事件时执行自定义的操作。 +*/ +void Channel::handleEventWithGuard(Timestamp receiveTime) +{ + eventHandling_ = true; // 这里用于防止在处理事件的过程中被析构。 + LOG_TRACE << reventsToString(); + + /* + revents_ & POLLHUP 这种 与 的语法表示: + 如果发生了 POLLHUP 事件且没有发生 POLLIN 事件,说明连接被挂起(通常表示对端关闭连接)。 + 在这种情况下,如果设置了 logHup_,则记录一个警告日志,然后调用 closeCallback_ 回调函数。 + */ + if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) { + if (logHup_) { + LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP"; + } + if (closeCallback_) closeCallback_(); + } + + /* + revents_ & POLLNVAL: 如果发生了 POLLNVAL 事件,表示描述符没有打开,输出警告日志。 + */ + if (revents_ & POLLNVAL) { + LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL"; + } + + if (revents_ & (POLLERR | POLLNVAL)) { + // 错误回调 + if (errorCallback_) errorCallback_(); + } + if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) { + // 读取回调 + if (readCallback_) readCallback_(receiveTime); + } + if (revents_ & POLLOUT) { + if (writeCallback_) writeCallback_(); + } + eventHandling_ = false; +} + +string Channel::reventsToString() const +{ + return eventsToString(fd_, revents_); +} + +string Channel::eventsToString() const { return eventsToString(fd_, events_); } + +string Channel::eventsToString(int fd, int ev) +{ + std::ostringstream oss; + oss << fd << ": "; + if (ev & POLLIN) oss << "IN "; + if (ev & POLLPRI) oss << "PRI "; + if (ev & POLLOUT) oss << "OUT "; + if (ev & POLLHUP) oss << "HUP "; + if (ev & POLLRDHUP) oss << "RDHUP "; + if (ev & POLLERR) oss << "ERR "; + if (ev & POLLNVAL) oss << "NVAL "; + + return oss.str(); +} diff --git a/muduo/net/Channel.h b/muduo/net/Channel.h new file mode 100644 index 0000000..d226f7c --- /dev/null +++ b/muduo/net/Channel.h @@ -0,0 +1,156 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_CHANNEL_H +#define MUDUO_NET_CHANNEL_H + +#include <functional> +#include <memory> + +#include "muduo/base/Timestamp.h" +#include "muduo/base/noncopyable.h" + +namespace muduo { +namespace net { + +class EventLoop; + +/* + + Channel 类用于表示一个文件描述符(fd)上的事件,这可以是套接字上的可读事件、可写事件等。 + + 每个 Channel 对象自始至终只属于一个 EventLoop,因此每个 Channel 对 + 象都只属于某一个 IO 线程。每个 Channel + 对象自始至终只负责一个文件描述符(fd) 的 IO 事件分发,但它并不拥有这个 + fd,也不会在析构的时候关闭这个 fd。Channel 会把不同的 IO + 事件分发为不同的回调,例如 ReadCallback、WriteCallback 等,而且“回调”用 + boost::function 表示,用户无须继承 Channel,Channel 不是基类。 muduo + 用户一般不直接使用 Channel,而会使用更上层的封装,如 TcpConnection。 Channel + 的生命期由其 owner class 负责管理,它一般是其他 class 的直接或间接成 员。 +*/ + +/// +/// A selectable I/O channel. +/// +/// This class doesn't own the file descriptor. +/// The file descriptor could be a socket, +/// an eventfd, a timerfd, or a signalfd +class Channel : noncopyable { +public: + typedef std::function<void()> EventCallback; + typedef std::function<void(Timestamp)> ReadEventCallback; + + Channel(EventLoop* loop, int fd); + ~Channel(); + + /* + Channel::handleEvent() 是 Channel 的核心,它由 EventLoop::loop() 调用,它 + 的功能是根据 revents_ 的值分别调用不同的用户回调。 + */ + void handleEvent(Timestamp receiveTime); + void setReadCallback(ReadEventCallback cb) + { + readCallback_ = std::move(cb); + } + void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); } + void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); } + void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); } + + /// Tie this channel to the owner object managed by shared_ptr, + /// prevent the owner object being destroyed in handleEvent. + void tie(const std::shared_ptr<void>&); + + int fd() const { return fd_; } + int events() const { return events_; } + void set_revents(int revt) { revents_ = revt; } // used by pollers + // int revents() const { return revents_; } + bool isNoneEvent() const { return events_ == kNoneEvent; } + + void enableReading() + { + events_ |= kReadEvent; + update(); + } + void disableReading() + { + events_ &= ~kReadEvent; + update(); + } + void enableWriting() + { + events_ |= kWriteEvent; + update(); + } + void disableWriting() + { + events_ &= ~kWriteEvent; + update(); + } + void disableAll() + { + events_ = kNoneEvent; + update(); + } + bool isWriting() const { return events_ & kWriteEvent; } + bool isReading() const { return events_ & kReadEvent; } + + // for Poller + int index() { return index_; } + void set_index(int idx) { index_ = idx; } + + // for debug + string reventsToString() const; + string eventsToString() const; + + void doNotLogHup() { logHup_ = false; } + + EventLoop* ownerLoop() { return loop_; } + void remove(); + +private: + static string eventsToString(int fd, int ev); + + /* + Channel 的成员函数都只能在 IO 线程调用,因此更新数据成员都不必加锁。 + */ + void update(); + void handleEventWithGuard(Timestamp receiveTime); + + static const int kNoneEvent; + static const int kReadEvent; + static const int kWriteEvent; + + EventLoop* loop_; + const int fd_; + int events_; // events_ 是它关心的 IO 事件, 由用户设置 + int revents_; // it's the received event types of epoll or poll + + /* + revents_ 是目前活动的事件,由 EventLoop/Poller 设置;这两个字段都是 bit + pattern,它们的名字来自 poll(2) 的 struct pollfd + */ + + int index_; // used by Poller. + bool logHup_; + + std::weak_ptr<void> tie_; + bool tied_; + bool eventHandling_; + bool addedToLoop_; + ReadEventCallback readCallback_; + EventCallback writeCallback_; + EventCallback closeCallback_; + EventCallback errorCallback_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_CHANNEL_H diff --git a/muduo/net/Connector.cc b/muduo/net/Connector.cc new file mode 100644 index 0000000..ddee07b --- /dev/null +++ b/muduo/net/Connector.cc @@ -0,0 +1,208 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/Connector.h" + +#include <errno.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/SocketsOps.h" + +using namespace muduo; +using namespace muduo::net; + +const int Connector::kMaxRetryDelayMs; + +Connector::Connector(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + serverAddr_(serverAddr), + connect_(false), + state_(kDisconnected), + retryDelayMs_(kInitRetryDelayMs) +{ + LOG_DEBUG << "ctor[" << this << "]"; +} + +Connector::~Connector() +{ + LOG_DEBUG << "dtor[" << this << "]"; + assert(!channel_); +} + +void Connector::start() +{ + connect_ = true; + loop_->runInLoop( + std::bind(&Connector::startInLoop, this)); // FIXME: unsafe +} + +void Connector::startInLoop() +{ + loop_->assertInLoopThread(); + assert(state_ == kDisconnected); + if (connect_) { + connect(); + } else { + LOG_DEBUG << "do not connect"; + } +} + +void Connector::stop() +{ + connect_ = false; + loop_->queueInLoop( + std::bind(&Connector::stopInLoop, this)); // FIXME: unsafe + // FIXME: cancel timer +} + +void Connector::stopInLoop() +{ + loop_->assertInLoopThread(); + if (state_ == kConnecting) { + setState(kDisconnected); + int sockfd = removeAndResetChannel(); + retry(sockfd); + } +} + +void Connector::connect() +{ + int sockfd = sockets::createNonblockingOrDie(serverAddr_.family()); + int ret = sockets::connect(sockfd, serverAddr_.getSockAddr()); + int savedErrno = (ret == 0) ? 0 : errno; + switch (savedErrno) { + case 0: + case EINPROGRESS: + case EINTR: + case EISCONN: + connecting(sockfd); + break; + + case EAGAIN: + case EADDRINUSE: + case EADDRNOTAVAIL: + case ECONNREFUSED: + case ENETUNREACH: + retry(sockfd); + break; + + case EACCES: + case EPERM: + case EAFNOSUPPORT: + case EALREADY: + case EBADF: + case EFAULT: + case ENOTSOCK: + LOG_SYSERR << "connect error in Connector::startInLoop " + << savedErrno; + sockets::close(sockfd); + break; + + default: + LOG_SYSERR << "Unexpected error in Connector::startInLoop " + << savedErrno; + sockets::close(sockfd); + // connectErrorCallback_(); + break; + } +} + +void Connector::restart() +{ + loop_->assertInLoopThread(); + setState(kDisconnected); + retryDelayMs_ = kInitRetryDelayMs; + connect_ = true; + startInLoop(); +} + +void Connector::connecting(int sockfd) +{ + setState(kConnecting); + assert(!channel_); + channel_.reset(new Channel(loop_, sockfd)); + channel_->setWriteCallback( + std::bind(&Connector::handleWrite, this)); // FIXME: unsafe + channel_->setErrorCallback( + std::bind(&Connector::handleError, this)); // FIXME: unsafe + + // channel_->tie(shared_from_this()); is not working, + // as channel_ is not managed by shared_ptr + channel_->enableWriting(); +} + +int Connector::removeAndResetChannel() +{ + channel_->disableAll(); + channel_->remove(); + int sockfd = channel_->fd(); + // Can't reset channel_ here, because we are inside Channel::handleEvent + loop_->queueInLoop( + std::bind(&Connector::resetChannel, this)); // FIXME: unsafe + return sockfd; +} + +void Connector::resetChannel() { channel_.reset(); } + +void Connector::handleWrite() +{ + LOG_TRACE << "Connector::handleWrite " << state_; + + if (state_ == kConnecting) { + int sockfd = removeAndResetChannel(); + int err = sockets::getSocketError(sockfd); + if (err) { + LOG_WARN << "Connector::handleWrite - SO_ERROR = " << err << " " + << strerror_tl(err); + retry(sockfd); + } else if (sockets::isSelfConnect(sockfd)) { + LOG_WARN << "Connector::handleWrite - Self connect"; + retry(sockfd); + } else { + setState(kConnected); + if (connect_) { + newConnectionCallback_(sockfd); + } else { + sockets::close(sockfd); + } + } + } else { + // what happened? + assert(state_ == kDisconnected); + } +} + +void Connector::handleError() +{ + LOG_ERROR << "Connector::handleError state=" << state_; + if (state_ == kConnecting) { + int sockfd = removeAndResetChannel(); + int err = sockets::getSocketError(sockfd); + LOG_TRACE << "SO_ERROR = " << err << " " << strerror_tl(err); + retry(sockfd); + } +} + +void Connector::retry(int sockfd) +{ + sockets::close(sockfd); + setState(kDisconnected); + if (connect_) { + LOG_INFO << "Connector::retry - Retry connecting to " + << serverAddr_.toIpPort() << " in " << retryDelayMs_ + << " milliseconds. "; + loop_->runAfter(retryDelayMs_ / 1000.0, + std::bind(&Connector::startInLoop, shared_from_this())); + retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs); + } else { + LOG_DEBUG << "do not connect"; + } +} diff --git a/muduo/net/Connector.h b/muduo/net/Connector.h new file mode 100644 index 0000000..4b79fb2 --- /dev/null +++ b/muduo/net/Connector.h @@ -0,0 +1,74 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_CONNECTOR_H +#define MUDUO_NET_CONNECTOR_H + +#include "muduo/base/noncopyable.h" +#include "muduo/net/InetAddress.h" + +#include <functional> +#include <memory> + +namespace muduo +{ +namespace net +{ + +class Channel; +class EventLoop; + +class Connector : noncopyable, + public std::enable_shared_from_this<Connector> +{ + public: + typedef std::function<void (int sockfd)> NewConnectionCallback; + + Connector(EventLoop* loop, const InetAddress& serverAddr); + ~Connector(); + + void setNewConnectionCallback(const NewConnectionCallback& cb) + { newConnectionCallback_ = cb; } + + void start(); // can be called in any thread + void restart(); // must be called in loop thread + void stop(); // can be called in any thread + + const InetAddress& serverAddress() const { return serverAddr_; } + + private: + enum States { kDisconnected, kConnecting, kConnected }; + static const int kMaxRetryDelayMs = 30*1000; + static const int kInitRetryDelayMs = 500; + + void setState(States s) { state_ = s; } + void startInLoop(); + void stopInLoop(); + void connect(); + void connecting(int sockfd); + void handleWrite(); + void handleError(); + void retry(int sockfd); + int removeAndResetChannel(); + void resetChannel(); + + EventLoop* loop_; + InetAddress serverAddr_; + bool connect_; // atomic + States state_; // FIXME: use atomic variable + std::unique_ptr<Channel> channel_; + NewConnectionCallback newConnectionCallback_; + int retryDelayMs_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_CONNECTOR_H diff --git a/muduo/net/Endian.h b/muduo/net/Endian.h new file mode 100644 index 0000000..82bd730 --- /dev/null +++ b/muduo/net/Endian.h @@ -0,0 +1,65 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_ENDIAN_H +#define MUDUO_NET_ENDIAN_H + +#include <stdint.h> +#include <endian.h> + +namespace muduo +{ +namespace net +{ +namespace sockets +{ + +// the inline assembler code makes type blur, +// so we disable warnings for a while. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wold-style-cast" +inline uint64_t hostToNetwork64(uint64_t host64) +{ + return htobe64(host64); +} + +inline uint32_t hostToNetwork32(uint32_t host32) +{ + return htobe32(host32); +} + +inline uint16_t hostToNetwork16(uint16_t host16) +{ + return htobe16(host16); +} + +inline uint64_t networkToHost64(uint64_t net64) +{ + return be64toh(net64); +} + +inline uint32_t networkToHost32(uint32_t net32) +{ + return be32toh(net32); +} + +inline uint16_t networkToHost16(uint16_t net16) +{ + return be16toh(net16); +} + +#pragma GCC diagnostic pop + +} // namespace sockets +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_ENDIAN_H diff --git a/muduo/net/EventLoop.cc b/muduo/net/EventLoop.cc new file mode 100644 index 0000000..ab472f1 --- /dev/null +++ b/muduo/net/EventLoop.cc @@ -0,0 +1,301 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/EventLoop.h" + +#include <signal.h> +#include <sys/eventfd.h> +#include <unistd.h> + +#include <algorithm> + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/Channel.h" +#include "muduo/net/Poller.h" +#include "muduo/net/SocketsOps.h" +#include "muduo/net/TimerQueue.h" + +using namespace muduo; +using namespace muduo::net; + +namespace { + +/* + __thread EventLoop* t_loopInThisThread = 0, + 中的`__thread`是什么语法或者写法? + + 在 C++ 中,`__thread` 是一种线程局部存储(Thread + LocalStorage,TLS)的修饰符。它用于声明一个变量, + 该变量的生命周期与线程的生命周期相绑定,每个线程都有一份独立的拷贝,互不干扰。需要注意的是,使用 + `__thread` 修饰符 的变量通常不建议直接用于 C++11 及以上的新代码。在 C++11 + 及以上版本,推荐使用 `thread_local` 关键字,因为它更加标准化和可移植。 +*/ + +// __thread EventLoop* t_loopInThisThread = 0; +thread_local EventLoop* t_loopInThisThread = 0; + +const int kPollTimeMs = 10000; + +int createEventfd() +{ + int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (evtfd < 0) { + LOG_SYSERR << "Failed in eventfd"; + abort(); + } + return evtfd; +} + +#pragma GCC diagnostic ignored "-Wold-style-cast" +class IgnoreSigPipe { +public: + IgnoreSigPipe() + { + ::signal(SIGPIPE, SIG_IGN); + // LOG_TRACE << "Ignore SIGPIPE"; + } +}; +#pragma GCC diagnostic error "-Wold-style-cast" + +IgnoreSigPipe initObj; +} // namespace + +EventLoop* EventLoop::getEventLoopOfCurrentThread() +{ + return t_loopInThisThread; +} + +EventLoop::EventLoop() + : looping_(false), + quit_(false), + eventHandling_(false), + callingPendingFunctors_(false), + iteration_(0), + // EventLoop 的构造函数会记住本对象所属的线程, 创建了 EventLoop + // 对象的线程是 IO 线程, 其主要功能是运行事件循环 EventLoop::loop() + threadId_(CurrentThread::tid()), + poller_(Poller::newDefaultPoller(this)), + timerQueue_(new TimerQueue(this)), + wakeupFd_(createEventfd()), + wakeupChannel_(new Channel(this, wakeupFd_)), + currentActiveChannel_(NULL) +{ + LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_; + if (t_loopInThisThread) { + LOG_FATAL << "Another EventLoop " << t_loopInThisThread + << " exists in this thread " << threadId_; + } else { + t_loopInThisThread = this; + } + wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this)); + // we are always reading the wakeupfd + wakeupChannel_->enableReading(); +} + +EventLoop::~EventLoop() +{ + LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_ + << " destructs in thread " << CurrentThread::tid(); + wakeupChannel_->disableAll(); + wakeupChannel_->remove(); + ::close(wakeupFd_); + t_loopInThisThread = NULL; +} + +void EventLoop::loop() +{ + assert(!looping_); + assertInLoopThread(); + looping_ = true; + quit_ = false; // FIXME: what if someone calls quit() before loop() ? + LOG_TRACE << "EventLoop " << this << " start looping"; + + while (!quit_) { + activeChannels_.clear(); + // 它调用 Poller::poll() 获得当前活动事件的 Channel + // 列表,然后依次调用每个 Channel 的 handleEvent() 函数。 + pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); + ++iteration_; + if (Logger::logLevel() <= Logger::TRACE) { + printActiveChannels(); + } + // TODO sort channel by priority + eventHandling_ = true; + for (Channel* channel : activeChannels_) { + currentActiveChannel_ = channel; + currentActiveChannel_->handleEvent(pollReturnTime_); + } + currentActiveChannel_ = NULL; + eventHandling_ = false; + doPendingFunctors(); + } + + LOG_TRACE << "EventLoop " << this << " stop looping"; + looping_ = false; +} + +void EventLoop::quit() +{ + quit_ = true; + // There is a chance that loop() just executes while(!quit_) and exits, + // then EventLoop destructs, then we are accessing an invalid object. + // Can be fixed using mutex_ in both places. + if (!isInLoopThread()) { + wakeup(); + } +} + +void EventLoop::runInLoop(Functor cb) +{ + if (isInLoopThread()) { + cb(); + } else { + queueInLoop(std::move(cb)); + } +} + +void EventLoop::queueInLoop(Functor cb) +{ + { + /* + pendingFunctors_ 暴露给了其他线程,因此用 mutex 保护。 + */ + MutexLockGuard lock(mutex_); + pendingFunctors_.push_back(std::move(cb)); + } + + /* + 如果用户在其他线程调用 runInLoop(),cb 会被加入队列,IO + 线程会被唤醒来调用这个 Functor。 + */ + if (!isInLoopThread() || callingPendingFunctors_) { + wakeup(); + } +} + +size_t EventLoop::queueSize() const +{ + MutexLockGuard lock(mutex_); + return pendingFunctors_.size(); +} + +TimerId EventLoop::runAt(Timestamp time, TimerCallback cb) +{ + return timerQueue_->addTimer(std::move(cb), time, 0.0); +} + +TimerId EventLoop::runAfter(double delay, TimerCallback cb) +{ + Timestamp time(addTime(Timestamp::now(), delay)); + return runAt(time, std::move(cb)); +} + +TimerId EventLoop::runEvery(double interval, TimerCallback cb) +{ + Timestamp time(addTime(Timestamp::now(), interval)); + return timerQueue_->addTimer(std::move(cb), time, interval); +} + +void EventLoop::cancel(TimerId timerId) { return timerQueue_->cancel(timerId); } + +void EventLoop::updateChannel(Channel* channel) +{ + assert(channel->ownerLoop() == this); // 验证传入的 channel 属于我这个 loop + assertInLoopThread(); // 验证是否在创建 loop 的线程中调用的 updateChannel + poller_->updateChannel(channel); +} + +void EventLoop::removeChannel(Channel* channel) +{ + assert(channel->ownerLoop() == this); + assertInLoopThread(); + if (eventHandling_) { + assert(currentActiveChannel_ == channel || + std::find(activeChannels_.begin(), activeChannels_.end(), + channel) == activeChannels_.end()); + } + poller_->removeChannel(channel); +} + +bool EventLoop::hasChannel(Channel* channel) +{ + assert(channel->ownerLoop() == this); + assertInLoopThread(); + return poller_->hasChannel(channel); +} + +void EventLoop::abortNotInLoopThread() +{ + LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this + << " was created in threadId_ = " << threadId_ + << ", current thread id = " << CurrentThread::tid(); +} + +/* + 由于 IO 线程平时阻塞在事件循环 EventLoop::loop() 的 poll(2) 调用中,为了 + 让 IO 线程能立刻执行用户回调,我们需要设法唤醒它。传统的办法是用 pipe(2), + IO 线程始终监视此管道的 readable 事件,在需要唤醒的时候,其他线程往管道里 + 写一个字节,这样 IO 线程就从 IO multiplexing 阻塞调用中返回。(原理类似 HTTP + long polling。)现在 Linux 有了 + eventfd(2),可以更高效地唤醒,因为它不必管理缓 冲区。 +*/ +void EventLoop::wakeup() +{ + uint64_t one = 1; + ssize_t n = sockets::write(wakeupFd_, &one, sizeof one); + if (n != sizeof one) { + LOG_ERROR << "EventLoop::wakeup() writes " << n + << " bytes instead of 8"; + } +} + +void EventLoop::handleRead() +{ + uint64_t one = 1; + ssize_t n = sockets::read(wakeupFd_, &one, sizeof one); + if (n != sizeof one) { + LOG_ERROR << "EventLoop::handleRead() reads " << n + << " bytes instead of 8"; + } +} + +/* + EventLoop::doPendingFunctors() 不是简单地在临界区内依次调用 Functor,而 + 是把回调列表 swap() 到局部变量 functors 中,这样一方面减小了临界区的长度(意 + 味着不会阻塞其他线程调用 queueInLoop()),另一方面也避免了死锁(因为 Functor + 可能再调用 queueInLoop())。 + + 由于 doPendingFunctors() 调用的 Functor 可能再调用 queueInLoop(cb),这时 + queueInLoop() 就必须 wakeup(),否则这些新加的 cb 就不能被及时调用了。muduo + 这里没有反复执行 doPendingFunctors() 直到 pendingFunctors_ + 为空,这是有意的, 否则 IO 线程有可能陷入死循环,无法处理 IO 事件。 + +*/ +void EventLoop::doPendingFunctors() +{ + std::vector<Functor> functors; + callingPendingFunctors_ = true; + + { + MutexLockGuard lock(mutex_); + functors.swap(pendingFunctors_); + } + + for (const Functor& functor : functors) { + functor(); + } + callingPendingFunctors_ = false; +} + +void EventLoop::printActiveChannels() const +{ + for (const Channel* channel : activeChannels_) { + LOG_TRACE << "{" << channel->reventsToString() << "} "; + } +} diff --git a/muduo/net/EventLoop.h b/muduo/net/EventLoop.h new file mode 100644 index 0000000..4e02484 --- /dev/null +++ b/muduo/net/EventLoop.h @@ -0,0 +1,198 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_EVENTLOOP_H +#define MUDUO_NET_EVENTLOOP_H + +#include <atomic> +#include <boost/any.hpp> +#include <functional> +#include <vector> + +#include "muduo/base/CurrentThread.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Timestamp.h" +#include "muduo/net/Callbacks.h" +#include "muduo/net/TimerId.h" + +namespace muduo { +namespace net { + +class Channel; +class Poller; +class TimerQueue; + +/* + EventLoop 的构造函数会记住本对象所属的线程(threadId_)。 + 创建了 EventLoop 对象的线程是 IO 线程,其主要功能是运行事件循环 EventLoop:: + loop()。 +*/ +/// +/// Reactor, at most one per thread. +/// +/// This is an interface class, so don't expose too much details. +class EventLoop : noncopyable { +public: + typedef std::function<void()> Functor; + + EventLoop(); + ~EventLoop(); // force out-line dtor, for std::unique_ptr members. + + /// + /// Loops forever. + /// + /// Must be called in the same thread as creation of the object. + /// + /* + 事件循环必须在 IO 线程执行,因此 EventLoop::loop() 会检查这一 + pre-condition。 + */ + void loop(); + + /// Quits loop. + /// + /// This is not 100% thread safe, if you call through a raw pointer, + /// better to call through shared_ptr<EventLoop> for 100% safety. + void quit(); + + /// + /// Time when poll returns, usually means data arrival. + /// + Timestamp pollReturnTime() const { return pollReturnTime_; } + + int64_t iteration() const { return iteration_; } + + /* + EventLoop 有一个非常有用的功能:在它的 IO 线程内执行某个用户任务回调, + 即 EventLoop::runInLoop(const Functor& cb),其中 Functor 是 + boost::function<void()>。 如果用户在当前 IO + 线程调用这个函数,回调会同步进行;如果用户在其他线程调用 runInLoop(),cb + 会被加入队列,IO 线程会被唤醒来调用这个 Functor。 + */ + /// Runs callback immediately in the loop thread. + /// It wakes up the loop, and run the cb. + /// If in the same loop thread, cb is run within the function. + /// Safe to call from other threads. + void runInLoop(Functor cb); + /// Queues callback in the loop thread. + /// Runs after finish pooling. + /// Safe to call from other threads. + void queueInLoop(Functor cb); + + size_t queueSize() const; + + // timers + + /* + EventLoop 新增了几个方便用户使用的定时器接口,这几个函数都转而调用 + TimerQueue::addTimer()。 + + 注意这几个 EventLoop 成员函数应该允许跨线程使用,比 + 方说我想在某个 IO + 线程中执行超时回调。这就带来线程安全性方面的问题,muduo + 的解决办法不是加锁,而是把对 TimerQueue 的操作转移到 IO + 线程来进行,这会用到 EventLoop::runInLoop() 函数。 + */ + + /// + /// Runs callback at 'time'. + /// Safe to call from other threads. + /// + TimerId runAt(Timestamp time, TimerCallback cb); + /// + /// Runs callback after @c delay seconds. + /// Safe to call from other threads. + /// + TimerId runAfter(double delay, TimerCallback cb); + /// + /// Runs callback every @c interval seconds. + /// Safe to call from other threads. + /// + TimerId runEvery(double interval, TimerCallback cb); + /// + /// Cancels the timer. + /// Safe to call from other threads. + /// + void cancel(TimerId timerId); + + // internal usage + void wakeup(); + void updateChannel(Channel* channel); + void removeChannel(Channel* channel); + bool hasChannel(Channel* channel); + + /* + muduo 的接口设计会明确哪些成员函数是线程安全的,可以跨线程调用;哪 + 些成员函数只能在某个特定线程调用(主要是 IO + 线程)。为了能在运行时检查这些 pre-condition,EventLoop 提供了 + isInLoopThread() 和 assertInLoopThread() 等函 数 + */ + // pid_t threadId() const { return threadId_; } + void assertInLoopThread() + { + if (!isInLoopThread()) { + abortNotInLoopThread(); + } + } + // 这里的 threadId_ 在实例对象被构造的时候就保存了构造该对象的线程的 ID + // 这里判断是不是在同一个线程中。 + bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); } + // bool callingPendingFunctors() const { return callingPendingFunctors_; } + bool eventHandling() const { return eventHandling_; } + + void setContext(const boost::any& context) { context_ = context; } + + const boost::any& getContext() const { return context_; } + + boost::any* getMutableContext() { return &context_; } + + /* + 既然每个线程至多有一个 EventLoop 对象,那么我们让 EventLoop 的 static + 成员 函数 getEventLoopOfCurrentThread() 返回这个对象。返回值可能为 + NULL,如果当前 线程不是 IO 线程的话。 + */ + static EventLoop* getEventLoopOfCurrentThread(); + +private: + void abortNotInLoopThread(); + void handleRead(); // waked up + void doPendingFunctors(); + + void printActiveChannels() const; // DEBUG + + typedef std::vector<Channel*> ChannelList; + + bool looping_; /* atomic */ + std::atomic<bool> quit_; + bool eventHandling_; /* atomic */ + bool callingPendingFunctors_; /* atomic */ + int64_t iteration_; + const pid_t threadId_; + Timestamp pollReturnTime_; + std::unique_ptr<Poller> poller_; + std::unique_ptr<TimerQueue> timerQueue_; + int wakeupFd_; + // unlike in TimerQueue, which is an internal class, + // we don't expose Channel to client. + std::unique_ptr<Channel> wakeupChannel_; + boost::any context_; + + // scratch variables + ChannelList activeChannels_; + Channel* currentActiveChannel_; + + mutable MutexLock mutex_; + std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_); +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_EVENTLOOP_H diff --git a/muduo/net/EventLoopThread.cc b/muduo/net/EventLoopThread.cc new file mode 100644 index 0000000..e0ac08e --- /dev/null +++ b/muduo/net/EventLoopThread.cc @@ -0,0 +1,87 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/EventLoopThread.h" + +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +EventLoopThread::EventLoopThread(const ThreadInitCallback& cb, + const string& name) + : loop_(NULL), + exiting_(false), + thread_(std::bind(&EventLoopThread::threadFunc, this), name), + mutex_(), + cond_(mutex_), + callback_(cb) +{ +} + +EventLoopThread::~EventLoopThread() +{ + exiting_ = true; + if (loop_ != + NULL) // not 100% race-free, eg. threadFunc could be running callback_. + { + // still a tiny chance to call destructed object, if threadFunc exits + // just now. but when EventLoopThread destructs, usually programming is + // exiting anyway. + loop_->quit(); + thread_.join(); + } +} + +/* + EventLoopThread 会启动自己的线程,并在其中运行 EventLoop::loop()。其中 + 关键的 startLoop() 函数定义如下,这个函数会返回新线程中 EventLoop 对象的地 + 址,因此用条件变量来等待线程的创建与运行。 +*/ +EventLoop* EventLoopThread::startLoop() +{ + assert(!thread_.started()); + thread_.start(); + + EventLoop* loop = NULL; + { + MutexLockGuard lock(mutex_); + while (loop_ == NULL) { + cond_.wait(); + } + loop = loop_; + } + + return loop; +} + +void EventLoopThread::threadFunc() +{ + EventLoop loop; + + if (callback_) { + callback_(&loop); + } + + { + MutexLockGuard lock(mutex_); + loop_ = &loop; + cond_.notify(); + } + + loop.loop(); + +/* + 由于 EventLoop 的生命期与线程主函数的作用域相同,因此在 threadFunc() 退 + 出之后这个指针就失效了。好在服务程序一般不要求能安全地退出(§9.2.2),这应该 + 不是什么大问题。 +*/ + // assert(exiting_); + MutexLockGuard lock(mutex_); + loop_ = NULL; +} diff --git a/muduo/net/EventLoopThread.h b/muduo/net/EventLoopThread.h new file mode 100644 index 0000000..a42d83b --- /dev/null +++ b/muduo/net/EventLoopThread.h @@ -0,0 +1,52 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_EVENTLOOPTHREAD_H +#define MUDUO_NET_EVENTLOOPTHREAD_H + +#include "muduo/base/Condition.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" + +namespace muduo { +namespace net { + +class EventLoop; + +/* + IO 线程不一定是主线程,我们可以在任何一个线程创建并运行 EventLoop。一个 + 程序也可以有不止一个 IO 线程,我们可以按优先级将不同的 socket 分给不同的 IO + 线程,避免优先级反转。为了方便将来使用,我们定义 EventLoopThread class,这正 + 是 one loop per thread 的本意。 +*/ +class EventLoopThread : noncopyable { +public: + typedef std::function<void(EventLoop*)> ThreadInitCallback; + + EventLoopThread(const ThreadInitCallback& cb = ThreadInitCallback(), + const string& name = string()); + ~EventLoopThread(); + EventLoop* startLoop(); + +private: + void threadFunc(); + + EventLoop* loop_ GUARDED_BY(mutex_); + bool exiting_; + Thread thread_; + MutexLock mutex_; + Condition cond_ GUARDED_BY(mutex_); + ThreadInitCallback callback_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_EVENTLOOPTHREAD_H diff --git a/muduo/net/EventLoopThreadPool.cc b/muduo/net/EventLoopThreadPool.cc new file mode 100644 index 0000000..8974927 --- /dev/null +++ b/muduo/net/EventLoopThreadPool.cc @@ -0,0 +1,90 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/EventLoopThreadPool.h" + +#include <stdio.h> + +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +using namespace muduo; +using namespace muduo::net; + +EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop, + const string& nameArg) + : baseLoop_(baseLoop), + name_(nameArg), + started_(false), + numThreads_(0), + next_(0) +{ +} + +EventLoopThreadPool::~EventLoopThreadPool() +{ + // Don't delete loop, it's stack variable +} + +void EventLoopThreadPool::start(const ThreadInitCallback& cb) +{ + assert(!started_); + baseLoop_->assertInLoopThread(); + + started_ = true; + + for (int i = 0; i < numThreads_; ++i) { + char buf[name_.size() + 32]; + snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i); + EventLoopThread* t = new EventLoopThread(cb, buf); + threads_.push_back(std::unique_ptr<EventLoopThread>(t)); + loops_.push_back(t->startLoop()); + } + if (numThreads_ == 0 && cb) { + cb(baseLoop_); + } +} + +EventLoop* EventLoopThreadPool::getNextLoop() +{ + baseLoop_->assertInLoopThread(); + assert(started_); + EventLoop* loop = baseLoop_; + + if (!loops_.empty()) { + // round-robin + loop = loops_[next_]; + ++next_; + if (implicit_cast<size_t>(next_) >= loops_.size()) { + next_ = 0; + } + } + return loop; +} + +EventLoop* EventLoopThreadPool::getLoopForHash(size_t hashCode) +{ + baseLoop_->assertInLoopThread(); + EventLoop* loop = baseLoop_; + + if (!loops_.empty()) { + loop = loops_[hashCode % loops_.size()]; + } + return loop; +} + +std::vector<EventLoop*> EventLoopThreadPool::getAllLoops() +{ + baseLoop_->assertInLoopThread(); + assert(started_); + if (loops_.empty()) { + return std::vector<EventLoop*>(1, baseLoop_); + } else { + return loops_; + } +} diff --git a/muduo/net/EventLoopThreadPool.h b/muduo/net/EventLoopThreadPool.h new file mode 100644 index 0000000..0c55e8f --- /dev/null +++ b/muduo/net/EventLoopThreadPool.h @@ -0,0 +1,63 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_EVENTLOOPTHREADPOOL_H +#define MUDUO_NET_EVENTLOOPTHREADPOOL_H + +#include <functional> +#include <memory> +#include <vector> + +#include "muduo/base/Types.h" +#include "muduo/base/noncopyable.h" + +namespace muduo { + +namespace net { + +class EventLoop; +class EventLoopThread; + +class EventLoopThreadPool : noncopyable { +public: + typedef std::function<void(EventLoop*)> ThreadInitCallback; + + EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg); + ~EventLoopThreadPool(); + void setThreadNum(int numThreads) { numThreads_ = numThreads; } + void start(const ThreadInitCallback& cb = ThreadInitCallback()); + + // valid after calling start() + /// round-robin + EventLoop* getNextLoop(); + + /// with the same hash code, it will always return the same EventLoop + EventLoop* getLoopForHash(size_t hashCode); + + std::vector<EventLoop*> getAllLoops(); + + bool started() const { return started_; } + + const string& name() const { return name_; } + +private: + EventLoop* baseLoop_; + string name_; + bool started_; + int numThreads_; + int next_; + std::vector<std::unique_ptr<EventLoopThread>> threads_; + std::vector<EventLoop*> loops_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_EVENTLOOPTHREADPOOL_H diff --git a/muduo/net/InetAddress.cc b/muduo/net/InetAddress.cc new file mode 100644 index 0000000..1aa1b2e --- /dev/null +++ b/muduo/net/InetAddress.cc @@ -0,0 +1,139 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/InetAddress.h" + +#include <netdb.h> +#include <netinet/in.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" +#include "muduo/net/SocketsOps.h" + +// INADDR_ANY use (type)value casting. +#pragma GCC diagnostic ignored "-Wold-style-cast" +static const in_addr_t kInaddrAny = INADDR_ANY; +static const in_addr_t kInaddrLoopback = INADDR_LOOPBACK; +#pragma GCC diagnostic error "-Wold-style-cast" + +// /* Structure describing an Internet socket address. */ +// struct sockaddr_in { +// sa_family_t sin_family; /* address family: AF_INET */ +// uint16_t sin_port; /* port in network byte order */ +// struct in_addr sin_addr; /* internet address */ +// }; + +// /* Internet address. */ +// typedef uint32_t in_addr_t; +// struct in_addr { +// in_addr_t s_addr; /* address in network byte order */ +// }; + +// struct sockaddr_in6 { +// sa_family_t sin6_family; /* address family: AF_INET6 */ +// uint16_t sin6_port; /* port in network byte order */ +// uint32_t sin6_flowinfo; /* IPv6 flow information */ +// struct in6_addr sin6_addr; /* IPv6 address */ +// uint32_t sin6_scope_id; /* IPv6 scope-id */ +// }; + +using namespace muduo; +using namespace muduo::net; + +static_assert(sizeof(InetAddress) == sizeof(struct sockaddr_in6), + "InetAddress is same size as sockaddr_in6"); +static_assert(offsetof(sockaddr_in, sin_family) == 0, "sin_family offset 0"); +static_assert(offsetof(sockaddr_in6, sin6_family) == 0, "sin6_family offset 0"); +static_assert(offsetof(sockaddr_in, sin_port) == 2, "sin_port offset 2"); +static_assert(offsetof(sockaddr_in6, sin6_port) == 2, "sin6_port offset 2"); + +InetAddress::InetAddress(uint16_t port, bool loopbackOnly, bool ipv6) +{ + static_assert(offsetof(InetAddress, addr6_) == 0, "addr6_ offset 0"); + static_assert(offsetof(InetAddress, addr_) == 0, "addr_ offset 0"); + if (ipv6) { + memZero(&addr6_, sizeof addr6_); + addr6_.sin6_family = AF_INET6; + in6_addr ip = loopbackOnly ? in6addr_loopback : in6addr_any; + addr6_.sin6_addr = ip; + addr6_.sin6_port = sockets::hostToNetwork16(port); + } else { + memZero(&addr_, sizeof addr_); + addr_.sin_family = AF_INET; + in_addr_t ip = loopbackOnly ? kInaddrLoopback : kInaddrAny; + addr_.sin_addr.s_addr = sockets::hostToNetwork32(ip); + addr_.sin_port = sockets::hostToNetwork16(port); + } +} + +InetAddress::InetAddress(StringArg ip, uint16_t port, bool ipv6) +{ + if (ipv6) { + memZero(&addr6_, sizeof addr6_); + sockets::fromIpPort(ip.c_str(), port, &addr6_); + } else { + memZero(&addr_, sizeof addr_); + sockets::fromIpPort(ip.c_str(), port, &addr_); + } +} + +string InetAddress::toIpPort() const +{ + char buf[64] = ""; + sockets::toIpPort(buf, sizeof buf, getSockAddr()); + return buf; +} + +string InetAddress::toIp() const +{ + char buf[64] = ""; + sockets::toIp(buf, sizeof buf, getSockAddr()); + return buf; +} + +uint32_t InetAddress::ipNetEndian() const +{ + assert(family() == AF_INET); + return addr_.sin_addr.s_addr; +} + +uint16_t InetAddress::toPort() const +{ + return sockets::networkToHost16(portNetEndian()); +} + +static __thread char t_resolveBuffer[64 * 1024]; + +bool InetAddress::resolve(StringArg hostname, InetAddress* out) +{ + assert(out != NULL); + struct hostent hent; + struct hostent* he = NULL; + int herrno = 0; + memZero(&hent, sizeof(hent)); + + int ret = gethostbyname_r(hostname.c_str(), &hent, t_resolveBuffer, + sizeof t_resolveBuffer, &he, &herrno); + if (ret == 0 && he != NULL) { + assert(he->h_addrtype == AF_INET && he->h_length == sizeof(uint32_t)); + out->addr_.sin_addr = *reinterpret_cast<struct in_addr*>(he->h_addr); + return true; + } else { + if (ret) { + LOG_SYSERR << "InetAddress::resolve"; + } + return false; + } +} + +void InetAddress::setScopeId(uint32_t scope_id) +{ + if (family() == AF_INET6) { + addr6_.sin6_scope_id = scope_id; + } +} \ No newline at end of file diff --git a/muduo/net/InetAddress.h b/muduo/net/InetAddress.h new file mode 100644 index 0000000..6e1a3e2 --- /dev/null +++ b/muduo/net/InetAddress.h @@ -0,0 +1,86 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_INETADDRESS_H +#define MUDUO_NET_INETADDRESS_H + +#include <netinet/in.h> + +#include "muduo/base/StringPiece.h" +#include "muduo/base/copyable.h" + +namespace muduo { +namespace net { +namespace sockets { +const struct sockaddr* sockaddr_cast(const struct sockaddr_in6* addr); +} + +/* + InetAddress class,这是对 struct sockaddr_in 的简单 + 封装,能自动转换字节序。InetAddress 具备值语义,是可以拷贝的。 +*/ +/// +/// Wrapper of sockaddr_in. +/// +/// This is an POD interface class. +class InetAddress : public muduo::copyable { +public: + /// Constructs an endpoint with given port number. + /// Mostly used in TcpServer listening. + explicit InetAddress(uint16_t port = 0, bool loopbackOnly = false, + bool ipv6 = false); + + /// Constructs an endpoint with given ip and port. + /// @c ip should be "1.2.3.4" + InetAddress(StringArg ip, uint16_t port, bool ipv6 = false); + + /// Constructs an endpoint with given struct @c sockaddr_in + /// Mostly used when accepting new connections + explicit InetAddress(const struct sockaddr_in& addr) : addr_(addr) {} + + explicit InetAddress(const struct sockaddr_in6& addr) : addr6_(addr) {} + + sa_family_t family() const { return addr_.sin_family; } + string toIp() const; + string toIpPort() const; + uint16_t toPort() const; + + // default copy/assignment are Okay + + const struct sockaddr* getSockAddr() const + { + return sockets::sockaddr_cast(&addr6_); + } + void setSockAddrInet6(const struct sockaddr_in6& addr6) { addr6_ = addr6; } + + uint32_t ipNetEndian() const; + uint16_t portNetEndian() const { return addr_.sin_port; } + + // resolve hostname to IP address, not changing port or sin_family + // return true on success. + // thread safe + static bool resolve(StringArg hostname, InetAddress* result); + // static std::vector<InetAddress> resolveAll(const char* hostname, uint16_t + // port = 0); + + // set IPv6 ScopeID + void setScopeId(uint32_t scope_id); + +private: + union { + struct sockaddr_in addr_; + struct sockaddr_in6 addr6_; + }; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_INETADDRESS_H diff --git a/muduo/net/Poller.cc b/muduo/net/Poller.cc new file mode 100644 index 0000000..cfe4c3c --- /dev/null +++ b/muduo/net/Poller.cc @@ -0,0 +1,29 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/Poller.h" + +#include "muduo/net/Channel.h" + +using namespace muduo; +using namespace muduo::net; + +Poller::Poller(EventLoop* loop) + : ownerLoop_(loop) +{ +} + +Poller::~Poller() = default; + +bool Poller::hasChannel(Channel* channel) const +{ + assertInLoopThread(); + ChannelMap::const_iterator it = channels_.find(channel->fd()); + return it != channels_.end() && it->second == channel; +} + diff --git a/muduo/net/Poller.h b/muduo/net/Poller.h new file mode 100644 index 0000000..f3c5f03 --- /dev/null +++ b/muduo/net/Poller.h @@ -0,0 +1,78 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_POLLER_H +#define MUDUO_NET_POLLER_H + +#include <map> +#include <vector> + +#include "muduo/base/Timestamp.h" +#include "muduo/net/EventLoop.h" + +namespace muduo { +namespace net { + +class Channel; + +/* + Poller class 是 IO multiplexing 的封装。它现在是个具体类,而在 muduo 中是 + 个抽象基类,因为 muduo 同时支持 poll(2) 和 epoll(4) 两种 IO multiplexing + 机制。 Poller 是 EventLoop 的间接成员,只供其 owner EventLoop 在 IO + 线程调用,因此无 须加锁。其生命期与 EventLoop 相等。 + + Poller 并不拥有 Channel,Channel 在析构之 + 前必须自己 unregister (EventLoop::removeChannel()),避免空悬指针。 +*/ + +/// +/// Base class for IO Multiplexing +/// +/// This class doesn't own the Channel objects. +class Poller : noncopyable { +public: + typedef std::vector<Channel*> ChannelList; + + Poller(EventLoop* loop); + virtual ~Poller(); + + /// Polls the I/O events. + /// Must be called in the loop thread. + virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0; + + /// Changes the interested I/O events. + /// Must be called in the loop thread. + virtual void updateChannel(Channel* channel) = 0; + + /// Remove the channel, when it destructs. + /// Must be called in the loop thread. + virtual void removeChannel(Channel* channel) = 0; + + virtual bool hasChannel(Channel* channel) const; + + static Poller* newDefaultPoller(EventLoop* loop); + + void assertInLoopThread() const { ownerLoop_->assertInLoopThread(); } + +protected: +/* + ChannelMap 是从 fd 到 Channel* 的映射。 +*/ + typedef std::map<int, Channel*> ChannelMap; + ChannelMap channels_; + +private: + EventLoop* ownerLoop_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_POLLER_H diff --git a/muduo/net/Socket.cc b/muduo/net/Socket.cc new file mode 100644 index 0000000..bf6ee46 --- /dev/null +++ b/muduo/net/Socket.cc @@ -0,0 +1,114 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/Socket.h" + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <stdio.h> // snprintf + +#include "muduo/base/Logging.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/SocketsOps.h" + +using namespace muduo; +using namespace muduo::net; + +Socket::~Socket() { sockets::close(sockfd_); } + +bool Socket::getTcpInfo(struct tcp_info* tcpi) const +{ + socklen_t len = sizeof(*tcpi); + memZero(tcpi, len); + return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0; +} + +bool Socket::getTcpInfoString(char* buf, int len) const +{ + struct tcp_info tcpi; + bool ok = getTcpInfo(&tcpi); + if (ok) { + snprintf(buf, len, + "unrecovered=%u " + "rto=%u ato=%u snd_mss=%u rcv_mss=%u " + "lost=%u retrans=%u rtt=%u rttvar=%u " + "sshthresh=%u cwnd=%u total_retrans=%u", + tcpi.tcpi_retransmits, // Number of unrecovered [RTO] timeouts + tcpi.tcpi_rto, // Retransmit timeout in usec + tcpi.tcpi_ato, // Predicted tick of soft clock in usec + tcpi.tcpi_snd_mss, tcpi.tcpi_rcv_mss, + tcpi.tcpi_lost, // Lost packets + tcpi.tcpi_retrans, // Retransmitted packets out + tcpi.tcpi_rtt, // Smoothed round trip time in usec + tcpi.tcpi_rttvar, // Medium deviation + tcpi.tcpi_snd_ssthresh, tcpi.tcpi_snd_cwnd, + tcpi.tcpi_total_retrans); // Total retransmits for entire + // connection + } + return ok; +} + +void Socket::bindAddress(const InetAddress& addr) +{ + sockets::bindOrDie(sockfd_, addr.getSockAddr()); +} + +void Socket::listen() { sockets::listenOrDie(sockfd_); } + +int Socket::accept(InetAddress* peeraddr) +{ + struct sockaddr_in6 addr; + memZero(&addr, sizeof addr); + int connfd = sockets::accept(sockfd_, &addr); + if (connfd >= 0) { + peeraddr->setSockAddrInet6(addr); + } + return connfd; +} + +void Socket::shutdownWrite() { sockets::shutdownWrite(sockfd_); } + +void Socket::setTcpNoDelay(bool on) +{ + int optval = on ? 1 : 0; + ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, + static_cast<socklen_t>(sizeof optval)); + // FIXME CHECK +} + +void Socket::setReuseAddr(bool on) +{ + int optval = on ? 1 : 0; + ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, + static_cast<socklen_t>(sizeof optval)); + // FIXME CHECK +} + +void Socket::setReusePort(bool on) +{ +#ifdef SO_REUSEPORT + int optval = on ? 1 : 0; + int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, + static_cast<socklen_t>(sizeof optval)); + if (ret < 0 && on) { + LOG_SYSERR << "SO_REUSEPORT failed."; + } +#else + if (on) { + LOG_ERROR << "SO_REUSEPORT is not supported."; + } +#endif +} + +void Socket::setKeepAlive(bool on) +{ + int optval = on ? 1 : 0; + ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, + static_cast<socklen_t>(sizeof optval)); + // FIXME CHECK +} diff --git a/muduo/net/Socket.h b/muduo/net/Socket.h new file mode 100644 index 0000000..f0297cd --- /dev/null +++ b/muduo/net/Socket.h @@ -0,0 +1,84 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_SOCKET_H +#define MUDUO_NET_SOCKET_H + +#include "muduo/base/noncopyable.h" + +// struct tcp_info is in <netinet/tcp.h> +struct tcp_info; + +namespace muduo { +/// +/// TCP networking. +/// +namespace net { + +class InetAddress; + +/// +/// Wrapper of socket file descriptor. +/// +/// It closes the sockfd when desctructs. +/// It's thread safe, all operations are delagated to OS. +class Socket : noncopyable { +public: + explicit Socket(int sockfd) : sockfd_(sockfd) {} + + // Socket(Socket&&) // move constructor in C++11 + ~Socket(); + + int fd() const { return sockfd_; } + // return true if success. + bool getTcpInfo(struct tcp_info*) const; + bool getTcpInfoString(char* buf, int len) const; + + /// abort if address in use + void bindAddress(const InetAddress& localaddr); + /// abort if address in use + void listen(); + + /// On success, returns a non-negative integer that is + /// a descriptor for the accepted socket, which has been + /// set to non-blocking and close-on-exec. *peeraddr is assigned. + /// On error, -1 is returned, and *peeraddr is untouched. + int accept(InetAddress* peeraddr); + + void shutdownWrite(); + + /// + /// Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm). + /// + void setTcpNoDelay(bool on); + + /// + /// Enable/disable SO_REUSEADDR + /// + void setReuseAddr(bool on); + + /// + /// Enable/disable SO_REUSEPORT + /// + void setReusePort(bool on); + + /// + /// Enable/disable SO_KEEPALIVE + /// + void setKeepAlive(bool on); + +private: + const int sockfd_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_SOCKET_H diff --git a/muduo/net/SocketsOps.cc b/muduo/net/SocketsOps.cc new file mode 100644 index 0000000..8a29a7e --- /dev/null +++ b/muduo/net/SocketsOps.cc @@ -0,0 +1,290 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/SocketsOps.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> // snprintf +#include <sys/socket.h> +#include <sys/uio.h> // readv +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/base/Types.h" +#include "muduo/net/Endian.h" + +using namespace muduo; +using namespace muduo::net; + +namespace { + +typedef struct sockaddr SA; + +#if VALGRIND || defined(NO_ACCEPT4) +void setNonBlockAndCloseOnExec(int sockfd) +{ + // non-block + int flags = ::fcntl(sockfd, F_GETFL, 0); + flags |= O_NONBLOCK; + int ret = ::fcntl(sockfd, F_SETFL, flags); + // FIXME check + + // close-on-exec + flags = ::fcntl(sockfd, F_GETFD, 0); + flags |= FD_CLOEXEC; + ret = ::fcntl(sockfd, F_SETFD, flags); + // FIXME check + + (void)ret; +} +#endif + +} // namespace + +const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in6* addr) +{ + return static_cast<const struct sockaddr*>( + implicit_cast<const void*>(addr)); +} + +struct sockaddr* sockets::sockaddr_cast(struct sockaddr_in6* addr) +{ + return static_cast<struct sockaddr*>(implicit_cast<void*>(addr)); +} + +const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in* addr) +{ + return static_cast<const struct sockaddr*>( + implicit_cast<const void*>(addr)); +} + +const struct sockaddr_in* sockets::sockaddr_in_cast(const struct sockaddr* addr) +{ + return static_cast<const struct sockaddr_in*>( + implicit_cast<const void*>(addr)); +} + +const struct sockaddr_in6* sockets::sockaddr_in6_cast( + const struct sockaddr* addr) +{ + return static_cast<const struct sockaddr_in6*>( + implicit_cast<const void*>(addr)); +} + +int sockets::createNonblockingOrDie(sa_family_t family) +{ +#if VALGRIND + int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) { + LOG_SYSFATAL << "sockets::createNonblockingOrDie"; + } + + setNonBlockAndCloseOnExec(sockfd); +#else + int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, + IPPROTO_TCP); + if (sockfd < 0) { + LOG_SYSFATAL << "sockets::createNonblockingOrDie"; + } +#endif + return sockfd; +} + +void sockets::bindOrDie(int sockfd, const struct sockaddr* addr) +{ + int ret = ::bind(sockfd, addr, + static_cast<socklen_t>(sizeof(struct sockaddr_in6))); + if (ret < 0) { + LOG_SYSFATAL << "sockets::bindOrDie"; + } +} + +void sockets::listenOrDie(int sockfd) +{ + int ret = ::listen(sockfd, SOMAXCONN); + if (ret < 0) { + LOG_SYSFATAL << "sockets::listenOrDie"; + } +} + +int sockets::accept(int sockfd, struct sockaddr_in6* addr) +{ + socklen_t addrlen = static_cast<socklen_t>(sizeof *addr); +#if VALGRIND || defined(NO_ACCEPT4) + int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen); + setNonBlockAndCloseOnExec(connfd); +#else + int connfd = ::accept4(sockfd, sockaddr_cast(addr), &addrlen, + SOCK_NONBLOCK | SOCK_CLOEXEC); +#endif + if (connfd < 0) { + int savedErrno = errno; + LOG_SYSERR << "Socket::accept"; + switch (savedErrno) { + case EAGAIN: + case ECONNABORTED: + case EINTR: + case EPROTO: // ??? + case EPERM: + case EMFILE: // per-process lmit of open file desctiptor ??? + // expected errors + errno = savedErrno; + break; + case EBADF: + case EFAULT: + case EINVAL: + case ENFILE: + case ENOBUFS: + case ENOMEM: + case ENOTSOCK: + case EOPNOTSUPP: + // unexpected errors + LOG_FATAL << "unexpected error of ::accept " << savedErrno; + break; + default: + LOG_FATAL << "unknown error of ::accept " << savedErrno; + break; + } + } + return connfd; +} + +int sockets::connect(int sockfd, const struct sockaddr* addr) +{ + return ::connect(sockfd, addr, + static_cast<socklen_t>(sizeof(struct sockaddr_in6))); +} + +ssize_t sockets::read(int sockfd, void* buf, size_t count) +{ + return ::read(sockfd, buf, count); +} + +ssize_t sockets::readv(int sockfd, const struct iovec* iov, int iovcnt) +{ + return ::readv(sockfd, iov, iovcnt); +} + +ssize_t sockets::write(int sockfd, const void* buf, size_t count) +{ + return ::write(sockfd, buf, count); +} + +void sockets::close(int sockfd) +{ + if (::close(sockfd) < 0) { + LOG_SYSERR << "sockets::close"; + } +} + +void sockets::shutdownWrite(int sockfd) +{ + if (::shutdown(sockfd, SHUT_WR) < 0) { + LOG_SYSERR << "sockets::shutdownWrite"; + } +} + +void sockets::toIpPort(char* buf, size_t size, const struct sockaddr* addr) +{ + toIp(buf, size, addr); + size_t end = ::strlen(buf); + const struct sockaddr_in* addr4 = sockaddr_in_cast(addr); + uint16_t port = sockets::networkToHost16(addr4->sin_port); + assert(size > end); + snprintf(buf + end, size - end, ":%u", port); +} + +void sockets::toIp(char* buf, size_t size, const struct sockaddr* addr) +{ + if (addr->sa_family == AF_INET) { + assert(size >= INET_ADDRSTRLEN); + const struct sockaddr_in* addr4 = sockaddr_in_cast(addr); + ::inet_ntop(AF_INET, &addr4->sin_addr, buf, + static_cast<socklen_t>(size)); + } else if (addr->sa_family == AF_INET6) { + assert(size >= INET6_ADDRSTRLEN); + const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr); + ::inet_ntop(AF_INET6, &addr6->sin6_addr, buf, + static_cast<socklen_t>(size)); + } +} + +void sockets::fromIpPort(const char* ip, uint16_t port, + struct sockaddr_in* addr) +{ + addr->sin_family = AF_INET; + addr->sin_port = hostToNetwork16(port); + if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0) { + LOG_SYSERR << "sockets::fromIpPort"; + } +} + +void sockets::fromIpPort(const char* ip, uint16_t port, + struct sockaddr_in6* addr) +{ + addr->sin6_family = AF_INET6; + addr->sin6_port = hostToNetwork16(port); + if (::inet_pton(AF_INET6, ip, &addr->sin6_addr) <= 0) { + LOG_SYSERR << "sockets::fromIpPort"; + } +} + +int sockets::getSocketError(int sockfd) +{ + int optval; + socklen_t optlen = static_cast<socklen_t>(sizeof optval); + + if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) { + return errno; + } else { + return optval; + } +} + +struct sockaddr_in6 sockets::getLocalAddr(int sockfd) +{ + struct sockaddr_in6 localaddr; + memZero(&localaddr, sizeof localaddr); + socklen_t addrlen = static_cast<socklen_t>(sizeof localaddr); + if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0) { + LOG_SYSERR << "sockets::getLocalAddr"; + } + return localaddr; +} + +struct sockaddr_in6 sockets::getPeerAddr(int sockfd) +{ + struct sockaddr_in6 peeraddr; + memZero(&peeraddr, sizeof peeraddr); + socklen_t addrlen = static_cast<socklen_t>(sizeof peeraddr); + if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0) { + LOG_SYSERR << "sockets::getPeerAddr"; + } + return peeraddr; +} + +bool sockets::isSelfConnect(int sockfd) +{ + struct sockaddr_in6 localaddr = getLocalAddr(sockfd); + struct sockaddr_in6 peeraddr = getPeerAddr(sockfd); + if (localaddr.sin6_family == AF_INET) { + const struct sockaddr_in* laddr4 = + reinterpret_cast<struct sockaddr_in*>(&localaddr); + const struct sockaddr_in* raddr4 = + reinterpret_cast<struct sockaddr_in*>(&peeraddr); + return laddr4->sin_port == raddr4->sin_port && + laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr; + } else if (localaddr.sin6_family == AF_INET6) { + return localaddr.sin6_port == peeraddr.sin6_port && + memcmp(&localaddr.sin6_addr, &peeraddr.sin6_addr, + sizeof localaddr.sin6_addr) == 0; + } else { + return false; + } +} diff --git a/muduo/net/SocketsOps.h b/muduo/net/SocketsOps.h new file mode 100644 index 0000000..82414ac --- /dev/null +++ b/muduo/net/SocketsOps.h @@ -0,0 +1,57 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_SOCKETSOPS_H +#define MUDUO_NET_SOCKETSOPS_H + +#include <arpa/inet.h> + +namespace muduo { +namespace net { +namespace sockets { + +/// +/// Creates a non-blocking socket file descriptor, +/// abort if any error. +int createNonblockingOrDie(sa_family_t family); + +int connect(int sockfd, const struct sockaddr* addr); +void bindOrDie(int sockfd, const struct sockaddr* addr); +void listenOrDie(int sockfd); +int accept(int sockfd, struct sockaddr_in6* addr); +ssize_t read(int sockfd, void* buf, size_t count); +ssize_t readv(int sockfd, const struct iovec* iov, int iovcnt); +ssize_t write(int sockfd, const void* buf, size_t count); +void close(int sockfd); +void shutdownWrite(int sockfd); + +void toIpPort(char* buf, size_t size, const struct sockaddr* addr); +void toIp(char* buf, size_t size, const struct sockaddr* addr); + +void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in* addr); +void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in6* addr); + +int getSocketError(int sockfd); + +const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr); +const struct sockaddr* sockaddr_cast(const struct sockaddr_in6* addr); +struct sockaddr* sockaddr_cast(struct sockaddr_in6* addr); +const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr); +const struct sockaddr_in6* sockaddr_in6_cast(const struct sockaddr* addr); + +struct sockaddr_in6 getLocalAddr(int sockfd); +struct sockaddr_in6 getPeerAddr(int sockfd); +bool isSelfConnect(int sockfd); + +} // namespace sockets +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_SOCKETSOPS_H diff --git a/muduo/net/TcpClient.cc b/muduo/net/TcpClient.cc new file mode 100644 index 0000000..0489929 --- /dev/null +++ b/muduo/net/TcpClient.cc @@ -0,0 +1,167 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/TcpClient.h" + +#include <stdio.h> // snprintf + +#include "muduo/base/Logging.h" +#include "muduo/net/Connector.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/SocketsOps.h" + +using namespace muduo; +using namespace muduo::net; + +// TcpClient::TcpClient(EventLoop* loop) +// : loop_(loop) +// { +// } + +// TcpClient::TcpClient(EventLoop* loop, const string& host, uint16_t port) +// : loop_(CHECK_NOTNULL(loop)), +// serverAddr_(host, port) +// { +// } + +namespace muduo { +namespace net { +namespace detail { + +void removeConnection(EventLoop* loop, const TcpConnectionPtr& conn) +{ + loop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn)); +} + +void removeConnector(const ConnectorPtr& connector) +{ + // connector-> +} + +} // namespace detail +} // namespace net +} // namespace muduo + +TcpClient::TcpClient(EventLoop* loop, const InetAddress& serverAddr, + const string& nameArg) + : loop_(CHECK_NOTNULL(loop)), + connector_(new Connector(loop, serverAddr)), + name_(nameArg), + connectionCallback_(defaultConnectionCallback), + messageCallback_(defaultMessageCallback), + retry_(false), + connect_(true), + nextConnId_(1) +{ + connector_->setNewConnectionCallback( + std::bind(&TcpClient::newConnection, this, _1)); + // FIXME setConnectFailedCallback + LOG_INFO << "TcpClient::TcpClient[" << name_ << "] - connector " + << get_pointer(connector_); +} + +TcpClient::~TcpClient() +{ + LOG_INFO << "TcpClient::~TcpClient[" << name_ << "] - connector " + << get_pointer(connector_); + TcpConnectionPtr conn; + bool unique = false; + { + MutexLockGuard lock(mutex_); + unique = connection_.unique(); + conn = connection_; + } + if (conn) { + assert(loop_ == conn->getLoop()); + // FIXME: not 100% safe, if we are in different thread + CloseCallback cb = std::bind(&detail::removeConnection, loop_, _1); + loop_->runInLoop(std::bind(&TcpConnection::setCloseCallback, conn, cb)); + if (unique) { + conn->forceClose(); + } + } else { + connector_->stop(); + // FIXME: HACK + loop_->runAfter(1, std::bind(&detail::removeConnector, connector_)); + } +} + +void TcpClient::connect() +{ + // FIXME: check state + LOG_INFO << "TcpClient::connect[" << name_ << "] - connecting to " + << connector_->serverAddress().toIpPort(); + connect_ = true; + connector_->start(); +} + +void TcpClient::disconnect() +{ + connect_ = false; + + { + MutexLockGuard lock(mutex_); + if (connection_) { + connection_->shutdown(); + } + } +} + +void TcpClient::stop() +{ + connect_ = false; + connector_->stop(); +} + +void TcpClient::newConnection(int sockfd) +{ + loop_->assertInLoopThread(); + InetAddress peerAddr(sockets::getPeerAddr(sockfd)); + char buf[32]; + snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toIpPort().c_str(), + nextConnId_); + ++nextConnId_; + string connName = name_ + buf; + + InetAddress localAddr(sockets::getLocalAddr(sockfd)); + // FIXME poll with zero timeout to double confirm the new connection + // FIXME use make_shared if necessary + TcpConnectionPtr conn( + new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr)); + + conn->setConnectionCallback(connectionCallback_); + conn->setMessageCallback(messageCallback_); + conn->setWriteCompleteCallback(writeCompleteCallback_); + conn->setCloseCallback( + std::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe + { + MutexLockGuard lock(mutex_); + connection_ = conn; + } + conn->connectEstablished(); +} + +void TcpClient::removeConnection(const TcpConnectionPtr& conn) +{ + loop_->assertInLoopThread(); + assert(loop_ == conn->getLoop()); + + { + MutexLockGuard lock(mutex_); + assert(connection_ == conn); + connection_.reset(); + } + + loop_->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn)); + if (retry_ && connect_) { + LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to " + << connector_->serverAddress().toIpPort(); + connector_->restart(); + } +} diff --git a/muduo/net/TcpClient.h b/muduo/net/TcpClient.h new file mode 100644 index 0000000..8b0ef02 --- /dev/null +++ b/muduo/net/TcpClient.h @@ -0,0 +1,91 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_TCPCLIENT_H +#define MUDUO_NET_TCPCLIENT_H + +#include "muduo/base/Mutex.h" +#include "muduo/net/TcpConnection.h" + +namespace muduo { +namespace net { + +class Connector; +typedef std::shared_ptr<Connector> ConnectorPtr; + +class TcpClient : noncopyable { +public: + // TcpClient(EventLoop* loop); + // TcpClient(EventLoop* loop, const string& host, uint16_t port); + TcpClient(EventLoop* loop, const InetAddress& serverAddr, + const string& nameArg); + ~TcpClient(); // force out-line dtor, for std::unique_ptr members. + + void connect(); + void disconnect(); + void stop(); + + TcpConnectionPtr connection() const + { + MutexLockGuard lock(mutex_); + return connection_; + } + + EventLoop* getLoop() const { return loop_; } + bool retry() const { return retry_; } + void enableRetry() { retry_ = true; } + + const string& name() const { return name_; } + + /// Set connection callback. + /// Not thread safe. + void setConnectionCallback(ConnectionCallback cb) + { + connectionCallback_ = std::move(cb); + } + + /// Set message callback. + /// Not thread safe. + void setMessageCallback(MessageCallback cb) + { + messageCallback_ = std::move(cb); + } + + /// Set write complete callback. + /// Not thread safe. + void setWriteCompleteCallback(WriteCompleteCallback cb) + { + writeCompleteCallback_ = std::move(cb); + } + +private: + /// Not thread safe, but in loop + void newConnection(int sockfd); + /// Not thread safe, but in loop + void removeConnection(const TcpConnectionPtr& conn); + + EventLoop* loop_; + ConnectorPtr connector_; // avoid revealing Connector + const string name_; + ConnectionCallback connectionCallback_; + MessageCallback messageCallback_; + WriteCompleteCallback writeCompleteCallback_; + bool retry_; // atomic + bool connect_; // atomic + // always in loop thread + int nextConnId_; + mutable MutexLock mutex_; + TcpConnectionPtr connection_ GUARDED_BY(mutex_); +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_TCPCLIENT_H diff --git a/muduo/net/TcpConnection.cc b/muduo/net/TcpConnection.cc new file mode 100644 index 0000000..dbc9fbb --- /dev/null +++ b/muduo/net/TcpConnection.cc @@ -0,0 +1,386 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/TcpConnection.h" + +#include <errno.h> + +#include "muduo/base/Logging.h" +#include "muduo/base/WeakCallback.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/Socket.h" +#include "muduo/net/SocketsOps.h" + +using namespace muduo; +using namespace muduo::net; + +void muduo::net::defaultConnectionCallback(const TcpConnectionPtr& conn) +{ + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + // do not call conn->forceClose(), because some users want to register + // message callback only. +} + +void muduo::net::defaultMessageCallback(const TcpConnectionPtr&, Buffer* buf, + Timestamp) +{ + buf->retrieveAll(); +} + +TcpConnection::TcpConnection(EventLoop* loop, const string& nameArg, int sockfd, + const InetAddress& localAddr, + const InetAddress& peerAddr) + : loop_(CHECK_NOTNULL(loop)), + name_(nameArg), + state_(kConnecting), + reading_(true), + socket_(new Socket(sockfd)), + channel_(new Channel(loop, sockfd)), + localAddr_(localAddr), + peerAddr_(peerAddr), + highWaterMark_(64 * 1024 * 1024) +{ + channel_->setReadCallback(std::bind(&TcpConnection::handleRead, this, _1)); + channel_->setWriteCallback(std::bind(&TcpConnection::handleWrite, this)); + channel_->setCloseCallback(std::bind(&TcpConnection::handleClose, this)); + channel_->setErrorCallback(std::bind(&TcpConnection::handleError, this)); + LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this + << " fd=" << sockfd; + socket_->setKeepAlive(true); +} + +TcpConnection::~TcpConnection() +{ + LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this + << " fd=" << channel_->fd() << " state=" << stateToString(); + assert(state_ == kDisconnected); +} + +bool TcpConnection::getTcpInfo(struct tcp_info* tcpi) const +{ + return socket_->getTcpInfo(tcpi); +} + +string TcpConnection::getTcpInfoString() const +{ + char buf[1024]; + buf[0] = '\0'; + socket_->getTcpInfoString(buf, sizeof buf); + return buf; +} + +void TcpConnection::send(const void* data, int len) +{ + send(StringPiece(static_cast<const char*>(data), len)); +} + +void TcpConnection::send(const StringPiece& message) +{ + if (state_ == kConnected) { + if (loop_->isInLoopThread()) { + sendInLoop(message); + } else { + void (TcpConnection::*fp)(const StringPiece& message) = + &TcpConnection::sendInLoop; + loop_->runInLoop(std::bind(fp, + this, // FIXME + message.as_string())); + // std::forward<string>(message))); + } + } +} + +// FIXME efficiency!!! +void TcpConnection::send(Buffer* buf) +{ + if (state_ == kConnected) { + if (loop_->isInLoopThread()) { + sendInLoop(buf->peek(), buf->readableBytes()); + buf->retrieveAll(); + } else { + void (TcpConnection::*fp)(const StringPiece& message) = + &TcpConnection::sendInLoop; + loop_->runInLoop(std::bind(fp, + this, // FIXME + buf->retrieveAllAsString())); + // std::forward<string>(message))); + } + } +} + +void TcpConnection::sendInLoop(const StringPiece& message) +{ + sendInLoop(message.data(), message.size()); +} + +void TcpConnection::sendInLoop(const void* data, size_t len) +{ + loop_->assertInLoopThread(); + ssize_t nwrote = 0; + size_t remaining = len; + bool faultError = false; + if (state_ == kDisconnected) { + LOG_WARN << "disconnected, give up writing"; + return; + } + // if no thing in output queue, try writing directly + if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) { + nwrote = sockets::write(channel_->fd(), data, len); + if (nwrote >= 0) { + remaining = len - nwrote; + if (remaining == 0 && writeCompleteCallback_) { + loop_->queueInLoop( + std::bind(writeCompleteCallback_, shared_from_this())); + } + } else // nwrote < 0 + { + nwrote = 0; + if (errno != EWOULDBLOCK) { + LOG_SYSERR << "TcpConnection::sendInLoop"; + if (errno == EPIPE || + errno == ECONNRESET) // FIXME: any others? + { + faultError = true; + } + } + } + } + + assert(remaining <= len); + if (!faultError && remaining > 0) { + size_t oldLen = outputBuffer_.readableBytes(); + if (oldLen + remaining >= highWaterMark_ && oldLen < highWaterMark_ && + highWaterMarkCallback_) { + loop_->queueInLoop(std::bind(highWaterMarkCallback_, + shared_from_this(), + oldLen + remaining)); + } + outputBuffer_.append(static_cast<const char*>(data) + nwrote, + remaining); + if (!channel_->isWriting()) { + channel_->enableWriting(); + } + } +} + +void TcpConnection::shutdown() +{ + // FIXME: use compare and swap + if (state_ == kConnected) { + setState(kDisconnecting); + // FIXME: shared_from_this()? + loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this)); + } +} + +void TcpConnection::shutdownInLoop() +{ + loop_->assertInLoopThread(); + if (!channel_->isWriting()) { + // we are not writing + socket_->shutdownWrite(); + } +} + +// void TcpConnection::shutdownAndForceCloseAfter(double seconds) +// { +// // FIXME: use compare and swap +// if (state_ == kConnected) +// { +// setState(kDisconnecting); +// loop_->runInLoop(std::bind(&TcpConnection::shutdownAndForceCloseInLoop, +// this, seconds)); +// } +// } + +// void TcpConnection::shutdownAndForceCloseInLoop(double seconds) +// { +// loop_->assertInLoopThread(); +// if (!channel_->isWriting()) +// { +// // we are not writing +// socket_->shutdownWrite(); +// } +// loop_->runAfter( +// seconds, +// makeWeakCallback(shared_from_this(), +// &TcpConnection::forceCloseInLoop)); +// } + +void TcpConnection::forceClose() +{ + // FIXME: use compare and swap + if (state_ == kConnected || state_ == kDisconnecting) { + setState(kDisconnecting); + loop_->queueInLoop( + std::bind(&TcpConnection::forceCloseInLoop, shared_from_this())); + } +} + +void TcpConnection::forceCloseWithDelay(double seconds) +{ + if (state_ == kConnected || state_ == kDisconnecting) { + setState(kDisconnecting); + loop_->runAfter( + seconds, + makeWeakCallback( + shared_from_this(), + &TcpConnection::forceClose)); // not forceCloseInLoop to avoid + // race condition + } +} + +void TcpConnection::forceCloseInLoop() +{ + loop_->assertInLoopThread(); + if (state_ == kConnected || state_ == kDisconnecting) { + // as if we received 0 byte in handleRead(); + handleClose(); + } +} + +const char* TcpConnection::stateToString() const +{ + switch (state_) { + case kDisconnected: + return "kDisconnected"; + case kConnecting: + return "kConnecting"; + case kConnected: + return "kConnected"; + case kDisconnecting: + return "kDisconnecting"; + default: + return "unknown state"; + } +} + +void TcpConnection::setTcpNoDelay(bool on) { socket_->setTcpNoDelay(on); } + +void TcpConnection::startRead() +{ + loop_->runInLoop(std::bind(&TcpConnection::startReadInLoop, this)); +} + +void TcpConnection::startReadInLoop() +{ + loop_->assertInLoopThread(); + if (!reading_ || !channel_->isReading()) { + channel_->enableReading(); + reading_ = true; + } +} + +void TcpConnection::stopRead() +{ + loop_->runInLoop(std::bind(&TcpConnection::stopReadInLoop, this)); +} + +void TcpConnection::stopReadInLoop() +{ + loop_->assertInLoopThread(); + if (reading_ || channel_->isReading()) { + channel_->disableReading(); + reading_ = false; + } +} + +void TcpConnection::connectEstablished() +{ + loop_->assertInLoopThread(); + assert(state_ == kConnecting); + setState(kConnected); + channel_->tie(shared_from_this()); + channel_->enableReading(); + + connectionCallback_(shared_from_this()); +} + +void TcpConnection::connectDestroyed() +{ + loop_->assertInLoopThread(); + if (state_ == kConnected) { + setState(kDisconnected); + channel_->disableAll(); + + connectionCallback_(shared_from_this()); + } + channel_->remove(); +} + +void TcpConnection::handleRead(Timestamp receiveTime) +{ + loop_->assertInLoopThread(); + int savedErrno = 0; + ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); + if (n > 0) { + messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); + } else if (n == 0) { + handleClose(); + } else { + errno = savedErrno; + LOG_SYSERR << "TcpConnection::handleRead"; + handleError(); + } +} + +void TcpConnection::handleWrite() +{ + loop_->assertInLoopThread(); + if (channel_->isWriting()) { + ssize_t n = sockets::write(channel_->fd(), outputBuffer_.peek(), + outputBuffer_.readableBytes()); + if (n > 0) { + outputBuffer_.retrieve(n); + if (outputBuffer_.readableBytes() == 0) { + channel_->disableWriting(); + if (writeCompleteCallback_) { + loop_->queueInLoop( + std::bind(writeCompleteCallback_, shared_from_this())); + } + if (state_ == kDisconnecting) { + shutdownInLoop(); + } + } + } else { + LOG_SYSERR << "TcpConnection::handleWrite"; + // if (state_ == kDisconnecting) + // { + // shutdownInLoop(); + // } + } + } else { + LOG_TRACE << "Connection fd = " << channel_->fd() + << " is down, no more writing"; + } +} + +void TcpConnection::handleClose() +{ + loop_->assertInLoopThread(); + LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString(); + assert(state_ == kConnected || state_ == kDisconnecting); + // we don't close fd, leave it to dtor, so we can find leaks easily. + setState(kDisconnected); + channel_->disableAll(); + + TcpConnectionPtr guardThis(shared_from_this()); + connectionCallback_(guardThis); + // must be the last line + closeCallback_(guardThis); +} + +void TcpConnection::handleError() +{ + int err = sockets::getSocketError(channel_->fd()); + LOG_ERROR << "TcpConnection::handleError [" << name_ + << "] - SO_ERROR = " << err << " " << strerror_tl(err); +} diff --git a/muduo/net/TcpConnection.h b/muduo/net/TcpConnection.h new file mode 100644 index 0000000..c0c8b14 --- /dev/null +++ b/muduo/net/TcpConnection.h @@ -0,0 +1,186 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_TCPCONNECTION_H +#define MUDUO_NET_TCPCONNECTION_H + +#include <boost/any.hpp> +#include <memory> + +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" +#include "muduo/base/noncopyable.h" +#include "muduo/net/Buffer.h" +#include "muduo/net/Callbacks.h" +#include "muduo/net/InetAddress.h" + +// struct tcp_info is in <netinet/tcp.h> +struct tcp_info; + +namespace muduo { +namespace net { + +class Channel; +class EventLoop; +class Socket; + +/* + TcpConnection class 可谓是 muduo 最核心也是最复杂的 class,它的头文件和源 + 文件一共有 450 多行,是 muduo 最大的 class。 + + TcpConnection 是 muduo 里唯一默认使用 shared_ptr 来管理的 class,也是唯 + 一继承 enable_shared_from_this 的 class,这源于其模糊的生命期。 + + TcpConnection 没有可供用户使用的函数。 + + TcpConnection 使用 Channel 来获得 socket 上的 IO 事件,它 + 会自己处理 writable 事件,而把 readable 事件通过 MessageCallback + 传达给客户。 TcpConnection 拥有 TCP socket,它的析构函数会 close(fd)(在 + Socket 的析构函数 中发生)。 + + 注意 TcpConnection 表示的是“一次 TCP 连接”,它是不可再生的,一旦连接断 + 开,这个 TcpConnection 对象就没啥用了。另外 TcpConnection 没有发起连接的功能, + 其构造函数的参数是已经建立好连接的 socket fd (无论是 TcpServer 被动接受还是 + TcpClient 主动发起),因此其初始状态是 kConnecting。 +*/ + +/// +/// TCP connection, for both client and server usage. +/// +/// This is an interface class, so don't expose too much details. +class TcpConnection : noncopyable, + public std::enable_shared_from_this<TcpConnection> { +public: + /// Constructs a TcpConnection with a connected sockfd + /// + /// User should not create this object. + TcpConnection(EventLoop* loop, const string& name, int sockfd, + const InetAddress& localAddr, const InetAddress& peerAddr); + ~TcpConnection(); + + EventLoop* getLoop() const { return loop_; } + const string& name() const { return name_; } + const InetAddress& localAddress() const { return localAddr_; } + const InetAddress& peerAddress() const { return peerAddr_; } + bool connected() const { return state_ == kConnected; } + bool disconnected() const { return state_ == kDisconnected; } + // return true if success. + bool getTcpInfo(struct tcp_info*) const; + string getTcpInfoString() const; + + // void send(string&& message); // C++11 + void send(const void* message, int len); + void send(const StringPiece& message); + // void send(Buffer&& message); // C++11 + void send(Buffer* message); // this one will swap data + void shutdown(); // NOT thread safe, no simultaneous calling + // void shutdownAndForceCloseAfter(double seconds); // NOT thread safe, no + // simultaneous calling + void forceClose(); + void forceCloseWithDelay(double seconds); + void setTcpNoDelay(bool on); + // reading or not + void startRead(); + void stopRead(); + bool isReading() const + { + return reading_; + }; // NOT thread safe, may race with start/stopReadInLoop + + void setContext(const boost::any& context) { context_ = context; } + + const boost::any& getContext() const { return context_; } + + boost::any* getMutableContext() { return &context_; } + + void setConnectionCallback(const ConnectionCallback& cb) + { + connectionCallback_ = cb; + } + + void setMessageCallback(const MessageCallback& cb) + { + messageCallback_ = cb; + } + + void setWriteCompleteCallback(const WriteCompleteCallback& cb) + { + writeCompleteCallback_ = cb; + } + + void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, + size_t highWaterMark) + { + highWaterMarkCallback_ = cb; + highWaterMark_ = highWaterMark; + } + + /// Advanced interface + Buffer* inputBuffer() { return &inputBuffer_; } + + Buffer* outputBuffer() { return &outputBuffer_; } + +/* + TcpConnection class 也新增了 CloseCallback 事件回调,但是这个回调是给 TcpServer 和 TcpClient 用的, + 用于通知它们移除所持有的 TcpConnectionPtr,这不是给普通用户用的,普通用户继续使用 ConnectionCallback。 +*/ + /// Internal use only. + void setCloseCallback(const CloseCallback& cb) { closeCallback_ = cb; } + + // called when TcpServer accepts a new connection + void connectEstablished(); // should be called only once + // called when TcpServer has removed me from its map + void connectDestroyed(); // should be called only once + +private: + enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting }; + void handleRead(Timestamp receiveTime); + void handleWrite(); + void handleClose(); + void handleError(); + // void sendInLoop(string&& message); + void sendInLoop(const StringPiece& message); + void sendInLoop(const void* message, size_t len); + void shutdownInLoop(); + // void shutdownAndForceCloseInLoop(double seconds); + void forceCloseInLoop(); + void setState(StateE s) { state_ = s; } + const char* stateToString() const; + void startReadInLoop(); + void stopReadInLoop(); + + EventLoop* loop_; + const string name_; + StateE state_; // FIXME: use atomic variable + bool reading_; + // we don't expose those classes to client. + std::unique_ptr<Socket> socket_; + std::unique_ptr<Channel> channel_; + const InetAddress localAddr_; + const InetAddress peerAddr_; + ConnectionCallback connectionCallback_; + MessageCallback messageCallback_; + WriteCompleteCallback writeCompleteCallback_; + HighWaterMarkCallback highWaterMarkCallback_; + CloseCallback closeCallback_; + size_t highWaterMark_; + Buffer inputBuffer_; + Buffer outputBuffer_; // FIXME: use list<Buffer> as output buffer. + boost::any context_; + // FIXME: creationTime_, lastReceiveTime_ + // bytesReceived_, bytesSent_ +}; + +typedef std::shared_ptr<TcpConnection> TcpConnectionPtr; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_TCPCONNECTION_H diff --git a/muduo/net/TcpServer.cc b/muduo/net/TcpServer.cc new file mode 100644 index 0000000..b06af66 --- /dev/null +++ b/muduo/net/TcpServer.cc @@ -0,0 +1,107 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/TcpServer.h" + +#include <stdio.h> // snprintf + +#include "muduo/base/Logging.h" +#include "muduo/net/Acceptor.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/SocketsOps.h" + +using namespace muduo; +using namespace muduo::net; + +TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr, + const string& nameArg, Option option) + : loop_(CHECK_NOTNULL(loop)), + ipPort_(listenAddr.toIpPort()), + name_(nameArg), + acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)), + threadPool_(new EventLoopThreadPool(loop, name_)), + connectionCallback_(defaultConnectionCallback), + messageCallback_(defaultMessageCallback), + nextConnId_(1) +{ + acceptor_->setNewConnectionCallback( + std::bind(&TcpServer::newConnection, this, _1, _2)); +} + +TcpServer::~TcpServer() +{ + loop_->assertInLoopThread(); + LOG_TRACE << "TcpServer::~TcpServer [" << name_ << "] destructing"; + + for (auto& item : connections_) { + TcpConnectionPtr conn(item.second); + item.second.reset(); + conn->getLoop()->runInLoop( + std::bind(&TcpConnection::connectDestroyed, conn)); + } +} + +void TcpServer::setThreadNum(int numThreads) +{ + assert(0 <= numThreads); + threadPool_->setThreadNum(numThreads); +} + +void TcpServer::start() +{ + if (started_.getAndSet(1) == 0) { + threadPool_->start(threadInitCallback_); + + assert(!acceptor_->listenning()); + loop_->runInLoop(std::bind(&Acceptor::listen, get_pointer(acceptor_))); + } +} + +void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr) +{ + loop_->assertInLoopThread(); + EventLoop* ioLoop = threadPool_->getNextLoop(); + char buf[64]; + snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_); + ++nextConnId_; + string connName = name_ + buf; + + LOG_INFO << "TcpServer::newConnection [" << name_ << "] - new connection [" + << connName << "] from " << peerAddr.toIpPort(); + InetAddress localAddr(sockets::getLocalAddr(sockfd)); + // FIXME poll with zero timeout to double confirm the new connection + // FIXME use make_shared if necessary + TcpConnectionPtr conn( + new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr)); + connections_[connName] = conn; + conn->setConnectionCallback(connectionCallback_); + conn->setMessageCallback(messageCallback_); + conn->setWriteCompleteCallback(writeCompleteCallback_); + conn->setCloseCallback( + std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe + ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn)); +} + +void TcpServer::removeConnection(const TcpConnectionPtr& conn) +{ + // FIXME: unsafe + loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn)); +} + +void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn) +{ + loop_->assertInLoopThread(); + LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_ + << "] - connection " << conn->name(); + size_t n = connections_.erase(conn->name()); + (void)n; + assert(n == 1); + EventLoop* ioLoop = conn->getLoop(); + ioLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn)); +} diff --git a/muduo/net/TcpServer.h b/muduo/net/TcpServer.h new file mode 100644 index 0000000..a3d2668 --- /dev/null +++ b/muduo/net/TcpServer.h @@ -0,0 +1,131 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_TCPSERVER_H +#define MUDUO_NET_TCPSERVER_H + +#include <map> + +#include "muduo/base/Atomic.h" +#include "muduo/base/Types.h" +#include "muduo/net/TcpConnection.h" + +namespace muduo { +namespace net { + +class Acceptor; +class EventLoop; +class EventLoopThreadPool; + +/* + TcpServer class 的功能是管理 accept(2) 获得的 TcpConnection。TcpServer 是 + 供用户直接使用的,生命期由用户控制。TcpServer 的接口如下,用户只需要设置好 + callback,再调用 start() 即可。 +*/ + +/// +/// TCP server, supports single-threaded and thread-pool models. +/// +/// This is an interface class, so don't expose too much details. +class TcpServer : noncopyable { +public: + typedef std::function<void(EventLoop*)> ThreadInitCallback; + enum Option { + kNoReusePort, + kReusePort, + }; + + // TcpServer(EventLoop* loop, const InetAddress& listenAddr); + TcpServer(EventLoop* loop, const InetAddress& listenAddr, + const string& nameArg, Option option = kNoReusePort); + ~TcpServer(); // force out-line dtor, for std::unique_ptr members. + + const string& ipPort() const { return ipPort_; } + const string& name() const { return name_; } + EventLoop* getLoop() const { return loop_; } + + /// Set the number of threads for handling input. + /// + /// Always accepts new connection in loop's thread. + /// Must be called before @c start + /// @param numThreads + /// - 0 means all I/O in loop's thread, no thread will created. + /// this is the default value. + /// - 1 means all I/O in another thread. + /// - N means a thread pool with N threads, new connections + /// are assigned on a round-robin basis. + void setThreadNum(int numThreads); + void setThreadInitCallback(const ThreadInitCallback& cb) + { + threadInitCallback_ = cb; + } + /// valid after calling start() + std::shared_ptr<EventLoopThreadPool> threadPool() { return threadPool_; } + + /// Starts the server if it's not listenning. + /// + /// It's harmless to call it multiple times. + /// Thread safe. + void start(); + + /// Set connection callback. + /// Not thread safe. + void setConnectionCallback(const ConnectionCallback& cb) + { + connectionCallback_ = cb; + } + + /// Set message callback. + /// Not thread safe. + void setMessageCallback(const MessageCallback& cb) + { + messageCallback_ = cb; + } + + /// Set write complete callback. + /// Not thread safe. + void setWriteCompleteCallback(const WriteCompleteCallback& cb) + { + writeCompleteCallback_ = cb; + } + +private: + /// Not thread safe, but in loop + void newConnection(int sockfd, const InetAddress& peerAddr); + /// Thread safe. + void removeConnection(const TcpConnectionPtr& conn); + /// Not thread safe, but in loop + void removeConnectionInLoop(const TcpConnectionPtr& conn); + + /* + 每个 TcpConnection 对象有一个名字,这个名字是由其所属的 TcpServer 在创建 + TcpConnection 对象时生成,名字是 ConnectionMap 的 key。 + */ + typedef std::map<string, TcpConnectionPtr> ConnectionMap; + + EventLoop* loop_; // the acceptor loop + const string ipPort_; + const string name_; + std::unique_ptr<Acceptor> acceptor_; // avoid revealing Acceptor + std::shared_ptr<EventLoopThreadPool> threadPool_; + ConnectionCallback connectionCallback_; + MessageCallback messageCallback_; + WriteCompleteCallback writeCompleteCallback_; + ThreadInitCallback threadInitCallback_; + AtomicInt32 started_; + // always in loop thread + int nextConnId_; + ConnectionMap connections_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_TCPSERVER_H diff --git a/muduo/net/Timer.cc b/muduo/net/Timer.cc new file mode 100644 index 0000000..2b63064 --- /dev/null +++ b/muduo/net/Timer.cc @@ -0,0 +1,23 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/Timer.h" + +using namespace muduo; +using namespace muduo::net; + +AtomicInt64 Timer::s_numCreated_; + +void Timer::restart(Timestamp now) +{ + if (repeat_) { + expiration_ = addTime(now, interval_); + } else { + expiration_ = Timestamp::invalid(); + } +} diff --git a/muduo/net/Timer.h b/muduo/net/Timer.h new file mode 100644 index 0000000..9253ebb --- /dev/null +++ b/muduo/net/Timer.h @@ -0,0 +1,58 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_TIMER_H +#define MUDUO_NET_TIMER_H + +#include "muduo/base/Atomic.h" +#include "muduo/base/Timestamp.h" +#include "muduo/net/Callbacks.h" + +namespace muduo { +namespace net { + +/// +/// Internal class for timer event. +/// +class Timer : noncopyable { +public: + Timer(TimerCallback cb, Timestamp when, double interval) + : callback_(std::move(cb)), + expiration_(when), + interval_(interval), + repeat_(interval > 0.0), + sequence_(s_numCreated_.incrementAndGet()) + { + } + + void run() const { callback_(); } + + Timestamp expiration() const { return expiration_; } + bool repeat() const { return repeat_; } + int64_t sequence() const { return sequence_; } + + void restart(Timestamp now); + + static int64_t numCreated() { return s_numCreated_.get(); } + +private: + const TimerCallback callback_; + Timestamp expiration_; + const double interval_; + const bool repeat_; + const int64_t sequence_; + + static AtomicInt64 s_numCreated_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_TIMER_H diff --git a/muduo/net/TimerId.h b/muduo/net/TimerId.h new file mode 100644 index 0000000..67fbd8d --- /dev/null +++ b/muduo/net/TimerId.h @@ -0,0 +1,54 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_TIMERID_H +#define MUDUO_NET_TIMERID_H + +#include <cstdint> +#include "muduo/base/copyable.h" + +namespace muduo +{ +namespace net +{ + +class Timer; + +/// +/// An opaque identifier, for canceling Timer. +/// +class TimerId : public muduo::copyable +{ + public: + TimerId() + : timer_(nullptr), + sequence_(0) + { + } + + TimerId(Timer* timer, int64_t seq) + : timer_(timer), + sequence_(seq) + { + } + + // default copy-ctor, dtor and assignment are okay + + friend class TimerQueue; + + private: + Timer* timer_; + int64_t sequence_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_TIMERID_H diff --git a/muduo/net/TimerQueue.cc b/muduo/net/TimerQueue.cc new file mode 100644 index 0000000..838a872 --- /dev/null +++ b/muduo/net/TimerQueue.cc @@ -0,0 +1,276 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include "muduo/net/TimerQueue.h" + +#include <sys/timerfd.h> +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/Timer.h" +#include "muduo/net/TimerId.h" + +namespace muduo { +namespace net { +namespace detail { + +int createTimerfd() +{ + int timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (timerfd < 0) { + LOG_SYSFATAL << "Failed in timerfd_create"; + } + return timerfd; +} + +struct timespec howMuchTimeFromNow(Timestamp when) +{ + int64_t microseconds = when.microSecondsSinceEpoch() - + Timestamp::now().microSecondsSinceEpoch(); + if (microseconds < 100) { + microseconds = 100; + } + struct timespec ts; + ts.tv_sec = + static_cast<time_t>(microseconds / Timestamp::kMicroSecondsPerSecond); + ts.tv_nsec = static_cast<long>( + (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); + return ts; +} + +void readTimerfd(int timerfd, Timestamp now) +{ + uint64_t howmany; + ssize_t n = ::read(timerfd, &howmany, sizeof howmany); + LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " + << now.toString(); + if (n != sizeof howmany) { + LOG_ERROR << "TimerQueue::handleRead() reads " << n + << " bytes instead of 8"; + } +} + +void resetTimerfd(int timerfd, Timestamp expiration) +{ + // wake up loop by timerfd_settime() + struct itimerspec newValue; + struct itimerspec oldValue; + memZero(&newValue, sizeof newValue); + memZero(&oldValue, sizeof oldValue); + newValue.it_value = howMuchTimeFromNow(expiration); + int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); + if (ret) { + LOG_SYSERR << "timerfd_settime()"; + } +} + +} // namespace detail +} // namespace net +} // namespace muduo + +using namespace muduo; +using namespace muduo::net; +using namespace muduo::net::detail; + +TimerQueue::TimerQueue(EventLoop* loop) + : loop_(loop), + timerfd_(createTimerfd()), + timerfdChannel_(loop, timerfd_), + timers_(), + callingExpiredTimers_(false) +{ + timerfdChannel_.setReadCallback(std::bind(&TimerQueue::handleRead, this)); + // we are always reading the timerfd, we disarm it with timerfd_settime. + timerfdChannel_.enableReading(); +} + +TimerQueue::~TimerQueue() +{ + timerfdChannel_.disableAll(); + timerfdChannel_.remove(); + ::close(timerfd_); + // do not remove channel, since we're in EventLoop::dtor(); + for (const Entry& timer : timers_) { + delete timer.second; + } +} + +/* + TimerQueue::addTimer() 只能在 IO 线程调用,因此 EventLoop::runAfter() 系列函数不是线程安全的。 + 把 addTimer() 拆成两部分,拆分后的 addTimer() 只负责转发,addTimerInLoop() 完成修改定时器列表的工作。 + 这样无论在哪个线程调用 addTimer() 都是安全的了。 +*/ +TimerId TimerQueue::addTimer(TimerCallback cb, Timestamp when, double interval) +{ + Timer* timer = new Timer(std::move(cb), when, interval); + loop_->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer)); + return TimerId(timer, timer->sequence()); +} + +void TimerQueue::cancel(TimerId timerId) +{ + loop_->runInLoop(std::bind(&TimerQueue::cancelInLoop, this, timerId)); +} + +/* + 借助 EventLoop::runInLoop(),我们可以很容易地将 TimerQueue::addTimer() + 做成线程安全的,而且无须用锁。办法是让 addTimer() 调用 runInLoop(),把实际工 + 作转移到 IO 线程来做。 +*/ +void TimerQueue::addTimerInLoop(Timer* timer) +{ + loop_->assertInLoopThread(); + bool earliestChanged = insert(timer); + + if (earliestChanged) { + resetTimerfd(timerfd_, timer->expiration()); + } +} + +void TimerQueue::cancelInLoop(TimerId timerId) +{ + loop_->assertInLoopThread(); + assert(timers_.size() == activeTimers_.size()); + ActiveTimer timer(timerId.timer_, timerId.sequence_); + ActiveTimerSet::iterator it = activeTimers_.find(timer); + if (it != activeTimers_.end()) { + size_t n = timers_.erase(Entry(it->first->expiration(), it->first)); + assert(n == 1); + (void)n; + delete it->first; // FIXME: no delete please + activeTimers_.erase(it); + } else if (callingExpiredTimers_) { + cancelingTimers_.insert(timer); + } + assert(timers_.size() == activeTimers_.size()); +} + +void TimerQueue::handleRead() +{ + loop_->assertInLoopThread(); + Timestamp now(Timestamp::now()); + readTimerfd(timerfd_, now); + + std::vector<Entry> expired = getExpired(now); + + callingExpiredTimers_ = true; + cancelingTimers_.clear(); + // safe to callback outside critical section + for (const Entry& it : expired) { + it.second->run(); + } + callingExpiredTimers_ = false; + + reset(expired, now); +} + +/* + getExpired 这个函数会从 timers_ 中移除已到期的 + Timer,并通过 vector 返回它们。编译器会实施 RVO 优化,不必太担心性能,必要 + 时可以像 EventLoop::activeChannels_ 那样复用 vector。 + + RVO(Return Value Optimization)是一种编译器优化技术,旨在优化函数的返回值的传递,避免不必要的复制操作。 + 在C++中,当一个函数返回一个临时对象时,通常会触发拷贝构造函数的调用,将临时对象复制到调用者的对象。 + 这个过程可能会导致性能开销,特别是当返回的临时对象较大时。 + RVO通过直接将临时对象的值放置到调用者的对象的存储空间中,避免了拷贝操作,从而提高了性能。 + 这样做的前提是编译器能够确定在函数内部创建的临时对象将会被直接用于返回值,而不会被修改或用于其他目的。 +*/ +std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now) +{ + assert(timers_.size() == activeTimers_.size()); + std::vector<Entry> expired; + + // sentry 哨兵值 + Entry sentry(now, + reinterpret_cast<Timer*>( + UINTPTR_MAX)); // NOLINT + // set::lower_bound() + // 返回的是第一个未到期的 Timer 的迭代器 + + /* + 在C++中,std::set 是一个 《有序》 的关联容器,其中的元素按照严格弱序进行排序。 + std::set 提供了 lower_bound 函数,用于查找在集合中大于或等于某个给定值的第一个元素的迭代器。 + + std::pair 默认情况下使用 operator< 进行比较。这是因为 std::pair 有一个默认的 operator<,该运算符首先比较第一个元素, + 如果它们相等,则比较第二个元素。这是标准库为 std::pair 提供的默认比较方式,它允许 std::pair 类型在使用关联容器(如 std::set)时进行自然排序。 + */ + TimerList::iterator end = timers_.lower_bound(sentry); + assert(end == timers_.end() || now < end->first); + + // 不同容器之间的复制 + std::copy(timers_.begin(), end, back_inserter(expired)); + timers_.erase(timers_.begin(), end); + + for (const Entry& it : expired) { + ActiveTimer timer(it.second, it.second->sequence()); + // QUESTION: 这里怎么删除的? 应该是 activeTimers 是 set 类型,可以这样删。 + size_t n = activeTimers_.erase(timer); + assert(n == 1); + (void)n; + } + + assert(timers_.size() == activeTimers_.size()); + return expired; +} + +void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now) +{ + Timestamp nextExpire; + + for (const Entry& it : expired) { + ActiveTimer timer(it.second, it.second->sequence()); + if (it.second->repeat() && + cancelingTimers_.find(timer) == cancelingTimers_.end()) { + it.second->restart(now); + insert(it.second); + } else { + // FIXME move to a free list + delete it.second; // FIXME: no delete please + } + } + + if (!timers_.empty()) { + nextExpire = timers_.begin()->second->expiration(); + } + + if (nextExpire.valid()) { + resetTimerfd(timerfd_, nextExpire); + } +} + +bool TimerQueue::insert(Timer* timer) +{ + loop_->assertInLoopThread(); + assert(timers_.size() == activeTimers_.size()); + bool earliestChanged = false; + Timestamp when = timer->expiration(); + TimerList::iterator it = timers_.begin(); + if (it == timers_.end() || when < it->first) { + earliestChanged = true; + } + { + std::pair<TimerList::iterator, bool> result = + timers_.insert(Entry(when, timer)); + assert(result.second); + (void)result; + } + { + std::pair<ActiveTimerSet::iterator, bool> result = + activeTimers_.insert(ActiveTimer(timer, timer->sequence())); + assert(result.second); + (void)result; + } + + assert(timers_.size() == activeTimers_.size()); + return earliestChanged; +} diff --git a/muduo/net/TimerQueue.h b/muduo/net/TimerQueue.h new file mode 100644 index 0000000..3ba6b4b --- /dev/null +++ b/muduo/net/TimerQueue.h @@ -0,0 +1,114 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_TIMERQUEUE_H +#define MUDUO_NET_TIMERQUEUE_H + +#include <set> +#include <vector> + +#include "muduo/base/Timestamp.h" +#include "muduo/net/Callbacks.h" +#include "muduo/net/Channel.h" + +namespace muduo { +namespace net { + +class EventLoop; +class Timer; +class TimerId; + +/* + 值得一提的是 TimerQueue 的数据结构的选择,TimerQueue 需要高效地组织目前 + 尚未到期的 Timer,能快速地根据当前时间找到已经到期的 Timer,也要能高效地添 + 加和删除 Timer。最简单的 TimerQueue 以按到期时间排好序的线性表为数据结构, + muduo 最早也是用这种结构。这种结构的常用操作都是线性查找,复杂度是 O(N)。 + + 另一种常用做法是二叉堆组织优先队列(libev 用的是更高效的 4-heap),这种 + 做法的复杂度降为 O(log N),但是 C++ 标准库的 make_heap() 等函数不能高效地删 + 除 heap 中间的某个元素,需要我们自己实现(令 Timer 记住自己在 heap 中的位置)。 + 还有一种做法是使用二叉搜索树(例如 std::set/std::map),把 Timer 按到期 + 时间先后排好序。操作的复杂度仍然是 O(log N),不过 memory locality 比 heap 要 + 差一些,实际速度可能略慢。但是我们不能直接用 map<Timestamp, Timer*>,因为这 + 样无法处理两个 Timer 到期时间相同的情况。有两个解决方案,一是用 multimap 或 + multiset,二是设法区分 key。muduo 现在采用的是第二种做法,这样可以避免使 + 用不常见的 multimap class。具体来说,以 pair<Timestamp, Timer*> 为 key,这样即 + 便两个 Timer 的到期时间相同,它们的地址也必定不同。 + +*/ + +/// +/// A best efforts timer queue. +/// No guarantee that the callback will be on time. +/// + +/* + TimerQueue 使用了一个 Channel 来观察 timerfd_ 上的 readable 事件。 + TimerQueue 是 EventLoop 的定时器功能。 + TimerQueue 的成员函数只能在其所属的 IO 线程调用,因此不必加锁。 + + 传统的Reactor 通过控制 select(2) 和 poll(2) 的等待时间来实现定时,而现在在 Linux 中 + 有了 timerfd,我们可以用和处理 IO 事件相同的方式来处理定时,代码的一致性更好。 + + muduo 的定时器功能由三个 class 实现,TimerId、Timer、TimerQueue,用户只 + 能看到第一个 class,另外两个都是内部实现细节。 +*/ +class TimerQueue : noncopyable { +public: + explicit TimerQueue(EventLoop* loop); + ~TimerQueue(); + +/* + addTimer() 是供 EventLoop 使用的, + EventLoop 会把它封装为更好用的 runAt()、runAfter()、runEvery() 等函数。 +*/ + /// + /// Schedules the callback to be run at given time, + /// repeats if @c interval > 0.0. + /// + /// Must be thread safe. Usually be called from other threads. + TimerId addTimer(TimerCallback cb, Timestamp when, double interval); + + void cancel(TimerId timerId); + +private: + // FIXME: use unique_ptr<Timer> instead of raw pointers. + // This requires heterogeneous comparison lookup (N3465) from C++14 + // so that we can find an T* in a set<unique_ptr<T>>. + typedef std::pair<Timestamp, Timer*> Entry; + typedef std::set<Entry> TimerList; + typedef std::pair<Timer*, int64_t> ActiveTimer; + typedef std::set<ActiveTimer> ActiveTimerSet; + + void addTimerInLoop(Timer* timer); + void cancelInLoop(TimerId timerId); + // called when timerfd alarms + void handleRead(); + // move out all expired timers + std::vector<Entry> getExpired(Timestamp now); + void reset(const std::vector<Entry>& expired, Timestamp now); + + bool insert(Timer* timer); + + EventLoop* loop_; + const int timerfd_; + Channel timerfdChannel_; + // Timer list sorted by expiration + TimerList timers_; + + // for cancel() + ActiveTimerSet activeTimers_; + bool callingExpiredTimers_; /* atomic */ + ActiveTimerSet cancelingTimers_; +}; + +} // namespace net +} // namespace muduo +#endif // MUDUO_NET_TIMERQUEUE_H diff --git a/muduo/net/ZlibStream.h b/muduo/net/ZlibStream.h new file mode 100644 index 0000000..d4a2d6c --- /dev/null +++ b/muduo/net/ZlibStream.h @@ -0,0 +1,123 @@ +#pragma once + +#include "muduo/base/noncopyable.h" +#include "muduo/net/Buffer.h" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#include <zlib.h> + +namespace muduo { +namespace net { + +// input is zlib compressed data, output uncompressed data +// FIXME: finish this +class ZlibInputStream : noncopyable { +public: + explicit ZlibInputStream(Buffer* output) : output_(output), zerror_(Z_OK) + { + memZero(&zstream_, sizeof zstream_); + zerror_ = inflateInit(&zstream_); + } + + ~ZlibInputStream() { finish(); } + + bool write(StringPiece buf); + bool write(Buffer* input); + bool finish(); + // inflateEnd(&zstream_); + +private: + int decompress(int flush); + + Buffer* output_; + z_stream zstream_; + int zerror_; +}; + +// input is uncompressed data, output zlib compressed data +class ZlibOutputStream : noncopyable { +public: + explicit ZlibOutputStream(Buffer* output) + : output_(output), zerror_(Z_OK), bufferSize_(1024) + { + memZero(&zstream_, sizeof zstream_); + zerror_ = deflateInit(&zstream_, Z_DEFAULT_COMPRESSION); + } + + ~ZlibOutputStream() { finish(); } + + // Return last error message or NULL if no error. + const char* zlibErrorMessage() const { return zstream_.msg; } + + int zlibErrorCode() const { return zerror_; } + int64_t inputBytes() const { return zstream_.total_in; } + int64_t outputBytes() const { return zstream_.total_out; } + int internalOutputBufferSize() const { return bufferSize_; } + + bool write(StringPiece buf) + { + if (zerror_ != Z_OK) return false; + + assert(zstream_.next_in == NULL && zstream_.avail_in == 0); + void* in = const_cast<char*>(buf.data()); + zstream_.next_in = static_cast<Bytef*>(in); + zstream_.avail_in = buf.size(); + while (zstream_.avail_in > 0 && zerror_ == Z_OK) { + zerror_ = compress(Z_NO_FLUSH); + } + if (zstream_.avail_in == 0) { + assert(static_cast<const void*>(zstream_.next_in) == buf.end()); + zstream_.next_in = NULL; + } + return zerror_ == Z_OK; + } + + // compress input as much as possible, not guarantee consuming all data. + bool write(Buffer* input) + { + if (zerror_ != Z_OK) return false; + + void* in = const_cast<char*>(input->peek()); + zstream_.next_in = static_cast<Bytef*>(in); + zstream_.avail_in = static_cast<int>(input->readableBytes()); + if (zstream_.avail_in > 0 && zerror_ == Z_OK) { + zerror_ = compress(Z_NO_FLUSH); + } + input->retrieve(input->readableBytes() - zstream_.avail_in); + return zerror_ == Z_OK; + } + + bool finish() + { + if (zerror_ != Z_OK) return false; + + while (zerror_ == Z_OK) { + zerror_ = compress(Z_FINISH); + } + zerror_ = deflateEnd(&zstream_); + bool ok = zerror_ == Z_OK; + zerror_ = Z_STREAM_END; + return ok; + } + +private: + int compress(int flush) + { + output_->ensureWritableBytes(bufferSize_); + zstream_.next_out = reinterpret_cast<Bytef*>(output_->beginWrite()); + zstream_.avail_out = static_cast<int>(output_->writableBytes()); + int error = ::deflate(&zstream_, flush); + output_->hasWritten(output_->writableBytes() - zstream_.avail_out); + if (output_->writableBytes() == 0 && bufferSize_ < 65536) { + bufferSize_ *= 2; + } + return error; + } + + Buffer* output_; + z_stream zstream_; + int zerror_; + int bufferSize_; +}; + +} // namespace net +} // namespace muduo diff --git a/muduo/net/boilerplate.cc b/muduo/net/boilerplate.cc new file mode 100644 index 0000000..d0ebe26 --- /dev/null +++ b/muduo/net/boilerplate.cc @@ -0,0 +1,15 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/BoilerPlate.h" + +using namespace muduo; +using namespace muduo::net; + + diff --git a/muduo/net/boilerplate.h b/muduo/net/boilerplate.h new file mode 100644 index 0000000..0f47dd5 --- /dev/null +++ b/muduo/net/boilerplate.h @@ -0,0 +1,28 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_BOILERPLATE_H +#define MUDUO_NET_BOILERPLATE_H + +#include "muduo/base/noncopyable.h" + +namespace muduo { +namespace net { + +class BoilerPlate : noncopyable { +public: +private: +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_BOILERPLATE_H diff --git a/muduo/net/http/BUILD.bazel b/muduo/net/http/BUILD.bazel new file mode 100644 index 0000000..08b98a4 --- /dev/null +++ b/muduo/net/http/BUILD.bazel @@ -0,0 +1,9 @@ +cc_library( + name = "http", + srcs = glob(["*.cc"]), + hdrs = glob(["*.h"]), + visibility = ["//visibility:public"], + deps = [ + "//muduo/net", + ], +) diff --git a/muduo/net/http/HttpContext.cc b/muduo/net/http/HttpContext.cc new file mode 100644 index 0000000..c46565e --- /dev/null +++ b/muduo/net/http/HttpContext.cc @@ -0,0 +1,90 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/http/HttpContext.h" + +#include "muduo/net/Buffer.h" + +using namespace muduo; +using namespace muduo::net; + +bool HttpContext::processRequestLine(const char* begin, const char* end) +{ + bool succeed = false; + const char* start = begin; + const char* space = std::find(start, end, ' '); + if (space != end && request_.setMethod(start, space)) { + start = space + 1; + space = std::find(start, end, ' '); + if (space != end) { + const char* question = std::find(start, space, '?'); + if (question != space) { + request_.setPath(start, question); + request_.setQuery(question, space); + } else { + request_.setPath(start, space); + } + start = space + 1; + succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1."); + if (succeed) { + if (*(end - 1) == '1') { + request_.setVersion(HttpRequest::kHttp11); + } else if (*(end - 1) == '0') { + request_.setVersion(HttpRequest::kHttp10); + } else { + succeed = false; + } + } + } + } + return succeed; +} + +// return false if any error +bool HttpContext::parseRequest(Buffer* buf, Timestamp receiveTime) +{ + bool ok = true; + bool hasMore = true; + while (hasMore) { + if (state_ == kExpectRequestLine) { + const char* crlf = buf->findCRLF(); + if (crlf) { + ok = processRequestLine(buf->peek(), crlf); + if (ok) { + request_.setReceiveTime(receiveTime); + buf->retrieveUntil(crlf + 2); + state_ = kExpectHeaders; + } else { + hasMore = false; + } + } else { + hasMore = false; + } + } else if (state_ == kExpectHeaders) { + const char* crlf = buf->findCRLF(); + if (crlf) { + const char* colon = std::find(buf->peek(), crlf, ':'); + if (colon != crlf) { + request_.addHeader(buf->peek(), colon, crlf); + } else { + // empty line, end of header + // FIXME: + state_ = kGotAll; + hasMore = false; + } + buf->retrieveUntil(crlf + 2); + } else { + hasMore = false; + } + } else if (state_ == kExpectBody) { + // FIXME: + } + } + return ok; +} diff --git a/muduo/net/http/HttpContext.h b/muduo/net/http/HttpContext.h new file mode 100644 index 0000000..4beee5c --- /dev/null +++ b/muduo/net/http/HttpContext.h @@ -0,0 +1,72 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_HTTP_HTTPCONTEXT_H +#define MUDUO_NET_HTTP_HTTPCONTEXT_H + +#include "muduo/base/copyable.h" + +#include "muduo/net/http/HttpRequest.h" + +namespace muduo +{ +namespace net +{ + +class Buffer; + +class HttpContext : public muduo::copyable +{ + public: + enum HttpRequestParseState + { + kExpectRequestLine, + kExpectHeaders, + kExpectBody, + kGotAll, + }; + + HttpContext() + : state_(kExpectRequestLine) + { + } + + // default copy-ctor, dtor and assignment are fine + + // return false if any error + bool parseRequest(Buffer* buf, Timestamp receiveTime); + + bool gotAll() const + { return state_ == kGotAll; } + + void reset() + { + state_ = kExpectRequestLine; + HttpRequest dummy; + request_.swap(dummy); + } + + const HttpRequest& request() const + { return request_; } + + HttpRequest& request() + { return request_; } + + private: + bool processRequestLine(const char* begin, const char* end); + + HttpRequestParseState state_; + HttpRequest request_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_HTTP_HTTPCONTEXT_H diff --git a/muduo/net/http/HttpRequest.h b/muduo/net/http/HttpRequest.h new file mode 100644 index 0000000..5b0e8a6 --- /dev/null +++ b/muduo/net/http/HttpRequest.h @@ -0,0 +1,150 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_HTTP_HTTPREQUEST_H +#define MUDUO_NET_HTTP_HTTPREQUEST_H + +#include <assert.h> +#include <stdio.h> + +#include <map> + +#include "muduo/base/Timestamp.h" +#include "muduo/base/Types.h" +#include "muduo/base/copyable.h" + +namespace muduo { +namespace net { + +class HttpRequest : public muduo::copyable { +public: + enum Method { kInvalid, kGet, kPost, kHead, kPut, kDelete }; + enum Version { kUnknown, kHttp10, kHttp11 }; + + HttpRequest() : method_(kInvalid), version_(kUnknown) {} + + void setVersion(Version v) { version_ = v; } + + Version getVersion() const { return version_; } + + bool setMethod(const char* start, const char* end) + { + assert(method_ == kInvalid); + string m(start, end); + if (m == "GET") { + method_ = kGet; + } else if (m == "POST") { + method_ = kPost; + } else if (m == "HEAD") { + method_ = kHead; + } else if (m == "PUT") { + method_ = kPut; + } else if (m == "DELETE") { + method_ = kDelete; + } else { + method_ = kInvalid; + } + return method_ != kInvalid; + } + + Method method() const { return method_; } + + const char* methodString() const + { + const char* result = "UNKNOWN"; + switch (method_) { + case kGet: + result = "GET"; + break; + case kPost: + result = "POST"; + break; + case kHead: + result = "HEAD"; + break; + case kPut: + result = "PUT"; + break; + case kDelete: + result = "DELETE"; + break; + default: + break; + } + return result; + } + + void setPath(const char* start, const char* end) + { + path_.assign(start, end); + } + + const string& path() const { return path_; } + + void setQuery(const char* start, const char* end) + { + query_.assign(start, end); + } + + const string& query() const { return query_; } + + void setReceiveTime(Timestamp t) { receiveTime_ = t; } + + Timestamp receiveTime() const { return receiveTime_; } + + void addHeader(const char* start, const char* colon, const char* end) + { + string field(start, colon); + ++colon; + while (colon < end && isspace(*colon)) { + ++colon; + } + string value(colon, end); + while (!value.empty() && isspace(value[value.size() - 1])) { + value.resize(value.size() - 1); + } + headers_[field] = value; + } + + string getHeader(const string& field) const + { + string result; + std::map<string, string>::const_iterator it = headers_.find(field); + if (it != headers_.end()) { + result = it->second; + } + return result; + } + + const std::map<string, string>& headers() const { return headers_; } + + void swap(HttpRequest& that) + { + std::swap(method_, that.method_); + std::swap(version_, that.version_); + path_.swap(that.path_); + query_.swap(that.query_); + receiveTime_.swap(that.receiveTime_); + headers_.swap(that.headers_); + } + +private: + Method method_; + Version version_; + string path_; + string query_; + Timestamp receiveTime_; + std::map<string, string> headers_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_HTTP_HTTPREQUEST_H diff --git a/muduo/net/http/HttpResponse.cc b/muduo/net/http/HttpResponse.cc new file mode 100644 index 0000000..cbfe8a0 --- /dev/null +++ b/muduo/net/http/HttpResponse.cc @@ -0,0 +1,44 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/http/HttpResponse.h" + +#include <stdio.h> + +#include "muduo/net/Buffer.h" + +using namespace muduo; +using namespace muduo::net; + +void HttpResponse::appendToBuffer(Buffer* output) const +{ + char buf[32]; + snprintf(buf, sizeof buf, "HTTP/1.1 %d ", statusCode_); + output->append(buf); + output->append(statusMessage_); + output->append("\r\n"); + + if (closeConnection_) { + output->append("Connection: close\r\n"); + } else { + snprintf(buf, sizeof buf, "Content-Length: %zd\r\n", body_.size()); + output->append(buf); + output->append("Connection: Keep-Alive\r\n"); + } + + for (const auto& header : headers_) { + output->append(header.first); + output->append(": "); + output->append(header.second); + output->append("\r\n"); + } + + output->append("\r\n"); + output->append(body_); +} diff --git a/muduo/net/http/HttpResponse.h b/muduo/net/http/HttpResponse.h new file mode 100644 index 0000000..eb3910f --- /dev/null +++ b/muduo/net/http/HttpResponse.h @@ -0,0 +1,79 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_HTTP_HTTPRESPONSE_H +#define MUDUO_NET_HTTP_HTTPRESPONSE_H + +#include "muduo/base/copyable.h" +#include "muduo/base/Types.h" + +#include <map> + +namespace muduo +{ +namespace net +{ + +class Buffer; +class HttpResponse : public muduo::copyable +{ + public: + enum HttpStatusCode + { + kUnknown, + k200Ok = 200, + k301MovedPermanently = 301, + k400BadRequest = 400, + k404NotFound = 404, + }; + + explicit HttpResponse(bool close) + : statusCode_(kUnknown), + closeConnection_(close) + { + } + + void setStatusCode(HttpStatusCode code) + { statusCode_ = code; } + + void setStatusMessage(const string& message) + { statusMessage_ = message; } + + void setCloseConnection(bool on) + { closeConnection_ = on; } + + bool closeConnection() const + { return closeConnection_; } + + void setContentType(const string& contentType) + { addHeader("Content-Type", contentType); } + + // FIXME: replace string with StringPiece + void addHeader(const string& key, const string& value) + { headers_[key] = value; } + + void setBody(const string& body) + { body_ = body; } + + void appendToBuffer(Buffer* output) const; + + private: + std::map<string, string> headers_; + HttpStatusCode statusCode_; + // FIXME: add http version + string statusMessage_; + bool closeConnection_; + string body_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_HTTP_HTTPRESPONSE_H diff --git a/muduo/net/http/HttpServer.cc b/muduo/net/http/HttpServer.cc new file mode 100644 index 0000000..49c08d2 --- /dev/null +++ b/muduo/net/http/HttpServer.cc @@ -0,0 +1,100 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/http/HttpServer.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/http/HttpContext.h" +#include "muduo/net/http/HttpRequest.h" +#include "muduo/net/http/HttpResponse.h" + +using namespace muduo; +using namespace muduo::net; + +namespace muduo +{ +namespace net +{ +namespace detail +{ + +void defaultHttpCallback(const HttpRequest&, HttpResponse* resp) +{ + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); +} + +} // namespace detail +} // namespace net +} // namespace muduo + +HttpServer::HttpServer(EventLoop* loop, + const InetAddress& listenAddr, + const string& name, + TcpServer::Option option) + : server_(loop, listenAddr, name, option), + httpCallback_(detail::defaultHttpCallback) +{ + server_.setConnectionCallback( + std::bind(&HttpServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&HttpServer::onMessage, this, _1, _2, _3)); +} + +void HttpServer::start() +{ + LOG_WARN << "HttpServer[" << server_.name() + << "] starts listenning on " << server_.ipPort(); + server_.start(); +} + +void HttpServer::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn->setContext(HttpContext()); + } +} + +void HttpServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + HttpContext* context = boost::any_cast<HttpContext>(conn->getMutableContext()); + + if (!context->parseRequest(buf, receiveTime)) + { + conn->send("HTTP/1.1 400 Bad Request\r\n\r\n"); + conn->shutdown(); + } + + if (context->gotAll()) + { + onRequest(conn, context->request()); + context->reset(); + } +} + +void HttpServer::onRequest(const TcpConnectionPtr& conn, const HttpRequest& req) +{ + const string& connection = req.getHeader("Connection"); + bool close = connection == "close" || + (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive"); + HttpResponse response(close); + httpCallback_(req, &response); + Buffer buf; + response.appendToBuffer(&buf); + conn->send(&buf); + if (response.closeConnection()) + { + conn->shutdown(); + } +} + diff --git a/muduo/net/http/HttpServer.h b/muduo/net/http/HttpServer.h new file mode 100644 index 0000000..9609a11 --- /dev/null +++ b/muduo/net/http/HttpServer.h @@ -0,0 +1,68 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_HTTP_HTTPSERVER_H +#define MUDUO_NET_HTTP_HTTPSERVER_H + +#include "muduo/net/TcpServer.h" + +namespace muduo +{ +namespace net +{ + +class HttpRequest; +class HttpResponse; + +/// A simple embeddable HTTP server designed for report status of a program. +/// It is not a fully HTTP 1.1 compliant server, but provides minimum features +/// that can communicate with HttpClient and Web browser. +/// It is synchronous, just like Java Servlet. +class HttpServer : noncopyable +{ + public: + typedef std::function<void (const HttpRequest&, + HttpResponse*)> HttpCallback; + + HttpServer(EventLoop* loop, + const InetAddress& listenAddr, + const string& name, + TcpServer::Option option = TcpServer::kNoReusePort); + + EventLoop* getLoop() const { return server_.getLoop(); } + + /// Not thread safe, callback be registered before calling start(). + void setHttpCallback(const HttpCallback& cb) + { + httpCallback_ = cb; + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start(); + + private: + void onConnection(const TcpConnectionPtr& conn); + void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime); + void onRequest(const TcpConnectionPtr&, const HttpRequest&); + + TcpServer server_; + HttpCallback httpCallback_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_HTTP_HTTPSERVER_H diff --git a/muduo/net/http/tests/HttpRequest_unittest.cc b/muduo/net/http/tests/HttpRequest_unittest.cc new file mode 100644 index 0000000..cb89305 --- /dev/null +++ b/muduo/net/http/tests/HttpRequest_unittest.cc @@ -0,0 +1,79 @@ +#include "muduo/net/http/HttpContext.h" +#include "muduo/net/Buffer.h" + +//#define BOOST_TEST_MODULE BufferTest +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +using muduo::string; +using muduo::Timestamp; +using muduo::net::Buffer; +using muduo::net::HttpContext; +using muduo::net::HttpRequest; + +BOOST_AUTO_TEST_CASE(testParseRequestAllInOne) +{ + HttpContext context; + Buffer input; + input.append("GET /index.html HTTP/1.1\r\n" + "Host: www.chenshuo.com\r\n" + "\r\n"); + + BOOST_CHECK(context.parseRequest(&input, Timestamp::now())); + BOOST_CHECK(context.gotAll()); + const HttpRequest& request = context.request(); + BOOST_CHECK_EQUAL(request.method(), HttpRequest::kGet); + BOOST_CHECK_EQUAL(request.path(), string("/index.html")); + BOOST_CHECK_EQUAL(request.getVersion(), HttpRequest::kHttp11); + BOOST_CHECK_EQUAL(request.getHeader("Host"), string("www.chenshuo.com")); + BOOST_CHECK_EQUAL(request.getHeader("User-Agent"), string("")); +} + +BOOST_AUTO_TEST_CASE(testParseRequestInTwoPieces) +{ + string all("GET /index.html HTTP/1.1\r\n" + "Host: www.chenshuo.com\r\n" + "\r\n"); + + for (size_t sz1 = 0; sz1 < all.size(); ++sz1) + { + HttpContext context; + Buffer input; + input.append(all.c_str(), sz1); + BOOST_CHECK(context.parseRequest(&input, Timestamp::now())); + BOOST_CHECK(!context.gotAll()); + + size_t sz2 = all.size() - sz1; + input.append(all.c_str() + sz1, sz2); + BOOST_CHECK(context.parseRequest(&input, Timestamp::now())); + BOOST_CHECK(context.gotAll()); + const HttpRequest& request = context.request(); + BOOST_CHECK_EQUAL(request.method(), HttpRequest::kGet); + BOOST_CHECK_EQUAL(request.path(), string("/index.html")); + BOOST_CHECK_EQUAL(request.getVersion(), HttpRequest::kHttp11); + BOOST_CHECK_EQUAL(request.getHeader("Host"), string("www.chenshuo.com")); + BOOST_CHECK_EQUAL(request.getHeader("User-Agent"), string("")); + } +} + +BOOST_AUTO_TEST_CASE(testParseRequestEmptyHeaderValue) +{ + HttpContext context; + Buffer input; + input.append("GET /index.html HTTP/1.1\r\n" + "Host: www.chenshuo.com\r\n" + "User-Agent:\r\n" + "Accept-Encoding: \r\n" + "\r\n"); + + BOOST_CHECK(context.parseRequest(&input, Timestamp::now())); + BOOST_CHECK(context.gotAll()); + const HttpRequest& request = context.request(); + BOOST_CHECK_EQUAL(request.method(), HttpRequest::kGet); + BOOST_CHECK_EQUAL(request.path(), string("/index.html")); + BOOST_CHECK_EQUAL(request.getVersion(), HttpRequest::kHttp11); + BOOST_CHECK_EQUAL(request.getHeader("Host"), string("www.chenshuo.com")); + BOOST_CHECK_EQUAL(request.getHeader("User-Agent"), string("")); + BOOST_CHECK_EQUAL(request.getHeader("Accept-Encoding"), string("")); +} diff --git a/muduo/net/http/tests/HttpServer_test.cc b/muduo/net/http/tests/HttpServer_test.cc new file mode 100644 index 0000000..0ec38fb --- /dev/null +++ b/muduo/net/http/tests/HttpServer_test.cc @@ -0,0 +1,150 @@ +#include "muduo/net/http/HttpServer.h" +#include "muduo/net/http/HttpRequest.h" +#include "muduo/net/http/HttpResponse.h" +#include "muduo/net/EventLoop.h" +#include "muduo/base/Logging.h" + +#include <iostream> +#include <map> + +using namespace muduo; +using namespace muduo::net; + +extern char favicon[555]; +bool benchmark = false; + +void onRequest(const HttpRequest& req, HttpResponse* resp) +{ + std::cout << "Headers " << req.methodString() << " " << req.path() << std::endl; + if (!benchmark) + { + const std::map<string, string>& headers = req.headers(); + for (const auto& header : headers) + { + std::cout << header.first << ": " << header.second << std::endl; + } + } + + if (req.path() == "/") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/html"); + resp->addHeader("Server", "Muduo"); + string now = Timestamp::now().toFormattedString(); + resp->setBody("<html><head><title>This is title</title></head>" + "<body><h1>Hello</h1>Now is " + now + + "</body></html>"); + } + else if (req.path() == "/favicon.ico") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("image/png"); + resp->setBody(string(favicon, sizeof favicon)); + } + else if (req.path() == "/hello") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/plain"); + resp->addHeader("Server", "Muduo"); + resp->setBody("hello, world!\n"); + } + else + { + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); + } +} + +int main(int argc, char* argv[]) +{ + int numThreads = 0; + if (argc > 1) + { + benchmark = true; + Logger::setLogLevel(Logger::WARN); + numThreads = atoi(argv[1]); + } + EventLoop loop; + HttpServer server(&loop, InetAddress(8000), "dummy"); + server.setHttpCallback(onRequest); + server.setThreadNum(numThreads); + server.start(); + loop.loop(); +} + +char favicon[555] = { + '\x89', 'P', 'N', 'G', '\xD', '\xA', '\x1A', '\xA', + '\x0', '\x0', '\x0', '\xD', 'I', 'H', 'D', 'R', + '\x0', '\x0', '\x0', '\x10', '\x0', '\x0', '\x0', '\x10', + '\x8', '\x6', '\x0', '\x0', '\x0', '\x1F', '\xF3', '\xFF', + 'a', '\x0', '\x0', '\x0', '\x19', 't', 'E', 'X', + 't', 'S', 'o', 'f', 't', 'w', 'a', 'r', + 'e', '\x0', 'A', 'd', 'o', 'b', 'e', '\x20', + 'I', 'm', 'a', 'g', 'e', 'R', 'e', 'a', + 'd', 'y', 'q', '\xC9', 'e', '\x3C', '\x0', '\x0', + '\x1', '\xCD', 'I', 'D', 'A', 'T', 'x', '\xDA', + '\x94', '\x93', '9', 'H', '\x3', 'A', '\x14', '\x86', + '\xFF', '\x5D', 'b', '\xA7', '\x4', 'R', '\xC4', 'm', + '\x22', '\x1E', '\xA0', 'F', '\x24', '\x8', '\x16', '\x16', + 'v', '\xA', '6', '\xBA', 'J', '\x9A', '\x80', '\x8', + 'A', '\xB4', 'q', '\x85', 'X', '\x89', 'G', '\xB0', + 'I', '\xA9', 'Q', '\x24', '\xCD', '\xA6', '\x8', '\xA4', + 'H', 'c', '\x91', 'B', '\xB', '\xAF', 'V', '\xC1', + 'F', '\xB4', '\x15', '\xCF', '\x22', 'X', '\x98', '\xB', + 'T', 'H', '\x8A', 'd', '\x93', '\x8D', '\xFB', 'F', + 'g', '\xC9', '\x1A', '\x14', '\x7D', '\xF0', 'f', 'v', + 'f', '\xDF', '\x7C', '\xEF', '\xE7', 'g', 'F', '\xA8', + '\xD5', 'j', 'H', '\x24', '\x12', '\x2A', '\x0', '\x5', + '\xBF', 'G', '\xD4', '\xEF', '\xF7', '\x2F', '6', '\xEC', + '\x12', '\x20', '\x1E', '\x8F', '\xD7', '\xAA', '\xD5', '\xEA', + '\xAF', 'I', '5', 'F', '\xAA', 'T', '\x5F', '\x9F', + '\x22', 'A', '\x2A', '\x95', '\xA', '\x83', '\xE5', 'r', + '9', 'd', '\xB3', 'Y', '\x96', '\x99', 'L', '\x6', + '\xE9', 't', '\x9A', '\x25', '\x85', '\x2C', '\xCB', 'T', + '\xA7', '\xC4', 'b', '1', '\xB5', '\x5E', '\x0', '\x3', + 'h', '\x9A', '\xC6', '\x16', '\x82', '\x20', 'X', 'R', + '\x14', 'E', '6', 'S', '\x94', '\xCB', 'e', 'x', + '\xBD', '\x5E', '\xAA', 'U', 'T', '\x23', 'L', '\xC0', + '\xE0', '\xE2', '\xC1', '\x8F', '\x0', '\x9E', '\xBC', '\x9', + 'A', '\x7C', '\x3E', '\x1F', '\x83', 'D', '\x22', '\x11', + '\xD5', 'T', '\x40', '\x3F', '8', '\x80', 'w', '\xE5', + '3', '\x7', '\xB8', '\x5C', '\x2E', 'H', '\x92', '\x4', + '\x87', '\xC3', '\x81', '\x40', '\x20', '\x40', 'g', '\x98', + '\xE9', '6', '\x1A', '\xA6', 'g', '\x15', '\x4', '\xE3', + '\xD7', '\xC8', '\xBD', '\x15', '\xE1', 'i', '\xB7', 'C', + '\xAB', '\xEA', 'x', '\x2F', 'j', 'X', '\x92', '\xBB', + '\x18', '\x20', '\x9F', '\xCF', '3', '\xC3', '\xB8', '\xE9', + 'N', '\xA7', '\xD3', 'l', 'J', '\x0', 'i', '6', + '\x7C', '\x8E', '\xE1', '\xFE', 'V', '\x84', '\xE7', '\x3C', + '\x9F', 'r', '\x2B', '\x3A', 'B', '\x7B', '7', 'f', + 'w', '\xAE', '\x8E', '\xE', '\xF3', '\xBD', 'R', '\xA9', + 'd', '\x2', 'B', '\xAF', '\x85', '2', 'f', 'F', + '\xBA', '\xC', '\xD9', '\x9F', '\x1D', '\x9A', 'l', '\x22', + '\xE6', '\xC7', '\x3A', '\x2C', '\x80', '\xEF', '\xC1', '\x15', + '\x90', '\x7', '\x93', '\xA2', '\x28', '\xA0', 'S', 'j', + '\xB1', '\xB8', '\xDF', '\x29', '5', 'C', '\xE', '\x3F', + 'X', '\xFC', '\x98', '\xDA', 'y', 'j', 'P', '\x40', + '\x0', '\x87', '\xAE', '\x1B', '\x17', 'B', '\xB4', '\x3A', + '\x3F', '\xBE', 'y', '\xC7', '\xA', '\x26', '\xB6', '\xEE', + '\xD9', '\x9A', '\x60', '\x14', '\x93', '\xDB', '\x8F', '\xD', + '\xA', '\x2E', '\xE9', '\x23', '\x95', '\x29', 'X', '\x0', + '\x27', '\xEB', 'n', 'V', 'p', '\xBC', '\xD6', '\xCB', + '\xD6', 'G', '\xAB', '\x3D', 'l', '\x7D', '\xB8', '\xD2', + '\xDD', '\xA0', '\x60', '\x83', '\xBA', '\xEF', '\x5F', '\xA4', + '\xEA', '\xCC', '\x2', 'N', '\xAE', '\x5E', 'p', '\x1A', + '\xEC', '\xB3', '\x40', '9', '\xAC', '\xFE', '\xF2', '\x91', + '\x89', 'g', '\x91', '\x85', '\x21', '\xA8', '\x87', '\xB7', + 'X', '\x7E', '\x7E', '\x85', '\xBB', '\xCD', 'N', 'N', + 'b', 't', '\x40', '\xFA', '\x93', '\x89', '\xEC', '\x1E', + '\xEC', '\x86', '\x2', 'H', '\x26', '\x93', '\xD0', 'u', + '\x1D', '\x7F', '\x9', '2', '\x95', '\xBF', '\x1F', '\xDB', + '\xD7', 'c', '\x8A', '\x1A', '\xF7', '\x5C', '\xC1', '\xFF', + '\x22', 'J', '\xC3', '\x87', '\x0', '\x3', '\x0', 'K', + '\xBB', '\xF8', '\xD6', '\x2A', 'v', '\x98', 'I', '\x0', + '\x0', '\x0', '\x0', 'I', 'E', 'N', 'D', '\xAE', + 'B', '\x60', '\x82', +}; diff --git a/muduo/net/inspect/BUILD.bazel b/muduo/net/inspect/BUILD.bazel new file mode 100644 index 0000000..0592e50 --- /dev/null +++ b/muduo/net/inspect/BUILD.bazel @@ -0,0 +1,9 @@ +cc_library( + name = "inspect", + srcs = glob(["*.cc"]), + hdrs = glob(["*.h"]), + visibility = ["//visibility:public"], + deps = [ + "//muduo/net/http", + ], +) diff --git a/muduo/net/inspect/Inspector.cc b/muduo/net/inspect/Inspector.cc new file mode 100644 index 0000000..894abd3 --- /dev/null +++ b/muduo/net/inspect/Inspector.cc @@ -0,0 +1,383 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/inspect/Inspector.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/http/HttpRequest.h" +#include "muduo/net/http/HttpResponse.h" +#include "muduo/net/inspect/ProcessInspector.h" +#include "muduo/net/inspect/PerformanceInspector.h" +#include "muduo/net/inspect/SystemInspector.h" + +//#include <iostream> +//#include <iterator> +//#include <sstream> +//#include <boost/algorithm/string/classification.hpp> +//#include <boost/algorithm/string/split.hpp> + +using namespace muduo; +using namespace muduo::net; + +namespace +{ +Inspector* g_globalInspector = 0; + +// Looks buggy +std::vector<string> split(const string& str) +{ + std::vector<string> result; + size_t start = 0; + size_t pos = str.find('/'); + while (pos != string::npos) + { + if (pos > start) + { + result.push_back(str.substr(start, pos-start)); + } + start = pos+1; + pos = str.find('/', start); + } + + if (start < str.length()) + { + result.push_back(str.substr(start)); + } + + return result; +} + +} // namespace + +extern char favicon[1743]; + +Inspector::Inspector(EventLoop* loop, + const InetAddress& httpAddr, + const string& name) + : server_(loop, httpAddr, "Inspector:"+name), + processInspector_(new ProcessInspector), + systemInspector_(new SystemInspector) +{ + assert(CurrentThread::isMainThread()); + assert(g_globalInspector == 0); + g_globalInspector = this; + server_.setHttpCallback(std::bind(&Inspector::onRequest, this, _1, _2)); + processInspector_->registerCommands(this); + systemInspector_->registerCommands(this); +#ifdef HAVE_TCMALLOC + performanceInspector_.reset(new PerformanceInspector); + performanceInspector_->registerCommands(this); +#endif + loop->runAfter(0, std::bind(&Inspector::start, this)); // little race condition +} + +Inspector::~Inspector() +{ + assert(CurrentThread::isMainThread()); + g_globalInspector = NULL; +} + +void Inspector::add(const string& module, + const string& command, + const Callback& cb, + const string& help) +{ + MutexLockGuard lock(mutex_); + modules_[module][command] = cb; + helps_[module][command] = help; +} + +void Inspector::remove(const string& module, const string& command) +{ + MutexLockGuard lock(mutex_); + std::map<string, CommandList>::iterator it = modules_.find(module); + if (it != modules_.end()) + { + it->second.erase(command); + helps_[module].erase(command); + } +} + +void Inspector::start() +{ + server_.start(); +} + +void Inspector::onRequest(const HttpRequest& req, HttpResponse* resp) +{ + if (req.path() == "/") + { + string result; + MutexLockGuard lock(mutex_); + for (std::map<string, HelpList>::const_iterator helpListI = helps_.begin(); + helpListI != helps_.end(); + ++helpListI) + { + const HelpList& list = helpListI->second; + for (const auto& it : list) + { + result += "/"; + result += helpListI->first; + result += "/"; + result += it.first; + size_t len = helpListI->first.size() + it.first.size(); + result += string(len >= 25 ? 1 : 25 - len, ' '); + result += it.second; + result += "\n"; + } + } + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/plain"); + resp->setBody(result); + } + else + { + std::vector<string> result = split(req.path()); + // boost::split(result, req.path(), boost::is_any_of("/")); + //std::copy(result.begin(), result.end(), std::ostream_iterator<string>(std::cout, ", ")); + //std::cout << "\n"; + bool ok = false; + if (result.size() == 0) + { + LOG_DEBUG << req.path(); + } + else if (result.size() == 1) + { + string module = result[0]; + if (module == "favicon.ico") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("image/png"); + resp->setBody(string(favicon, sizeof favicon)); + + ok = true; + } + else + { + LOG_ERROR << "Unimplemented " << module; + } + } + else + { + string module = result[0]; + MutexLockGuard lock(mutex_); + std::map<string, CommandList>::const_iterator commListI = modules_.find(module); + if (commListI != modules_.end()) + { + string command = result[1]; + const CommandList& commList = commListI->second; + CommandList::const_iterator it = commList.find(command); + if (it != commList.end()) + { + ArgList args(result.begin()+2, result.end()); + if (it->second) + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/plain"); + const Callback& cb = it->second; + resp->setBody(cb(req.method(), args)); + ok = true; + } + } + } + + } + + if (!ok) + { + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + } + //resp->setCloseConnection(true); + } +} + +char favicon[1743] = +{ + '\x89', '\x50', '\x4e', '\x47', '\x0d', '\x0a', '\x1a', '\x0a', '\x00', '\x00', + '\x00', '\x0d', '\x49', '\x48', '\x44', '\x52', '\x00', '\x00', '\x00', '\x10', + '\x00', '\x00', '\x00', '\x10', '\x08', '\x06', '\x00', '\x00', '\x00', '\x1f', + '\xf3', '\xff', '\x61', '\x00', '\x00', '\x00', '\x04', '\x73', '\x42', '\x49', + '\x54', '\x08', '\x08', '\x08', '\x08', '\x7c', '\x08', '\x64', '\x88', '\x00', + '\x00', '\x00', '\x09', '\x70', '\x48', '\x59', '\x73', '\x00', '\x00', '\x0b', + '\x12', '\x00', '\x00', '\x0b', '\x12', '\x01', '\xd2', '\xdd', '\x7e', '\xfc', + '\x00', '\x00', '\x00', '\x1c', '\x74', '\x45', '\x58', '\x74', '\x53', '\x6f', + '\x66', '\x74', '\x77', '\x61', '\x72', '\x65', '\x00', '\x41', '\x64', '\x6f', + '\x62', '\x65', '\x20', '\x46', '\x69', '\x72', '\x65', '\x77', '\x6f', '\x72', + '\x6b', '\x73', '\x20', '\x43', '\x53', '\x33', '\x98', '\xd6', '\x46', '\x03', + '\x00', '\x00', '\x00', '\x15', '\x74', '\x45', '\x58', '\x74', '\x43', '\x72', + '\x65', '\x61', '\x74', '\x69', '\x6f', '\x6e', '\x20', '\x54', '\x69', '\x6d', + '\x65', '\x00', '\x32', '\x2f', '\x31', '\x37', '\x2f', '\x30', '\x38', '\x20', + '\x9c', '\xaa', '\x58', '\x00', '\x00', '\x04', '\x11', '\x74', '\x45', '\x58', + '\x74', '\x58', '\x4d', '\x4c', '\x3a', '\x63', '\x6f', '\x6d', '\x2e', '\x61', + '\x64', '\x6f', '\x62', '\x65', '\x2e', '\x78', '\x6d', '\x70', '\x00', '\x3c', + '\x3f', '\x78', '\x70', '\x61', '\x63', '\x6b', '\x65', '\x74', '\x20', '\x62', + '\x65', '\x67', '\x69', '\x6e', '\x3d', '\x22', '\x20', '\x20', '\x20', '\x22', + '\x20', '\x69', '\x64', '\x3d', '\x22', '\x57', '\x35', '\x4d', '\x30', '\x4d', + '\x70', '\x43', '\x65', '\x68', '\x69', '\x48', '\x7a', '\x72', '\x65', '\x53', + '\x7a', '\x4e', '\x54', '\x63', '\x7a', '\x6b', '\x63', '\x39', '\x64', '\x22', + '\x3f', '\x3e', '\x0a', '\x3c', '\x78', '\x3a', '\x78', '\x6d', '\x70', '\x6d', + '\x65', '\x74', '\x61', '\x20', '\x78', '\x6d', '\x6c', '\x6e', '\x73', '\x3a', + '\x78', '\x3d', '\x22', '\x61', '\x64', '\x6f', '\x62', '\x65', '\x3a', '\x6e', + '\x73', '\x3a', '\x6d', '\x65', '\x74', '\x61', '\x2f', '\x22', '\x20', '\x78', + '\x3a', '\x78', '\x6d', '\x70', '\x74', '\x6b', '\x3d', '\x22', '\x41', '\x64', + '\x6f', '\x62', '\x65', '\x20', '\x58', '\x4d', '\x50', '\x20', '\x43', '\x6f', + '\x72', '\x65', '\x20', '\x34', '\x2e', '\x31', '\x2d', '\x63', '\x30', '\x33', + '\x34', '\x20', '\x34', '\x36', '\x2e', '\x32', '\x37', '\x32', '\x39', '\x37', + '\x36', '\x2c', '\x20', '\x53', '\x61', '\x74', '\x20', '\x4a', '\x61', '\x6e', + '\x20', '\x32', '\x37', '\x20', '\x32', '\x30', '\x30', '\x37', '\x20', '\x32', + '\x32', '\x3a', '\x31', '\x31', '\x3a', '\x34', '\x31', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x22', '\x3e', '\x0a', '\x20', '\x20', + '\x20', '\x3c', '\x72', '\x64', '\x66', '\x3a', '\x52', '\x44', '\x46', '\x20', + '\x78', '\x6d', '\x6c', '\x6e', '\x73', '\x3a', '\x72', '\x64', '\x66', '\x3d', + '\x22', '\x68', '\x74', '\x74', '\x70', '\x3a', '\x2f', '\x2f', '\x77', '\x77', + '\x77', '\x2e', '\x77', '\x33', '\x2e', '\x6f', '\x72', '\x67', '\x2f', '\x31', + '\x39', '\x39', '\x39', '\x2f', '\x30', '\x32', '\x2f', '\x32', '\x32', '\x2d', + '\x72', '\x64', '\x66', '\x2d', '\x73', '\x79', '\x6e', '\x74', '\x61', '\x78', + '\x2d', '\x6e', '\x73', '\x23', '\x22', '\x3e', '\x0a', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x3c', '\x72', '\x64', '\x66', '\x3a', '\x44', '\x65', + '\x73', '\x63', '\x72', '\x69', '\x70', '\x74', '\x69', '\x6f', '\x6e', '\x20', + '\x72', '\x64', '\x66', '\x3a', '\x61', '\x62', '\x6f', '\x75', '\x74', '\x3d', + '\x22', '\x22', '\x0a', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x78', '\x6d', '\x6c', '\x6e', '\x73', + '\x3a', '\x78', '\x61', '\x70', '\x3d', '\x22', '\x68', '\x74', '\x74', '\x70', + '\x3a', '\x2f', '\x2f', '\x6e', '\x73', '\x2e', '\x61', '\x64', '\x6f', '\x62', + '\x65', '\x2e', '\x63', '\x6f', '\x6d', '\x2f', '\x78', '\x61', '\x70', '\x2f', + '\x31', '\x2e', '\x30', '\x2f', '\x22', '\x3e', '\x0a', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x3c', '\x78', '\x61', '\x70', + '\x3a', '\x43', '\x72', '\x65', '\x61', '\x74', '\x6f', '\x72', '\x54', '\x6f', + '\x6f', '\x6c', '\x3e', '\x41', '\x64', '\x6f', '\x62', '\x65', '\x20', '\x46', + '\x69', '\x72', '\x65', '\x77', '\x6f', '\x72', '\x6b', '\x73', '\x20', '\x43', + '\x53', '\x33', '\x3c', '\x2f', '\x78', '\x61', '\x70', '\x3a', '\x43', '\x72', + '\x65', '\x61', '\x74', '\x6f', '\x72', '\x54', '\x6f', '\x6f', '\x6c', '\x3e', + '\x0a', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x3c', '\x78', '\x61', '\x70', '\x3a', '\x43', '\x72', '\x65', '\x61', '\x74', + '\x65', '\x44', '\x61', '\x74', '\x65', '\x3e', '\x32', '\x30', '\x30', '\x38', + '\x2d', '\x30', '\x32', '\x2d', '\x31', '\x37', '\x54', '\x30', '\x32', '\x3a', + '\x33', '\x36', '\x3a', '\x34', '\x35', '\x5a', '\x3c', '\x2f', '\x78', '\x61', + '\x70', '\x3a', '\x43', '\x72', '\x65', '\x61', '\x74', '\x65', '\x44', '\x61', + '\x74', '\x65', '\x3e', '\x0a', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x3c', '\x78', '\x61', '\x70', '\x3a', '\x4d', '\x6f', + '\x64', '\x69', '\x66', '\x79', '\x44', '\x61', '\x74', '\x65', '\x3e', '\x32', + '\x30', '\x30', '\x38', '\x2d', '\x30', '\x33', '\x2d', '\x32', '\x34', '\x54', + '\x31', '\x39', '\x3a', '\x30', '\x30', '\x3a', '\x34', '\x32', '\x5a', '\x3c', + '\x2f', '\x78', '\x61', '\x70', '\x3a', '\x4d', '\x6f', '\x64', '\x69', '\x66', + '\x79', '\x44', '\x61', '\x74', '\x65', '\x3e', '\x0a', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x3c', '\x2f', '\x72', '\x64', '\x66', '\x3a', '\x44', + '\x65', '\x73', '\x63', '\x72', '\x69', '\x70', '\x74', '\x69', '\x6f', '\x6e', + '\x3e', '\x0a', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x3c', '\x72', + '\x64', '\x66', '\x3a', '\x44', '\x65', '\x73', '\x63', '\x72', '\x69', '\x70', + '\x74', '\x69', '\x6f', '\x6e', '\x20', '\x72', '\x64', '\x66', '\x3a', '\x61', + '\x62', '\x6f', '\x75', '\x74', '\x3d', '\x22', '\x22', '\x0a', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x78', '\x6d', '\x6c', '\x6e', '\x73', '\x3a', '\x64', '\x63', '\x3d', '\x22', + '\x68', '\x74', '\x74', '\x70', '\x3a', '\x2f', '\x2f', '\x70', '\x75', '\x72', + '\x6c', '\x2e', '\x6f', '\x72', '\x67', '\x2f', '\x64', '\x63', '\x2f', '\x65', + '\x6c', '\x65', '\x6d', '\x65', '\x6e', '\x74', '\x73', '\x2f', '\x31', '\x2e', + '\x31', '\x2f', '\x22', '\x3e', '\x0a', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x3c', '\x64', '\x63', '\x3a', '\x66', '\x6f', + '\x72', '\x6d', '\x61', '\x74', '\x3e', '\x69', '\x6d', '\x61', '\x67', '\x65', + '\x2f', '\x70', '\x6e', '\x67', '\x3c', '\x2f', '\x64', '\x63', '\x3a', '\x66', + '\x6f', '\x72', '\x6d', '\x61', '\x74', '\x3e', '\x0a', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x3c', '\x2f', '\x72', '\x64', '\x66', '\x3a', '\x44', + '\x65', '\x73', '\x63', '\x72', '\x69', '\x70', '\x74', '\x69', '\x6f', '\x6e', + '\x3e', '\x0a', '\x20', '\x20', '\x20', '\x3c', '\x2f', '\x72', '\x64', '\x66', + '\x3a', '\x52', '\x44', '\x46', '\x3e', '\x0a', '\x3c', '\x2f', '\x78', '\x3a', + '\x78', '\x6d', '\x70', '\x6d', '\x65', '\x74', '\x61', '\x3e', '\x0a', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x0a', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x0a', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', '\x20', + '\x20', '\x20', '\x35', '\x1d', '\x52', '\x64', '\x00', '\x00', '\x02', '\x0b', + '\x49', '\x44', '\x41', '\x54', '\x38', '\x8d', '\xa5', '\x93', '\x41', '\x6b', + '\x1a', '\x41', '\x18', '\x86', '\x9f', '\x89', '\x06', '\x23', '\x2e', '\x74', + '\xf5', '\x20', '\x4a', '\x0e', '\x6b', '\x20', '\xe4', '\xe2', '\xa5', '\x78', + '\xc8', '\xa1', '\x78', '\x89', '\x82', '\x17', '\x85', '\x82', '\x82', '\x3d', + '\x26', '\x62', '\x58', '\x72', '\xcd', '\x4f', '\xf1', '\x18', '\x4b', '\x1b', + '\x2f', '\xa5', '\x32', '\xe0', '\xb5', '\x9e', '\x35', '\x1a', '\x58', '\x2f', + '\xe9', '\x0f', '\xe8', '\x65', '\x6d', '\x84', '\xa2', '\x84', '\x2c', '\x0a', + '\x6b', '\x58', '\x62', '\xa7', '\x87', '\x68', '\x68', '\x13', '\xa1', '\xa5', + '\x79', '\xe1', '\x3b', '\x0c', '\xc3', '\xfb', '\xcc', '\x37', '\xef', '\x7c', + '\x23', '\x94', '\x52', '\xbc', '\x44', '\x7e', '\x00', '\x21', '\x04', '\x00', + '\xad', '\x56', '\xcb', '\x18', '\x8f', '\xc7', '\x0d', '\xdb', '\xb6', '\xd3', + '\x8e', '\xe3', '\xf8', '\x83', '\xc1', '\xa0', '\x8a', '\xc7', '\xe3', '\x3f', + '\x12', '\x89', '\x44', '\xb9', '\x5c', '\x2e', '\xf7', '\x56', '\xa6', '\xdf', + '\x0f', '\x15', '\x4a', '\x29', '\x84', '\x10', '\x34', '\x9b', '\xcd', '\x77', + '\x96', '\x65', '\x7d', '\x36', '\x0c', '\x43', '\xe4', '\x72', '\x39', '\xe2', + '\xf1', '\x38', '\xb7', '\xb7', '\xb7', '\xf4', '\xfb', '\x7d', '\x06', '\x83', + '\x01', '\xa9', '\x54', '\xea', '\x43', '\xa5', '\x52', '\x39', '\x7e', '\x0a', + '\x40', '\x29', '\x45', '\xab', '\xd5', '\x32', '\x4e', '\x4f', '\x4f', '\x7f', + '\x4a', '\x29', '\xd5', '\xdd', '\xdd', '\xdd', '\xb3', '\xea', '\xf5', '\x7a', + '\xea', '\xe4', '\xe4', '\x44', '\x49', '\x29', '\xd3', '\x2b', '\xcf', '\xaa', + '\x36', '\x00', '\xc6', '\xe3', '\x71', '\x63', '\x67', '\x67', '\x47', '\xe4', + '\xf3', '\x79', '\x00', '\x3c', '\xcf', '\xa3', '\x50', '\x28', '\xe0', '\xba', + '\x2e', '\x9e', '\xe7', '\x91', '\x4a', '\xa5', '\xd8', '\xdb', '\xdb', '\x63', + '\x34', '\x1a', '\x7d', '\x7c', '\x9a', '\xc1', '\x06', '\x80', '\x6d', '\xdb', + '\xe9', '\x6c', '\x36', '\x8b', '\xcf', '\xe7', '\xc3', '\xf3', '\x3c', '\x5c', + '\xd7', '\x05', '\x60', '\x36', '\x9b', '\xe1', '\xba', '\x2e', '\x8b', '\xc5', + '\x82', '\x4c', '\x26', '\x83', '\x6d', '\xdb', '\xbb', '\x6b', '\x43', '\x74', + '\x1c', '\xc7', '\x1f', '\x8b', '\xc5', '\x00', '\x28', '\x16', '\x8b', '\x8f', + '\x9b', '\xd5', '\x6a', '\x15', '\x00', '\x29', '\x25', '\x9a', '\xa6', '\x31', + '\x9f', '\xcf', '\xc5', '\x5a', '\xc0', '\xd6', '\xd6', '\x96', '\x9a', '\x4c', + '\x26', '\x22', '\x14', '\x0a', '\x21', '\xa5', '\x64', '\x36', '\x9b', '\x51', + '\xad', '\x56', '\xa9', '\xd5', '\x6a', '\x68', '\x9a', '\x06', '\xc0', '\xf5', + '\xf5', '\x35', '\xba', '\xae', '\xdf', '\xaf', '\xbd', '\x82', '\x61', '\x18', + '\xdf', '\x2c', '\xcb', '\x7a', '\x20', '\xfa', '\xfd', '\x04', '\x02', '\x01', + '\x00', '\x34', '\x4d', '\x23', '\x10', '\x08', '\xb0', '\x58', '\x2c', '\xe8', + '\x76', '\xbb', '\x24', '\x12', '\x89', '\x8b', '\xb5', '\x1d', '\x6c', '\x6f', + '\x6f', '\x1f', '\x75', '\x3a', '\x9d', '\x7e', '\x38', '\x1c', '\x26', '\x97', + '\xcb', '\x21', '\x84', '\x40', '\x4a', '\x09', '\xc0', '\xe6', '\xe6', '\x26', + '\xed', '\x76', '\x9b', '\xc9', '\x64', '\xa2', '\xa6', '\xd3', '\xe9', '\x27', + '\x78', '\x98', '\x9b', '\xd5', '\x53', '\x3e', '\xce', '\xc1', '\xf9', '\xf9', + '\xf9', '\xfb', '\xc1', '\x60', '\x70', '\x9c', '\x4c', '\x26', '\xd9', '\xdf', + '\xdf', '\x27', '\x12', '\x89', '\x30', '\x1c', '\x0e', '\xb9', '\xbc', '\xbc', + '\xe4', '\xe6', '\xe6', '\x46', '\xf9', '\x7c', '\xbe', '\x2f', '\xd3', '\xe9', + '\x34', '\x0f', '\x34', '\xea', '\xf5', '\x7a', '\xe5', '\x19', '\x60', '\x19', + '\xd6', '\x9b', '\xd1', '\x68', '\xd4', '\x18', '\x0e', '\x87', '\xbb', '\xf3', + '\xf9', '\x5c', '\xe8', '\xba', '\x7e', '\x6f', '\x18', '\xc6', '\x45', '\x34', + '\x1a', '\x3d', '\x2c', '\x95', '\x4a', '\xdf', '\x4d', '\xd3', '\xbc', '\x02', + '\x5e', '\x03', '\x5f', '\x81', '\x83', '\xb3', '\xb3', '\x33', '\xe7', '\x0f', + '\xc0', '\xdf', '\x64', '\x9a', '\xa6', '\xb1', '\x34', '\xeb', '\x80', '\x03', + '\x1c', '\x6c', '\xfc', '\x93', '\x73', '\xa9', '\x7a', '\xbd', '\x6e', '\x03', + '\x47', '\xcb', '\xa5', '\x0e', '\xbc', '\xe2', '\x7f', '\x7e', '\xa3', '\x69', + '\x9a', '\x87', '\xa6', '\x69', '\xbe', '\x55', '\x4a', '\x3d', '\x64', '\xf0', + '\x12', '\xfd', '\x02', '\x0d', '\x53', '\x06', '\x24', '\x88', '\x3f', '\xe1', + '\x69', '\x00', '\x00', '\x00', '\x00', '\x49', '\x45', '\x4e', '\x44', '\xae', + '\x42', '\x60', '\x82', +}; diff --git a/muduo/net/inspect/Inspector.h b/muduo/net/inspect/Inspector.h new file mode 100644 index 0000000..09041d1 --- /dev/null +++ b/muduo/net/inspect/Inspector.h @@ -0,0 +1,67 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_INSPECT_INSPECTOR_H +#define MUDUO_NET_INSPECT_INSPECTOR_H + +#include "muduo/base/Mutex.h" +#include "muduo/net/http/HttpRequest.h" +#include "muduo/net/http/HttpServer.h" + +#include <map> + +namespace muduo +{ +namespace net +{ + +class ProcessInspector; +class PerformanceInspector; +class SystemInspector; + +// An internal inspector of the running process, usually a singleton. +// Better to run in a seperated thread, as some method may block for seconds +class Inspector : noncopyable +{ + public: + typedef std::vector<string> ArgList; + typedef std::function<string (HttpRequest::Method, const ArgList& args)> Callback; + Inspector(EventLoop* loop, + const InetAddress& httpAddr, + const string& name); + ~Inspector(); + + /// Add a Callback for handling the special uri : /mudule/command + void add(const string& module, + const string& command, + const Callback& cb, + const string& help); + void remove(const string& module, const string& command); + + private: + typedef std::map<string, Callback> CommandList; + typedef std::map<string, string> HelpList; + + void start(); + void onRequest(const HttpRequest& req, HttpResponse* resp); + + HttpServer server_; + std::unique_ptr<ProcessInspector> processInspector_; + std::unique_ptr<PerformanceInspector> performanceInspector_; + std::unique_ptr<SystemInspector> systemInspector_; + MutexLock mutex_; + std::map<string, CommandList> modules_ GUARDED_BY(mutex_); + std::map<string, HelpList> helps_ GUARDED_BY(mutex_); +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_INSPECT_INSPECTOR_H diff --git a/muduo/net/inspect/PerformanceInspector.cc b/muduo/net/inspect/PerformanceInspector.cc new file mode 100644 index 0000000..a0fb9e9 --- /dev/null +++ b/muduo/net/inspect/PerformanceInspector.cc @@ -0,0 +1,106 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/inspect/PerformanceInspector.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/LogStream.h" +#include "muduo/base/ProcessInfo.h" + +#include <unistd.h> + +#ifdef HAVE_TCMALLOC +#include <gperftools/malloc_extension.h> +#include <gperftools/profiler.h> + +using namespace muduo; +using namespace muduo::net; + +void PerformanceInspector::registerCommands(Inspector* ins) +{ + ins->add("pprof", "heap", PerformanceInspector::heap, "get heap information"); + ins->add("pprof", "growth", PerformanceInspector::growth, "get heap growth information"); + ins->add("pprof", "profile", PerformanceInspector::profile, + "get cpu profiling information. CAUTION: blocking thread for 30 seconds!"); + ins->add("pprof", "cmdline", PerformanceInspector::cmdline, "get command line"); + ins->add("pprof", "memstats", PerformanceInspector::memstats, "get memory stats"); + ins->add("pprof", "memhistogram", PerformanceInspector::memhistogram, "get memory histogram"); + ins->add("pprof", "releasefreememory", PerformanceInspector::releaseFreeMemory, "release free memory"); +} + +string PerformanceInspector::heap(HttpRequest::Method, const Inspector::ArgList&) +{ + std::string result; + MallocExtension::instance()->GetHeapSample(&result); + return string(result.data(), result.size()); +} + +string PerformanceInspector::growth(HttpRequest::Method, const Inspector::ArgList&) +{ + std::string result; + MallocExtension::instance()->GetHeapGrowthStacks(&result); + return string(result.data(), result.size()); +} + +string PerformanceInspector::profile(HttpRequest::Method, const Inspector::ArgList&) +{ + string filename = "/tmp/" + ProcessInfo::procname(); + filename += "."; + filename += ProcessInfo::pidString(); + filename += "."; + filename += Timestamp::now().toString(); + filename += ".profile"; + + string profile; + if (ProfilerStart(filename.c_str())) + { + // FIXME: async + CurrentThread::sleepUsec(30 * 1000 * 1000); + ProfilerStop(); + FileUtil::readFile(filename, 1024*1024, &profile, NULL, NULL); + ::unlink(filename.c_str()); + } + return profile; +} + +string PerformanceInspector::cmdline(HttpRequest::Method, const Inspector::ArgList&) +{ + return ""; +} + +string PerformanceInspector::memstats(HttpRequest::Method, const Inspector::ArgList&) +{ + char buf[1024*64]; + MallocExtension::instance()->GetStats(buf, sizeof buf); + return buf; +} + +string PerformanceInspector::memhistogram(HttpRequest::Method, const Inspector::ArgList&) +{ + int blocks = 0; + size_t total = 0; + int histogram[kMallocHistogramSize] = { 0, }; + + MallocExtension::instance()->MallocMemoryStats(&blocks, &total, histogram); + LogStream s; + s << "blocks " << blocks << "\ntotal " << total << "\n"; + for (int i = 0; i < kMallocHistogramSize; ++i) + s << i << " " << histogram[i] << "\n"; + return s.buffer().toString(); +} + +string PerformanceInspector::releaseFreeMemory(HttpRequest::Method, const Inspector::ArgList&) +{ + char buf[256]; + snprintf(buf, sizeof buf, "memory release rate: %f\nAll free memory released.\n", + MallocExtension::instance()->GetMemoryReleaseRate()); + MallocExtension::instance()->ReleaseFreeMemory(); + return buf; +} + +#endif diff --git a/muduo/net/inspect/PerformanceInspector.h b/muduo/net/inspect/PerformanceInspector.h new file mode 100644 index 0000000..2416824 --- /dev/null +++ b/muduo/net/inspect/PerformanceInspector.h @@ -0,0 +1,40 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_INSPECT_PERFORMANCEINSPECTOR_H +#define MUDUO_NET_INSPECT_PERFORMANCEINSPECTOR_H + +#include "muduo/net/inspect/Inspector.h" + +namespace muduo +{ +namespace net +{ + +class PerformanceInspector : noncopyable +{ + public: + void registerCommands(Inspector* ins); + + static string heap(HttpRequest::Method, const Inspector::ArgList&); + static string growth(HttpRequest::Method, const Inspector::ArgList&); + static string profile(HttpRequest::Method, const Inspector::ArgList&); + static string cmdline(HttpRequest::Method, const Inspector::ArgList&); + static string memstats(HttpRequest::Method, const Inspector::ArgList&); + static string memhistogram(HttpRequest::Method, const Inspector::ArgList&); + static string releaseFreeMemory(HttpRequest::Method, const Inspector::ArgList&); + + static string symbol(HttpRequest::Method, const Inspector::ArgList&); +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_INSPECT_PERFORMANCEINSPECTOR_H diff --git a/muduo/net/inspect/ProcessInspector.cc b/muduo/net/inspect/ProcessInspector.cc new file mode 100644 index 0000000..d89f0ce --- /dev/null +++ b/muduo/net/inspect/ProcessInspector.cc @@ -0,0 +1,239 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/inspect/ProcessInspector.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/ProcessInfo.h" +#include <limits.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +namespace muduo +{ +namespace inspect +{ + +string uptime(Timestamp now, Timestamp start, bool showMicroseconds) +{ + char buf[256]; + int64_t age = now.microSecondsSinceEpoch() - start.microSecondsSinceEpoch(); + int seconds = static_cast<int>(age / Timestamp::kMicroSecondsPerSecond); + int days = seconds/86400; + int hours = (seconds % 86400) / 3600; + int minutes = (seconds % 3600) / 60; + if (showMicroseconds) + { + int microseconds = static_cast<int>(age % Timestamp::kMicroSecondsPerSecond); + snprintf(buf, sizeof buf, "%d days %02d:%02d:%02d.%06d", + days, hours, minutes, seconds % 60, microseconds); + } + else + { + snprintf(buf, sizeof buf, "%d days %02d:%02d:%02d", + days, hours, minutes, seconds % 60); + } + return buf; +} + +long getLong(const string& procStatus, const char* key) +{ + long result = 0; + size_t pos = procStatus.find(key); + if (pos != string::npos) + { + result = ::atol(procStatus.c_str() + pos + strlen(key)); + } + return result; +} + +string getProcessName(const string& procStatus) +{ + string result; + size_t pos = procStatus.find("Name:"); + if (pos != string::npos) + { + pos += strlen("Name:"); + while (procStatus[pos] == '\t') + ++pos; + size_t eol = pos; + while (procStatus[eol] != '\n') + ++eol; + result = procStatus.substr(pos, eol-pos); + } + return result; +} + +StringPiece next(StringPiece data) +{ + const char* sp = static_cast<const char*>(::memchr(data.data(), ' ', data.size())); + if (sp) + { + data.remove_prefix(static_cast<int>(sp+1-data.begin())); + return data; + } + return ""; +} + +ProcessInfo::CpuTime getCpuTime(StringPiece data) +{ + ProcessInfo::CpuTime t; + + for (int i = 0; i < 10; ++i) + { + data = next(data); + } + long utime = strtol(data.data(), NULL, 10); + data = next(data); + long stime = strtol(data.data(), NULL, 10); + const double hz = static_cast<double>(ProcessInfo::clockTicksPerSecond()); + t.userSeconds = static_cast<double>(utime) / hz; + t.systemSeconds = static_cast<double>(stime) / hz; + return t; +} + +int stringPrintf(string* out, const char* fmt, ...) __attribute__ ((format (printf, 2, 3))); + +int stringPrintf(string* out, const char* fmt, ...) +{ + char buf[256]; + va_list args; + va_start(args, fmt); + int ret = vsnprintf(buf, sizeof buf, fmt, args); + va_end(args); + out->append(buf); + return ret; +} + +} // namespace inspect +} // namespace muduo + +using namespace muduo::inspect; + +string ProcessInspector::username_ = ProcessInfo::username(); + +void ProcessInspector::registerCommands(Inspector* ins) +{ + ins->add("proc", "overview", ProcessInspector::overview, "print basic overview"); + ins->add("proc", "pid", ProcessInspector::pid, "print pid"); + ins->add("proc", "status", ProcessInspector::procStatus, "print /proc/self/status"); + // ins->add("proc", "opened_files", ProcessInspector::openedFiles, "count /proc/self/fd"); + ins->add("proc", "threads", ProcessInspector::threads, "list /proc/self/task"); +} + +string ProcessInspector::overview(HttpRequest::Method, const Inspector::ArgList&) +{ + string result; + result.reserve(1024); + Timestamp now = Timestamp::now(); + result += "Page generated at "; + result += now.toFormattedString(); + result += " (UTC)\nStarted at "; + result += ProcessInfo::startTime().toFormattedString(); + result += " (UTC), up for "; + result += uptime(now, ProcessInfo::startTime(), true/* show microseconds */); + result += "\n"; + + string procStatus = ProcessInfo::procStatus(); + result += getProcessName(procStatus); + result += " ("; + result += ProcessInfo::exePath(); + result += ") running as "; + result += username_; + result += " on "; + result += ProcessInfo::hostname(); // cache ? + result += "\n"; + + if (ProcessInfo::isDebugBuild()) + { + result += "WARNING: debug build!\n"; + } + + stringPrintf(&result, "pid %d, num of threads %ld, bits %zd\n", + ProcessInfo::pid(), getLong(procStatus, "Threads:"), CHAR_BIT * sizeof(void*)); + + result += "Virtual memory: "; + stringPrintf(&result, "%.3f MiB, ", + static_cast<double>(getLong(procStatus, "VmSize:")) / 1024.0); + + result += "RSS memory: "; + stringPrintf(&result, "%.3f MiB\n", + static_cast<double>(getLong(procStatus, "VmRSS:")) / 1024.0); + + // FIXME: VmData: + + stringPrintf(&result, "Opened files: %d, limit: %d\n", + ProcessInfo::openedFiles(), ProcessInfo::maxOpenFiles()); + + // string procStat = ProcessInfo::procStat(); + + /* + stringPrintf(&result, "ppid %ld\n", getStatField(procStat, 0)); + stringPrintf(&result, "pgid %ld\n", getStatField(procStat, 1)); + */ + + ProcessInfo::CpuTime t = ProcessInfo::cpuTime(); + stringPrintf(&result, "User time: %12.3fs\nSys time: %12.3fs\n", + t.userSeconds, t.systemSeconds); + + // FIXME: add context switches + + return result; +} + +string ProcessInspector::pid(HttpRequest::Method, const Inspector::ArgList&) +{ + char buf[32]; + snprintf(buf, sizeof buf, "%d", ProcessInfo::pid()); + return buf; +} + +string ProcessInspector::procStatus(HttpRequest::Method, const Inspector::ArgList&) +{ + return ProcessInfo::procStatus(); +} + +string ProcessInspector::openedFiles(HttpRequest::Method, const Inspector::ArgList&) +{ + char buf[32]; + snprintf(buf, sizeof buf, "%d", ProcessInfo::openedFiles()); + return buf; +} + +string ProcessInspector::threads(HttpRequest::Method, const Inspector::ArgList&) +{ + std::vector<pid_t> threads = ProcessInfo::threads(); + string result = " TID NAME S User Time System Time\n"; + result.reserve(threads.size() * 64); + string stat; + for (pid_t tid : threads) + { + char buf[256]; + snprintf(buf, sizeof buf, "/proc/%d/task/%d/stat", ProcessInfo::pid(), tid); + if (FileUtil::readFile(buf, 65536, &stat) == 0) + { + StringPiece name = ProcessInfo::procname(stat); + const char* rp = name.end(); + assert(*rp == ')'); + const char* state = rp + 2; + *const_cast<char*>(rp) = '\0'; // don't do this at home + StringPiece data(stat); + data.remove_prefix(static_cast<int>(state - data.data() + 2)); + ProcessInfo::CpuTime t = getCpuTime(data); + snprintf(buf, sizeof buf, "%5d %-16s %c %12.3f %12.3f\n", + tid, name.data(), *state, t.userSeconds, t.systemSeconds); + result += buf; + } + } + return result; +} + diff --git a/muduo/net/inspect/ProcessInspector.h b/muduo/net/inspect/ProcessInspector.h new file mode 100644 index 0000000..f1df478 --- /dev/null +++ b/muduo/net/inspect/ProcessInspector.h @@ -0,0 +1,38 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_INSPECT_PROCESSINSPECTOR_H +#define MUDUO_NET_INSPECT_PROCESSINSPECTOR_H + +#include "muduo/net/inspect/Inspector.h" + +namespace muduo +{ +namespace net +{ + +class ProcessInspector : noncopyable +{ + public: + void registerCommands(Inspector* ins); + + static string overview(HttpRequest::Method, const Inspector::ArgList&); + static string pid(HttpRequest::Method, const Inspector::ArgList&); + static string procStatus(HttpRequest::Method, const Inspector::ArgList&); + static string openedFiles(HttpRequest::Method, const Inspector::ArgList&); + static string threads(HttpRequest::Method, const Inspector::ArgList&); + + static string username_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_INSPECT_PROCESSINSPECTOR_H diff --git a/muduo/net/inspect/SystemInspector.cc b/muduo/net/inspect/SystemInspector.cc new file mode 100644 index 0000000..fb45e76 --- /dev/null +++ b/muduo/net/inspect/SystemInspector.cc @@ -0,0 +1,132 @@ +// Copyright 2014, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// + +#include "muduo/net/inspect/SystemInspector.h" +#include "muduo/base/FileUtil.h" + +#include <sys/utsname.h> + +using namespace muduo; +using namespace muduo::net; + +namespace muduo +{ +namespace inspect +{ +string uptime(Timestamp now, Timestamp start, bool showMicroseconds); +long getLong(const string& content, const char* key); +int stringPrintf(string* out, const char* fmt, ...) __attribute__ ((format (printf, 2, 3))); +} +} + +using namespace muduo::inspect; + +void SystemInspector::registerCommands(Inspector* ins) +{ + ins->add("sys", "overview", SystemInspector::overview, "print system overview"); + ins->add("sys", "loadavg", SystemInspector::loadavg, "print /proc/loadavg"); + ins->add("sys", "version", SystemInspector::version, "print /proc/version"); + ins->add("sys", "cpuinfo", SystemInspector::cpuinfo, "print /proc/cpuinfo"); + ins->add("sys", "meminfo", SystemInspector::meminfo, "print /proc/meminfo"); + ins->add("sys", "stat", SystemInspector::stat, "print /proc/stat"); +} + +string SystemInspector::loadavg(HttpRequest::Method, const Inspector::ArgList&) +{ + string loadavg; + FileUtil::readFile("/proc/loadavg", 65536, &loadavg); + return loadavg; +} + +string SystemInspector::version(HttpRequest::Method, const Inspector::ArgList&) +{ + string version; + FileUtil::readFile("/proc/version", 65536, &version); + return version; +} + +string SystemInspector::cpuinfo(HttpRequest::Method, const Inspector::ArgList&) +{ + string cpuinfo; + FileUtil::readFile("/proc/cpuinfo", 65536, &cpuinfo); + return cpuinfo; +} + +string SystemInspector::meminfo(HttpRequest::Method, const Inspector::ArgList&) +{ + string meminfo; + FileUtil::readFile("/proc/meminfo", 65536, &meminfo); + return meminfo; +} + +string SystemInspector::stat(HttpRequest::Method, const Inspector::ArgList&) +{ + string stat; + FileUtil::readFile("/proc/stat", 65536, &stat); + return stat; +} + +string SystemInspector::overview(HttpRequest::Method, const Inspector::ArgList&) +{ + string result; + result.reserve(1024); + Timestamp now = Timestamp::now(); + result += "Page generated at "; + result += now.toFormattedString(); + result += " (UTC)\n"; + // Hardware and OS + { + struct utsname un; + if (::uname(&un) == 0) + { + stringPrintf(&result, "Hostname: %s\n", un.nodename); + stringPrintf(&result, "Machine: %s\n", un.machine); + stringPrintf(&result, "OS: %s %s %s\n", un.sysname, un.release, un.version); + } + } + string stat; + FileUtil::readFile("/proc/stat", 65536, &stat); + Timestamp bootTime(Timestamp::kMicroSecondsPerSecond * getLong(stat, "btime ")); + result += "Boot time: "; + result += bootTime.toFormattedString(false /* show microseconds */); + result += " (UTC)\n"; + result += "Up time: "; + result += uptime(now, bootTime, false /* show microseconds */); + result += "\n"; + + // CPU load + { + string loadavg; + FileUtil::readFile("/proc/loadavg", 65536, &loadavg); + stringPrintf(&result, "Processes created: %ld\n", getLong(stat, "processes ")); + stringPrintf(&result, "Loadavg: %s\n", loadavg.c_str()); + } + + // Memory + { + string meminfo; + FileUtil::readFile("/proc/meminfo", 65536, &meminfo); + long total_kb = getLong(meminfo, "MemTotal:"); + long free_kb = getLong(meminfo, "MemFree:"); + long buffers_kb = getLong(meminfo, "Buffers:"); + long cached_kb = getLong(meminfo, "Cached:"); + + stringPrintf(&result, "Total Memory: %6ld MiB\n", total_kb / 1024); + stringPrintf(&result, "Free Memory: %6ld MiB\n", free_kb / 1024); + stringPrintf(&result, "Buffers: %6ld MiB\n", buffers_kb / 1024); + stringPrintf(&result, "Cached: %6ld MiB\n", cached_kb / 1024); + stringPrintf(&result, "Real Used: %6ld MiB\n", (total_kb - free_kb - buffers_kb - cached_kb) / 1024); + stringPrintf(&result, "Real Free: %6ld MiB\n", (free_kb + buffers_kb + cached_kb) / 1024); + + // Swap + } + // Disk + // Network + return result; +} diff --git a/muduo/net/inspect/SystemInspector.h b/muduo/net/inspect/SystemInspector.h new file mode 100644 index 0000000..8f2a604 --- /dev/null +++ b/muduo/net/inspect/SystemInspector.h @@ -0,0 +1,37 @@ +// Copyright 2014, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_INSPECT_SYSTEMINSPECTOR_H +#define MUDUO_NET_INSPECT_SYSTEMINSPECTOR_H + +#include "muduo/net/inspect/Inspector.h" + +namespace muduo +{ +namespace net +{ + +class SystemInspector : noncopyable +{ + public: + void registerCommands(Inspector* ins); + + static string overview(HttpRequest::Method, const Inspector::ArgList&); + static string loadavg(HttpRequest::Method, const Inspector::ArgList&); + static string version(HttpRequest::Method, const Inspector::ArgList&); + static string cpuinfo(HttpRequest::Method, const Inspector::ArgList&); + static string meminfo(HttpRequest::Method, const Inspector::ArgList&); + static string stat(HttpRequest::Method, const Inspector::ArgList&); +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_INSPECT_SYSTEMINSPECTOR_H diff --git a/muduo/net/inspect/tests/BUILD.bazel b/muduo/net/inspect/tests/BUILD.bazel new file mode 100644 index 0000000..e7f0ca7 --- /dev/null +++ b/muduo/net/inspect/tests/BUILD.bazel @@ -0,0 +1,7 @@ +cc_binary( + name = "inspector", + srcs = ["Inspector_test.cc"], + deps = [ + "//muduo/net/inspect", + ], +) diff --git a/muduo/net/inspect/tests/Inspector_test.cc b/muduo/net/inspect/tests/Inspector_test.cc new file mode 100644 index 0000000..20025ff --- /dev/null +++ b/muduo/net/inspect/tests/Inspector_test.cc @@ -0,0 +1,15 @@ +#include "muduo/net/inspect/Inspector.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +using namespace muduo; +using namespace muduo::net; + +int main() +{ + EventLoop loop; + EventLoopThread t; + Inspector ins(t.startLoop(), InetAddress(12345), "test"); + loop.loop(); +} + diff --git a/muduo/net/poller/DefaultPoller.cc b/muduo/net/poller/DefaultPoller.cc new file mode 100644 index 0000000..50ed1c1 --- /dev/null +++ b/muduo/net/poller/DefaultPoller.cc @@ -0,0 +1,24 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include <stdlib.h> + +#include "muduo/net/Poller.h" +#include "muduo/net/poller/EPollPoller.h" +#include "muduo/net/poller/PollPoller.h" + +using namespace muduo::net; + +Poller* Poller::newDefaultPoller(EventLoop* loop) +{ + if (::getenv("MUDUO_USE_POLL")) { + return new PollPoller(loop); + } else { + return new EPollPoller(loop); + } +} diff --git a/muduo/net/poller/EPollPoller.cc b/muduo/net/poller/EPollPoller.cc new file mode 100644 index 0000000..f2a650a --- /dev/null +++ b/muduo/net/poller/EPollPoller.cc @@ -0,0 +1,182 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/poller/EPollPoller.h" + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <sys/epoll.h> +#include <unistd.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" + +using namespace muduo; +using namespace muduo::net; + +// On Linux, the constants of poll(2) and epoll(4) +// are expected to be the same. +static_assert(EPOLLIN == POLLIN, "epoll uses same flag values as poll"); +static_assert(EPOLLPRI == POLLPRI, "epoll uses same flag values as poll"); +static_assert(EPOLLOUT == POLLOUT, "epoll uses same flag values as poll"); +static_assert(EPOLLRDHUP == POLLRDHUP, "epoll uses same flag values as poll"); +static_assert(EPOLLERR == POLLERR, "epoll uses same flag values as poll"); +static_assert(EPOLLHUP == POLLHUP, "epoll uses same flag values as poll"); + +namespace { +const int kNew = -1; +const int kAdded = 1; +const int kDeleted = 2; +} // namespace + +EPollPoller::EPollPoller(EventLoop* loop) + : Poller(loop), + epollfd_(::epoll_create1(EPOLL_CLOEXEC)), + events_(kInitEventListSize) +{ + if (epollfd_ < 0) { + LOG_SYSFATAL << "EPollPoller::EPollPoller"; + } +} + +EPollPoller::~EPollPoller() { ::close(epollfd_); } + +Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels) +{ + LOG_TRACE << "fd total count " << channels_.size(); + int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), + static_cast<int>(events_.size()), timeoutMs); + int savedErrno = errno; + Timestamp now(Timestamp::now()); + if (numEvents > 0) { + LOG_TRACE << numEvents << " events happened"; + fillActiveChannels(numEvents, activeChannels); + if (implicit_cast<size_t>(numEvents) == events_.size()) { + events_.resize(events_.size() * 2); + } + } else if (numEvents == 0) { + LOG_TRACE << "nothing happened"; + } else { + // error happens, log uncommon ones + if (savedErrno != EINTR) { + errno = savedErrno; + LOG_SYSERR << "EPollPoller::poll()"; + } + } + return now; +} + +void EPollPoller::fillActiveChannels(int numEvents, + ChannelList* activeChannels) const +{ + assert(implicit_cast<size_t>(numEvents) <= events_.size()); + for (int i = 0; i < numEvents; ++i) { + Channel* channel = static_cast<Channel*>(events_[i].data.ptr); +#ifndef NDEBUG + int fd = channel->fd(); + ChannelMap::const_iterator it = channels_.find(fd); + assert(it != channels_.end()); + assert(it->second == channel); +#endif + channel->set_revents(events_[i].events); + activeChannels->push_back(channel); + } +} + +void EPollPoller::updateChannel(Channel* channel) +{ + Poller::assertInLoopThread(); + const int index = channel->index(); + LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events() + << " index = " << index; + if (index == kNew || index == kDeleted) { + // a new one, add with EPOLL_CTL_ADD + int fd = channel->fd(); + if (index == kNew) { + assert(channels_.find(fd) == channels_.end()); + channels_[fd] = channel; + } else // index == kDeleted + { + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + } + + channel->set_index(kAdded); + update(EPOLL_CTL_ADD, channel); + } else { + // update existing one with EPOLL_CTL_MOD/DEL + int fd = channel->fd(); + (void)fd; + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + assert(index == kAdded); + if (channel->isNoneEvent()) { + update(EPOLL_CTL_DEL, channel); + channel->set_index(kDeleted); + } else { + update(EPOLL_CTL_MOD, channel); + } + } +} + +void EPollPoller::removeChannel(Channel* channel) +{ + Poller::assertInLoopThread(); + int fd = channel->fd(); + LOG_TRACE << "fd = " << fd; + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + assert(channel->isNoneEvent()); + int index = channel->index(); + assert(index == kAdded || index == kDeleted); + size_t n = channels_.erase(fd); + (void)n; + assert(n == 1); + + if (index == kAdded) { + update(EPOLL_CTL_DEL, channel); + } + channel->set_index(kNew); +} + +void EPollPoller::update(int operation, Channel* channel) +{ + struct epoll_event event; + memZero(&event, sizeof event); + event.events = channel->events(); + event.data.ptr = channel; + int fd = channel->fd(); + LOG_TRACE << "epoll_ctl op = " << operationToString(operation) + << " fd = " << fd << " event = { " << channel->eventsToString() + << " }"; + if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) { + if (operation == EPOLL_CTL_DEL) { + LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) + << " fd =" << fd; + } else { + LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation) + << " fd =" << fd; + } + } +} + +const char* EPollPoller::operationToString(int op) +{ + switch (op) { + case EPOLL_CTL_ADD: + return "ADD"; + case EPOLL_CTL_DEL: + return "DEL"; + case EPOLL_CTL_MOD: + return "MOD"; + default: + assert(false && "ERROR op"); + return "Unknown Operation"; + } +} diff --git a/muduo/net/poller/EPollPoller.h b/muduo/net/poller/EPollPoller.h new file mode 100644 index 0000000..00c7669 --- /dev/null +++ b/muduo/net/poller/EPollPoller.h @@ -0,0 +1,51 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_POLLER_EPOLLPOLLER_H +#define MUDUO_NET_POLLER_EPOLLPOLLER_H + +#include <vector> + +#include "muduo/net/Poller.h" + +struct epoll_event; + +namespace muduo { +namespace net { + +/// +/// IO Multiplexing with epoll(4). +/// +class EPollPoller : public Poller { +public: + EPollPoller(EventLoop* loop); + ~EPollPoller() override; + + Timestamp poll(int timeoutMs, ChannelList* activeChannels) override; + void updateChannel(Channel* channel) override; + void removeChannel(Channel* channel) override; + +private: + static const int kInitEventListSize = 16; + + static const char* operationToString(int op); + + void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; + void update(int operation, Channel* channel); + + typedef std::vector<struct epoll_event> EventList; + + int epollfd_; + EventList events_; +}; + +} // namespace net +} // namespace muduo +#endif // MUDUO_NET_POLLER_EPOLLPOLLER_H diff --git a/muduo/net/poller/PollPoller.cc b/muduo/net/poller/PollPoller.cc new file mode 100644 index 0000000..a48fc23 --- /dev/null +++ b/muduo/net/poller/PollPoller.cc @@ -0,0 +1,161 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/poller/PollPoller.h" + +#include <assert.h> +#include <errno.h> +#include <poll.h> + +#include "muduo/base/Logging.h" +#include "muduo/base/Types.h" +#include "muduo/net/Channel.h" + +using namespace muduo; +using namespace muduo::net; + +PollPoller::PollPoller(EventLoop* loop) : Poller(loop) {} + +PollPoller::~PollPoller() = default; + +Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels) +{ + /* + Poller::poll() 是 Poller 的核心功能,它调用 poll(2) 获得当前活动的 IO 事 + 件,然后填充调用方传入的 activeChannels,并返回 poll(2) return 的时刻。 + + ::poll(...): 调用全局命名空间中的 poll 函数,对给定的文件描述符进行轮询。 + 将 poll 函数的返回值存储在 numEvents 中,该返回值表示发生事件的文件描述符数量。 + timeoutMs 是轮询的超时时间。 + */ + // XXX pollfds_ shouldn't change + int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs); + int savedErrno = errno; + Timestamp now(Timestamp::now()); + if (numEvents > 0) { + LOG_TRACE << numEvents << " events happened"; + fillActiveChannels(numEvents, activeChannels); + } else if (numEvents == 0) { + LOG_TRACE << " nothing happened"; + } else { + /* + savedErrno != EINTR: 在 poll 函数被信号中断(EINTR)的情况下,poll 函数返回 -1, + 但并不是真正的错误,而是表示等待被中断。所以,这个条件判断是否发生了信号中断。 + 如果 poll 函数返回了 EINTR,则将 errno 设置为之前保存的错误码 savedErrno,以便进一步处理。 + */ + if (savedErrno != EINTR) { + errno = savedErrno; + LOG_SYSERR << "PollPoller::poll()"; + } + } + return now; +} + +/* + fillActiveChannels() 遍历 pollfds_,找出有活动事件的 fd,把它对应的 Channel + 填入 activeChannels。 这个函数的复杂度是 O(N),其中 N 是 pollfds_ 的长度, + 即文件描述符数目。为了提前结束循环,每找到一个活动 fd 就递减 numEvents,这样 + 当 numEvents 减为 0 时表示活动 fd 都找完了,不必做无用功。当前活动事件 + revents 会保存在 Channel 中,供 Channel::handleEvent() 使用。 + 注意这里我们不能一边遍历 pollfds_,一边调用 Channel::handleEvent(),因 + 为后者会添加或删除 Channel,从而造成 pollfds_ 在遍历期间改变大小,这是非常危 + 险的。另外一个原因是简化 Poller 的职责,它只负责 IO multiplexing,不负责事件 + 分发(dispatching)。这样将来可以方便地替换为其他更高效的 IO multiplexing 机 + 制,如 epoll(4)。 +*/ +void PollPoller::fillActiveChannels(int numEvents, + ChannelList* activeChannels) const +{ + for (PollFdList::const_iterator pfd = pollfds_.begin(); + pfd != pollfds_.end() && numEvents > 0; ++pfd) { + if (pfd->revents > 0) { + --numEvents; + ChannelMap::const_iterator ch = channels_.find(pfd->fd); + assert(ch != channels_.end()); + Channel* channel = ch->second; + assert(channel->fd() == pfd->fd); + channel->set_revents(pfd->revents); + // pfd->revents = 0; + activeChannels->push_back(channel); + } + } +} + +/* + Poller::updateChannel() 的主要功能是负责维护和更新 pollfds_ 数组。添加新 + Channel 的复杂度是 O(log N),更新已有的 Channel 的复杂度是 O(1),因为 + Channel 记住了自己在 pollfds_ 数组中的下标,因此可以快速定位。removeChannel() + 的复杂 度也将会是 O(log N)。这里用了大量的 assert 来检查 invariant +*/ +void PollPoller::updateChannel(Channel* channel) +{ + Poller::assertInLoopThread(); + LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); + if (channel->index() < 0) { + // a new one, add to pollfds_ + assert(channels_.find(channel->fd()) == channels_.end()); + struct pollfd pfd; + pfd.fd = channel->fd(); + pfd.events = static_cast<short>(channel->events()); + pfd.revents = 0; + pollfds_.push_back(pfd); + int idx = static_cast<int>(pollfds_.size()) - 1; + channel->set_index(idx); + channels_[pfd.fd] = channel; + } else { + // update existing one + assert(channels_.find(channel->fd()) != channels_.end()); + assert(channels_[channel->fd()] == channel); + int idx = channel->index(); + assert(0 <= idx && idx < static_cast<int>(pollfds_.size())); + struct pollfd& pfd = pollfds_[idx]; + assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd() - 1); + pfd.fd = channel->fd(); + pfd.events = static_cast<short>(channel->events()); + pfd.revents = 0; + if (channel->isNoneEvent()) { + /* + 在这个特定的情况下,为了标记一个不感兴趣的 Channel,通过对文件描述符进行特殊处理,将其设置为 -channel->fd() - 1, + 以便在后续的轮询操作中能够识别并跳过这个文件描述符。 + + -1 这并不是对文件描述符的通常操作,而是一种特定的技巧用于实现某种标记或标志的效果。将文件描述符设置为负数并减去1, + 通常是为了避免与合法的文件描述符产生冲突。在Linux系统中,通常情况下合法的文件描述符是非负整数。 + */ + // ignore this pollfd + pfd.fd = -channel->fd() - 1; + } + } +} + +void PollPoller::removeChannel(Channel* channel) +{ + Poller::assertInLoopThread(); + LOG_TRACE << "fd = " << channel->fd(); + assert(channels_.find(channel->fd()) != channels_.end()); + assert(channels_[channel->fd()] == channel); + assert(channel->isNoneEvent()); + int idx = channel->index(); + assert(0 <= idx && idx < static_cast<int>(pollfds_.size())); + const struct pollfd& pfd = pollfds_[idx]; + (void)pfd; + assert(pfd.fd == -channel->fd() - 1 && pfd.events == channel->events()); + size_t n = channels_.erase(channel->fd()); + assert(n == 1); + (void)n; + if (implicit_cast<size_t>(idx) == pollfds_.size() - 1) { + pollfds_.pop_back(); + } else { + int channelAtEnd = pollfds_.back().fd; + iter_swap(pollfds_.begin() + idx, pollfds_.end() - 1); + if (channelAtEnd < 0) { + channelAtEnd = -channelAtEnd - 1; + } + channels_[channelAtEnd]->set_index(idx); + pollfds_.pop_back(); + } +} diff --git a/muduo/net/poller/PollPoller.h b/muduo/net/poller/PollPoller.h new file mode 100644 index 0000000..0b79643 --- /dev/null +++ b/muduo/net/poller/PollPoller.h @@ -0,0 +1,44 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is an internal header file, you should not include this. + +#ifndef MUDUO_NET_POLLER_POLLPOLLER_H +#define MUDUO_NET_POLLER_POLLPOLLER_H + +#include <vector> + +#include "muduo/net/Poller.h" + +struct pollfd; + +namespace muduo { +namespace net { + +/// +/// IO Multiplexing with poll(2). +/// +class PollPoller : public Poller { +public: + PollPoller(EventLoop* loop); + ~PollPoller() override; + + Timestamp poll(int timeoutMs, ChannelList* activeChannels) override; + void updateChannel(Channel* channel) override; + void removeChannel(Channel* channel) override; + +private: + void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; + + typedef std::vector<struct pollfd> PollFdList; + PollFdList pollfds_; +}; + +} // namespace net +} // namespace muduo +#endif // MUDUO_NET_POLLER_POLLPOLLER_H diff --git a/muduo/net/protobuf/BufferStream.h b/muduo/net/protobuf/BufferStream.h new file mode 100644 index 0000000..322bc44 --- /dev/null +++ b/muduo/net/protobuf/BufferStream.h @@ -0,0 +1,56 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. +#pragma once +#include "muduo/net/Buffer.h" +#include <google/protobuf/io/zero_copy_stream.h> +namespace muduo +{ +namespace net +{ + +// FIXME: +// class BufferInputStream : google::protobuf::io::ZeroCopyInputStream +// { +// }; + +class BufferOutputStream : public google::protobuf::io::ZeroCopyOutputStream +{ + public: + BufferOutputStream(Buffer* buf) + : buffer_(CHECK_NOTNULL(buf)), + originalSize_(buffer_->readableBytes()) + { + } + + virtual bool Next(void** data, int* size) // override + { + buffer_->ensureWritableBytes(4096); + *data = buffer_->beginWrite(); + *size = static_cast<int>(buffer_->writableBytes()); + buffer_->hasWritten(*size); + return true; + } + + virtual void BackUp(int count) // override + { + buffer_->unwrite(count); + } + + virtual int64_t ByteCount() const // override + { + return buffer_->readableBytes() - originalSize_; + } + + private: + Buffer* buffer_; + size_t originalSize_; +}; + +} +} diff --git a/muduo/net/protobuf/ProtobufCodecLite.cc b/muduo/net/protobuf/ProtobufCodecLite.cc new file mode 100644 index 0000000..9d406ee --- /dev/null +++ b/muduo/net/protobuf/ProtobufCodecLite.cc @@ -0,0 +1,243 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/protobuf/ProtobufCodecLite.h" +// #include <muduo/net/protobuf/BufferStream.h> + +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" +#include "muduo/net/TcpConnection.h" +#include "muduo/net/protorpc/google-inl.h" + +#include <google/protobuf/message.h> +#include <zlib.h> + +using namespace muduo; +using namespace muduo::net; + +namespace +{ + int ProtobufVersionCheck() + { + GOOGLE_PROTOBUF_VERIFY_VERSION; + return 0; + } + int __attribute__ ((unused)) dummy = ProtobufVersionCheck(); +} + +void ProtobufCodecLite::send(const TcpConnectionPtr& conn, + const ::google::protobuf::Message& message) +{ + // FIXME: serialize to TcpConnection::outputBuffer() + muduo::net::Buffer buf; + fillEmptyBuffer(&buf, message); + conn->send(&buf); +} + +void ProtobufCodecLite::fillEmptyBuffer(muduo::net::Buffer* buf, + const google::protobuf::Message& message) +{ + assert(buf->readableBytes() == 0); + // FIXME: can we move serialization & checksum to other thread? + buf->append(tag_); + + int byte_size = serializeToBuffer(message, buf); + + int32_t checkSum = checksum(buf->peek(), static_cast<int>(buf->readableBytes())); + buf->appendInt32(checkSum); + assert(buf->readableBytes() == tag_.size() + byte_size + kChecksumLen); (void) byte_size; + int32_t len = sockets::hostToNetwork32(static_cast<int32_t>(buf->readableBytes())); + buf->prepend(&len, sizeof len); +} + +void ProtobufCodecLite::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + while (buf->readableBytes() >= static_cast<uint32_t>(kMinMessageLen+kHeaderLen)) + { + const int32_t len = buf->peekInt32(); + if (len > kMaxMessageLen || len < kMinMessageLen) + { + errorCallback_(conn, buf, receiveTime, kInvalidLength); + break; + } + else if (buf->readableBytes() >= implicit_cast<size_t>(kHeaderLen+len)) + { + if (rawCb_ && !rawCb_(conn, StringPiece(buf->peek(), kHeaderLen+len), receiveTime)) + { + buf->retrieve(kHeaderLen+len); + continue; + } + MessagePtr message(prototype_->New()); + // FIXME: can we move deserialization & callback to other thread? + ErrorCode errorCode = parse(buf->peek()+kHeaderLen, len, message.get()); + if (errorCode == kNoError) + { + // FIXME: try { } catch (...) { } + messageCallback_(conn, message, receiveTime); + buf->retrieve(kHeaderLen+len); + } + else + { + errorCallback_(conn, buf, receiveTime, errorCode); + break; + } + } + else + { + break; + } + } +} + +bool ProtobufCodecLite::parseFromBuffer(StringPiece buf, google::protobuf::Message* message) +{ + return message->ParseFromArray(buf.data(), buf.size()); +} + +int ProtobufCodecLite::serializeToBuffer(const google::protobuf::Message& message, Buffer* buf) +{ + // TODO: use BufferOutputStream + // BufferOutputStream os(buf); + // message.SerializeToZeroCopyStream(&os); + // return static_cast<int>(os.ByteCount()); + + // code copied from MessageLite::SerializeToArray() and MessageLite::SerializePartialToArray(). + GOOGLE_DCHECK(message.IsInitialized()) << InitializationErrorMessage("serialize", message); + + /** + * 'ByteSize()' of message is deprecated in Protocol Buffers v3.4.0 firstly. But, till to v3.11.0, it just getting start to be marked by '__attribute__((deprecated()))'. + * So, here, v3.9.2 is selected as maximum version using 'ByteSize()' to avoid potential effect for previous muduo code/projects as far as possible. + * Note: All information above just INFER from + * 1) https://github.com/protocolbuffers/protobuf/releases/tag/v3.4.0 + * 2) MACRO in file 'include/google/protobuf/port_def.inc'. eg. '#define PROTOBUF_DEPRECATED_MSG(msg) __attribute__((deprecated(msg)))'. + * In addition, usage of 'ToIntSize()' comes from Impl of ByteSize() in new version's Protocol Buffers. + */ + + #if GOOGLE_PROTOBUF_VERSION > 3009002 + int byte_size = google::protobuf::internal::ToIntSize(message.ByteSizeLong()); + #else + int byte_size = message.ByteSize(); + #endif + buf->ensureWritableBytes(byte_size + kChecksumLen); + + uint8_t* start = reinterpret_cast<uint8_t*>(buf->beginWrite()); + uint8_t* end = message.SerializeWithCachedSizesToArray(start); + if (end - start != byte_size) + { + #if GOOGLE_PROTOBUF_VERSION > 3009002 + ByteSizeConsistencyError(byte_size, google::protobuf::internal::ToIntSize(message.ByteSizeLong()), static_cast<int>(end - start)); + #else + ByteSizeConsistencyError(byte_size, message.ByteSize(), static_cast<int>(end - start)); + #endif + } + buf->hasWritten(byte_size); + return byte_size; +} + +namespace +{ + const string kNoErrorStr = "NoError"; + const string kInvalidLengthStr = "InvalidLength"; + const string kCheckSumErrorStr = "CheckSumError"; + const string kInvalidNameLenStr = "InvalidNameLen"; + const string kUnknownMessageTypeStr = "UnknownMessageType"; + const string kParseErrorStr = "ParseError"; + const string kUnknownErrorStr = "UnknownError"; +} + +const string& ProtobufCodecLite::errorCodeToString(ErrorCode errorCode) +{ + switch (errorCode) + { + case kNoError: + return kNoErrorStr; + case kInvalidLength: + return kInvalidLengthStr; + case kCheckSumError: + return kCheckSumErrorStr; + case kInvalidNameLen: + return kInvalidNameLenStr; + case kUnknownMessageType: + return kUnknownMessageTypeStr; + case kParseError: + return kParseErrorStr; + default: + return kUnknownErrorStr; + } +} + +void ProtobufCodecLite::defaultErrorCallback(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp, + ErrorCode errorCode) +{ + LOG_ERROR << "ProtobufCodecLite::defaultErrorCallback - " << errorCodeToString(errorCode); + if (conn && conn->connected()) + { + conn->shutdown(); + } +} + +int32_t ProtobufCodecLite::asInt32(const char* buf) +{ + int32_t be32 = 0; + ::memcpy(&be32, buf, sizeof(be32)); + return sockets::networkToHost32(be32); +} + +int32_t ProtobufCodecLite::checksum(const void* buf, int len) +{ + return static_cast<int32_t>( + ::adler32(1, static_cast<const Bytef*>(buf), len)); +} + +bool ProtobufCodecLite::validateChecksum(const char* buf, int len) +{ + // check sum + int32_t expectedCheckSum = asInt32(buf + len - kChecksumLen); + int32_t checkSum = checksum(buf, len - kChecksumLen); + return checkSum == expectedCheckSum; +} + +ProtobufCodecLite::ErrorCode ProtobufCodecLite::parse(const char* buf, + int len, + ::google::protobuf::Message* message) +{ + ErrorCode error = kNoError; + + if (validateChecksum(buf, len)) + { + if (memcmp(buf, tag_.data(), tag_.size()) == 0) + { + // parse from buffer + const char* data = buf + tag_.size(); + int32_t dataLen = len - kChecksumLen - static_cast<int>(tag_.size()); + if (parseFromBuffer(StringPiece(data, dataLen), message)) + { + error = kNoError; + } + else + { + error = kParseError; + } + } + else + { + error = kUnknownMessageType; + } + } + else + { + error = kCheckSumError; + } + + return error; +} + diff --git a/muduo/net/protobuf/ProtobufCodecLite.h b/muduo/net/protobuf/ProtobufCodecLite.h new file mode 100644 index 0000000..134ee7b --- /dev/null +++ b/muduo/net/protobuf/ProtobufCodecLite.h @@ -0,0 +1,192 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +// For Protobuf codec supporting multiple message types, check +// examples/protobuf/codec + +#ifndef MUDUO_NET_PROTOBUF_PROTOBUFCODECLITE_H +#define MUDUO_NET_PROTOBUF_PROTOBUFCODECLITE_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Timestamp.h" +#include "muduo/net/Callbacks.h" + +#include <memory> +#include <type_traits> + +namespace google +{ +namespace protobuf +{ +class Message; +} +} + +namespace muduo +{ +namespace net +{ + +typedef std::shared_ptr<google::protobuf::Message> MessagePtr; + +// wire format +// +// Field Length Content +// +// size 4-byte M+N+4 +// tag M-byte could be "RPC0", etc. +// payload N-byte +// checksum 4-byte adler32 of tag+payload +// +// This is an internal class, you should use ProtobufCodecT instead. +class ProtobufCodecLite : noncopyable +{ + public: + const static int kHeaderLen = sizeof(int32_t); + const static int kChecksumLen = sizeof(int32_t); + const static int kMaxMessageLen = 64*1024*1024; // same as codec_stream.h kDefaultTotalBytesLimit + + enum ErrorCode + { + kNoError = 0, + kInvalidLength, + kCheckSumError, + kInvalidNameLen, + kUnknownMessageType, + kParseError, + }; + + // return false to stop parsing protobuf message + typedef std::function<bool (const TcpConnectionPtr&, + StringPiece, + Timestamp)> RawMessageCallback; + + typedef std::function<void (const TcpConnectionPtr&, + const MessagePtr&, + Timestamp)> ProtobufMessageCallback; + + typedef std::function<void (const TcpConnectionPtr&, + Buffer*, + Timestamp, + ErrorCode)> ErrorCallback; + + ProtobufCodecLite(const ::google::protobuf::Message* prototype, + StringPiece tagArg, + const ProtobufMessageCallback& messageCb, + const RawMessageCallback& rawCb = RawMessageCallback(), + const ErrorCallback& errorCb = defaultErrorCallback) + : prototype_(prototype), + tag_(tagArg.as_string()), + messageCallback_(messageCb), + rawCb_(rawCb), + errorCallback_(errorCb), + kMinMessageLen(tagArg.size() + kChecksumLen) + { + } + + virtual ~ProtobufCodecLite() = default; + + const string& tag() const { return tag_; } + + void send(const TcpConnectionPtr& conn, + const ::google::protobuf::Message& message); + + void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime); + + virtual bool parseFromBuffer(StringPiece buf, google::protobuf::Message* message); + virtual int serializeToBuffer(const google::protobuf::Message& message, Buffer* buf); + + static const string& errorCodeToString(ErrorCode errorCode); + + // public for unit tests + ErrorCode parse(const char* buf, int len, ::google::protobuf::Message* message); + void fillEmptyBuffer(muduo::net::Buffer* buf, const google::protobuf::Message& message); + + static int32_t checksum(const void* buf, int len); + static bool validateChecksum(const char* buf, int len); + static int32_t asInt32(const char* buf); + static void defaultErrorCallback(const TcpConnectionPtr&, + Buffer*, + Timestamp, + ErrorCode); + + private: + const ::google::protobuf::Message* prototype_; + const string tag_; + ProtobufMessageCallback messageCallback_; + RawMessageCallback rawCb_; + ErrorCallback errorCallback_; + const int kMinMessageLen; +}; + +template<typename MSG, const char* TAG, typename CODEC=ProtobufCodecLite> // TAG must be a variable with external linkage, not a string literal +class ProtobufCodecLiteT +{ + static_assert(std::is_base_of<ProtobufCodecLite, CODEC>::value, "CODEC should be derived from ProtobufCodecLite"); + public: + typedef std::shared_ptr<MSG> ConcreteMessagePtr; + typedef std::function<void (const TcpConnectionPtr&, + const ConcreteMessagePtr&, + Timestamp)> ProtobufMessageCallback; + typedef ProtobufCodecLite::RawMessageCallback RawMessageCallback; + typedef ProtobufCodecLite::ErrorCallback ErrorCallback; + + explicit ProtobufCodecLiteT(const ProtobufMessageCallback& messageCb, + const RawMessageCallback& rawCb = RawMessageCallback(), + const ErrorCallback& errorCb = ProtobufCodecLite::defaultErrorCallback) + : messageCallback_(messageCb), + codec_(&MSG::default_instance(), + TAG, + std::bind(&ProtobufCodecLiteT::onRpcMessage, this, _1, _2, _3), + rawCb, + errorCb) + { + } + + const string& tag() const { return codec_.tag(); } + + void send(const TcpConnectionPtr& conn, + const MSG& message) + { + codec_.send(conn, message); + } + + void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) + { + codec_.onMessage(conn, buf, receiveTime); + } + + // internal + void onRpcMessage(const TcpConnectionPtr& conn, + const MessagePtr& message, + Timestamp receiveTime) + { + messageCallback_(conn, ::muduo::down_pointer_cast<MSG>(message), receiveTime); + } + + void fillEmptyBuffer(muduo::net::Buffer* buf, const MSG& message) + { + codec_.fillEmptyBuffer(buf, message); + } + + private: + ProtobufMessageCallback messageCallback_; + CODEC codec_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_PROTOBUF_PROTOBUFCODECLITE_H diff --git a/muduo/net/protorpc/README b/muduo/net/protorpc/README new file mode 100644 index 0000000..0db4c85 --- /dev/null +++ b/muduo/net/protorpc/README @@ -0,0 +1,5 @@ +This is an proof of concept implementation of Google Protobuf RPC using muduo. +The object lifetime management is for from ideal and doesn't follow the usual +muduo approach. + +Please consider using http://github.com/chenshuo/muduo-protorpc instead. diff --git a/muduo/net/protorpc/RpcChannel.cc b/muduo/net/protorpc/RpcChannel.cc new file mode 100644 index 0000000..d18e081 --- /dev/null +++ b/muduo/net/protorpc/RpcChannel.cc @@ -0,0 +1,184 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/protorpc/RpcChannel.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/protorpc/rpc.pb.h" + +#include <google/protobuf/descriptor.h> + +using namespace muduo; +using namespace muduo::net; + +RpcChannel::RpcChannel() + : codec_(std::bind(&RpcChannel::onRpcMessage, this, _1, _2, _3)), + services_(NULL) +{ + LOG_INFO << "RpcChannel::ctor - " << this; +} + +RpcChannel::RpcChannel(const TcpConnectionPtr& conn) + : codec_(std::bind(&RpcChannel::onRpcMessage, this, _1, _2, _3)), + conn_(conn), + services_(NULL) +{ + LOG_INFO << "RpcChannel::ctor - " << this; +} + +RpcChannel::~RpcChannel() +{ + LOG_INFO << "RpcChannel::dtor - " << this; + for (const auto& outstanding : outstandings_) + { + OutstandingCall out = outstanding.second; + delete out.response; + delete out.done; + } +} + + // Call the given method of the remote service. The signature of this + // procedure looks the same as Service::CallMethod(), but the requirements + // are less strict in one important way: the request and response objects + // need not be of any specific class as long as their descriptors are + // method->input_type() and method->output_type(). +void RpcChannel::CallMethod(const ::google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done) +{ + RpcMessage message; + message.set_type(REQUEST); + int64_t id = id_.incrementAndGet(); + message.set_id(id); + message.set_service(method->service()->full_name()); + message.set_method(method->name()); + message.set_request(request->SerializeAsString()); // FIXME: error check + + OutstandingCall out = { response, done }; + { + MutexLockGuard lock(mutex_); + outstandings_[id] = out; + } + codec_.send(conn_, message); +} + +void RpcChannel::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + codec_.onMessage(conn, buf, receiveTime); +} + +void RpcChannel::onRpcMessage(const TcpConnectionPtr& conn, + const RpcMessagePtr& messagePtr, + Timestamp receiveTime) +{ + assert(conn == conn_); + //printf("%s\n", message.DebugString().c_str()); + RpcMessage& message = *messagePtr; + if (message.type() == RESPONSE) + { + int64_t id = message.id(); + assert(message.has_response() || message.has_error()); + + OutstandingCall out = { NULL, NULL }; + + { + MutexLockGuard lock(mutex_); + std::map<int64_t, OutstandingCall>::iterator it = outstandings_.find(id); + if (it != outstandings_.end()) + { + out = it->second; + outstandings_.erase(it); + } + } + + if (out.response) + { + std::unique_ptr<google::protobuf::Message> d(out.response); + if (message.has_response()) + { + out.response->ParseFromString(message.response()); + } + if (out.done) + { + out.done->Run(); + } + } + } + else if (message.type() == REQUEST) + { + // FIXME: extract to a function + ErrorCode error = WRONG_PROTO; + if (services_) + { + std::map<std::string, google::protobuf::Service*>::const_iterator it = services_->find(message.service()); + if (it != services_->end()) + { + google::protobuf::Service* service = it->second; + assert(service != NULL); + const google::protobuf::ServiceDescriptor* desc = service->GetDescriptor(); + const google::protobuf::MethodDescriptor* method + = desc->FindMethodByName(message.method()); + if (method) + { + std::unique_ptr<google::protobuf::Message> request(service->GetRequestPrototype(method).New()); + if (request->ParseFromString(message.request())) + { + google::protobuf::Message* response = service->GetResponsePrototype(method).New(); + // response is deleted in doneCallback + int64_t id = message.id(); + service->CallMethod(method, NULL, get_pointer(request), response, + NewCallback(this, &RpcChannel::doneCallback, response, id)); + error = NO_ERROR; + } + else + { + error = INVALID_REQUEST; + } + } + else + { + error = NO_METHOD; + } + } + else + { + error = NO_SERVICE; + } + } + else + { + error = NO_SERVICE; + } + if (error != NO_ERROR) + { + RpcMessage response; + response.set_type(RESPONSE); + response.set_id(message.id()); + response.set_error(error); + codec_.send(conn_, response); + } + } + else if (message.type() == ERROR) + { + } +} + +void RpcChannel::doneCallback(::google::protobuf::Message* response, int64_t id) +{ + std::unique_ptr<google::protobuf::Message> d(response); + RpcMessage message; + message.set_type(RESPONSE); + message.set_id(id); + message.set_response(response->SerializeAsString()); // FIXME: error check + codec_.send(conn_, message); +} + diff --git a/muduo/net/protorpc/RpcChannel.h b/muduo/net/protorpc/RpcChannel.h new file mode 100644 index 0000000..172b41e --- /dev/null +++ b/muduo/net/protorpc/RpcChannel.h @@ -0,0 +1,152 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_PROTORPC_RPCCHANNEL_H +#define MUDUO_NET_PROTORPC_RPCCHANNEL_H + +#include "muduo/base/Atomic.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/protorpc/RpcCodec.h" + +#include <google/protobuf/service.h> + +#include <map> + +// Service and RpcChannel classes are incorporated from +// google/protobuf/service.h + +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. + +namespace google { +namespace protobuf { + +// Defined in other files. +class Descriptor; // descriptor.h +class ServiceDescriptor; // descriptor.h +class MethodDescriptor; // descriptor.h +class Message; // message.h + +class Closure; + +class RpcController; +class Service; + +} // namespace protobuf +} // namespace google + + +namespace muduo +{ +namespace net +{ + +// Abstract interface for an RPC channel. An RpcChannel represents a +// communication line to a Service which can be used to call that Service's +// methods. The Service may be running on another machine. Normally, you +// should not call an RpcChannel directly, but instead construct a stub Service +// wrapping it. Example: +// FIXME: update here +// RpcChannel* channel = new MyRpcChannel("remotehost.example.com:1234"); +// MyService* service = new MyService::Stub(channel); +// service->MyMethod(request, &response, callback); +class RpcChannel : public ::google::protobuf::RpcChannel +{ + public: + RpcChannel(); + + explicit RpcChannel(const TcpConnectionPtr& conn); + + ~RpcChannel() override; + + void setConnection(const TcpConnectionPtr& conn) + { + conn_ = conn; + } + + void setServices(const std::map<std::string, ::google::protobuf::Service*>* services) + { + services_ = services; + } + + // Call the given method of the remote service. The signature of this + // procedure looks the same as Service::CallMethod(), but the requirements + // are less strict in one important way: the request and response objects + // need not be of any specific class as long as their descriptors are + // method->input_type() and method->output_type(). + void CallMethod(const ::google::protobuf::MethodDescriptor* method, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done) override; + + void onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime); + + private: + void onRpcMessage(const TcpConnectionPtr& conn, + const RpcMessagePtr& messagePtr, + Timestamp receiveTime); + + void doneCallback(::google::protobuf::Message* response, int64_t id); + + struct OutstandingCall + { + ::google::protobuf::Message* response; + ::google::protobuf::Closure* done; + }; + + RpcCodec codec_; + TcpConnectionPtr conn_; + AtomicInt64 id_; + + MutexLock mutex_; + std::map<int64_t, OutstandingCall> outstandings_ GUARDED_BY(mutex_); + + const std::map<std::string, ::google::protobuf::Service*>* services_; +}; +typedef std::shared_ptr<RpcChannel> RpcChannelPtr; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_PROTORPC_RPCCHANNEL_H diff --git a/muduo/net/protorpc/RpcCodec.cc b/muduo/net/protorpc/RpcCodec.cc new file mode 100644 index 0000000..e466e1d --- /dev/null +++ b/muduo/net/protorpc/RpcCodec.cc @@ -0,0 +1,37 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/protorpc/RpcCodec.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" +#include "muduo/net/TcpConnection.h" + +#include "muduo/net/protorpc/rpc.pb.h" +#include "muduo/net/protorpc/google-inl.h" + +using namespace muduo; +using namespace muduo::net; + +namespace +{ + int ProtobufVersionCheck() + { + GOOGLE_PROTOBUF_VERIFY_VERSION; + return 0; + } + int dummy __attribute__ ((unused)) = ProtobufVersionCheck(); +} + +namespace muduo +{ +namespace net +{ +const char rpctag [] = "RPC0"; +} +} diff --git a/muduo/net/protorpc/RpcCodec.h b/muduo/net/protorpc/RpcCodec.h new file mode 100644 index 0000000..07fab9c --- /dev/null +++ b/muduo/net/protorpc/RpcCodec.h @@ -0,0 +1,45 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_PROTORPC_RPCCODEC_H +#define MUDUO_NET_PROTORPC_RPCCODEC_H + +#include "muduo/base/Timestamp.h" +#include "muduo/net/protobuf/ProtobufCodecLite.h" + +namespace muduo +{ +namespace net +{ + +class Buffer; +class TcpConnection; +typedef std::shared_ptr<TcpConnection> TcpConnectionPtr; + +class RpcMessage; +typedef std::shared_ptr<RpcMessage> RpcMessagePtr; +extern const char rpctag[];// = "RPC0"; + +// wire format +// +// Field Length Content +// +// size 4-byte N+8 +// "RPC0" 4-byte +// payload N-byte +// checksum 4-byte adler32 of "RPC0"+payload +// + +typedef ProtobufCodecLiteT<RpcMessage, rpctag> RpcCodec; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_PROTORPC_RPCCODEC_H diff --git a/muduo/net/protorpc/RpcCodec_test.cc b/muduo/net/protorpc/RpcCodec_test.cc new file mode 100644 index 0000000..645fffd --- /dev/null +++ b/muduo/net/protorpc/RpcCodec_test.cc @@ -0,0 +1,81 @@ +#undef NDEBUG +#include "muduo/net/protorpc/RpcCodec.h" +#include "muduo/net/protorpc/rpc.pb.h" +#include "muduo/net/protobuf/ProtobufCodecLite.h" +#include "muduo/net/Buffer.h" + +#include <stdio.h> + +using namespace muduo; +using namespace muduo::net; + +void rpcMessageCallback(const TcpConnectionPtr&, + const RpcMessagePtr&, + Timestamp) +{ +} + +MessagePtr g_msgptr; +void messageCallback(const TcpConnectionPtr&, + const MessagePtr& msg, + Timestamp) +{ + g_msgptr = msg; +} + +void print(const Buffer& buf) +{ + printf("encoded to %zd bytes\n", buf.readableBytes()); + for (size_t i = 0; i < buf.readableBytes(); ++i) + { + unsigned char ch = static_cast<unsigned char>(buf.peek()[i]); + + printf("%2zd: 0x%02x %c\n", i, ch, isgraph(ch) ? ch : ' '); + } +} + +char rpctag[] = "RPC0"; + +int main() +{ + RpcMessage message; + message.set_type(REQUEST); + message.set_id(2); + char wire[] = "\0\0\0\x13" "RPC0" "\x08\x01\x11\x02\0\0\0\0\0\0\0" "\x0f\xef\x01\x32"; + string expected(wire, sizeof(wire)-1); + string s1, s2; + Buffer buf1, buf2; + { + RpcCodec codec(rpcMessageCallback); + codec.fillEmptyBuffer(&buf1, message); + print(buf1); + s1 = buf1.toStringPiece().as_string(); + } + + { + ProtobufCodecLite codec(&RpcMessage::default_instance(), "RPC0", messageCallback); + codec.fillEmptyBuffer(&buf2, message); + print(buf2); + s2 = buf2.toStringPiece().as_string(); + codec.onMessage(TcpConnectionPtr(), &buf1, Timestamp::now()); + assert(g_msgptr); + assert(g_msgptr->DebugString() == message.DebugString()); + g_msgptr.reset(); + } + assert(s1 == s2); + assert(s1 == expected); + assert(s2 == expected); + + { + Buffer buf; + ProtobufCodecLite codec(&RpcMessage::default_instance(), "XYZ", messageCallback); + codec.fillEmptyBuffer(&buf, message); + print(buf); + s2 = buf.toStringPiece().as_string(); + codec.onMessage(TcpConnectionPtr(), &buf, Timestamp::now()); + assert(g_msgptr); + assert(g_msgptr->DebugString() == message.DebugString()); + } + + google::protobuf::ShutdownProtobufLibrary(); +} diff --git a/muduo/net/protorpc/RpcServer.cc b/muduo/net/protorpc/RpcServer.cc new file mode 100644 index 0000000..fc1248b --- /dev/null +++ b/muduo/net/protorpc/RpcServer.cc @@ -0,0 +1,68 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) + +#include "muduo/net/protorpc/RpcServer.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/protorpc/RpcChannel.h" + +#include <google/protobuf/descriptor.h> +#include <google/protobuf/service.h> + +using namespace muduo; +using namespace muduo::net; + +RpcServer::RpcServer(EventLoop* loop, + const InetAddress& listenAddr) + : server_(loop, listenAddr, "RpcServer") +{ + server_.setConnectionCallback( + std::bind(&RpcServer::onConnection, this, _1)); +// server_.setMessageCallback( +// std::bind(&RpcServer::onMessage, this, _1, _2, _3)); +} + +void RpcServer::registerService(google::protobuf::Service* service) +{ + const google::protobuf::ServiceDescriptor* desc = service->GetDescriptor(); + services_[desc->full_name()] = service; +} + +void RpcServer::start() +{ + server_.start(); +} + +void RpcServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "RpcServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + RpcChannelPtr channel(new RpcChannel(conn)); + channel->setServices(&services_); + conn->setMessageCallback( + std::bind(&RpcChannel::onMessage, get_pointer(channel), _1, _2, _3)); + conn->setContext(channel); + } + else + { + conn->setContext(RpcChannelPtr()); + // FIXME: + } +} + +// void RpcServer::onMessage(const TcpConnectionPtr& conn, +// Buffer* buf, +// Timestamp time) +// { +// RpcChannelPtr& channel = boost::any_cast<RpcChannelPtr&>(conn->getContext()); +// channel->onMessage(conn, buf, time); +// } + diff --git a/muduo/net/protorpc/RpcServer.h b/muduo/net/protorpc/RpcServer.h new file mode 100644 index 0000000..6818801 --- /dev/null +++ b/muduo/net/protorpc/RpcServer.h @@ -0,0 +1,57 @@ +// Copyright 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Use of this source code is governed by a BSD-style license +// that can be found in the License file. + +// Author: Shuo Chen (chenshuo at chenshuo dot com) +// +// This is a public header file, it must only include public header files. + +#ifndef MUDUO_NET_PROTORPC_RPCSERVER_H +#define MUDUO_NET_PROTORPC_RPCSERVER_H + +#include "muduo/net/TcpServer.h" + +namespace google { +namespace protobuf { + +class Service; + +} // namespace protobuf +} // namespace google + +namespace muduo +{ +namespace net +{ + +class RpcServer +{ + public: + RpcServer(EventLoop* loop, + const InetAddress& listenAddr); + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void registerService(::google::protobuf::Service*); + void start(); + + private: + void onConnection(const TcpConnectionPtr& conn); + + // void onMessage(const TcpConnectionPtr& conn, + // Buffer* buf, + // Timestamp time); + + TcpServer server_; + std::map<std::string, ::google::protobuf::Service*> services_; +}; + +} // namespace net +} // namespace muduo + +#endif // MUDUO_NET_PROTORPC_RPCSERVER_H diff --git a/muduo/net/protorpc/google-inl.h b/muduo/net/protorpc/google-inl.h new file mode 100644 index 0000000..2a905a9 --- /dev/null +++ b/muduo/net/protorpc/google-inl.h @@ -0,0 +1,84 @@ +// ByteSizeConsistencyError and InitializationErrorMessage are +// copied from google/protobuf/message_lite.cc + +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Authors: wink@google.com (Wink Saville), +// kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. + +#include <google/protobuf/message.h> + +// When serializing, we first compute the byte size, then serialize the message. +// If serialization produces a different number of bytes than expected, we +// call this function, which crashes. The problem could be due to a bug in the +// protobuf implementation but is more likely caused by concurrent modification +// of the message. This function attempts to distinguish between the two and +// provide a useful error message. +inline +void ByteSizeConsistencyError(int byte_size_before_serialization, + int byte_size_after_serialization, + int bytes_produced_by_serialization) +{ + GOOGLE_CHECK_EQ(byte_size_before_serialization, byte_size_after_serialization) + << "Protocol message was modified concurrently during serialization."; + GOOGLE_CHECK_EQ(bytes_produced_by_serialization, byte_size_before_serialization) + << "Byte size calculation and serialization were inconsistent. This " + "may indicate a bug in protocol buffers or it may be caused by " + "concurrent modification of the message."; + GOOGLE_LOG(FATAL) << "This shouldn't be called if all the sizes are equal."; +} + +inline +std::string InitializationErrorMessage(const char* action, + const google::protobuf::MessageLite& message) +{ + // Note: We want to avoid depending on strutil in the lite library, otherwise + // we'd use: + // + // return strings::Substitute( + // "Can't $0 message of type \"$1\" because it is missing required " + // "fields: $2", + // action, message.GetTypeName(), + // message.InitializationErrorString()); + + std::string result; + result += "Can't "; + result += action; + result += " message of type \""; + result += message.GetTypeName(); + result += "\" because it is missing required fields: "; + result += message.InitializationErrorString(); + return result; +} + + diff --git a/muduo/net/protorpc/rpc.proto b/muduo/net/protorpc/rpc.proto new file mode 100644 index 0000000..941e235 --- /dev/null +++ b/muduo/net/protorpc/rpc.proto @@ -0,0 +1,36 @@ +package muduo.net; +// option go_package = "muduorpc"; +option java_package = "com.chenshuo.muduo.protorpc"; +option java_outer_classname = "RpcProto"; + +enum MessageType +{ + REQUEST = 1; + RESPONSE = 2; + ERROR = 3; // not used +} + +enum ErrorCode +{ + NO_ERROR = 0; + WRONG_PROTO = 1; + NO_SERVICE = 2; + NO_METHOD = 3; + INVALID_REQUEST = 4; + INVALID_RESPONSE = 5; + TIMEOUT = 6; +} + +message RpcMessage +{ + required MessageType type = 1; + required fixed64 id = 2; + + optional string service = 3; + optional string method = 4; + optional bytes request = 5; + + optional bytes response = 6; + + optional ErrorCode error = 7; +} diff --git a/muduo/net/protorpc/rpcservice.proto b/muduo/net/protorpc/rpcservice.proto new file mode 100644 index 0000000..f973234 --- /dev/null +++ b/muduo/net/protorpc/rpcservice.proto @@ -0,0 +1,44 @@ +package muduo.net; + +option cc_generic_services = true; +option java_generic_services = true; +option py_generic_services = true; + +option java_package = "com.chenshuo.muduo.protorpc"; +option java_outer_classname = "RpcServiceProto"; + +//import "google/protobuf/descriptor.proto"; +import "rpc.proto"; + +message ListRpcRequest +{ + optional string service_name = 1; + optional bool list_method = 2; +} + +message ListRpcResponse +{ + required ErrorCode error = 1; + repeated string service_name = 2; + repeated string method_name = 3; +} + +message GetServiceRequest +{ + required string service_name = 1; +} + +message GetServiceResponse +{ + required ErrorCode error = 1; + repeated string proto_file = 2; + repeated string proto_file_name = 3; +} + +// the meta service +service RpcService +{ + rpc listRpc (ListRpcRequest) returns (ListRpcResponse); + rpc getService (GetServiceRequest) returns (GetServiceResponse); +} + diff --git a/muduo/net/tests/Buffer_unittest.cc b/muduo/net/tests/Buffer_unittest.cc new file mode 100644 index 0000000..af7fb81 --- /dev/null +++ b/muduo/net/tests/Buffer_unittest.cc @@ -0,0 +1,170 @@ +#include "muduo/net/Buffer.h" + +//#define BOOST_TEST_MODULE BufferTest +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +using muduo::string; +using muduo::net::Buffer; + +BOOST_AUTO_TEST_CASE(testBufferAppendRetrieve) +{ + Buffer buf; + BOOST_CHECK_EQUAL(buf.readableBytes(), 0); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); + + const string str(200, 'x'); + buf.append(str); + BOOST_CHECK_EQUAL(buf.readableBytes(), str.size()); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize - str.size()); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); + + const string str2 = buf.retrieveAsString(50); + BOOST_CHECK_EQUAL(str2.size(), 50); + BOOST_CHECK_EQUAL(buf.readableBytes(), str.size() - str2.size()); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize - str.size()); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend + str2.size()); + BOOST_CHECK_EQUAL(str2, string(50, 'x')); + + buf.append(str); + BOOST_CHECK_EQUAL(buf.readableBytes(), 2*str.size() - str2.size()); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize - 2*str.size()); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend + str2.size()); + + const string str3 = buf.retrieveAllAsString(); + BOOST_CHECK_EQUAL(str3.size(), 350); + BOOST_CHECK_EQUAL(buf.readableBytes(), 0); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); + BOOST_CHECK_EQUAL(str3, string(350, 'x')); +} + +BOOST_AUTO_TEST_CASE(testBufferGrow) +{ + Buffer buf; + buf.append(string(400, 'y')); + BOOST_CHECK_EQUAL(buf.readableBytes(), 400); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-400); + + buf.retrieve(50); + BOOST_CHECK_EQUAL(buf.readableBytes(), 350); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-400); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+50); + + buf.append(string(1000, 'z')); + BOOST_CHECK_EQUAL(buf.readableBytes(), 1350); + BOOST_CHECK_EQUAL(buf.writableBytes(), 0); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+50); // FIXME + + buf.retrieveAll(); + BOOST_CHECK_EQUAL(buf.readableBytes(), 0); + BOOST_CHECK_EQUAL(buf.writableBytes(), 1400); // FIXME + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); +} + +BOOST_AUTO_TEST_CASE(testBufferInsideGrow) +{ + Buffer buf; + buf.append(string(800, 'y')); + BOOST_CHECK_EQUAL(buf.readableBytes(), 800); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-800); + + buf.retrieve(500); + BOOST_CHECK_EQUAL(buf.readableBytes(), 300); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-800); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+500); + + buf.append(string(300, 'z')); + BOOST_CHECK_EQUAL(buf.readableBytes(), 600); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-600); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); +} + +BOOST_AUTO_TEST_CASE(testBufferShrink) +{ + Buffer buf; + buf.append(string(2000, 'y')); + BOOST_CHECK_EQUAL(buf.readableBytes(), 2000); + BOOST_CHECK_EQUAL(buf.writableBytes(), 0); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); + + buf.retrieve(1500); + BOOST_CHECK_EQUAL(buf.readableBytes(), 500); + BOOST_CHECK_EQUAL(buf.writableBytes(), 0); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+1500); + + buf.shrink(0); + BOOST_CHECK_EQUAL(buf.readableBytes(), 500); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-500); + BOOST_CHECK_EQUAL(buf.retrieveAllAsString(), string(500, 'y')); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); +} + +BOOST_AUTO_TEST_CASE(testBufferPrepend) +{ + Buffer buf; + buf.append(string(200, 'y')); + BOOST_CHECK_EQUAL(buf.readableBytes(), 200); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-200); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend); + + int x = 0; + buf.prepend(&x, sizeof x); + BOOST_CHECK_EQUAL(buf.readableBytes(), 204); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-200); + BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend - 4); +} + +BOOST_AUTO_TEST_CASE(testBufferReadInt) +{ + Buffer buf; + buf.append("HTTP"); + + BOOST_CHECK_EQUAL(buf.readableBytes(), 4); + BOOST_CHECK_EQUAL(buf.peekInt8(), 'H'); + int top16 = buf.peekInt16(); + BOOST_CHECK_EQUAL(top16, 'H'*256 + 'T'); + BOOST_CHECK_EQUAL(buf.peekInt32(), top16*65536 + 'T'*256 + 'P'); + + BOOST_CHECK_EQUAL(buf.readInt8(), 'H'); + BOOST_CHECK_EQUAL(buf.readInt16(), 'T'*256 + 'T'); + BOOST_CHECK_EQUAL(buf.readInt8(), 'P'); + BOOST_CHECK_EQUAL(buf.readableBytes(), 0); + BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize); + + buf.appendInt8(-1); + buf.appendInt16(-2); + buf.appendInt32(-3); + BOOST_CHECK_EQUAL(buf.readableBytes(), 7); + BOOST_CHECK_EQUAL(buf.readInt8(), -1); + BOOST_CHECK_EQUAL(buf.readInt16(), -2); + BOOST_CHECK_EQUAL(buf.readInt32(), -3); +} + +BOOST_AUTO_TEST_CASE(testBufferFindEOL) +{ + Buffer buf; + buf.append(string(100000, 'x')); + const char* null = NULL; + BOOST_CHECK_EQUAL(buf.findEOL(), null); + BOOST_CHECK_EQUAL(buf.findEOL(buf.peek()+90000), null); +} + +void output(Buffer&& buf, const void* inner) +{ + Buffer newbuf(std::move(buf)); + // printf("New Buffer at %p, inner %p\n", &newbuf, newbuf.peek()); + BOOST_CHECK_EQUAL(inner, newbuf.peek()); +} + +// NOTE: This test fails in g++ 4.4, passes in g++ 4.6. +BOOST_AUTO_TEST_CASE(testMove) +{ + Buffer buf; + buf.append("muduo", 5); + const void* inner = buf.peek(); + // printf("Buffer at %p, inner %p\n", &buf, inner); + output(std::move(buf), inner); +} diff --git a/muduo/net/tests/Channel_test.cc b/muduo/net/tests/Channel_test.cc new file mode 100644 index 0000000..6276531 --- /dev/null +++ b/muduo/net/tests/Channel_test.cc @@ -0,0 +1,112 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include <functional> +#include <map> + +#include <stdio.h> +#include <unistd.h> +#include <sys/timerfd.h> + +using namespace muduo; +using namespace muduo::net; + +void print(const char* msg) +{ + static std::map<const char*, Timestamp> lasts; + Timestamp& last = lasts[msg]; + Timestamp now = Timestamp::now(); + printf("%s tid %d %s delay %f\n", now.toString().c_str(), CurrentThread::tid(), + msg, timeDifference(now, last)); + last = now; +} + +namespace muduo +{ +namespace net +{ +namespace detail +{ +int createTimerfd(); +void readTimerfd(int timerfd, Timestamp now); +} +} +} + +// Use relative time, immunized to wall clock changes. +class PeriodicTimer +{ + public: + PeriodicTimer(EventLoop* loop, double interval, const TimerCallback& cb) + : loop_(loop), + timerfd_(muduo::net::detail::createTimerfd()), + timerfdChannel_(loop, timerfd_), + interval_(interval), + cb_(cb) + { + timerfdChannel_.setReadCallback( + std::bind(&PeriodicTimer::handleRead, this)); + timerfdChannel_.enableReading(); + } + + void start() + { + struct itimerspec spec; + memZero(&spec, sizeof spec); + spec.it_interval = toTimeSpec(interval_); + spec.it_value = spec.it_interval; + int ret = ::timerfd_settime(timerfd_, 0 /* relative timer */, &spec, NULL); + if (ret) + { + LOG_SYSERR << "timerfd_settime()"; + } + } + + ~PeriodicTimer() + { + timerfdChannel_.disableAll(); + timerfdChannel_.remove(); + ::close(timerfd_); + } + + private: + void handleRead() + { + loop_->assertInLoopThread(); + muduo::net::detail::readTimerfd(timerfd_, Timestamp::now()); + if (cb_) + cb_(); + } + + static struct timespec toTimeSpec(double seconds) + { + struct timespec ts; + memZero(&ts, sizeof ts); + const int64_t kNanoSecondsPerSecond = 1000000000; + const int kMinInterval = 100000; + int64_t nanoseconds = static_cast<int64_t>(seconds * kNanoSecondsPerSecond); + if (nanoseconds < kMinInterval) + nanoseconds = kMinInterval; + ts.tv_sec = static_cast<time_t>(nanoseconds / kNanoSecondsPerSecond); + ts.tv_nsec = static_cast<long>(nanoseconds % kNanoSecondsPerSecond); + return ts; + } + + EventLoop* loop_; + const int timerfd_; + Channel timerfdChannel_; + const double interval_; // in seconds + TimerCallback cb_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid() + << " Try adjusting the wall clock, see what happens."; + EventLoop loop; + PeriodicTimer timer(&loop, 1, std::bind(print, "PeriodicTimer")); + timer.start(); + loop.runEvery(1, std::bind(print, "EventLoop::runEvery")); + loop.loop(); +} diff --git a/muduo/net/tests/EchoClient_unittest.cc b/muduo/net/tests/EchoClient_unittest.cc new file mode 100644 index 0000000..44d973a --- /dev/null +++ b/muduo/net/tests/EchoClient_unittest.cc @@ -0,0 +1,114 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; +class EchoClient; +std::vector<std::unique_ptr<EchoClient>> clients; +int current = 0; + +class EchoClient : noncopyable +{ + public: + EchoClient(EventLoop* loop, const InetAddress& listenAddr, const string& id) + : loop_(loop), + client_(loop, listenAddr, "EchoClient"+id) + { + client_.setConnectionCallback( + std::bind(&EchoClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&EchoClient::onMessage, this, _1, _2, _3)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + // void stop(); + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + ++current; + if (implicit_cast<size_t>(current) < clients.size()) + { + clients[current]->connect(); + } + LOG_INFO << "*** connected " << current; + } + conn->send("world\n"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + string msg(buf->retrieveAllAsString()); + LOG_TRACE << conn->name() << " recv " << msg.size() << " bytes at " << time.toString(); + if (msg == "quit\n") + { + conn->send("bye\n"); + conn->shutdown(); + } + else if (msg == "shutdown\n") + { + loop_->quit(); + } + else + { + conn->send(msg); + } + } + + EventLoop* loop_; + TcpClient client_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + EventLoop loop; + bool ipv6 = argc > 3; + InetAddress serverAddr(argv[1], 2000, ipv6); + + int n = 1; + if (argc > 2) + { + n = atoi(argv[2]); + } + + clients.reserve(n); + for (int i = 0; i < n; ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "%d", i+1); + clients.emplace_back(new EchoClient(&loop, serverAddr, buf)); + } + + clients[current]->connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip [current#]\n", argv[0]); + } +} + diff --git a/muduo/net/tests/EchoServer_unittest.cc b/muduo/net/tests/EchoServer_unittest.cc new file mode 100644 index 0000000..0875668 --- /dev/null +++ b/muduo/net/tests/EchoServer_unittest.cc @@ -0,0 +1,86 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include <utility> + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class EchoServer +{ + public: + EchoServer(EventLoop* loop, const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "EchoServer") + { + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + } + + void start() + { + server_.start(); + } + // void stop(); + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + LOG_INFO << conn->getTcpInfoString(); + + conn->send("hello\n"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + string msg(buf->retrieveAllAsString()); + LOG_TRACE << conn->name() << " recv " << msg.size() << " bytes at " << time.toString(); + if (msg == "exit\n") + { + conn->send("bye\n"); + conn->shutdown(); + } + if (msg == "quit\n") + { + loop_->quit(); + } + conn->send(msg); + } + + EventLoop* loop_; + TcpServer server_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + LOG_INFO << "sizeof TcpConnection = " << sizeof(TcpConnection); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + bool ipv6 = argc > 2; + EventLoop loop; + InetAddress listenAddr(2000, false, ipv6); + EchoServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/muduo/net/tests/EventLoopThreadPool_unittest.cc b/muduo/net/tests/EventLoopThreadPool_unittest.cc new file mode 100644 index 0000000..1bf1bd3 --- /dev/null +++ b/muduo/net/tests/EventLoopThreadPool_unittest.cc @@ -0,0 +1,68 @@ +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/EventLoop.h" +#include "muduo/base/Thread.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +void print(EventLoop* p = NULL) +{ + printf("main(): pid = %d, tid = %d, loop = %p\n", + getpid(), CurrentThread::tid(), p); +} + +void init(EventLoop* p) +{ + printf("init(): pid = %d, tid = %d, loop = %p\n", + getpid(), CurrentThread::tid(), p); +} + +int main() +{ + print(); + + EventLoop loop; + loop.runAfter(11, std::bind(&EventLoop::quit, &loop)); + + { + printf("Single thread %p:\n", &loop); + EventLoopThreadPool model(&loop, "single"); + model.setThreadNum(0); + model.start(init); + assert(model.getNextLoop() == &loop); + assert(model.getNextLoop() == &loop); + assert(model.getNextLoop() == &loop); + } + + { + printf("Another thread:\n"); + EventLoopThreadPool model(&loop, "another"); + model.setThreadNum(1); + model.start(init); + EventLoop* nextLoop = model.getNextLoop(); + nextLoop->runAfter(2, std::bind(print, nextLoop)); + assert(nextLoop != &loop); + assert(nextLoop == model.getNextLoop()); + assert(nextLoop == model.getNextLoop()); + ::sleep(3); + } + + { + printf("Three threads:\n"); + EventLoopThreadPool model(&loop, "three"); + model.setThreadNum(3); + model.start(init); + EventLoop* nextLoop = model.getNextLoop(); + nextLoop->runInLoop(std::bind(print, nextLoop)); + assert(nextLoop != &loop); + assert(nextLoop != model.getNextLoop()); + assert(nextLoop != model.getNextLoop()); + assert(nextLoop == model.getNextLoop()); + } + + loop.loop(); +} + diff --git a/muduo/net/tests/EventLoopThread_unittest.cc b/muduo/net/tests/EventLoopThread_unittest.cc new file mode 100644 index 0000000..d67c128 --- /dev/null +++ b/muduo/net/tests/EventLoopThread_unittest.cc @@ -0,0 +1,48 @@ +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/base/Thread.h" +#include "muduo/base/CountDownLatch.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +void print(EventLoop* p = NULL) +{ + printf("print: pid = %d, tid = %d, loop = %p\n", + getpid(), CurrentThread::tid(), p); +} + +void quit(EventLoop* p) +{ + print(p); + p->quit(); +} + +int main() +{ + print(); + + { + EventLoopThread thr1; // never start + } + + { + // dtor calls quit() + EventLoopThread thr2; + EventLoop* loop = thr2.startLoop(); + loop->runInLoop(std::bind(print, loop)); + CurrentThread::sleepUsec(500 * 1000); + } + + { + // quit() before dtor + EventLoopThread thr3; + EventLoop* loop = thr3.startLoop(); + loop->runInLoop(std::bind(quit, loop)); + CurrentThread::sleepUsec(500 * 1000); + } +} + diff --git a/muduo/net/tests/EventLoop_unittest.cc b/muduo/net/tests/EventLoop_unittest.cc new file mode 100644 index 0000000..6cb6d7d --- /dev/null +++ b/muduo/net/tests/EventLoop_unittest.cc @@ -0,0 +1,42 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/base/Thread.h" + +#include <assert.h> +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +EventLoop* g_loop; + +void callback() +{ + printf("callback(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); + EventLoop anotherLoop; +} + +void threadFunc() +{ + printf("threadFunc(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); + + assert(EventLoop::getEventLoopOfCurrentThread() == NULL); + EventLoop loop; + assert(EventLoop::getEventLoopOfCurrentThread() == &loop); + loop.runAfter(1.0, callback); + loop.loop(); +} + +int main() +{ + printf("main(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); + + assert(EventLoop::getEventLoopOfCurrentThread() == NULL); + EventLoop loop; + assert(EventLoop::getEventLoopOfCurrentThread() == &loop); + + Thread thread(threadFunc); + thread.start(); + + loop.loop(); +} diff --git a/muduo/net/tests/InetAddress_unittest.cc b/muduo/net/tests/InetAddress_unittest.cc new file mode 100644 index 0000000..5ba64d6 --- /dev/null +++ b/muduo/net/tests/InetAddress_unittest.cc @@ -0,0 +1,47 @@ +#include "muduo/net/InetAddress.h" + +#include "muduo/base/Logging.h" + +//#define BOOST_TEST_MODULE InetAddressTest +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +using muduo::string; +using muduo::net::InetAddress; + +BOOST_AUTO_TEST_CASE(testInetAddress) +{ + InetAddress addr0(1234); + BOOST_CHECK_EQUAL(addr0.toIp(), string("0.0.0.0")); + BOOST_CHECK_EQUAL(addr0.toIpPort(), string("0.0.0.0:1234")); + BOOST_CHECK_EQUAL(addr0.toPort(), 1234); + + InetAddress addr1(4321, true); + BOOST_CHECK_EQUAL(addr1.toIp(), string("127.0.0.1")); + BOOST_CHECK_EQUAL(addr1.toIpPort(), string("127.0.0.1:4321")); + BOOST_CHECK_EQUAL(addr1.toPort(), 4321); + + InetAddress addr2("1.2.3.4", 8888); + BOOST_CHECK_EQUAL(addr2.toIp(), string("1.2.3.4")); + BOOST_CHECK_EQUAL(addr2.toIpPort(), string("1.2.3.4:8888")); + BOOST_CHECK_EQUAL(addr2.toPort(), 8888); + + InetAddress addr3("255.254.253.252", 65535); + BOOST_CHECK_EQUAL(addr3.toIp(), string("255.254.253.252")); + BOOST_CHECK_EQUAL(addr3.toIpPort(), string("255.254.253.252:65535")); + BOOST_CHECK_EQUAL(addr3.toPort(), 65535); +} + +BOOST_AUTO_TEST_CASE(testInetAddressResolve) +{ + InetAddress addr(80); + if (InetAddress::resolve("google.com", &addr)) + { + LOG_INFO << "google.com resolved to " << addr.toIpPort(); + } + else + { + LOG_ERROR << "Unable to resolve google.com"; + } +} diff --git a/muduo/net/tests/TcpClient_reg1.cc b/muduo/net/tests/TcpClient_reg1.cc new file mode 100644 index 0000000..41b76b2 --- /dev/null +++ b/muduo/net/tests/TcpClient_reg1.cc @@ -0,0 +1,29 @@ +// TcpClient::stop() called in the same iteration of IO event + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +TcpClient* g_client; + +void timeout() +{ + LOG_INFO << "timeout"; + g_client->stop(); +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + InetAddress serverAddr("127.0.0.1", 2); // no such server + TcpClient client(&loop, serverAddr, "TcpClient"); + g_client = &client; + loop.runAfter(0.0, timeout); + loop.runAfter(1.0, std::bind(&EventLoop::quit, &loop)); + client.connect(); + CurrentThread::sleepUsec(100 * 1000); + loop.loop(); +} diff --git a/muduo/net/tests/TcpClient_reg2.cc b/muduo/net/tests/TcpClient_reg2.cc new file mode 100644 index 0000000..1637dbd --- /dev/null +++ b/muduo/net/tests/TcpClient_reg2.cc @@ -0,0 +1,30 @@ +// TcpClient destructs when TcpConnection is connected but unique. + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +void threadFunc(EventLoop* loop) +{ + InetAddress serverAddr("127.0.0.1", 1234); // should succeed + TcpClient client(loop, serverAddr, "TcpClient"); + client.connect(); + + CurrentThread::sleepUsec(1000*1000); + // client destructs when connected. +} + +int main(int argc, char* argv[]) +{ + Logger::setLogLevel(Logger::DEBUG); + + EventLoop loop; + loop.runAfter(3.0, std::bind(&EventLoop::quit, &loop)); + Thread thr(std::bind(threadFunc, &loop)); + thr.start(); + loop.loop(); +} diff --git a/muduo/net/tests/TcpClient_reg3.cc b/muduo/net/tests/TcpClient_reg3.cc new file mode 100644 index 0000000..bb1e5ee --- /dev/null +++ b/muduo/net/tests/TcpClient_reg3.cc @@ -0,0 +1,24 @@ +// TcpClient destructs in a different thread. + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/TcpClient.h" + +using namespace muduo; +using namespace muduo::net; + +int main(int argc, char* argv[]) +{ + Logger::setLogLevel(Logger::DEBUG); + + EventLoopThread loopThread; + { + InetAddress serverAddr("127.0.0.1", 1234); // should succeed + TcpClient client(loopThread.startLoop(), serverAddr, "TcpClient"); + client.connect(); + CurrentThread::sleepUsec(500 * 1000); // wait for connect + client.disconnect(); + } + + CurrentThread::sleepUsec(1000 * 1000); +} diff --git a/muduo/net/tests/TimerQueue_unittest.cc b/muduo/net/tests/TimerQueue_unittest.cc new file mode 100644 index 0000000..65cff93 --- /dev/null +++ b/muduo/net/tests/TimerQueue_unittest.cc @@ -0,0 +1,66 @@ +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/base/Thread.h" + +#include <stdio.h> +#include <unistd.h> + +using namespace muduo; +using namespace muduo::net; + +int cnt = 0; +EventLoop* g_loop; + +void printTid() +{ + printf("pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); + printf("now %s\n", Timestamp::now().toString().c_str()); +} + +void print(const char* msg) +{ + printf("msg %s %s\n", Timestamp::now().toString().c_str(), msg); + if (++cnt == 20) + { + g_loop->quit(); + } +} + +void cancel(TimerId timer) +{ + g_loop->cancel(timer); + printf("cancelled at %s\n", Timestamp::now().toString().c_str()); +} + +int main() +{ + printTid(); + sleep(1); + { + EventLoop loop; + g_loop = &loop; + + print("main"); + loop.runAfter(1, std::bind(print, "once1")); + loop.runAfter(1.5, std::bind(print, "once1.5")); + loop.runAfter(2.5, std::bind(print, "once2.5")); + loop.runAfter(3.5, std::bind(print, "once3.5")); + TimerId t45 = loop.runAfter(4.5, std::bind(print, "once4.5")); + loop.runAfter(4.2, std::bind(cancel, t45)); + loop.runAfter(4.8, std::bind(cancel, t45)); + loop.runEvery(2, std::bind(print, "every2")); + TimerId t3 = loop.runEvery(3, std::bind(print, "every3")); + loop.runAfter(9.001, std::bind(cancel, t3)); + + loop.loop(); + print("main loop exits"); + } + sleep(1); + { + EventLoopThread loopThread; + EventLoop* loop = loopThread.startLoop(); + loop->runAfter(2, printTid); + sleep(3); + print("thread loop exits"); + } +} diff --git a/muduo/net/tests/ZlibStream_unittest.cc b/muduo/net/tests/ZlibStream_unittest.cc new file mode 100644 index 0000000..144dda0 --- /dev/null +++ b/muduo/net/tests/ZlibStream_unittest.cc @@ -0,0 +1,91 @@ +#include "muduo/net/ZlibStream.h" + +#include "muduo/base/Logging.h" + +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include <boost/test/unit_test.hpp> + +#include <stdio.h> + +BOOST_AUTO_TEST_CASE(testZlibOutputStream) +{ + muduo::net::Buffer output; + { + muduo::net::ZlibOutputStream stream(&output); + BOOST_CHECK_EQUAL(output.readableBytes(), 0); + } + BOOST_CHECK_EQUAL(output.readableBytes(), 8); +} + +BOOST_AUTO_TEST_CASE(testZlibOutputStream1) +{ + muduo::net::Buffer output; + muduo::net::ZlibOutputStream stream(&output); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_OK); + stream.finish(); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_STREAM_END); +} + +BOOST_AUTO_TEST_CASE(testZlibOutputStream2) +{ + muduo::net::Buffer output; + muduo::net::ZlibOutputStream stream(&output); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_OK); + BOOST_CHECK(stream.write("01234567890123456789012345678901234567890123456789")); + stream.finish(); + // printf("%zd\n", output.readableBytes()); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_STREAM_END); +} + +BOOST_AUTO_TEST_CASE(testZlibOutputStream3) +{ + muduo::net::Buffer output; + muduo::net::ZlibOutputStream stream(&output); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_OK); + for (int i = 0; i < 1024*1024; ++i) + { + BOOST_CHECK(stream.write("01234567890123456789012345678901234567890123456789")); + } + stream.finish(); + // printf("total %zd\n", output.readableBytes()); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_STREAM_END); +} + +BOOST_AUTO_TEST_CASE(testZlibOutputStream4) +{ + muduo::net::Buffer output; + muduo::net::ZlibOutputStream stream(&output); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_OK); + muduo::string input; + for (int i = 0; i < 32768; ++i) + { + input += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-"[rand() % 64]; + } + + for (int i = 0; i < 10; ++i) + { + BOOST_CHECK(stream.write(input)); + } + stream.finish(); + // printf("total %zd\n", output.readableBytes()); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_STREAM_END); +} + +BOOST_AUTO_TEST_CASE(testZlibOutputStream5) +{ + muduo::net::Buffer output; + muduo::net::ZlibOutputStream stream(&output); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_OK); + muduo::string input(1024*1024, '_'); + for (int i = 0; i < 64; ++i) + { + BOOST_CHECK(stream.write(input)); + } + printf("bufsiz %d\n", stream.internalOutputBufferSize()); + LOG_INFO << "total_in " << stream.inputBytes(); + LOG_INFO << "total_out " << stream.outputBytes(); + stream.finish(); + printf("total %zd\n", output.readableBytes()); + BOOST_CHECK_EQUAL(stream.zlibErrorCode(), Z_STREAM_END); +} diff --git a/patches/MacOSX.diff b/patches/MacOSX.diff new file mode 100644 index 0000000..89ce240 --- /dev/null +++ b/patches/MacOSX.diff @@ -0,0 +1,854 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index d18840f..8c9f075 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -11,14 +11,15 @@ endif() + set(CXX_FLAGS + -g + # -DVALGRIND +- # -DMUDUO_STD_STRING +- -DCHECK_PTHREAD_RETURN_VALUE ++ # -DCHECK_PTHREAD_RETURN_VALUE ++ -DMUDUO_STD_STRING + -D_FILE_OFFSET_BITS=64 + -Wall + -Wextra +- -Werror ++ # -Werror + -Wconversion + -Wno-unused-parameter ++ -Wno-sign-conversion + -Wold-style-cast + -Woverloaded-virtual + -Wpointer-arith +@@ -27,16 +28,15 @@ set(CXX_FLAGS + -march=native + # -MMD + # -std=c++0x +- -rdynamic + ) + if(CMAKE_BUILD_BITS EQUAL 32) + list(APPEND CXX_FLAGS "-m32") + endif() + string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") + +-set(CMAKE_CXX_COMPILER "g++") ++set(CMAKE_CXX_COMPILER "clang++") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") +-set(CMAKE_CXX_FLAGS_RELEASE "-O2 -finline-limit=1000 -DNDEBUG") ++set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") + set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) + set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +diff --git a/examples/roundtrip/roundtrip_udp.cc b/examples/roundtrip/roundtrip_udp.cc +index 5f171b8..d612570 100644 +--- a/examples/roundtrip/roundtrip_udp.cc ++++ b/examples/roundtrip/roundtrip_udp.cc +@@ -17,7 +17,12 @@ const size_t frameLen = 2*sizeof(int64_t); + + int createNonblockingUDP() + { ++#ifndef __MACH__ + int sockfd = ::socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_UDP); ++#else ++ int sockfd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ++ sockets::setNonBlockAndCloseOnExec(sockfd); ++#endif + if (sockfd < 0) + { + LOG_SYSFATAL << "::socket"; +diff --git a/examples/socks4a/tcprelay.cc b/examples/socks4a/tcprelay.cc +index a4c6ec9..09a6a3a 100644 +--- a/examples/socks4a/tcprelay.cc ++++ b/examples/socks4a/tcprelay.cc +@@ -1,6 +1,5 @@ + #include "tunnel.h" + +-#include <malloc.h> + #include <stdio.h> + #include <sys/resource.h> + +@@ -43,7 +42,6 @@ void onServerMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + + void memstat() + { +- malloc_stats(); + } + + int main(int argc, char* argv[]) +diff --git a/muduo/base/CMakeLists.txt b/muduo/base/CMakeLists.txt +index 6e250d4..a76404f 100644 +--- a/muduo/base/CMakeLists.txt ++++ b/muduo/base/CMakeLists.txt +@@ -16,10 +16,10 @@ set(base_SRCS + ) + + add_library(muduo_base ${base_SRCS}) +-target_link_libraries(muduo_base pthread rt) ++target_link_libraries(muduo_base pthread) + + add_library(muduo_base_cpp11 ${base_SRCS}) +-target_link_libraries(muduo_base_cpp11 pthread rt) ++target_link_libraries(muduo_base_cpp11 pthread) + set_target_properties(muduo_base_cpp11 PROPERTIES COMPILE_FLAGS "-std=c++0x") + + install(TARGETS muduo_base DESTINATION lib) +diff --git a/muduo/base/Condition.cc b/muduo/base/Condition.cc +index f10ace3..73a1715 100644 +--- a/muduo/base/Condition.cc ++++ b/muduo/base/Condition.cc +@@ -6,13 +6,21 @@ + #include <muduo/base/Condition.h> + + #include <errno.h> ++#include <sys/time.h> + + // returns true if time out, false otherwise. + bool muduo::Condition::waitForSeconds(int seconds) + { + struct timespec abstime; ++#ifdef CLOCK_REALTIME + // FIXME: use CLOCK_MONOTONIC or CLOCK_MONOTONIC_RAW to prevent time rewind. + clock_gettime(CLOCK_REALTIME, &abstime); ++#else // Mac OS X ++ struct timeval tv; ++ gettimeofday(&tv, NULL); ++ abstime.tv_sec = tv.tv_sec; ++ abstime.tv_nsec = tv.tv_usec * 1000; ++#endif + abstime.tv_sec += seconds; + MutexLock::UnassignGuard ug(mutex_); + return ETIMEDOUT == pthread_cond_timedwait(&pcond_, mutex_.getPthreadMutex(), &abstime); +diff --git a/muduo/base/FileUtil.cc b/muduo/base/FileUtil.cc +index 999b0e5..ad9da8a 100644 +--- a/muduo/base/FileUtil.cc ++++ b/muduo/base/FileUtil.cc +@@ -64,8 +64,12 @@ void FileUtil::AppendFile::flush() + + size_t FileUtil::AppendFile::write(const char* logline, size_t len) + { ++#ifdef fwrite_unlocked + // #undef fwrite_unlocked + return ::fwrite_unlocked(logline, 1, len, fp_); ++#else ++ return ::fwrite(logline, 1, len, fp_); ++#endif + } + + FileUtil::ReadSmallFile::ReadSmallFile(StringArg filename) +diff --git a/muduo/base/Logging.cc b/muduo/base/Logging.cc +index 341d627..9376007 100644 +--- a/muduo/base/Logging.cc ++++ b/muduo/base/Logging.cc +@@ -36,7 +36,12 @@ __thread time_t t_lastSecond; + + const char* strerror_tl(int savedErrno) + { ++#ifndef __MACH__ + return strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf); ++#else ++ strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf); ++ return t_errnobuf; ++#endif + } + + Logger::LogLevel initLogLevel() +diff --git a/muduo/base/Thread.cc b/muduo/base/Thread.cc +index 9d64780..cddcac1 100644 +--- a/muduo/base/Thread.cc ++++ b/muduo/base/Thread.cc +@@ -15,10 +15,12 @@ + #include <errno.h> + #include <stdio.h> + #include <unistd.h> +-#include <sys/prctl.h> + #include <sys/syscall.h> + #include <sys/types.h> ++#ifndef __MACH__ ++#include <sys/prctl.h> + #include <linux/unistd.h> ++#endif + + namespace muduo + { +@@ -35,10 +37,17 @@ namespace CurrentThread + namespace detail + { + ++#ifdef __MACH__ ++pid_t gettid() ++{ ++ return pthread_mach_thread_np(pthread_self()); ++} ++#else + pid_t gettid() + { + return static_cast<pid_t>(::syscall(SYS_gettid)); + } ++#endif + + void afterFork() + { +@@ -88,7 +97,9 @@ struct ThreadData + } + + muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str(); ++#ifndef __MACH__ + ::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName); ++#endif + try + { + func_(); +diff --git a/muduo/base/TimeZone.cc b/muduo/base/TimeZone.cc +index 37959d9..f95beb3 100644 +--- a/muduo/base/TimeZone.cc ++++ b/muduo/base/TimeZone.cc +@@ -8,7 +8,7 @@ + #include <vector> + + //#define _BSD_SOURCE +-#include <endian.h> ++#include <muduo/net/Endian.h> + + #include <stdint.h> + #include <stdio.h> +@@ -285,7 +285,7 @@ struct tm TimeZone::toLocalTime(time_t seconds) const + ::gmtime_r(&localSeconds, &localTime); // FIXME: fromUtcTime + localTime.tm_isdst = local->isDst; + localTime.tm_gmtoff = local->gmtOffset; +- localTime.tm_zone = &data.abbreviation[local->arrbIdx]; ++ localTime.tm_zone = const_cast<char*>(&data.abbreviation[local->arrbIdx]); + } + + return localTime; +diff --git a/muduo/base/tests/AsyncLogging_test.cc b/muduo/base/tests/AsyncLogging_test.cc +index bd9fe59..e510fd4 100644 +--- a/muduo/base/tests/AsyncLogging_test.cc ++++ b/muduo/base/tests/AsyncLogging_test.cc +@@ -4,6 +4,9 @@ + + #include <stdio.h> + #include <sys/resource.h> ++#ifdef __MACH__ ++#include <libgen.h> // basename() ++#endif + + int kRollSize = 500*1000*1000; + +diff --git a/muduo/base/tests/BlockingQueue_test.cc b/muduo/base/tests/BlockingQueue_test.cc +index c392773..6977578 100644 +--- a/muduo/base/tests/BlockingQueue_test.cc ++++ b/muduo/base/tests/BlockingQueue_test.cc +@@ -80,9 +80,6 @@ class Test + void testMove() + { + #ifdef __GXX_EXPERIMENTAL_CXX0X__ +- +-// std::unique_ptr requires gcc 4.4 or later +-#if __GNUC_PREREQ (4,4) + muduo::BlockingQueue<std::unique_ptr<int>> queue; + queue.put(std::unique_ptr<int>(new int(42))); + std::unique_ptr<int> x = queue.take(); +@@ -92,8 +89,6 @@ void testMove() + std::unique_ptr<int> y = queue.take(); + printf("took %d\n", *y); + #endif +- +-#endif + } + + int main() +diff --git a/muduo/base/tests/GzipFile_test.cc b/muduo/base/tests/GzipFile_test.cc +index 6dc0d4d..b051ca8 100644 +--- a/muduo/base/tests/GzipFile_test.cc ++++ b/muduo/base/tests/GzipFile_test.cc +@@ -2,6 +2,8 @@ + + #include <muduo/base/Logging.h> + ++#include <errno.h> ++ + int main() + { + const char* filename = "/tmp/gzipfile_test.gz"; +diff --git a/muduo/base/tests/LogFile_test.cc b/muduo/base/tests/LogFile_test.cc +index e77d68d..d27d65e 100644 +--- a/muduo/base/tests/LogFile_test.cc ++++ b/muduo/base/tests/LogFile_test.cc +@@ -1,5 +1,8 @@ + #include <muduo/base/LogFile.h> + #include <muduo/base/Logging.h> ++#ifdef __MACH__ ++#include <libgen.h> // basename() ++#endif + + boost::scoped_ptr<muduo::LogFile> g_logFile; + +diff --git a/muduo/net/CMakeLists.txt b/muduo/net/CMakeLists.txt +index 0127c48..9ea16ed 100644 +--- a/muduo/net/CMakeLists.txt ++++ b/muduo/net/CMakeLists.txt +@@ -16,7 +16,6 @@ set(net_SRCS + InetAddress.cc + Poller.cc + poller/DefaultPoller.cc +- poller/EPollPoller.cc + poller/PollPoller.cc + Socket.cc + SocketsOps.cc +diff --git a/muduo/net/Channel.cc b/muduo/net/Channel.cc +index f5e6624..62fbd6f 100644 +--- a/muduo/net/Channel.cc ++++ b/muduo/net/Channel.cc +@@ -102,6 +102,9 @@ void Channel::handleEventWithGuard(Timestamp receiveTime) + { + if (errorCallback_) errorCallback_(); + } ++#ifndef POLLRDHUP ++ const int POLLRDHUP = 0; ++#endif + if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) + { + if (readCallback_) readCallback_(receiveTime); +@@ -135,8 +138,10 @@ string Channel::eventsToString(int fd, int ev) + oss << "OUT "; + if (ev & POLLHUP) + oss << "HUP "; ++#ifdef POLLRDHUP + if (ev & POLLRDHUP) + oss << "RDHUP "; ++#endif + if (ev & POLLERR) + oss << "ERR "; + if (ev & POLLNVAL) +diff --git a/muduo/net/Endian.h b/muduo/net/Endian.h +index b277503..851e449 100644 +--- a/muduo/net/Endian.h ++++ b/muduo/net/Endian.h +@@ -12,7 +12,28 @@ + #define MUDUO_NET_ENDIAN_H + + #include <stdint.h> ++ ++#ifdef __MACH__ ++#include <libkern/OSByteOrder.h> ++ ++#define htobe16(x) OSSwapHostToBigInt16(x) ++#define htole16(x) OSSwapHostToLittleInt16(x) ++#define be16toh(x) OSSwapBigToHostInt16(x) ++#define le16toh(x) OSSwapLittleToHostInt16(x) ++ ++#define htobe32(x) OSSwapHostToBigInt32(x) ++#define htole32(x) OSSwapHostToLittleInt32(x) ++#define be32toh(x) OSSwapBigToHostInt32(x) ++#define le32toh(x) OSSwapLittleToHostInt32(x) ++ ++#define htobe64(x) OSSwapHostToBigInt64(x) ++#define htole64(x) OSSwapHostToLittleInt64(x) ++#define be64toh(x) OSSwapBigToHostInt64(x) ++#define le64toh(x) OSSwapLittleToHostInt64(x) ++#else + #include <endian.h> ++#endif ++ + + namespace muduo + { +@@ -60,8 +81,8 @@ inline uint16_t networkToHost16(uint16_t net16) + #if defined(__clang__) || __GNUC_MINOR__ >= 6 + #pragma GCC diagnostic pop + #else +-#pragma GCC diagnostic warning "-Wconversion" +-#pragma GCC diagnostic warning "-Wold-style-cast" ++//#pragma GCC diagnostic error "-Wconversion" ++//#pragma GCC diagnostic error "-Wold-style-cast" + #endif + + +diff --git a/muduo/net/EventLoop.cc b/muduo/net/EventLoop.cc +index 7346838..19fde05 100644 +--- a/muduo/net/EventLoop.cc ++++ b/muduo/net/EventLoop.cc +@@ -18,7 +18,8 @@ + #include <boost/bind.hpp> + + #include <signal.h> +-#include <sys/eventfd.h> ++#include <sys/types.h> ++#include <sys/socket.h> + + using namespace muduo; + using namespace muduo::net; +@@ -29,18 +30,6 @@ __thread EventLoop* t_loopInThisThread = 0; + + const int kPollTimeMs = 10000; + +-int createEventfd() +-{ +- int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); +- if (evtfd < 0) +- { +- LOG_SYSERR << "Failed in eventfd"; +- abort(); +- } +- return evtfd; +-} +- +-#pragma GCC diagnostic ignored "-Wold-style-cast" + class IgnoreSigPipe + { + public: +@@ -50,7 +39,6 @@ class IgnoreSigPipe + // LOG_TRACE << "Ignore SIGPIPE"; + } + }; +-#pragma GCC diagnostic error "-Wold-style-cast" + + IgnoreSigPipe initObj; + } +@@ -69,11 +57,15 @@ EventLoop::EventLoop() + threadId_(CurrentThread::tid()), + poller_(Poller::newDefaultPoller(this)), + timerQueue_(new TimerQueue(this)), +- wakeupFd_(createEventfd()), +- wakeupChannel_(new Channel(this, wakeupFd_)), + currentActiveChannel_(NULL) + { + LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_; ++ if (::socketpair(AF_UNIX, SOCK_STREAM, 0, wakeupFd_) < 0) ++ { ++ LOG_SYSFATAL << "Failed in socketpair"; ++ } ++ wakeupChannel_.reset(new Channel(this, wakeupFd_[0])); ++ + if (t_loopInThisThread) + { + LOG_FATAL << "Another EventLoop " << t_loopInThisThread +@@ -95,7 +87,8 @@ EventLoop::~EventLoop() + << " destructs in thread " << CurrentThread::tid(); + wakeupChannel_->disableAll(); + wakeupChannel_->remove(); +- ::close(wakeupFd_); ++ ::close(wakeupFd_[0]); ++ ::close(wakeupFd_[1]); + t_loopInThisThread = NULL; + } + +@@ -110,12 +103,13 @@ void EventLoop::loop() + while (!quit_) + { + activeChannels_.clear(); +- pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); ++ pollReturnTime_ = poller_->poll(timerQueue_->getTimeout(), &activeChannels_); + ++iteration_; + if (Logger::logLevel() <= Logger::TRACE) + { + printActiveChannels(); + } ++ timerQueue_->processTimers(); + // TODO sort channel by priority + eventHandling_ = true; + for (ChannelList::iterator it = activeChannels_.begin(); +@@ -273,7 +267,7 @@ void EventLoop::abortNotInLoopThread() + void EventLoop::wakeup() + { + uint64_t one = 1; +- ssize_t n = sockets::write(wakeupFd_, &one, sizeof one); ++ ssize_t n = sockets::write(wakeupFd_[1], &one, sizeof one); + if (n != sizeof one) + { + LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8"; +@@ -283,7 +277,7 @@ void EventLoop::wakeup() + void EventLoop::handleRead() + { + uint64_t one = 1; +- ssize_t n = sockets::read(wakeupFd_, &one, sizeof one); ++ ssize_t n = sockets::read(wakeupFd_[0], &one, sizeof one); + if (n != sizeof one) + { + LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8"; +diff --git a/muduo/net/EventLoop.h b/muduo/net/EventLoop.h +index 5741961..b03dd67 100644 +--- a/muduo/net/EventLoop.h ++++ b/muduo/net/EventLoop.h +@@ -156,7 +156,7 @@ class EventLoop : boost::noncopyable + Timestamp pollReturnTime_; + boost::scoped_ptr<Poller> poller_; + boost::scoped_ptr<TimerQueue> timerQueue_; +- int wakeupFd_; ++ int wakeupFd_[2]; + // unlike in TimerQueue, which is an internal class, + // we don't expose Channel to client. + boost::scoped_ptr<Channel> wakeupChannel_; +diff --git a/muduo/net/InetAddress.cc b/muduo/net/InetAddress.cc +index 394870a..05bb5de 100644 +--- a/muduo/net/InetAddress.cc ++++ b/muduo/net/InetAddress.cc +@@ -19,10 +19,10 @@ + #include <boost/static_assert.hpp> + + // INADDR_ANY use (type)value casting. +-#pragma GCC diagnostic ignored "-Wold-style-cast" ++// #pragma GCC diagnostic ignored "-Wold-style-cast" + static const in_addr_t kInaddrAny = INADDR_ANY; + static const in_addr_t kInaddrLoopback = INADDR_LOOPBACK; +-#pragma GCC diagnostic error "-Wold-style-cast" ++// #pragma GCC diagnostic error "-Wold-style-cast" + + // /* Structure describing an Internet socket address. */ + // struct sockaddr_in { +@@ -83,10 +83,15 @@ bool InetAddress::resolve(StringArg hostname, InetAddress* out) + assert(out != NULL); + struct hostent hent; + struct hostent* he = NULL; +- int herrno = 0; + bzero(&hent, sizeof(hent)); + ++#ifndef __MACH__ ++ int herrno = 0; + int ret = gethostbyname_r(hostname.c_str(), &hent, t_resolveBuffer, sizeof t_resolveBuffer, &he, &herrno); ++#else ++ he = gethostbyname(hostname.c_str()); ++ int ret = 0; ++#endif + if (ret == 0 && he != NULL) + { + assert(he->h_addrtype == AF_INET && he->h_length == sizeof(uint32_t)); +diff --git a/muduo/net/Socket.cc b/muduo/net/Socket.cc +index 111d87d..4e0efa7 100644 +--- a/muduo/net/Socket.cc ++++ b/muduo/net/Socket.cc +@@ -27,13 +27,18 @@ Socket::~Socket() + + bool Socket::getTcpInfo(struct tcp_info* tcpi) const + { ++#ifndef __MACH__ + socklen_t len = sizeof(*tcpi); + bzero(tcpi, len); + return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0; ++#else ++ return false; ++#endif + } + + bool Socket::getTcpInfoString(char* buf, int len) const + { ++#ifndef __MACH__ + struct tcp_info tcpi; + bool ok = getTcpInfo(&tcpi); + if (ok) +@@ -56,6 +61,9 @@ bool Socket::getTcpInfoString(char* buf, int len) const + tcpi.tcpi_total_retrans); // Total retransmits for entire connection + } + return ok; ++#else ++ return false; ++#endif + } + + void Socket::bindAddress(const InetAddress& addr) +diff --git a/muduo/net/SocketsOps.cc b/muduo/net/SocketsOps.cc +index 188c3cb..1e5f268 100644 +--- a/muduo/net/SocketsOps.cc ++++ b/muduo/net/SocketsOps.cc +@@ -17,18 +17,26 @@ + #include <stdio.h> // snprintf + #include <strings.h> // bzero + #include <sys/socket.h> ++#ifdef __MACH__ ++#include <sys/uio.h> // readv ++#endif + #include <unistd.h> + + using namespace muduo; + using namespace muduo::net; + +-namespace ++namespace muduo + { + + typedef struct sockaddr SA; + + + #if VALGRIND || defined (NO_ACCEPT4) ++namespace net ++{ ++namespace sockets ++{ ++ + void setNonBlockAndCloseOnExec(int sockfd) + { + // non-block +@@ -45,6 +53,9 @@ void setNonBlockAndCloseOnExec(int sockfd) + + (void)ret; + } ++ ++} ++} + #endif + + } +@@ -71,7 +82,6 @@ struct sockaddr_in* sockets::sockaddr_in_cast(struct sockaddr* addr) + + int sockets::createNonblockingOrDie() + { +-#if VALGRIND + int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) + { +@@ -79,13 +89,6 @@ int sockets::createNonblockingOrDie() + } + + setNonBlockAndCloseOnExec(sockfd); +-#else +- int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); +- if (sockfd < 0) +- { +- LOG_SYSFATAL << "sockets::createNonblockingOrDie"; +- } +-#endif + return sockfd; + } + +@@ -110,13 +113,8 @@ void sockets::listenOrDie(int sockfd) + int sockets::accept(int sockfd, struct sockaddr_in* addr) + { + socklen_t addrlen = static_cast<socklen_t>(sizeof *addr); +-#if VALGRIND || defined (NO_ACCEPT4) + int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen); + setNonBlockAndCloseOnExec(connfd); +-#else +- int connfd = ::accept4(sockfd, sockaddr_cast(addr), +- &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); +-#endif + if (connfd < 0) + { + int savedErrno = errno; +diff --git a/muduo/net/SocketsOps.h b/muduo/net/SocketsOps.h +index a07b1fe..efc779a 100644 +--- a/muduo/net/SocketsOps.h ++++ b/muduo/net/SocketsOps.h +@@ -24,6 +24,9 @@ namespace sockets + /// Creates a non-blocking socket file descriptor, + /// abort if any error. + int createNonblockingOrDie(); ++#ifdef __MACH__ ++void setNonBlockAndCloseOnExec(int sockfd); ++#endif + + int connect(int sockfd, const struct sockaddr_in& addr); + void bindOrDie(int sockfd, const struct sockaddr_in& addr); +diff --git a/muduo/net/TimerQueue.cc b/muduo/net/TimerQueue.cc +index 0f199e5..7f4813a 100644 +--- a/muduo/net/TimerQueue.cc ++++ b/muduo/net/TimerQueue.cc +@@ -19,8 +19,6 @@ + + #include <boost/bind.hpp> + +-#include <sys/timerfd.h> +- + namespace muduo + { + namespace net +@@ -28,57 +26,15 @@ namespace net + namespace detail + { + +-int createTimerfd() +-{ +- int timerfd = ::timerfd_create(CLOCK_MONOTONIC, +- TFD_NONBLOCK | TFD_CLOEXEC); +- if (timerfd < 0) +- { +- LOG_SYSFATAL << "Failed in timerfd_create"; +- } +- return timerfd; +-} +- +-struct timespec howMuchTimeFromNow(Timestamp when) ++int howMuchTimeFromNow(Timestamp when) + { + int64_t microseconds = when.microSecondsSinceEpoch() + - Timestamp::now().microSecondsSinceEpoch(); +- if (microseconds < 100) +- { +- microseconds = 100; +- } +- struct timespec ts; +- ts.tv_sec = static_cast<time_t>( +- microseconds / Timestamp::kMicroSecondsPerSecond); +- ts.tv_nsec = static_cast<long>( +- (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); +- return ts; +-} +- +-void readTimerfd(int timerfd, Timestamp now) +-{ +- uint64_t howmany; +- ssize_t n = ::read(timerfd, &howmany, sizeof howmany); +- LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString(); +- if (n != sizeof howmany) +- { +- LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8"; +- } +-} +- +-void resetTimerfd(int timerfd, Timestamp expiration) +-{ +- // wake up loop by timerfd_settime() +- struct itimerspec newValue; +- struct itimerspec oldValue; +- bzero(&newValue, sizeof newValue); +- bzero(&oldValue, sizeof oldValue); +- newValue.it_value = howMuchTimeFromNow(expiration); +- int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); +- if (ret) ++ if (microseconds < 1000) + { +- LOG_SYSERR << "timerfd_settime()"; ++ microseconds = 1000; + } ++ return static_cast<int>(microseconds / 1000); + } + + } +@@ -91,22 +47,13 @@ using namespace muduo::net::detail; + + TimerQueue::TimerQueue(EventLoop* loop) + : loop_(loop), +- timerfd_(createTimerfd()), +- timerfdChannel_(loop, timerfd_), + timers_(), + callingExpiredTimers_(false) + { +- timerfdChannel_.setReadCallback( +- boost::bind(&TimerQueue::handleRead, this)); +- // we are always reading the timerfd, we disarm it with timerfd_settime. +- timerfdChannel_.enableReading(); + } + + TimerQueue::~TimerQueue() + { +- timerfdChannel_.disableAll(); +- timerfdChannel_.remove(); +- ::close(timerfd_); + // do not remove channel, since we're in EventLoop::dtor(); + for (TimerList::iterator it = timers_.begin(); + it != timers_.end(); ++it) +@@ -146,11 +93,19 @@ void TimerQueue::cancel(TimerId timerId) + void TimerQueue::addTimerInLoop(Timer* timer) + { + loop_->assertInLoopThread(); +- bool earliestChanged = insert(timer); ++ insert(timer); ++} + +- if (earliestChanged) ++int TimerQueue::getTimeout() const ++{ ++ loop_->assertInLoopThread(); ++ if (timers_.empty()) ++ { ++ return 10000; ++ } ++ else + { +- resetTimerfd(timerfd_, timer->expiration()); ++ return howMuchTimeFromNow(timers_.begin()->second->expiration()); + } + } + +@@ -174,11 +129,10 @@ void TimerQueue::cancelInLoop(TimerId timerId) + assert(timers_.size() == activeTimers_.size()); + } + +-void TimerQueue::handleRead() ++void TimerQueue::processTimers() + { + loop_->assertInLoopThread(); + Timestamp now(Timestamp::now()); +- readTimerfd(timerfd_, now); + + std::vector<Entry> expired = getExpired(now); + +@@ -242,11 +196,6 @@ void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now) + { + nextExpire = timers_.begin()->second->expiration(); + } +- +- if (nextExpire.valid()) +- { +- resetTimerfd(timerfd_, nextExpire); +- } + } + + bool TimerQueue::insert(Timer* timer) +diff --git a/muduo/net/TimerQueue.h b/muduo/net/TimerQueue.h +index 0cfb02f..d882b71 100644 +--- a/muduo/net/TimerQueue.h ++++ b/muduo/net/TimerQueue.h +@@ -56,6 +56,9 @@ class TimerQueue : boost::noncopyable + + void cancel(TimerId timerId); + ++ int getTimeout() const; ++ void processTimers(); ++ + private: + + // FIXME: use unique_ptr<Timer> instead of raw pointers. +@@ -66,8 +69,6 @@ class TimerQueue : boost::noncopyable + + void addTimerInLoop(Timer* timer); + void cancelInLoop(TimerId timerId); +- // called when timerfd alarms +- void handleRead(); + // move out all expired timers + std::vector<Entry> getExpired(Timestamp now); + void reset(const std::vector<Entry>& expired, Timestamp now); +@@ -75,9 +76,6 @@ class TimerQueue : boost::noncopyable + bool insert(Timer* timer); + + EventLoop* loop_; +- const int timerfd_; +- Channel timerfdChannel_; +- // Timer list sorted by expiration + TimerList timers_; + + // for cancel() +diff --git a/muduo/net/poller/DefaultPoller.cc b/muduo/net/poller/DefaultPoller.cc +index f42f5a4..a6a3133 100644 +--- a/muduo/net/poller/DefaultPoller.cc ++++ b/muduo/net/poller/DefaultPoller.cc +@@ -16,6 +16,9 @@ using namespace muduo::net; + + Poller* Poller::newDefaultPoller(EventLoop* loop) + { ++#ifdef __MACH__ ++ return new PollPoller(loop); ++#else + if (::getenv("MUDUO_USE_POLL")) + { + return new PollPoller(loop); +@@ -24,4 +27,5 @@ Poller* Poller::newDefaultPoller(EventLoop* loop) + { + return new EPollPoller(loop); + } ++#endif + } + +diff --git a/examples/protobuf/rpcbalancer/balancer_raw.cc b/examples/protobuf/rpcbalancer/balancer_raw.cc +index 9c2e1db..c30b19d 100644 +--- a/examples/protobuf/rpcbalancer/balancer_raw.cc ++++ b/examples/protobuf/rpcbalancer/balancer_raw.cc +@@ -12,7 +12,7 @@ + #include <boost/bind.hpp> + #include <boost/ptr_container/ptr_vector.hpp> + +-#include <endian.h> ++#include <machine/endian.h> + #include <stdio.h> + + using namespace muduo; diff --git a/patches/armlinux.diff b/patches/armlinux.diff new file mode 100644 index 0000000..9a396bf --- /dev/null +++ b/patches/armlinux.diff @@ -0,0 +1,137 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2c8880a..af0d174 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -21,7 +21,7 @@ set(CXX_FLAGS + -Wpointer-arith + -Wshadow + -Wwrite-strings +- -march=native ++ -march=armv4 + # -MMD + # -std=c++0x + -rdynamic +@@ -31,7 +31,7 @@ if(CMAKE_BUILD_BITS EQUAL 32) + endif() + string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") + +-set(CMAKE_CXX_COMPILER "g++") ++set(CMAKE_CXX_COMPILER "arm-g++") + #set(CMAKE_CXX_COMPILER "icpc") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") + set(CMAKE_CXX_FLAGS_RELEASE "-O2 -finline-limit=1000 -DNDEBUG") +diff --git a/muduo/base/Atomic.h b/muduo/base/Atomic.h +index 3478da0..cc1dd45 100644 +--- a/muduo/base/Atomic.h ++++ b/muduo/base/Atomic.h +@@ -8,6 +8,7 @@ + + #include <boost/noncopyable.hpp> + #include <stdint.h> ++#include <muduo/base/Mutex.h> + + namespace muduo + { +@@ -83,10 +84,88 @@ class AtomicIntegerT : boost::noncopyable + private: + volatile T value_; + }; ++ ++template<typename T> ++class AtomicIntegerLock : boost::noncopyable ++{ ++ public: ++ AtomicIntegerLock() ++ : value_(0) ++ { ++ } ++ ++ // uncomment if you need copying and assignment ++ // ++ // AtomicIntegerT(const AtomicIntegerT& that) ++ // : value_(that.get()) ++ // {} ++ // ++ // AtomicIntegerT& operator=(const AtomicIntegerT& that) ++ // { ++ // getAndSet(that.get()); ++ // return *this; ++ // } ++ ++ T get() ++ { ++ MutexLockGuard lock(mutex_); ++ return value_; ++ } ++ ++ T getAndAdd(T x) ++ { ++ MutexLockGuard lock(mutex_); ++ T old = value_; ++ value_ += x; ++ return old; ++ } ++ ++ T addAndGet(T x) ++ { ++ return getAndAdd(x) + x; ++ } ++ ++ T incrementAndGet() ++ { ++ return addAndGet(1); ++ } ++ ++ T decrementAndGet() ++ { ++ return addAndGet(-1); ++ } ++ ++ void add(T x) ++ { ++ getAndAdd(x); ++ } ++ ++ void increment() ++ { ++ incrementAndGet(); ++ } ++ ++ void decrement() ++ { ++ decrementAndGet(); ++ } ++ ++ T getAndSet(T newValue) ++ { ++ MutexLockGuard lock(mutex_); ++ T old = value_; ++ value_ = newValue; ++ return old; ++ } ++ ++ private: ++ volatile T value_; ++ MutexLock mutex_; ++}; + } + + typedef detail::AtomicIntegerT<int32_t> AtomicInt32; +-typedef detail::AtomicIntegerT<int64_t> AtomicInt64; ++typedef detail::AtomicIntegerLock<int64_t> AtomicInt64; + } + + #endif // MUDUO_BASE_ATOMIC_H +diff --git a/muduo/base/tests/CMakeLists.txt b/muduo/base/tests/CMakeLists.txt +index 2c3f1c4..73d90fd 100644 +--- a/muduo/base/tests/CMakeLists.txt ++++ b/muduo/base/tests/CMakeLists.txt +@@ -2,7 +2,7 @@ + target_link_libraries(asynclogging_test muduo_base) + + add_executable(atomic_unittest Atomic_unittest.cc) +-# target_link_libraries(atomic_unittest muduo_base) ++target_link_libraries(atomic_unittest muduo_base) + + add_executable(blockingqueue_test BlockingQueue_test.cc) + target_link_libraries(blockingqueue_test muduo_base) diff --git a/patches/backport.diff b/patches/backport.diff new file mode 100644 index 0000000..30f4e5f --- /dev/null +++ b/patches/backport.diff @@ -0,0 +1,551 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -9,19 +9,19 @@ endif() + set(CXX_FLAGS + -g + # -DVALGRIND +- # -DMUDUO_STD_STRING ++ -DMUDUO_STD_STRING + -D_FILE_OFFSET_BITS=64 + -Wall + -Wextra + -Werror + -Wconversion + -Wno-unused-parameter +- -Wold-style-cast ++ # -Wold-style-cast + -Woverloaded-virtual + -Wpointer-arith +- -Wshadow ++ # -Wshadow + -Wwrite-strings +- -march=native ++ -march=nocona + # -MMD + # -std=c++0x + -rdynamic +@@ -31,7 +31,7 @@ if(CMAKE_BUILD_BITS EQUAL 32) + endif() + string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") + +-set(CMAKE_CXX_COMPILER "g++") ++set(CMAKE_CXX_COMPILER "g++4") + #set(CMAKE_CXX_COMPILER "icpc") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") + set(CMAKE_CXX_FLAGS_RELEASE "-O2 -finline-limit=1000 -DNDEBUG") +diff --git a/examples/idleconnection/CMakeLists.txt b/examples/idleconnection/CMakeLists.txt +--- a/examples/idleconnection/CMakeLists.txt ++++ b/examples/idleconnection/CMakeLists.txt +@@ -1,4 +1,4 @@ +-add_executable(idleconnection_echo echo.cc main.cc) ++add_executable(idleconnection_echo EXCLUDE_FROM_ALL echo.cc main.cc) + target_link_libraries(idleconnection_echo muduo_net) + + add_executable(idleconnection_echo2 sortedlist.cc) +diff --git a/muduo/base/Timestamp.cc b/muduo/base/Timestamp.cc +--- a/muduo/base/Timestamp.cc ++++ b/muduo/base/Timestamp.cc +@@ -4,7 +4,7 @@ + #include <stdio.h> + #define __STDC_FORMAT_MACROS + #include <inttypes.h> +-#undef __STDC_FORMAT_MACROS ++//#undef __STDC_FORMAT_MACROS + + #include <boost/static_assert.hpp> + +diff --git a/muduo/base/tests/CMakeLists.txt b/muduo/base/tests/CMakeLists.txt +--- a/muduo/base/tests/CMakeLists.txt ++++ b/muduo/base/tests/CMakeLists.txt +@@ -10,7 +10,7 @@ target_link_libraries(blockingqueue_test muduo_base) + add_executable(blockingqueue_bench BlockingQueue_bench.cc) + target_link_libraries(blockingqueue_bench muduo_base) + +-add_executable(boundedblockingqueue_test BoundedBlockingQueue_test.cc) ++add_executable(boundedblockingqueue_test EXCLUDE_FROM_ALL BoundedBlockingQueue_test.cc) + target_link_libraries(boundedblockingqueue_test muduo_base) + + add_executable(date_unittest Date_unittest.cc) +diff --git a/muduo/net/Channel.cc b/muduo/net/Channel.cc +--- a/muduo/net/Channel.cc ++++ b/muduo/net/Channel.cc +@@ -21,6 +21,10 @@ const int Channel::kNoneEvent = 0; + const int Channel::kReadEvent = POLLIN | POLLPRI; + const int Channel::kWriteEvent = POLLOUT; + ++#ifndef POLLRDHUP ++#define POLLRDHUP 0x2000 ++#endif ++ + Channel::Channel(EventLoop* loop, int fd__) + : loop_(loop), + fd_(fd__), +diff --git a/muduo/net/Endian.h b/muduo/net/Endian.h +--- a/muduo/net/Endian.h ++++ b/muduo/net/Endian.h +@@ -13,6 +13,7 @@ + + #include <stdint.h> + #include <endian.h> ++#include <byteswap.h> + + namespace muduo + { +@@ -23,48 +24,38 @@ namespace sockets + + // the inline assembler code makes type blur, + // so we disable warnings for a while. +-#if __GNUC_MINOR__ >= 6 +-#pragma GCC diagnostic push +-#endif +-#pragma GCC diagnostic ignored "-Wconversion" +-#pragma GCC diagnostic ignored "-Wold-style-cast" ++#if __BYTE_ORDER == __LITTLE_ENDIAN + inline uint64_t hostToNetwork64(uint64_t host64) + { +- return htobe64(host64); ++ return bswap_64(host64); + } + + inline uint32_t hostToNetwork32(uint32_t host32) + { +- return htobe32(host32); ++ return bswap_32(host32); + } + + inline uint16_t hostToNetwork16(uint16_t host16) + { +- return htobe16(host16); ++ return bswap_16(host16); + } + + inline uint64_t networkToHost64(uint64_t net64) + { +- return be64toh(net64); ++ return bswap_64(net64); + } + + inline uint32_t networkToHost32(uint32_t net32) + { +- return be32toh(net32); ++ return bswap_32(net32); + } + + inline uint16_t networkToHost16(uint16_t net16) + { +- return be16toh(net16); ++ return bswap_16(net16); + } +-#if __GNUC_MINOR__ >= 6 +-#pragma GCC diagnostic pop +-#else +-#pragma GCC diagnostic error "-Wconversion" +-#pragma GCC diagnostic error "-Wold-style-cast" + #endif + +- + } + } + } +diff --git a/muduo/net/EventLoop.cc b/muduo/net/EventLoop.cc +--- a/muduo/net/EventLoop.cc ++++ b/muduo/net/EventLoop.cc +@@ -18,7 +18,8 @@ + #include <boost/bind.hpp> + + #include <signal.h> +-#include <sys/eventfd.h> ++#include <sys/types.h> ++#include <sys/socket.h> + + using namespace muduo; + using namespace muduo::net; +@@ -29,18 +30,6 @@ __thread EventLoop* t_loopInThisThread = 0; + + const int kPollTimeMs = 10000; + +-int createEventfd() +-{ +- int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); +- if (evtfd < 0) +- { +- LOG_SYSERR << "Failed in eventfd"; +- abort(); +- } +- return evtfd; +-} +- +-#pragma GCC diagnostic ignored "-Wold-style-cast" + class IgnoreSigPipe + { + public: +@@ -50,7 +39,6 @@ class IgnoreSigPipe + LOG_TRACE << "Ignore SIGPIPE"; + } + }; +-#pragma GCC diagnostic error "-Wold-style-cast" + + IgnoreSigPipe initObj; + } +@@ -69,11 +57,17 @@ EventLoop::EventLoop() + threadId_(CurrentThread::tid()), + poller_(Poller::newDefaultPoller(this)), + timerQueue_(new TimerQueue(this)), +- wakeupFd_(createEventfd()), +- wakeupChannel_(new Channel(this, wakeupFd_)), + currentActiveChannel_(NULL) + { + LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_; ++ if (::socketpair(AF_UNIX, SOCK_STREAM, 0, wakeupFd_) < 0) ++ { ++ LOG_SYSFATAL << "Failed in socketpair"; ++ } ++ sockets::setNonBlockAndCloseOnExec(wakeupFd_[0]); ++ sockets::setNonBlockAndCloseOnExec(wakeupFd_[1]); ++ wakeupChannel_.reset(new Channel(this, wakeupFd_[0])); ++ + if (t_loopInThisThread) + { + LOG_FATAL << "Another EventLoop " << t_loopInThisThread +@@ -93,7 +87,8 @@ EventLoop::~EventLoop() + { + LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_ + << " destructs in thread " << CurrentThread::tid(); +- ::close(wakeupFd_); ++ ::close(wakeupFd_[0]); ++ ::close(wakeupFd_[1]); + t_loopInThisThread = NULL; + } + +@@ -108,12 +103,13 @@ void EventLoop::loop() + while (!quit_) + { + activeChannels_.clear(); +- pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); ++ pollReturnTime_ = poller_->poll(timerQueue_->getTimeout(), &activeChannels_); + ++iteration_; + if (Logger::logLevel() <= Logger::TRACE) + { + printActiveChannels(); + } ++ timerQueue_->processTimers(); + // TODO sort channel by priority + eventHandling_ = true; + for (ChannelList::iterator it = activeChannels_.begin(); +@@ -215,22 +211,19 @@ void EventLoop::abortNotInLoopThread() + + void EventLoop::wakeup() + { +- uint64_t one = 1; +- ssize_t n = sockets::write(wakeupFd_, &one, sizeof one); ++ char one = '1'; ++ ssize_t n = sockets::write(wakeupFd_[1], &one, sizeof one); + if (n != sizeof one) + { +- LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8"; ++ LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 1"; + } + } + + void EventLoop::handleRead() + { +- uint64_t one = 1; +- ssize_t n = sockets::read(wakeupFd_, &one, sizeof one); +- if (n != sizeof one) +- { +- LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8"; +- } ++ char buf[4096] = { 0 }; ++ ssize_t n = sockets::read(wakeupFd_[0], buf, sizeof buf); ++ (void)n; + } + + void EventLoop::doPendingFunctors() +diff --git a/muduo/net/EventLoop.h b/muduo/net/EventLoop.h +--- a/muduo/net/EventLoop.h ++++ b/muduo/net/EventLoop.h +@@ -130,7 +130,7 @@ class EventLoop : boost::noncopyable + Timestamp pollReturnTime_; + boost::scoped_ptr<Poller> poller_; + boost::scoped_ptr<TimerQueue> timerQueue_; +- int wakeupFd_; ++ int wakeupFd_[2]; + // unlike in TimerQueue, which is an internal class, + // we don't expose Channel to client. + boost::scoped_ptr<Channel> wakeupChannel_; +diff --git a/muduo/net/InetAddress.cc b/muduo/net/InetAddress.cc +--- a/muduo/net/InetAddress.cc ++++ b/muduo/net/InetAddress.cc +@@ -17,9 +17,7 @@ + #include <boost/static_assert.hpp> + + // INADDR_ANY use (type)value casting. +-#pragma GCC diagnostic ignored "-Wold-style-cast" + static const in_addr_t kInaddrAny = INADDR_ANY; +-#pragma GCC diagnostic error "-Wold-style-cast" + + // /* Structure describing an Internet socket address. */ + // struct sockaddr_in { +diff --git a/muduo/net/SocketsOps.cc b/muduo/net/SocketsOps.cc +--- a/muduo/net/SocketsOps.cc ++++ b/muduo/net/SocketsOps.cc +@@ -37,7 +37,9 @@ SA* sockaddr_cast(struct sockaddr_in* addr) + return static_cast<SA*>(implicit_cast<void*>(addr)); + } + +-void setNonBlockAndCloseOnExec(int sockfd) ++} ++ ++void sockets::setNonBlockAndCloseOnExec(int sockfd) + { + // non-block + int flags = ::fcntl(sockfd, F_GETFL, 0); +@@ -54,12 +56,9 @@ void setNonBlockAndCloseOnExec(int sockfd) + (void)ret; + } + +-} +- + int sockets::createNonblockingOrDie() + { + // socket +-#if VALGRIND + int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) + { +@@ -67,13 +66,6 @@ int sockets::createNonblockingOrDie() + } + + setNonBlockAndCloseOnExec(sockfd); +-#else +- int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); +- if (sockfd < 0) +- { +- LOG_SYSFATAL << "sockets::createNonblockingOrDie"; +- } +-#endif + return sockfd; + } + +@@ -98,13 +90,8 @@ void sockets::listenOrDie(int sockfd) + int sockets::accept(int sockfd, struct sockaddr_in* addr) + { + socklen_t addrlen = static_cast<socklen_t>(sizeof *addr); +-#if VALGRIND + int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen); + setNonBlockAndCloseOnExec(connfd); +-#else +- int connfd = ::accept4(sockfd, sockaddr_cast(addr), +- &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); +-#endif + if (connfd < 0) + { + int savedErrno = errno; +diff --git a/muduo/net/SocketsOps.h b/muduo/net/SocketsOps.h +--- a/muduo/net/SocketsOps.h ++++ b/muduo/net/SocketsOps.h +@@ -25,6 +25,8 @@ namespace sockets + /// abort if any error. + int createNonblockingOrDie(); + ++void setNonBlockAndCloseOnExec(int sockfd); ++ + int connect(int sockfd, const struct sockaddr_in& addr); + void bindOrDie(int sockfd, const struct sockaddr_in& addr); + void listenOrDie(int sockfd); +diff --git a/muduo/net/TimerQueue.cc b/muduo/net/TimerQueue.cc +--- a/muduo/net/TimerQueue.cc ++++ b/muduo/net/TimerQueue.cc +@@ -16,8 +16,6 @@ + + #include <boost/bind.hpp> + +-#include <sys/timerfd.h> +- + namespace muduo + { + namespace net +@@ -25,57 +23,15 @@ namespace net + namespace detail + { + +-int createTimerfd() +-{ +- int timerfd = ::timerfd_create(CLOCK_MONOTONIC, +- TFD_NONBLOCK | TFD_CLOEXEC); +- if (timerfd < 0) +- { +- LOG_SYSFATAL << "Failed in timerfd_create"; +- } +- return timerfd; +-} +- +-struct timespec howMuchTimeFromNow(Timestamp when) ++int howMuchTimeFromNow(Timestamp when) + { + int64_t microseconds = when.microSecondsSinceEpoch() + - Timestamp::now().microSecondsSinceEpoch(); +- if (microseconds < 100) +- { +- microseconds = 100; +- } +- struct timespec ts; +- ts.tv_sec = static_cast<time_t>( +- microseconds / Timestamp::kMicroSecondsPerSecond); +- ts.tv_nsec = static_cast<long>( +- (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); +- return ts; +-} +- +-void readTimerfd(int timerfd, Timestamp now) +-{ +- uint64_t howmany; +- ssize_t n = ::read(timerfd, &howmany, sizeof howmany); +- LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString(); +- if (n != sizeof howmany) +- { +- LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8"; +- } +-} +- +-void resetTimerfd(int timerfd, Timestamp expiration) +-{ +- // wake up loop by timerfd_settime() +- struct itimerspec newValue; +- struct itimerspec oldValue; +- bzero(&newValue, sizeof newValue); +- bzero(&oldValue, sizeof oldValue); +- newValue.it_value = howMuchTimeFromNow(expiration); +- int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); +- if (ret) ++ if (microseconds < 1000) + { +- LOG_SYSERR << "timerfd_settime()"; ++ microseconds = 1000; + } ++ return static_cast<int>(microseconds / 1000); + } + + } +@@ -88,20 +44,13 @@ using namespace muduo::net::detail; + + TimerQueue::TimerQueue(EventLoop* loop) + : loop_(loop), +- timerfd_(createTimerfd()), +- timerfdChannel_(loop, timerfd_), + timers_(), + callingExpiredTimers_(false) + { +- timerfdChannel_.setReadCallback( +- boost::bind(&TimerQueue::handleRead, this)); +- // we are always reading the timerfd, we disarm it with timerfd_settime. +- timerfdChannel_.enableReading(); + } + + TimerQueue::~TimerQueue() + { +- ::close(timerfd_); + // do not remove channel, since we're in EventLoop::dtor(); + for (TimerList::iterator it = timers_.begin(); + it != timers_.end(); ++it) +@@ -129,11 +78,19 @@ void TimerQueue::cancel(TimerId timerId) + void TimerQueue::addTimerInLoop(Timer* timer) + { + loop_->assertInLoopThread(); +- bool earliestChanged = insert(timer); ++ insert(timer); ++} + +- if (earliestChanged) ++int TimerQueue::getTimeout() const ++{ ++ loop_->assertInLoopThread(); ++ if (timers_.empty()) ++ { ++ return 10000; ++ } ++ else + { +- resetTimerfd(timerfd_, timer->expiration()); ++ return howMuchTimeFromNow(timers_.begin()->second->expiration()); + } + } + +@@ -157,11 +114,10 @@ void TimerQueue::cancelInLoop(TimerId timerId) + assert(timers_.size() == activeTimers_.size()); + } + +-void TimerQueue::handleRead() ++void TimerQueue::processTimers() + { + loop_->assertInLoopThread(); + Timestamp now(Timestamp::now()); +- readTimerfd(timerfd_, now); + + std::vector<Entry> expired = getExpired(now); + +@@ -225,11 +181,6 @@ void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now) + { + nextExpire = timers_.begin()->second->expiration(); + } +- +- if (nextExpire.valid()) +- { +- resetTimerfd(timerfd_, nextExpire); +- } + } + + bool TimerQueue::insert(Timer* timer) +diff --git a/muduo/net/TimerQueue.h b/muduo/net/TimerQueue.h +--- a/muduo/net/TimerQueue.h ++++ b/muduo/net/TimerQueue.h +@@ -51,6 +51,9 @@ class TimerQueue : boost::noncopyable + + void cancel(TimerId timerId); + ++ int getTimeout() const; ++ void processTimers(); ++ + private: + + // FIXME: use unique_ptr<Timer> instead of raw pointers. +@@ -61,8 +64,6 @@ class TimerQueue : boost::noncopyable + + void addTimerInLoop(Timer* timer); + void cancelInLoop(TimerId timerId); +- // called when timerfd alarms +- void handleRead(); + // move out all expired timers + std::vector<Entry> getExpired(Timestamp now); + void reset(const std::vector<Entry>& expired, Timestamp now); +@@ -70,9 +71,6 @@ class TimerQueue : boost::noncopyable + bool insert(Timer* timer); + + EventLoop* loop_; +- const int timerfd_; +- Channel timerfdChannel_; +- // Timer list sorted by expiration + TimerList timers_; + + // for cancel() +diff --git a/muduo/net/poller/EPollPoller.cc b/muduo/net/poller/EPollPoller.cc +--- a/muduo/net/poller/EPollPoller.cc ++++ b/muduo/net/poller/EPollPoller.cc +@@ -26,7 +26,6 @@ using namespace muduo::net; + BOOST_STATIC_ASSERT(EPOLLIN == POLLIN); + BOOST_STATIC_ASSERT(EPOLLPRI == POLLPRI); + BOOST_STATIC_ASSERT(EPOLLOUT == POLLOUT); +-BOOST_STATIC_ASSERT(EPOLLRDHUP == POLLRDHUP); + BOOST_STATIC_ASSERT(EPOLLERR == POLLERR); + BOOST_STATIC_ASSERT(EPOLLHUP == POLLHUP); + +@@ -39,7 +38,7 @@ const int kDeleted = 2; + + EPollPoller::EPollPoller(EventLoop* loop) + : Poller(loop), +- epollfd_(::epoll_create1(EPOLL_CLOEXEC)), ++ epollfd_(::epoll_create(32)), + events_(kInitEventListSize) + { + if (epollfd_ < 0)