first commit
This commit is contained in:
commit
0df1e7d379
17
.clang-format
Normal file
17
.clang-format
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# .clang-format
|
||||||
|
|
||||||
|
# 风格格式化
|
||||||
|
BasedOnStyle: Google
|
||||||
|
# 4 空格缩进
|
||||||
|
IndentWidth: 4
|
||||||
|
# 连续对齐变量的声明
|
||||||
|
AlignConsecutiveDeclarations: true
|
||||||
|
# 指针左侧对齐
|
||||||
|
PointerAlignment: Left
|
||||||
|
# 访问说明符(public、private等)的偏移
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
# 大括号
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
# 函数定义后面大括号在新行
|
||||||
|
AfterFunction: true
|
13
.clangd
Normal file
13
.clangd
Normal file
@ -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
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
*.swp
|
||||||
|
bazel-*
|
||||||
|
compile_commands.json
|
||||||
|
.cache
|
||||||
|
build
|
||||||
|
cmake*
|
19
.travis.yml
Normal file
19
.travis.yml
Normal file
@ -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
|
21
.vscode/settings.json
vendored
Normal file
21
.vscode/settings.json
vendored
Normal file
@ -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++"
|
||||||
|
]
|
||||||
|
}
|
1
BUILD.bazel
Normal file
1
BUILD.bazel
Normal file
@ -0,0 +1 @@
|
|||||||
|
# See https://github.com/chenshuo/muduo-tutorial for how to use muduo in your project.
|
441
ChangeLog
Normal file
441
ChangeLog
Normal file
@ -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.
|
12
ChangeLog2
Normal file
12
ChangeLog2
Normal file
@ -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
|
||||||
|
|
29
License
Normal file
29
License
Normal file
@ -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.
|
||||||
|
|
32
README
Normal file
32
README
Normal file
@ -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.
|
||||||
|
__ __ _
|
||||||
|
| \/ | | |
|
||||||
|
| \ / |_ _ __| |_ _ ___
|
||||||
|
| |\/| | | | |/ _` | | | |/ _ \
|
||||||
|
| | | | |_| | (_| | |_| | (_) |
|
||||||
|
|_| |_|\__,_|\__,_|\__,_|\___/
|
||||||
|
|
27
build.sh
Normal file
27
build.sh
Normal file
@ -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
|
||||||
|
|
239
contrib/hiredis/Hiredis.cc
Normal file
239
contrib/hiredis/Hiredis.cc
Normal file
@ -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;
|
||||||
|
}
|
91
contrib/hiredis/Hiredis.h
Normal file
91
contrib/hiredis/Hiredis.h
Normal file
@ -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
|
5
contrib/hiredis/README.md
Normal file
5
contrib/hiredis/README.md
Normal file
@ -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)
|
141
contrib/hiredis/mrediscli.cc
Normal file
141
contrib/hiredis/mrediscli.cc
Normal file
@ -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;
|
||||||
|
}
|
121
contrib/thrift/ThriftConnection.cc
Normal file
121
contrib/thrift/ThriftConnection.cc
Normal file
@ -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();
|
||||||
|
}
|
66
contrib/thrift/ThriftConnection.h
Normal file
66
contrib/thrift/ThriftConnection.h
Normal file
@ -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
|
51
contrib/thrift/ThriftServer.cc
Normal file
51
contrib/thrift/ThriftServer.cc
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
222
contrib/thrift/ThriftServer.h
Normal file
222
contrib/thrift/ThriftServer.h
Normal file
@ -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
|
2
contrib/thrift/tests/.gitignore
vendored
Normal file
2
contrib/thrift/tests/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
gen-cpp
|
||||||
|
gen-py
|
51
contrib/thrift/tests/echo/EchoServer.cc
Normal file
51
contrib/thrift/tests/echo/EchoServer.cc
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
8
contrib/thrift/tests/echo/echo.thrift
Normal file
8
contrib/thrift/tests/echo/echo.thrift
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace cpp echo
|
||||||
|
namespace py echo
|
||||||
|
|
||||||
|
service Echo
|
||||||
|
{
|
||||||
|
string echo(1: string arg);
|
||||||
|
}
|
||||||
|
|
28
contrib/thrift/tests/echo/echoclient.py
Normal file
28
contrib/thrift/tests/echo/echoclient.py
Normal file
@ -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()
|
47
contrib/thrift/tests/ping/PingServer.cc
Normal file
47
contrib/thrift/tests/ping/PingServer.cc
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
8
contrib/thrift/tests/ping/ping.thrift
Normal file
8
contrib/thrift/tests/ping/ping.thrift
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace cpp ping
|
||||||
|
namespace py ping
|
||||||
|
|
||||||
|
service Ping
|
||||||
|
{
|
||||||
|
void ping();
|
||||||
|
}
|
||||||
|
|
26
contrib/thrift/tests/ping/pingclient.py
Normal file
26
contrib/thrift/tests/ping/pingclient.py
Normal file
@ -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()
|
141
examples/ace/logging/client.cc
Normal file
141
examples/ace/logging/client.cc
Normal file
@ -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();
|
||||||
|
}
|
29
examples/ace/logging/logrecord.proto
Normal file
29
examples/ace/logging/logrecord.proto
Normal file
@ -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
|
||||||
|
}
|
131
examples/ace/logging/server.cc
Normal file
131
examples/ace/logging/server.cc
Normal file
@ -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();
|
||||||
|
|
||||||
|
}
|
76
examples/ace/ttcp/common.cc
Normal file
76
examples/ace/ttcp/common.cc
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
37
examples/ace/ttcp/common.h
Normal file
37
examples/ace/ttcp/common.h
Normal file
@ -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);
|
24
examples/ace/ttcp/main.cc
Normal file
24
examples/ace/ttcp/main.cc
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
214
examples/ace/ttcp/ttcp.cc
Normal file
214
examples/ace/ttcp/ttcp.cc
Normal file
@ -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();
|
||||||
|
}
|
183
examples/ace/ttcp/ttcp_asio_async.cc
Normal file
183
examples/ace/ttcp/ttcp_asio_async.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
86
examples/ace/ttcp/ttcp_asio_sync.cc
Normal file
86
examples/ace/ttcp/ttcp_asio_sync.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
204
examples/ace/ttcp/ttcp_blocking.cc
Normal file
204
examples/ace/ttcp/ttcp_blocking.cc
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
103
examples/asio/chat/client.cc
Normal file
103
examples/asio/chat/client.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
68
examples/asio/chat/codec.h
Normal file
68
examples/asio/chat/codec.h
Normal file
@ -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
|
168
examples/asio/chat/loadtest.cc
Normal file
168
examples/asio/chat/loadtest.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
86
examples/asio/chat/server.cc
Normal file
86
examples/asio/chat/server.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
98
examples/asio/chat/server_threaded.cc
Normal file
98
examples/asio/chat/server_threaded.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
113
examples/asio/chat/server_threaded_efficient.cc
Normal file
113
examples/asio/chat/server_threaded_efficient.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
128
examples/asio/chat/server_threaded_highperformance.cc
Normal file
128
examples/asio/chat/server_threaded_highperformance.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
0
examples/asio/echo_see_simple
Normal file
0
examples/asio/echo_see_simple
Normal file
0
examples/asio/tutorial/daytime_see_simple
Normal file
0
examples/asio/tutorial/daytime_see_simple
Normal file
0
examples/asio/tutorial/there_is_no_timer1
Normal file
0
examples/asio/tutorial/there_is_no_timer1
Normal file
16
examples/asio/tutorial/timer2/timer.cc
Normal file
16
examples/asio/tutorial/timer2/timer.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
29
examples/asio/tutorial/timer3/timer.cc
Normal file
29
examples/asio/tutorial/timer3/timer.cc
Normal file
@ -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";
|
||||||
|
}
|
||||||
|
|
47
examples/asio/tutorial/timer4/timer.cc
Normal file
47
examples/asio/tutorial/timer4/timer.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
74
examples/asio/tutorial/timer5/timer.cc
Normal file
74
examples/asio/tutorial/timer5/timer.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
114
examples/asio/tutorial/timer6/timer.cc
Normal file
114
examples/asio/tutorial/timer6/timer.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
201
examples/cdns/Resolver.cc
Normal file
201
examples/cdns/Resolver.cc
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
77
examples/cdns/Resolver.h
Normal file
77
examples/cdns/Resolver.h
Normal file
@ -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
|
51
examples/cdns/dns.cc
Normal file
51
examples/cdns/dns.cc
Normal file
@ -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();
|
||||||
|
}
|
270
examples/curl/Curl.cc
Normal file
270
examples/curl/Curl.cc
Normal file
@ -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_;
|
||||||
|
}
|
143
examples/curl/Curl.h
Normal file
143
examples/curl/Curl.h
Normal file
@ -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
|
6
examples/curl/README
Normal file
6
examples/curl/README
Normal file
@ -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.
|
207
examples/curl/download.cc
Normal file
207
examples/curl/download.cc
Normal file
@ -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();
|
||||||
|
}
|
48
examples/curl/mcurl.cc
Normal file
48
examples/curl/mcurl.cc
Normal file
@ -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();
|
||||||
|
}
|
247
examples/fastcgi/fastcgi.cc
Normal file
247
examples/fastcgi/fastcgi.cc
Normal file
@ -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;
|
||||||
|
}
|
68
examples/fastcgi/fastcgi.h
Normal file
68
examples/fastcgi/fastcgi.h
Normal file
@ -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
|
73
examples/fastcgi/fastcgi_test.cc
Normal file
73
examples/fastcgi/fastcgi_test.cc
Normal file
@ -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();
|
||||||
|
}
|
110
examples/fastcgi/nginx.conf
Normal file
110
examples/fastcgi/nginx.conf
Normal file
@ -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;
|
||||||
|
#}
|
||||||
|
}
|
||||||
|
}
|
70
examples/filetransfer/download.cc
Normal file
70
examples/filetransfer/download.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
81
examples/filetransfer/download2.cc
Normal file
81
examples/filetransfer/download2.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
73
examples/filetransfer/download3.cc
Normal file
73
examples/filetransfer/download3.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
62
examples/filetransfer/loadtest/Client.java
Normal file
62
examples/filetransfer/loadtest/Client.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
68
examples/filetransfer/loadtest/Handler.java
Normal file
68
examples/filetransfer/loadtest/Handler.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
5
examples/hub/README
Normal file
5
examples/hub/README
Normal file
@ -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
|
||||||
|
|
53
examples/hub/codec.cc
Normal file
53
examples/hub/codec.cc
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
27
examples/hub/codec.h
Normal file
27
examples/hub/codec.h
Normal file
@ -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
|
||||||
|
|
217
examples/hub/hub.cc
Normal file
217
examples/hub/hub.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
82
examples/hub/pub.cc
Normal file
82
examples/hub/pub.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
106
examples/hub/pubsub.cc
Normal file
106
examples/hub/pubsub.cc
Normal file
@ -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;
|
||||||
|
}
|
47
examples/hub/pubsub.h
Normal file
47
examples/hub/pubsub.h
Normal file
@ -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
|
69
examples/hub/sub.cc
Normal file
69
examples/hub/sub.cc
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
99
examples/idleconnection/echo.cc
Normal file
99
examples/idleconnection/echo.cc
Normal file
@ -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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
55
examples/idleconnection/echo.h
Normal file
55
examples/idleconnection/echo.h
Normal file
@ -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
|
40
examples/idleconnection/main.cc
Normal file
40
examples/idleconnection/main.cc
Normal file
@ -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();
|
||||||
|
}
|
179
examples/idleconnection/sortedlist.cc
Normal file
179
examples/idleconnection/sortedlist.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
56
examples/maxconnection/echo.cc
Normal file
56
examples/maxconnection/echo.cc
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
28
examples/maxconnection/echo.h
Normal file
28
examples/maxconnection/echo.h
Normal file
@ -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
|
26
examples/maxconnection/main.cc
Normal file
26
examples/maxconnection/main.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
19
examples/memcached/README
Normal file
19
examples/memcached/README
Normal file
@ -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
|
239
examples/memcached/client/bench.cc
Normal file
239
examples/memcached/client/bench.cc
Normal file
@ -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";
|
||||||
|
}
|
63
examples/memcached/server/Item.cc
Normal file
63
examples/memcached/server/Item.cc
Normal file
@ -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());
|
||||||
|
}
|
129
examples/memcached/server/Item.h
Normal file
129
examples/memcached/server/Item.h
Normal file
@ -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
|
174
examples/memcached/server/MemcacheServer.cc
Normal file
174
examples/memcached/server/MemcacheServer.cc
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
87
examples/memcached/server/MemcacheServer.h
Normal file
87
examples/memcached/server/MemcacheServer.h
Normal file
@ -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
|
423
examples/memcached/server/Session.cc
Normal file
423
examples/memcached/server/Session.cc
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
examples/memcached/server/Session.h
Normal file
114
examples/memcached/server/Session.h
Normal file
@ -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
|
66
examples/memcached/server/footprint_test.cc
Normal file
66
examples/memcached/server/footprint_test.cc
Normal file
@ -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
|
||||||
|
}
|
55
examples/memcached/server/server.cc
Normal file
55
examples/memcached/server/server.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
240
examples/multiplexer/demux.cc
Normal file
240
examples/multiplexer/demux.cc
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
8
examples/multiplexer/harness/run.sh
Normal file
8
examples/multiplexer/harness/run.sh
Normal file
@ -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
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.chenshuo.muduo.example.multiplexer;
|
||||||
|
|
||||||
|
public class Event {
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.chenshuo.muduo.example.multiplexer;
|
||||||
|
|
||||||
|
public enum EventSource {
|
||||||
|
kBackend, kClient
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user