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