first commit

This commit is contained in:
taynpg 2024-03-08 14:03:37 +08:00
commit 0df1e7d379
366 changed files with 36387 additions and 0 deletions

17
.clang-format Normal file
View 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
View 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
View File

@ -0,0 +1,6 @@
*.swp
bazel-*
compile_commands.json
.cache
build
cmake*

19
.travis.yml Normal file
View 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
View 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
View File

@ -0,0 +1 @@
# See https://github.com/chenshuo/muduo-tutorial for how to use muduo in your project.

441
ChangeLog Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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)

View 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;
}

View 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();
}

View 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

View 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());
}
}

View 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
View File

@ -0,0 +1,2 @@
gen-cpp
gen-py

View 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;
}

View File

@ -0,0 +1,8 @@
namespace cpp echo
namespace py echo
service Echo
{
string echo(1: string arg);
}

View 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()

View 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;
}

View File

@ -0,0 +1,8 @@
namespace cpp ping
namespace py ping
service Ping
{
void ping();
}

View 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()

View 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();
}

View 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
}

View 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();
}

View 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;
}

View 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
View 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
View 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();
}

View 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();
}
}

View 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();
}
}

View 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);
}

View 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]);
}
}

View 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

View 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]);
}
}

View 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]);
}
}

View 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]);
}
}

View 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]);
}
}

View 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]);
}
}

View File

View 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();
}

View 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";
}

View 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();
}

View 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();
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}

View 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

View 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
View 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;
#}
}
}

View 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]);
}
}

View 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]);
}
}

View 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]);
}
}

View 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);
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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]);
}
}

View 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("");
}
}

View 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

View 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();
}

View 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();
}

View 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);
}

View 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

View 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
View 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

View 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";
}

View 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());
}

View 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

View 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);
}
}

View 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

View 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");
}
}
}

View 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

View 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
}

View 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();
}
}

View 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();
}

View 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

View File

@ -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());
}
}

View File

@ -0,0 +1,5 @@
package com.chenshuo.muduo.example.multiplexer;
public class Event {
}

View File

@ -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();
}
}

View File

@ -0,0 +1,5 @@
package com.chenshuo.muduo.example.multiplexer;
public enum EventSource {
kBackend, kClient
}

View File

@ -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