commit 0df1e7d3790ad8ca6e1d8d1bfd5f534635b49e6d Author: taynpg Date: Fri Mar 8 14:03:37 2024 +0800 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b63abc9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +# .clang-format + +# 风格格式化 +BasedOnStyle: Google +# 4 空格缩进 +IndentWidth: 4 +# 连续对齐变量的声明 +AlignConsecutiveDeclarations: true +# 指针左侧对齐 +PointerAlignment: Left +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 +# 大括号 +BreakBeforeBraces: Custom +BraceWrapping: + # 函数定义后面大括号在新行 + AfterFunction: true \ No newline at end of file diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..2911e29 --- /dev/null +++ b/.clangd @@ -0,0 +1,13 @@ +Hover: + ShowAKA: Yes +Diagnostics: + UnusedIncludes: None # 禁用未使用头文件提示 + Suppress: [ + anon_type_definition, # 禁用匿名的typedef提示 + unused-variable, # 禁用未使用变量提示 + unused-function, # 禁用未使用函数提示 + unused-includes, # 禁用未使用的头文件提示 + sign-conversion + ] + ClangTidy: + Remove: misc-unused-alias-decls \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7c7004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.swp +bazel-* +compile_commands.json +.cache +build +cmake* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a568370 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: cpp +sudo: required +dist: trusty +compiler: + - gcc + - clang +os: + - linux +install: + - sudo apt-get install libboost-dev + - sudo apt-get install libprotobuf-dev protobuf-compiler libprotoc-dev libgoogle-perftools-dev + - sudo apt-get install libboost-test-dev libboost-program-options-dev libboost-system-dev + - sudo apt-get install libc-ares-dev libcurl4-openssl-dev + - sudo apt-get install zlib1g-dev libgd-dev +env: + - BUILD_TYPE=debug + - BUILD_TYPE=release +script: + - ./build.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4179146 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "files.autoSave": "onFocusChange", + "editor.fontSize": 14, + "editor.fontFamily": "'FiraCode Nerd Font Mono', 'FiraCode Nerd Font Mono', 'FiraCode Nerd Font Mono'", + "cmake.configureOnOpen": true, + "cmake.options.statusBarVisibility": "visible", + "editor.inlayHints.enabled": "off", + "C_Cpp.intelliSenseEngine": "disabled", + "clangd.arguments": [ + "--header-insertion=never", + "--all-scopes-completion", + "--completion-style=detailed", + "--clang-tidy", + "-j=4", + "--pch-storage=memory", + "--compile-commands-dir=build", + "--background-index", + "--ranking-model=heuristics", + "--query-driver=/usr/bin/g++" + ] +} \ No newline at end of file diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..1fdfcc2 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1 @@ +# See https://github.com/chenshuo/muduo-tutorial for how to use muduo in your project. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..3e94641 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,441 @@ +2018-10-22 Shuo Chen + * 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 + * 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 + * Add Travis CI + * Add EvevtLoop::queueSize() by + * 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 + * Preliminary support of IPv6. + * Add stop/startRead in TcpConnection by + * Version 1.0.8 + +2015-11-09 Shuo Chen + * Add stats to Sudoku examples. + * Add example of PeriodicTimer class. + * Add thrift examples by . + * Move hiredis example by to contrib/. + * Move HTTP parseRequest to HttpContext class by . + * Other fixes from , , , . + * Version 1.0.7 + +2015-04-03 Shuo Chen + * Fix ProcessInspector::threads(). + * Minor fixes and improvements from liyuan989 and zieckey. + * More Sudoku examples. + * Version 1.0.6 + +2015-01-30 Shuo Chen + * Add examples/procmon + * EventLoop supports set/get context by + * Fix bug #107 + * Version 1.0.5 + +2014-10-05 Shuo Chen + * Enrich interfaces of EventLoopThreadPool by + * Buffer supports reading int64_t by + * Add hiredis example by + * Fix bug about TcpClient life time again. + * Other minor fixes, including some from + * Version 1.0.4 + +2014-08-02 Shuo Chen + * 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 + * Fix boundary check in Buffer::findEOL() by . + * Fix typos in InetAddress.cc by . + * Fix 32-bit integer overflow bug in time_client by . + * Update comments in Buffer::readFd() by . + * Add ThreadPool::setThreadInitCallback(). + * Rename GzipStream to ZlibStream. + * Version 1.0.2 + +2014-04-10 Shuo Chen + * 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 + * Add TCP and RPC balancer examples + * Version 1.0.0 + +2014-03-05 Shuo Chen + * Introduce class StringArg for passing C-style string arguments. + * Support localtime in logging. + * Version 1.0.0-rc2 + +2014-02-22 Shuo Chen + * Default to release build. + * Version 1.0.0-rc1 + +2014-02-22 Shuo Chen + * 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 + * Add TcpConnection::forceClose(). + * Add fastcgi nginx.conf example + * Fix iterator invalidation in hub.cc. + * Version 0.9.7 + +2013-10-21 Shuo Chen + * Minor fixes. + * Version 0.9.6 + +2013-08-31 Shuo Chen + * 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 + * 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 + * 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 + * Fix bugs + * Add Sudoku client + * Version 0.9.2 + +2013-01-16 Shuo Chen + * Fix bug introduced in dd26871 + * Version 0.9.1 + +2013-01-09 Shuo Chen + * 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 + * Version for the book + * Fix Buffer::shrink() + * Fix race condition of ThreadPool::stop() + * Version 0.8.2 + +2012-09-30 Shuo Chen + * Add Channel::remove() + * Logger::SourceFile supports char* + * Fix for g++ 4.7 + * Version 0.8.1 + +2012-09-06 Shuo Chen + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * Add example for async rpc (resolver). + * Install muduo_cdns + * Version 0.3.4 + +2012-03-16 Shuo Chen + + * 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 + + * 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 + + * Support multi-threaded http server. + * Do not install SocketsOps.h + * Version 0.3.1 + +2012-02-24 Shuo Chen + + * 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 + + * 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 + + * 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 + + * Add a proof of concept implementation of Protobuf RPC. + * Version 0.2.7 + +2011-06-27 Shuo Chen + + * Fix decoding of Sudoku request. + * Backport to older Linux. + * Add BoundedBlockingQueue + * Version 0.2.6 + +2011-06-15 Shuo Chen + + * Add examples/sudoku. + * Add thread benchmark. + * Version 0.2.5 + +2011-06-02 Shuo Chen + + * Add examples/shorturl. + * Version 0.2.4 + +2011-05-24 Shuo Chen + + * 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 + + * Changes from reactor tutorial + * Version 0.2.2 + +2011-05-07 Shuo Chen + + * 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 + + * 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 + + * 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 + + * 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 + + * Fix LengthHeaderCodec::onMessage() in examples/asio/chat. + * Version 0.1.7 + +2011-02-01 Shuo Chen + + * Fix onConnection() in simple examples. + * Reset t_cachedTid after fork(). + * Version 0.1.6 + +2010-12-15 Shuo Chen + + * Add examples/multiplexer + * Fix epoll kNoneEvent + * Version 0.1.5 + +2010-11-20 Shuo Chen + + * Fix retry logic + * Version 0.1.4 + +2010-09-26 Shuo Chen + + * Check SO_ERROR when connection is made. + +2010-09-11 Shuo Chen + + * Gracefully refuse clients when accept(2) returns EMFILE. + * Version 0.1.3 + +2010-09-07 Shuo Chen + + * Libevent benchmark for event handling. + * Version 0.1.2 + +2010-09-04 Shuo Chen + + * Ping-pong benchmark, version 0.1.1 + +2010-08-30 Shuo Chen + + * First pre-alpha release, version 0.1.0 + +2010-08-29 Shuo Chen + + * Sub works. + +2010-08-28 Shuo Chen + + * Add twisted finger examples. + +2010-08-27 Shuo Chen + + * Add simple chargen example. + +2010-08-07 Shuo Chen + + * Add Date. + +2010-05-15 Shuo Chen + + * Hub works. + +2010-05-14 Shuo Chen + + * Inspects opened files and threads. + +2010-05-11 Shuo Chen + + * Add inspector for process info. + +2010-05-04 Shuo Chen + + * Add simple http server and client. + +2010-04-25 Shuo Chen + + * Add examples. + +2010-04-11 Shuo Chen + + * TcpClient works. + +2010-04-03 Shuo Chen + + * TcpServer works. + +2010-03-15 Shuo Chen + + * TcpConnection at server side works. + +2010-03-14 Shuo Chen + + * Acceptor works. + +2010-03-13 Shuo Chen + + * TimerQueue works. + +2010-03-12 Shuo Chen + + * Starts working on Muduo. diff --git a/ChangeLog2 b/ChangeLog2 new file mode 100644 index 0000000..0b69e99 --- /dev/null +++ b/ChangeLog2 @@ -0,0 +1,12 @@ +2018-10-24 Shuo Chen + * 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 with std::vector>. + * Replace boost::noncopyable with muduo::noncopyable. + * Replace boost::scoped_ptr with std::unique_ptr. + * Replace BOOST_STATIC_ASSERT with static_assert. + * Replace boost type_traits with std type_traits. + * Version 2.0.0 + diff --git a/License b/License new file mode 100644 index 0000000..836aa8b --- /dev/null +++ b/License @@ -0,0 +1,29 @@ +// Muduo - A reactor-based C++ network library for Linux +// Copyright (c) 2010, Shuo Chen. All rights reserved. +// http://code.google.com/p/muduo/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Shuo Chen nor the names of other contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README b/README new file mode 100644 index 0000000..2cb2607 --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +Muduo is a multithreaded C++ network library based on +the reactor pattern. + +http://github.com/chenshuo/muduo + +Copyright (c) 2010, Shuo Chen. All rights reserved. + +Use of this source code is governed by a BSD-style +license that can be found in the License file. + +Requires: + Linux kernel version >= 2.6.28. + GCC >= 4.7 or Clang >= 3.5 + Boost (for boost::any only.) + +Tested on: + Debian 7 and above + Unbuntu 14.04 and above + CentOS 7 and above + +To build, run: + ./build.sh + +See https://github.com/chenshuo/muduo-tutorial for +how to use muduo in your project. + __ __ _ + | \/ | | | + | \ / |_ _ __| |_ _ ___ + | |\/| | | | |/ _` | | | |/ _ \ + | | | | |_| | (_| | |_| | (_) | + |_| |_|\__,_|\__,_|\__,_|\___/ + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..3458320 --- /dev/null +++ b/build.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -x + +SOURCE_DIR=`pwd` +BUILD_DIR=${BUILD_DIR:-../build} +BUILD_TYPE=${BUILD_TYPE:-debug} +INSTALL_DIR=${INSTALL_DIR:-../${BUILD_TYPE}-install-cpp11} +CXX=${CXX:-g++} + +ln -sf $BUILD_DIR/$BUILD_TYPE-cpp11/compile_commands.json + +mkdir -p $BUILD_DIR/$BUILD_TYPE-cpp11 \ + && cd $BUILD_DIR/$BUILD_TYPE-cpp11 \ + && cmake \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + $SOURCE_DIR \ + && make $* + +# Use the following command to run all the unit tests +# at the dir $BUILD_DIR/$BUILD_TYPE : +# CTEST_OUTPUT_ON_FAILURE=TRUE make test + +# cd $SOURCE_DIR && doxygen + diff --git a/contrib/hiredis/Hiredis.cc b/contrib/hiredis/Hiredis.cc new file mode 100644 index 0000000..3b62930 --- /dev/null +++ b/contrib/hiredis/Hiredis.cc @@ -0,0 +1,239 @@ +#include "contrib/hiredis/Hiredis.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/SocketsOps.h" + +#include + +using namespace muduo; +using namespace muduo::net; +using namespace hiredis; + +static void dummy(const std::shared_ptr&) +{ +} + +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(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(privdata); + hiredis->channel_->enableReading(); +} + +void Hiredis::delRead(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast(privdata); + hiredis->channel_->disableReading(); +} + +void Hiredis::addWrite(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast(privdata); + hiredis->channel_->enableWriting(); +} + +void Hiredis::delWrite(void* privdata) +{ + LOG_TRACE; + Hiredis* hiredis = static_cast(privdata); + hiredis->channel_->disableWriting(); +} + +void Hiredis::cleanup(void* privdata) +{ + Hiredis* hiredis = static_cast(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(r); + CommandCallback* cb = static_cast(privdata); + getHiredis(ac)->commandCallback(reply, cb); +} + +void Hiredis::commandCallback(redisReply* reply, CommandCallback* cb) +{ + (*cb)(this, reply); + delete cb; +} + +int Hiredis::ping() +{ + return command(std::bind(&Hiredis::pingCallback, this, _1, _2), "PING"); +} + +void Hiredis::pingCallback(Hiredis* me, redisReply* reply) +{ + assert(this == me); + LOG_DEBUG << reply->str; +} diff --git a/contrib/hiredis/Hiredis.h b/contrib/hiredis/Hiredis.h new file mode 100644 index 0000000..a48af03 --- /dev/null +++ b/contrib/hiredis/Hiredis.h @@ -0,0 +1,91 @@ +#ifndef MUDUO_CONTRIB_HIREDIS_HIREDIS_H +#define MUDUO_CONTRIB_HIREDIS_HIREDIS_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" +#include "muduo/net/Callbacks.h" +#include "muduo/net/InetAddress.h" + +#include + +struct redisAsyncContext; + +namespace muduo +{ +namespace net +{ +class Channel; +class EventLoop; +} +} + +namespace hiredis +{ + +class Hiredis : public std::enable_shared_from_this, + muduo::noncopyable +{ + public: + typedef std::function ConnectCallback; + typedef std::function DisconnectCallback; + typedef std::function 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 channel_; + ConnectCallback connectCb_; + DisconnectCallback disconnectCb_; +}; + +} // namespace hiredis + +#endif // MUDUO_CONTRIB_HIREDIS_HIREDIS_H diff --git a/contrib/hiredis/README.md b/contrib/hiredis/README.md new file mode 100644 index 0000000..a856574 --- /dev/null +++ b/contrib/hiredis/README.md @@ -0,0 +1,5 @@ +# Hiredis + +The version of hiredis must be 0.11.0 or greater + +See also issue [#92](https://github.com/chenshuo/muduo/issues/92) diff --git a/contrib/hiredis/mrediscli.cc b/contrib/hiredis/mrediscli.cc new file mode 100644 index 0000000..b509323 --- /dev/null +++ b/contrib/hiredis/mrediscli.cc @@ -0,0 +1,141 @@ +#include "contrib/hiredis/Hiredis.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include + +using namespace muduo; +using namespace muduo::net; + +string toString(long long value) +{ + char buf[32]; + snprintf(buf, sizeof buf, "%lld", value); + return buf; +} + +string redisReplyToString(const redisReply* reply) +{ + static const char* const types[] = { "", + "REDIS_REPLY_STRING", "REDIS_REPLY_ARRAY", + "REDIS_REPLY_INTEGER", "REDIS_REPLY_NIL", + "REDIS_REPLY_STATUS", "REDIS_REPLY_ERROR" }; + string str; + if (!reply) return str; + + str += types[reply->type] + string("(") + toString(reply->type) + ") "; + + str += "{ "; + if (reply->type == REDIS_REPLY_STRING || + reply->type == REDIS_REPLY_STATUS || + reply->type == REDIS_REPLY_ERROR) + { + str += '"' + string(reply->str, reply->len) + '"'; + } + else if (reply->type == REDIS_REPLY_INTEGER) + { + str += toString(reply->integer); + } + else if (reply->type == REDIS_REPLY_ARRAY) + { + str += toString(reply->elements) + " "; + for (size_t i = 0; i < reply->elements; i++) + { + str += " " + redisReplyToString(reply->element[i]); + } + } + str += " }"; + + return str; +} + +void connectCallback(hiredis::Hiredis* c, int status) +{ + if (status != REDIS_OK) + { + LOG_ERROR << "connectCallback Error:" << c->errstr(); + } + else + { + LOG_INFO << "Connected..."; + } +} + +void disconnectCallback(hiredis::Hiredis* c, int status) +{ + if (status != REDIS_OK) + { + LOG_ERROR << "disconnectCallback Error:" << c->errstr(); + } + else + { + LOG_INFO << "Disconnected..."; + } +} + +void timeCallback(hiredis::Hiredis* c, redisReply* reply) +{ + LOG_INFO << "time " << redisReplyToString(reply); +} + +void echoCallback(hiredis::Hiredis* c, redisReply* reply, string* echo) +{ + LOG_INFO << *echo << " " << redisReplyToString(reply); + c->disconnect(); +} + +void dbsizeCallback(hiredis::Hiredis* c, redisReply* reply) +{ + LOG_INFO << "dbsize " << redisReplyToString(reply); +} + +void selectCallback(hiredis::Hiredis* c, redisReply* reply, uint16_t* index) +{ + LOG_INFO << "select " << *index << " " << redisReplyToString(reply); +} + +void authCallback(hiredis::Hiredis* c, redisReply* reply, string* password) +{ + LOG_INFO << "auth " << *password << " " << redisReplyToString(reply); +} + +void echo(hiredis::Hiredis* c, string* s) +{ + c->command(std::bind(echoCallback, _1, _2, s), "echo %s", s->c_str()); +} + +int main(int argc, char** argv) +{ + Logger::setLogLevel(Logger::DEBUG); + + EventLoop loop; + + InetAddress serverAddr("127.0.0.1", 6379); + hiredis::Hiredis hiredis(&loop, serverAddr); + + hiredis.setConnectCallback(connectCallback); + hiredis.setDisconnectCallback(disconnectCallback); + hiredis.connect(); + + //hiredis.ping(); + loop.runEvery(1.0, std::bind(&hiredis::Hiredis::ping, &hiredis)); + + hiredis.command(timeCallback, "time"); + + string hi = "hi"; + hiredis.command(std::bind(echoCallback, _1, _2, &hi), "echo %s", hi.c_str()); + loop.runEvery(2.0, std::bind(echo, &hiredis, &hi)); + + hiredis.command(dbsizeCallback, "dbsize"); + + uint16_t index = 8; + hiredis.command(std::bind(selectCallback, _1, _2, &index), "select %d", index); + + string password = "password"; + hiredis.command(std::bind(authCallback, _1, _2, &password), "auth %s", password.c_str()); + + loop.loop(); + + return 0; +} diff --git a/contrib/thrift/ThriftConnection.cc b/contrib/thrift/ThriftConnection.cc new file mode 100644 index 0000000..89c094b --- /dev/null +++ b/contrib/thrift/ThriftConnection.cc @@ -0,0 +1,121 @@ +#include "contrib/thrift/ThriftConnection.h" + +#include + +#include "muduo/base/Logging.h" + +#include + +#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(buffer->readInt32()); + state_ = kExpectFrame; + } + else + { + more = false; + } + } + else if (state_ == kExpectFrame) + { + if (buffer->readableBytes() >= frameSize_) + { + uint8_t* buf = reinterpret_cast((const_cast(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(htonl(size - 4)); + memcpy(buf, &frameSize, 4); + + conn_->send(buf, size); + } catch (const TTransportException& ex) + { + LOG_ERROR << "ThriftServer TTransportException: " << ex.what(); + close(); + } catch (const std::exception& ex) + { + LOG_ERROR << "ThriftServer std::exception: " << ex.what(); + close(); + } catch (...) + { + LOG_ERROR << "ThriftServer unknown exception"; + close(); + } +} + +void ThriftConnection::close() +{ + nullTransport_->close(); + factoryInputTransport_->close(); + factoryOutputTransport_->close(); +} diff --git a/contrib/thrift/ThriftConnection.h b/contrib/thrift/ThriftConnection.h new file mode 100644 index 0000000..394097b --- /dev/null +++ b/contrib/thrift/ThriftConnection.h @@ -0,0 +1,66 @@ +#ifndef MUDUO_CONTRIB_THRIFT_THRIFTCONNECTION_H +#define MUDUO_CONTRIB_THRIFT_THRIFTCONNECTION_H + +#include +#include + +#include "muduo/net/TcpConnection.h" + +#include +#include +#include + +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 +{ + 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 nullTransport_; + + boost::shared_ptr inputTransport_; + boost::shared_ptr outputTransport_; + + boost::shared_ptr factoryInputTransport_; + boost::shared_ptr factoryOutputTransport_; + + boost::shared_ptr inputProtocol_; + boost::shared_ptr outputProtocol_; + + boost::shared_ptr processor_; + + enum State state_; + uint32_t frameSize_; +}; + +typedef boost::shared_ptr ThriftConnectionPtr; + +#endif // MUDUO_CONTRIB_THRIFT_THRIFTCONNECTION_H diff --git a/contrib/thrift/ThriftServer.cc b/contrib/thrift/ThriftServer.cc new file mode 100644 index 0000000..672a308 --- /dev/null +++ b/contrib/thrift/ThriftServer.cc @@ -0,0 +1,51 @@ +#include "contrib/thrift/ThriftServer.h" + +#include + +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +ThriftServer::~ThriftServer() = default; + +void ThriftServer::serve() +{ + start(); +} + +void ThriftServer::start() +{ + if (numWorkerThreads_ > 0) + { + workerThreadPool_.start(numWorkerThreads_); + } + server_.start(); +} + +void ThriftServer::stop() +{ + if (numWorkerThreads_ > 0) + { + workerThreadPool_.stop(); + } + server_.getLoop()->runAfter(3.0, boost::bind(&EventLoop::quit, + server_.getLoop())); +} + +void ThriftServer::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + ThriftConnectionPtr ptr(new ThriftConnection(this, conn)); + MutexLockGuard lock(mutex_); + assert(conns_.find(conn->name()) == conns_.end()); + conns_[conn->name()] = ptr; + } + else + { + MutexLockGuard lock(mutex_); + assert(conns_.find(conn->name()) != conns_.end()); + conns_.erase(conn->name()); + } +} diff --git a/contrib/thrift/ThriftServer.h b/contrib/thrift/ThriftServer.h new file mode 100644 index 0000000..c90c99e --- /dev/null +++ b/contrib/thrift/ThriftServer.h @@ -0,0 +1,222 @@ +#ifndef MUDUO_CONTRIB_THRIFT_THRIFTSERVER_H +#define MUDUO_CONTRIB_THRIFT_THRIFTSERVER_H + +#include + +#include +#include + +#include "muduo/base/ThreadPool.h" +#include "muduo/net/TcpServer.h" + +#include + +#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 + ThriftServer(const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& processorFactory, + const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& processor, + const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& processorFactory, + const boost::shared_ptr& transportFactory, + const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& processor, + const boost::shared_ptr& transportFactory, + const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& processorFactory, + const boost::shared_ptr& inputTransportFactory, + const boost::shared_ptr& outputTransportFactory, + const boost::shared_ptr& inputProtocolFactory, + const boost::shared_ptr& 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 + ThriftServer(const boost::shared_ptr& processor, + const boost::shared_ptr& inputTransportFactory, + const boost::shared_ptr& outputTransportFactory, + const boost::shared_ptr& inputProtocolFactory, + const boost::shared_ptr& 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 conns_; +}; + +#endif // MUDUO_CONTRIB_THRIFT_THRIFTSERVER_H diff --git a/contrib/thrift/tests/.gitignore b/contrib/thrift/tests/.gitignore new file mode 100644 index 0000000..159151f --- /dev/null +++ b/contrib/thrift/tests/.gitignore @@ -0,0 +1,2 @@ +gen-cpp +gen-py diff --git a/contrib/thrift/tests/echo/EchoServer.cc b/contrib/thrift/tests/echo/EchoServer.cc new file mode 100644 index 0000000..568c3dc --- /dev/null +++ b/contrib/thrift/tests/echo/EchoServer.cc @@ -0,0 +1,51 @@ +#include + +#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(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 handler(new EchoHandler()); + boost::shared_ptr processor(new EchoProcessor(handler)); + + ThriftServer server(processor, &eventloop, addr, name); + server.setWorkerThreadNum(NumCPU() * 2); + server.start(); + eventloop.loop(); + + return 0; +} + diff --git a/contrib/thrift/tests/echo/echo.thrift b/contrib/thrift/tests/echo/echo.thrift new file mode 100644 index 0000000..af714d5 --- /dev/null +++ b/contrib/thrift/tests/echo/echo.thrift @@ -0,0 +1,8 @@ +namespace cpp echo +namespace py echo + +service Echo +{ + string echo(1: string arg); +} + diff --git a/contrib/thrift/tests/echo/echoclient.py b/contrib/thrift/tests/echo/echoclient.py new file mode 100644 index 0000000..e7b3b00 --- /dev/null +++ b/contrib/thrift/tests/echo/echoclient.py @@ -0,0 +1,28 @@ +import sys +sys.path.append('gen-py') + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol + +from echo import Echo + + +def echo(s): + transport = TSocket.TSocket('127.0.0.1', 9090) + tranport = TTransport.TFramedTransport(transport) + protocol = TBinaryProtocol.TBinaryProtocol(tranport) + client = Echo.Client(protocol) + tranport.open() + s = client.echo(s) + tranport.close() + + return s + + +def main(): + print(echo('42')) + + +if __name__ == '__main__': + main() diff --git a/contrib/thrift/tests/ping/PingServer.cc b/contrib/thrift/tests/ping/PingServer.cc new file mode 100644 index 0000000..88a2a8f --- /dev/null +++ b/contrib/thrift/tests/ping/PingServer.cc @@ -0,0 +1,47 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include + +#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 handler(new PingHandler()); + boost::shared_ptr processor(new PingProcessor(handler)); + boost::shared_ptr protcolFactory(new TCompactProtocolFactory()); + + ThriftServer server(processor, protcolFactory, &eventloop, addr, name); + server.start(); + eventloop.loop(); + + return 0; +} + diff --git a/contrib/thrift/tests/ping/ping.thrift b/contrib/thrift/tests/ping/ping.thrift new file mode 100644 index 0000000..e38a0e8 --- /dev/null +++ b/contrib/thrift/tests/ping/ping.thrift @@ -0,0 +1,8 @@ +namespace cpp ping +namespace py ping + +service Ping +{ + void ping(); +} + diff --git a/contrib/thrift/tests/ping/pingclient.py b/contrib/thrift/tests/ping/pingclient.py new file mode 100644 index 0000000..166bc48 --- /dev/null +++ b/contrib/thrift/tests/ping/pingclient.py @@ -0,0 +1,26 @@ +import sys +sys.path.append('gen-py') + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TCompactProtocol + +from ping import Ping + + +def ping(): + transport = TSocket.TSocket('127.0.0.1', 9090) + tranport = TTransport.TFramedTransport(transport) + protocol = TCompactProtocol.TCompactProtocol(tranport) + client = Ping.Client(protocol) + tranport.open() + client.ping() + tranport.close() + + +def main(): + ping() + + +if __name__ == '__main__': + main() diff --git a/examples/ace/logging/client.cc b/examples/ace/logging/client.cc new file mode 100644 index 0000000..3cdd5dc --- /dev/null +++ b/examples/ace/logging/client.cc @@ -0,0 +1,141 @@ +#include "examples/ace/logging/logrecord.pb.h" + +#include "muduo/base/Mutex.h" +#include "muduo/base/Logging.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/protobuf/ProtobufCodecLite.h" + +#include +#include + +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 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(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(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + logging::LogClient client(loopThread.startLoop(), serverAddr); + client.connect(); + std::string line; + while (std::getline(std::cin, line)) + { + client.write(line); + } + client.disconnect(); + CurrentThread::sleepUsec(1000*1000); // wait for disconnect, then safe to destruct LogClient (esp. TcpClient). Otherwise mutex_ is used after dtor. + } + google::protobuf::ShutdownProtobufLibrary(); +} diff --git a/examples/ace/logging/logrecord.proto b/examples/ace/logging/logrecord.proto new file mode 100644 index 0000000..2000905 --- /dev/null +++ b/examples/ace/logging/logrecord.proto @@ -0,0 +1,29 @@ +package logging; + +message LogRecord { + // must present in first message + message Heartbeat { + required string hostname = 1; + required string process_name = 2; + required int32 process_id = 3; + required int64 process_start_time = 4; // microseconds sinch epoch + required string username = 5; + } + + optional Heartbeat heartbeat = 1; + // muduo/base/Logging.h + // enum LogLevel + // { + // TRACE, // 0 + // DEBUG, // 1 + // INFO, // 2 + // WARN, // 3 + // ERROR, // 4 + // FATAL, // 5 + // }; + required int32 level = 2; + required int32 thread_id = 3; + required int64 timestamp = 4; // microseconds sinch epoch + required string message = 5; + // optional: source file, source line, function name +} diff --git a/examples/ace/logging/server.cc b/examples/ace/logging/server.cc new file mode 100644 index 0000000..3b21fae --- /dev/null +++ b/examples/ace/logging/server.cc @@ -0,0 +1,131 @@ +#include "examples/ace/logging/logrecord.pb.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" +#include "muduo/net/protobuf/ProtobufCodecLite.h" + +#include + +using namespace muduo; +using namespace muduo::net; + +namespace logging +{ +extern const char logtag[] = "LOG0"; +typedef ProtobufCodecLiteT 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(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 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(port)); + int numThreads = argc > 2 ? atoi(argv[2]) : 1; + logging::LogServer server(&loop, listenAddr, numThreads); + server.start(); + loop.loop(); + +} diff --git a/examples/ace/ttcp/common.cc b/examples/ace/ttcp/common.cc new file mode 100644 index 0000000..dc5e6c7 --- /dev/null +++ b/examples/ace/ttcp/common.cc @@ -0,0 +1,76 @@ +#include "examples/ace/ttcp/common.h" +#include "muduo/base/Types.h" + +#include + +#include + +#include +#include + +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(&opt->port)->default_value(5001), "TCP port") + ("length,l", po::value(&opt->length)->default_value(65536), "Buffer length") + ("number,n", po::value(&opt->number)->default_value(8192), "Number of buffers") + ("trans,t", po::value(&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(he->h_addr); + return addr; +} + diff --git a/examples/ace/ttcp/common.h b/examples/ace/ttcp/common.h new file mode 100644 index 0000000..ab76428 --- /dev/null +++ b/examples/ace/ttcp/common.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +struct Options +{ + uint16_t port; + int length; + int number; + bool transmit, receive, nodelay; + std::string host; + Options() + : port(0), length(0), number(0), + transmit(false), receive(false), nodelay(false) + { + } +}; + +bool parseCommandLine(int argc, char* argv[], Options* opt); +struct sockaddr_in resolveOrDie(const char* host, uint16_t port); + +struct SessionMessage +{ + int32_t number; + int32_t length; +} __attribute__ ((__packed__)); + +struct PayloadMessage +{ + int32_t length; + char data[0]; +}; + +void transmit(const Options& opt); + +void receive(const Options& opt); diff --git a/examples/ace/ttcp/main.cc b/examples/ace/ttcp/main.cc new file mode 100644 index 0000000..08dabf3 --- /dev/null +++ b/examples/ace/ttcp/main.cc @@ -0,0 +1,24 @@ +#include "examples/ace/ttcp/common.h" + +#include + +int main(int argc, char* argv[]) +{ + Options options; + if (parseCommandLine(argc, argv, &options)) + { + if (options.transmit) + { + transmit(options); + } + else if (options.receive) + { + receive(options); + } + else + { + assert(0); + } + } +} + diff --git a/examples/ace/ttcp/ttcp.cc b/examples/ace/ttcp/ttcp.cc new file mode 100644 index 0000000..97f839b --- /dev/null +++ b/examples/ace/ttcp/ttcp.cc @@ -0,0 +1,214 @@ +#include "examples/ace/ttcp/common.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include + +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(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(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(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(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(sizeof(int32_t)); + const int32_t length = buf->peekInt32(); + if (length == session.length) + { + if (buf->readableBytes() >= total_len) + { + buf->retrieve(total_len); + conn->send(context->output.toStringPiece()); + ++context->count; + context->bytes += length; + if (context->count >= session.number) + { + conn->shutdown(); + break; + } + } + else + { + break; + } + } + else + { + printf("wrong length %d\n", length); + conn->shutdown(); + break; + } + } + } +} + +} // namespace receiving + +void receive(const Options& opt) +{ + EventLoop loop; + g_loop = &loop; + InetAddress listenAddr(opt.port); + TcpServer server(&loop, listenAddr, "TtcpReceive"); + server.setConnectionCallback( + std::bind(&receiving::onConnection, _1)); + server.setMessageCallback( + std::bind(&receiving::onMessage, _1, _2, _3)); + server.start(); + loop.loop(); +} diff --git a/examples/ace/ttcp/ttcp_asio_async.cc b/examples/ace/ttcp/ttcp_asio_async.cc new file mode 100644 index 0000000..ffa138b --- /dev/null +++ b/examples/ace/ttcp/ttcp_asio_async.cc @@ -0,0 +1,183 @@ +#include "examples/ace/ttcp/common.h" + +#include "muduo/base/Logging.h" +#include +#include + +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, + 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(sizeof(int32_t) + sessionMessage_.length); + payload_ = static_cast(::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(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 TtcpServerConnectionPtr; + +void doAccept(tcp::acceptor& acceptor) +{ +#if BOOST_VERSION < 107000L + // no need to pre-create new_connection if we use asio 1.12 or boost 1.66+ + TtcpServerConnectionPtr new_connection(new TtcpServerConnection(acceptor.get_io_service())); +#else + TtcpServerConnectionPtr new_connection(new TtcpServerConnection(acceptor.get_executor())); +#endif + acceptor.async_accept( + new_connection->socket(), + [&acceptor, new_connection](boost::system::error_code error) // move new_connection in C++14 + { + if (!error) + { + new_connection->start(); + } + doAccept(acceptor); + }); +} + +void receive(const Options& opt) +{ + try + { + boost::asio::io_service io_service; + tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), opt.port)); + doAccept(acceptor); + io_service.run(); + } + catch (std::exception& e) + { + LOG_ERROR << e.what(); + } +} + diff --git a/examples/ace/ttcp/ttcp_asio_sync.cc b/examples/ace/ttcp/ttcp_asio_sync.cc new file mode 100644 index 0000000..708d34f --- /dev/null +++ b/examples/ace/ttcp/ttcp_asio_sync.cc @@ -0,0 +1,86 @@ +#include "examples/ace/ttcp/common.h" + +#include "muduo/base/Logging.h" +#include +#include + +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(sizeof(int32_t) + sessionMessage.length); + PayloadMessage* payload = static_cast(::malloc(total_len)); + std::unique_ptr 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(payload->length)) + { + LOG_ERROR << "read payload data: " << error.message(); + exit(1); + } + int32_t ack = htonl(payload->length); + if (boost::asio::write(socket, boost::asio::buffer(&ack, sizeof(ack))) != sizeof(ack)) + { + LOG_ERROR << "write ack: " << error.message(); + exit(1); + } + } + } + catch (std::exception& e) + { + LOG_ERROR << e.what(); + } +} diff --git a/examples/ace/ttcp/ttcp_blocking.cc b/examples/ace/ttcp/ttcp_blocking.cc new file mode 100644 index 0000000..72c9e46 --- /dev/null +++ b/examples/ace/ttcp/ttcp_blocking.cc @@ -0,0 +1,204 @@ +#include "examples/ace/ttcp/common.h" +#include "muduo/base/Timestamp.h" +#include "muduo/base/Types.h" + +#undef NDEBUG + +#include +#include +#include +#include + +#include +#include + +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(&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(&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(buf) + written, length - written); + if (nw > 0) + { + written += static_cast(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(buf) + nread, length - nread); + if (nr > 0) + { + nread += static_cast(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(&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(sizeof(int32_t) + opt.length); + PayloadMessage* payload = static_cast(::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(sizeof(int32_t) + sessionMessage.length); + PayloadMessage* payload = static_cast(::malloc(total_len)); + assert(payload); + + for (int i = 0; i < sessionMessage.number; ++i) + { + payload->length = 0; + if (read_n(sockfd, &payload->length, sizeof(payload->length)) != sizeof(payload->length)) + { + perror("read length"); + exit(1); + } + payload->length = ntohl(payload->length); + assert(payload->length == sessionMessage.length); + if (read_n(sockfd, payload->data, payload->length) != payload->length) + { + perror("read payload data"); + exit(1); + } + int32_t ack = htonl(payload->length); + if (write_n(sockfd, &ack, sizeof(ack)) != sizeof(ack)) + { + perror("write ack"); + exit(1); + } + } + ::free(payload); + ::close(sockfd); +} + diff --git a/examples/asio/chat/client.cc b/examples/asio/chat/client.cc new file mode 100644 index 0000000..7e5e303 --- /dev/null +++ b/examples/asio/chat/client.cc @@ -0,0 +1,103 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/TcpClient.h" + +#include +#include +#include + +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(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + ChatClient client(loopThread.startLoop(), serverAddr); + client.connect(); + std::string line; + while (std::getline(std::cin, line)) + { + client.write(line); + } + client.disconnect(); + CurrentThread::sleepUsec(1000*1000); // wait for disconnect, see ace/logging/client.cc + } + else + { + printf("Usage: %s host_ip port\n", argv[0]); + } +} + diff --git a/examples/asio/chat/codec.h b/examples/asio/chat/codec.h new file mode 100644 index 0000000..ca264fa --- /dev/null +++ b/examples/asio/chat/codec.h @@ -0,0 +1,68 @@ +#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H +#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H + +#include "muduo/base/Logging.h" +#include "muduo/net/Buffer.h" +#include "muduo/net/Endian.h" +#include "muduo/net/TcpConnection.h" + +class LengthHeaderCodec : muduo::noncopyable +{ + public: + typedef std::function 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(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(message.size()); + int32_t be32 = muduo::net::sockets::hostToNetwork32(len); + buf.prepend(&be32, sizeof be32); + conn->send(&buf); + } + + private: + StringMessageCallback messageCallback_; + const static size_t kHeaderLen = sizeof(int32_t); +}; + +#endif // MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H diff --git a/examples/asio/chat/loadtest.cc b/examples/asio/chat/loadtest.cc new file mode 100644 index 0000000..a03ccb8 --- /dev/null +++ b/examples/asio/chat/loadtest.cc @@ -0,0 +1,168 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/TcpClient.h" + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +int g_connections = 0; +AtomicInt32 g_aliveConnections; +AtomicInt32 g_messagesReceived; +Timestamp g_startTime; +std::vector g_receiveTime; +EventLoop* g_loop; +std::function 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>& clients) +{ + LOG_INFO << "statistic " << clients.size(); + std::vector 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(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(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> clients(g_connections); + g_statistic = std::bind(statistic, std::ref(clients)); + + for (int i = 0; i < g_connections; ++i) + { + clients[i].reset(new ChatClient(loopPool.getNextLoop(), serverAddr)); + clients[i]->connect(); + usleep(200); + } + + loop.loop(); + // client.disconnect(); + } + else + { + printf("Usage: %s host_ip port connections [threads]\n", argv[0]); + } +} + + diff --git a/examples/asio/chat/server.cc b/examples/asio/chat/server.cc new file mode 100644 index 0000000..fc321cd --- /dev/null +++ b/examples/asio/chat/server.cc @@ -0,0 +1,86 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include +#include +#include + +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 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(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port\n", argv[0]); + } +} + diff --git a/examples/asio/chat/server_threaded.cc b/examples/asio/chat/server_threaded.cc new file mode 100644 index 0000000..3dc88d6 --- /dev/null +++ b/examples/asio/chat/server_threaded.cc @@ -0,0 +1,98 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include +#include +#include + +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 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(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + if (argc > 2) + { + server.setThreadNum(atoi(argv[2])); + } + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port [thread_num]\n", argv[0]); + } +} + diff --git a/examples/asio/chat/server_threaded_efficient.cc b/examples/asio/chat/server_threaded_efficient.cc new file mode 100644 index 0000000..5bd2738 --- /dev/null +++ b/examples/asio/chat/server_threaded_efficient.cc @@ -0,0 +1,113 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include +#include +#include + +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 ConnectionList; + typedef std::shared_ptr 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(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + if (argc > 2) + { + server.setThreadNum(atoi(argv[2])); + } + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port [thread_num]\n", argv[0]); + } +} + diff --git a/examples/asio/chat/server_threaded_highperformance.cc b/examples/asio/chat/server_threaded_highperformance.cc new file mode 100644 index 0000000..18e704a --- /dev/null +++ b/examples/asio/chat/server_threaded_highperformance.cc @@ -0,0 +1,128 @@ +#include "examples/asio/chat/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/ThreadLocalSingleton.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include +#include +#include + +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::iterator it = loops_.begin(); + it != loops_.end(); + ++it) + { + (*it)->queueInLoop(f); + } + LOG_DEBUG; + } + + typedef std::set 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 LocalConnections; + + MutexLock mutex_; + std::set loops_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) + { + EventLoop loop; + uint16_t port = static_cast(atoi(argv[1])); + InetAddress serverAddr(port); + ChatServer server(&loop, serverAddr); + if (argc > 2) + { + server.setThreadNum(atoi(argv[2])); + } + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s port [thread_num]\n", argv[0]); + } +} + + diff --git a/examples/asio/echo_see_simple b/examples/asio/echo_see_simple new file mode 100644 index 0000000..e69de29 diff --git a/examples/asio/tutorial/daytime_see_simple b/examples/asio/tutorial/daytime_see_simple new file mode 100644 index 0000000..e69de29 diff --git a/examples/asio/tutorial/there_is_no_timer1 b/examples/asio/tutorial/there_is_no_timer1 new file mode 100644 index 0000000..e69de29 diff --git a/examples/asio/tutorial/timer2/timer.cc b/examples/asio/tutorial/timer2/timer.cc new file mode 100644 index 0000000..26a46a7 --- /dev/null +++ b/examples/asio/tutorial/timer2/timer.cc @@ -0,0 +1,16 @@ +#include "muduo/net/EventLoop.h" + +#include + +void print() +{ + std::cout << "Hello, world!\n"; +} + +int main() +{ + muduo::net::EventLoop loop; + loop.runAfter(5, print); + loop.loop(); +} + diff --git a/examples/asio/tutorial/timer3/timer.cc b/examples/asio/tutorial/timer3/timer.cc new file mode 100644 index 0000000..c6f7e63 --- /dev/null +++ b/examples/asio/tutorial/timer3/timer.cc @@ -0,0 +1,29 @@ +#include "muduo/net/EventLoop.h" + +#include + +void print(muduo::net::EventLoop* loop, int* count) +{ + if (*count < 5) + { + std::cout << *count << "\n"; + ++(*count); + + loop->runAfter(1, std::bind(print, loop, count)); + } + else + { + loop->quit(); + } +} + +int main() +{ + muduo::net::EventLoop loop; + int count = 0; + // Note: loop.runEvery() is better for this use case. + loop.runAfter(1, std::bind(print, &loop, &count)); + loop.loop(); + std::cout << "Final count is " << count << "\n"; +} + diff --git a/examples/asio/tutorial/timer4/timer.cc b/examples/asio/tutorial/timer4/timer.cc new file mode 100644 index 0000000..ed42fe2 --- /dev/null +++ b/examples/asio/tutorial/timer4/timer.cc @@ -0,0 +1,47 @@ +#include "muduo/net/EventLoop.h" + +#include + +class Printer : muduo::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop) + : loop_(loop), + count_(0) + { + // Note: loop.runEvery() is better for this use case. + loop_->runAfter(1, std::bind(&Printer::print, this)); + } + + ~Printer() + { + std::cout << "Final count is " << count_ << "\n"; + } + + void print() + { + if (count_ < 5) + { + std::cout << count_ << "\n"; + ++count_; + + loop_->runAfter(1, std::bind(&Printer::print, this)); + } + else + { + loop_->quit(); + } + } + +private: + muduo::net::EventLoop* loop_; + int count_; +}; + +int main() +{ + muduo::net::EventLoop loop; + Printer printer(&loop); + loop.loop(); +} + diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc new file mode 100644 index 0000000..ea54974 --- /dev/null +++ b/examples/asio/tutorial/timer5/timer.cc @@ -0,0 +1,74 @@ +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +#include + +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; // make sure printer lives longer than loops, to avoid + // race condition of calling print2() on destructed object. + muduo::net::EventLoop loop; + muduo::net::EventLoopThread loopThread; + muduo::net::EventLoop* loopInAnotherThread = loopThread.startLoop(); + printer.reset(new Printer(&loop, loopInAnotherThread)); + loop.loop(); +} + diff --git a/examples/asio/tutorial/timer6/timer.cc b/examples/asio/tutorial/timer6/timer.cc new file mode 100644 index 0000000..aa6e752 --- /dev/null +++ b/examples/asio/tutorial/timer6/timer.cc @@ -0,0 +1,114 @@ +#include "muduo/base/Mutex.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +#include + +// +// 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; // make sure printer lives longer than loops, to avoid + // race condition of calling print2() on destructed object. + muduo::net::EventLoop loop; + muduo::net::EventLoopThread loopThread; + muduo::net::EventLoop* loopInAnotherThread = loopThread.startLoop(); + printer.reset(new Printer(&loop, loopInAnotherThread)); + loop.loop(); +} + diff --git a/examples/cdns/Resolver.cc b/examples/cdns/Resolver.cc new file mode 100644 index 0000000..8a01253 --- /dev/null +++ b/examples/cdns/Resolver.cc @@ -0,0 +1,201 @@ +#include "examples/cdns/Resolver.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include +#include +#include // inet_ntop +#include + +#include +#include +#include + +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(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(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(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(data)->onSockStateChange(sockfd, read, write); +} + diff --git a/examples/cdns/Resolver.h b/examples/cdns/Resolver.h new file mode 100644 index 0000000..f33b47d --- /dev/null +++ b/examples/cdns/Resolver.h @@ -0,0 +1,77 @@ +#ifndef MUDUO_EXAMPLES_CDNS_RESOLVER_H +#define MUDUO_EXAMPLES_CDNS_RESOLVER_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Timestamp.h" +#include "muduo/net/InetAddress.h" + +#include +#include +#include + +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 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> ChannelList; + ChannelList channels_; + + void onRead(int sockfd, muduo::Timestamp t); + void onTimer(); + void onQueryResult(int status, struct hostent* result, const Callback& cb); + void onSockCreate(int sockfd, int type); + void onSockStateChange(int sockfd, bool read, bool write); + + static void ares_host_callback(void* data, int status, int timeouts, struct hostent* hostent); + static int ares_sock_create_callback(int sockfd, int type, void* data); + static void ares_sock_state_callback(void* data, int sockfd, int read, int write); +}; +} // namespace cdns + +#endif // MUDUO_EXAMPLES_CDNS_RESOLVER_H diff --git a/examples/cdns/dns.cc b/examples/cdns/dns.cc new file mode 100644 index 0000000..865503a --- /dev/null +++ b/examples/cdns/dns.cc @@ -0,0 +1,51 @@ +#include "examples/cdns/Resolver.h" +#include "muduo/net/EventLoop.h" +#include + +using namespace muduo; +using namespace muduo::net; +using namespace cdns; + +EventLoop* g_loop; +int count = 0; +int total = 0; + +void quit() +{ + g_loop->quit(); +} + +void resolveCallback(const string& host, const InetAddress& addr) +{ + printf("resolveCallback %s -> %s\n", host.c_str(), addr.toIpPort().c_str()); + if (++count == total) + quit(); +} + +void resolve(Resolver* res, const string& host) +{ + res->resolve(host, std::bind(&resolveCallback, host, _1)); +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + loop.runAfter(10, quit); + g_loop = &loop; + Resolver resolver(&loop, + argc == 1 ? Resolver::kDNSonly : Resolver::kDNSandHostsFile); + if (argc == 1) + { + total = 3; + resolve(&resolver, "www.chenshuo.com"); + resolve(&resolver, "www.example.com"); + resolve(&resolver, "www.google.com"); + } + else + { + total = argc-1; + for (int i = 1; i < argc; ++i) + resolve(&resolver, argv[i]); + } + loop.loop(); +} diff --git a/examples/curl/Curl.cc b/examples/curl/Curl.cc new file mode 100644 index 0000000..444ab32 --- /dev/null +++ b/examples/curl/Curl.cc @@ -0,0 +1,270 @@ +#include "examples/curl/Curl.h" +#include "muduo/base/Logging.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include +#include + +using namespace curl; +using namespace muduo; +using namespace muduo::net; + +static void dummy(const std::shared_ptr&) +{ +} + +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(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(userp); + req->dataCallback(buffer, static_cast(nmemb)); + return nmemb; +} + +size_t Request::headerData(char* buffer, size_t size, size_t nmemb, void* userp) +{ + assert(size == 1); + Request* req = static_cast(userp); + req->headerCallback(buffer, static_cast(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(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(socketp); + assert(req->getChannel() == ch); + req->removeChannel(); + ch = NULL; + curl_multi_assign(curl->curlm_, fd, ch); + } + else + { + muduo::net::Channel* ch = static_cast(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(userp); + LOG_DEBUG << curl << " " << ms << " ms"; + curl->loop_->runAfter(static_cast(ms)/1000.0, std::bind(&Curl::onTimer, curl)); + return 0; +} + +Curl::Curl(EventLoop* loop) + : loop_(loop), + curlm_(CHECK_NOTNULL(curl_multi_init())), + runningHandles_(0), + prevRunningHandles_(0) +{ + curl_multi_setopt(curlm_, CURLMOPT_SOCKETFUNCTION, &Curl::socketCallback); + curl_multi_setopt(curlm_, CURLMOPT_SOCKETDATA, this); + curl_multi_setopt(curlm_, CURLMOPT_TIMERFUNCTION, &Curl::timerCallback); + curl_multi_setopt(curlm_, CURLMOPT_TIMERDATA, this); +} + +Curl::~Curl() +{ + curl_multi_cleanup(curlm_); +} + +RequestPtr Curl::getUrl(StringArg url) +{ + RequestPtr req(new Request(this, url.c_str())); + return req; +} + +void Curl::onTimer() +{ + CURLMcode rc = CURLM_OK; + do { + LOG_TRACE; + rc = curl_multi_socket_action(curlm_, CURL_SOCKET_TIMEOUT, 0, &runningHandles_); + LOG_TRACE << rc << " " << runningHandles_; + } while (rc == CURLM_CALL_MULTI_PERFORM); + checkFinish(); +} + +void Curl::onRead(int fd) +{ + CURLMcode rc = CURLM_OK; + do { + LOG_TRACE << fd; + rc = curl_multi_socket_action(curlm_, fd, CURL_POLL_IN, &runningHandles_); + LOG_TRACE << fd << " " << rc << " " << runningHandles_; + } while (rc == CURLM_CALL_MULTI_PERFORM); + checkFinish(); +} + +void Curl::onWrite(int fd) +{ + CURLMcode rc = CURLM_OK; + do { + LOG_TRACE << fd; + rc = curl_multi_socket_action(curlm_, fd, CURL_POLL_OUT, &runningHandles_); + LOG_TRACE << fd << " " << rc << " " << runningHandles_; + } while (rc == CURLM_CALL_MULTI_PERFORM); + checkFinish(); +} + +void Curl::checkFinish() +{ + if (prevRunningHandles_ > runningHandles_ || runningHandles_ == 0) + { + CURLMsg* msg = NULL; + int left = 0; + while ( (msg = curl_multi_info_read(curlm_, &left)) != NULL) + { + if (msg->msg == CURLMSG_DONE) + { + CURL* c = msg->easy_handle; + CURLcode res = msg->data.result; + Request* req = NULL; + curl_easy_getinfo(c, CURLINFO_PRIVATE, &req); + assert(req->getCurl() == c); + LOG_TRACE << req << " done"; + req->done(res); + } + } + } + prevRunningHandles_ = runningHandles_; +} diff --git a/examples/curl/Curl.h b/examples/curl/Curl.h new file mode 100644 index 0000000..23cfd95 --- /dev/null +++ b/examples/curl/Curl.h @@ -0,0 +1,143 @@ +#ifndef MUDUO_EXAMPLES_CURL_CURL_H +#define MUDUO_EXAMPLES_CURL_CURL_H + +#include "muduo/base/noncopyable.h" +#include "muduo/base/StringPiece.h" + +#include "muduo/net/Callbacks.h" + +extern "C" +{ +typedef void CURLM; +typedef void CURL; +} + +namespace muduo +{ +namespace net +{ +class Channel; +class EventLoop; +} +} + +namespace curl +{ + +class Curl; + +class Request : public std::enable_shared_from_this, + muduo::noncopyable +{ + public: + typedef std::function DataCallback; + typedef std::function 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 + int setopt(OPT opt, long p) + { + return curl_easy_setopt(curl_, opt, p); + } + + template + int setopt(OPT opt, const char* p) + { + return curl_easy_setopt(curl_, opt, p); + } + + template + int setopt(OPT opt, void* p) + { + return curl_easy_setopt(curl_, opt, p); + } + + template + 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 channel_; + DataCallback dataCb_; + DataCallback headerCb_; + DoneCallback doneCb_; +}; + +typedef std::shared_ptr RequestPtr; + +class Curl : muduo::noncopyable +{ + public: + + enum Option + { + kCURLnossl = 0, + kCURLssl = 1, + }; + + explicit Curl(muduo::net::EventLoop* loop); + ~Curl(); + + RequestPtr getUrl(muduo::StringArg url); + + static void initialize(Option opt = kCURLnossl); + + // internal + CURLM* getCurlm() { return curlm_; } + muduo::net::EventLoop* getLoop() { return loop_; } + + private: + void onTimer(); + void onRead(int fd); + void onWrite(int fd); + void checkFinish(); + + static int socketCallback(CURL*, int, int, void*, void*); + static int timerCallback(CURLM*, long, void*); + + muduo::net::EventLoop* loop_; + CURLM* curlm_; + int runningHandles_; + int prevRunningHandles_; +}; + +} // namespace curl + +#endif // MUDUO_EXAMPLES_CURL_CURL_H diff --git a/examples/curl/README b/examples/curl/README new file mode 100644 index 0000000..8ddbcf8 --- /dev/null +++ b/examples/curl/README @@ -0,0 +1,6 @@ +This is a proof-of-concept implementation of muduo-curl bridge. +It demostrates the simplest use case of curl with muduo. + +Note: +1. DNS resolving could be blocking, if your curl is not built with c-ares. +2. Request object should survive doneCallback. diff --git a/examples/curl/download.cc b/examples/curl/download.cc new file mode 100644 index 0000000..8767cbd --- /dev/null +++ b/examples/curl/download.cc @@ -0,0 +1,207 @@ +// Concurrent downloading one file from HTTP + +#include "examples/curl/Curl.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include +#include + +using namespace muduo; +using namespace muduo::net; + +typedef std::shared_ptr FilePtr; + +template +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 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 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> pieces_; + int concurrent_; + + const static int kConcurrent = 4; +}; + +int main(int argc, char* argv[]) +{ + EventLoop loop; + curl::Curl::initialize(curl::Curl::kCURLssl); + string url = argc > 1 ? argv[1] : "https://chenshuo-public.s3.amazonaws.com/pdf/allinone.pdf"; + Downloader d(&loop, url); + loop.loop(); +} diff --git a/examples/curl/mcurl.cc b/examples/curl/mcurl.cc new file mode 100644 index 0000000..6375fd2 --- /dev/null +++ b/examples/curl/mcurl.cc @@ -0,0 +1,48 @@ +#include "examples/curl/Curl.h" +#include "muduo/net/EventLoop.h" +#include + +using namespace muduo::net; + +EventLoop* g_loop = NULL; + +void onData(const char* data, int len) +{ + printf("len %d\n", len); +} + +void done(curl::Request* c, int code) +{ + printf("done %p %s %d\n", c, c->getEffectiveUrl(), code); +} + +void done2(curl::Request* c, int code) +{ + printf("done2 %p %s %d %d\n", c, c->getRedirectUrl(), c->getResponseCode(), code); + // g_loop->quit(); +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + g_loop = &loop; + loop.runAfter(30.0, std::bind(&EventLoop::quit, &loop)); + curl::Curl::initialize(curl::Curl::kCURLssl); + curl::Curl curl(&loop); + + curl::RequestPtr req = curl.getUrl("http://chenshuo.com"); + req->setDataCallback(onData); + req->setDoneCallback(done); + + curl::RequestPtr req2 = curl.getUrl("https://github.com"); + // req2->allowRedirect(5); + req2->setDataCallback(onData); + req2->setDoneCallback(done); + + curl::RequestPtr req3 = curl.getUrl("http://example.com"); + // req3->allowRedirect(5); + req3->setDataCallback(onData); + req3->setDoneCallback(done2); + + loop.loop(); +} diff --git a/examples/fastcgi/fastcgi.cc b/examples/fastcgi/fastcgi.cc new file mode 100644 index 0000000..7017e92 --- /dev/null +++ b/examples/fastcgi/fastcgi.cc @@ -0,0 +1,247 @@ +#include "examples/fastcgi/fastcgi.h" +#include "muduo/base/Logging.h" +#include "muduo/net/Endian.h" + +struct FastCgiCodec::RecordHeader +{ + uint8_t version; + uint8_t type; + uint16_t id; + uint16_t length; + uint8_t padding; + uint8_t unused; +}; + +const unsigned FastCgiCodec::kRecordHeader = static_cast(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(-1)) + return false; + uint32_t valueLen = readLen(); + if (valueLen == static_cast(-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(response->readableBytes())), + static_cast(-response->readableBytes() & 7), + 0, + }; + response->prepend(&header, kRecordHeader); + response->append("\0\0\0\0\0\0\0\0", header.padding); + } + else + { + // FIXME: + } + + endStdout(response); + endRequest(response); +} + +bool FastCgiCodec::parseRequest(Buffer* buf) +{ + while (buf->readableBytes() >= kRecordHeader) + { + RecordHeader header; + memcpy(&header, buf->peek(), kRecordHeader); + header.id = sockets::networkToHost16(header.id); + header.length = sockets::networkToHost16(header.length); + size_t total = kRecordHeader + header.length + header.padding; + if (buf->readableBytes() >= total) + { + switch (header.type) + { + case kFcgiBeginRequest: + onBeginRequest(header, buf); + // FIXME: check + break; + case kFcgiParams: + onParams(buf->peek() + kRecordHeader, header.length); + // FIXME: check + break; + case kFcgiStdin: + onStdin(buf->peek() + kRecordHeader, header.length); + break; + case kFcgiData: + // FIXME: + break; + case kFcgiGetValues: + // FIXME: + break; + default: + // FIXME: + break; + } + buf->retrieve(total); + } + else + { + break; + } + } + return true; +} + +uint16_t readInt16(const void* p) +{ + uint16_t be16 = 0; + ::memcpy(&be16, p, sizeof be16); + return sockets::networkToHost16(be16); +} + +bool FastCgiCodec::onBeginRequest(const RecordHeader& header, const Buffer* buf) +{ + assert(buf->readableBytes() >= header.length); + assert(header.type == kFcgiBeginRequest); + + if (header.length >= kRecordHeader) + { + uint16_t role = readInt16(buf->peek()+kRecordHeader); + uint8_t flags = buf->peek()[kRecordHeader + sizeof(int16_t)]; + if (role == kFcgiResponder) + { + keepConn_ = flags == kFcgiKeepConn; + return true; + } + } + return false; +} diff --git a/examples/fastcgi/fastcgi.h b/examples/fastcgi/fastcgi.h new file mode 100644 index 0000000..d630492 --- /dev/null +++ b/examples/fastcgi/fastcgi.h @@ -0,0 +1,68 @@ +#ifndef MUDUO_EXAMPLES_FASTCGI_FASTCGI_H +#define MUDUO_EXAMPLES_FASTCGI_FASTCGI_H + +#include "muduo/net/TcpConnection.h" +#include + +// 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 ParamMap; + typedef std::function Callback; + + explicit FastCgiCodec(const Callback& cb) + : cb_(cb), + gotRequest_(false), + keepConn_(false) + { + } + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp receiveTime) + { + parseRequest(buf); + if (gotRequest_) + { + cb_(conn, params_, &stdin_); + stdin_.retrieveAll(); + paramsStream_.retrieveAll(); + params_.clear(); + gotRequest_ = false; + if (!keepConn_) + { + conn->shutdown(); + } + } + } + + static void respond(muduo::net::Buffer* response); + + private: + struct RecordHeader; + bool parseRequest(muduo::net::Buffer* buf); + bool onBeginRequest(const RecordHeader& header, const muduo::net::Buffer* buf); + void onStdin(const char* content, uint16_t length); + bool onParams(const char* content, uint16_t length); + bool parseAllParams(); + uint32_t readLen(); + + static void endStdout(muduo::net::Buffer* buf); + static void endRequest(muduo::net::Buffer* buf); + + Callback cb_; + bool gotRequest_; + bool keepConn_; + muduo::net::Buffer stdin_; + muduo::net::Buffer paramsStream_; + ParamMap params_; + + const static unsigned kRecordHeader; +}; + +#endif // MUDUO_EXAMPLES_FASTCGI_FASTCGI_H diff --git a/examples/fastcgi/fastcgi_test.cc b/examples/fastcgi/fastcgi_test.cc new file mode 100644 index 0000000..1c7970d --- /dev/null +++ b/examples/fastcgi/fastcgi_test.cc @@ -0,0 +1,73 @@ +#include "examples/fastcgi/fastcgi.h" +#include "examples/sudoku/sudoku.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +const string kPath = "/sudoku/"; + +void onRequest(const TcpConnectionPtr& conn, + FastCgiCodec::ParamMap& params, + Buffer* in) +{ + string uri = params["REQUEST_URI"]; + LOG_INFO << conn->name() << ": " << uri; + + for (FastCgiCodec::ParamMap::const_iterator it = params.begin(); + it != params.end(); ++it) + { + LOG_DEBUG << it->first << " = " << it->second; + } + if (in->readableBytes() > 0) + LOG_DEBUG << "stdin " << in->retrieveAllAsString(); + Buffer response; + response.append("Context-Type: text/plain\r\n\r\n"); + if (uri.size() == kCells + kPath.size() && uri.find(kPath) == 0) + { + response.append(solveSudoku(uri.substr(kPath.size()))); + } + else + { + // FIXME: set http status code 400 + response.append("bad request"); + } + + FastCgiCodec::respond(&response); + conn->send(&response); +} + +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + typedef std::shared_ptr 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(port)); + LOG_INFO << "Sudoku FastCGI listens on " << addr.toIpPort() + << " threads " << threads; + muduo::net::EventLoop loop; + TcpServer server(&loop, addr, "FastCGI"); + server.setConnectionCallback(onConnection); + server.setThreadNum(threads); + server.start(); + loop.loop(); +} diff --git a/examples/fastcgi/nginx.conf b/examples/fastcgi/nginx.conf new file mode 100644 index 0000000..7abcc93 --- /dev/null +++ b/examples/fastcgi/nginx.conf @@ -0,0 +1,110 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + +events { + worker_connections 1024; +} + +http { + #include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + access_log off; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + upstream muduo_backend { + server localhost:19981; + #server localhost:19982; + keepalive 32; + } + + server { + listen 10080; + server_name localhost; + + #access_log logs/host.access.log main; + + location / { + root html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # pass /sudoku/ to muduo FastCGI server listening on 127.0.0.1:19981 + # + location /sudoku/ { + fastcgi_keep_conn on; + fastcgi_pass muduo_backend; + #include fastcgi_params; + #fastcgi_param QUERY_STRING $query_string; + #fastcgi_param REQUEST_METHOD $request_method; + #fastcgi_param CONTENT_TYPE $content_type; + #fastcgi_param CONTENT_LENGTH $content_length; + + #fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + #fastcgi_param DOCUMENT_URI $document_uri; + #fastcgi_param DOCUMENT_ROOT $document_root; + #fastcgi_param SERVER_PROTOCOL $server_protocol; + #fastcgi_param HTTPS $https if_not_empty; + + #fastcgi_param GATEWAY_INTERFACE CGI/1.1; + #fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + + #fastcgi_param REMOTE_ADDR $remote_addr; + #fastcgi_param REMOTE_PORT $remote_port; + #fastcgi_param SERVER_ADDR $server_addr; + #fastcgi_param SERVER_PORT $server_port; + #fastcgi_param SERVER_NAME $server_name; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} + } +} diff --git a/examples/filetransfer/download.cc b/examples/filetransfer/download.cc new file mode 100644 index 0000000..fb0b5a3 --- /dev/null +++ b/examples/filetransfer/download.cc @@ -0,0 +1,70 @@ +#include +#include + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +using namespace muduo; +using namespace muduo::net; + +const char* g_file = NULL; + +// FIXME: use FileUtil::readFile() +string readFile(const char* filename) +{ + string content; + FILE* fp = ::fopen(filename, "rb"); + if (fp) { + // inefficient!!! + const int kBufSize = 1024 * 1024; + char iobuf[kBufSize]; + ::setbuffer(fp, iobuf, sizeof iobuf); + + char buf[kBufSize]; + size_t nread = 0; + while ((nread = ::fread(buf, 1, sizeof buf, fp)) > 0) { + content.append(buf, nread); + } + ::fclose(fp); + } + return content; +} + +void onHighWaterMark(const TcpConnectionPtr& conn, size_t len) +{ + LOG_INFO << "HighWaterMark " << len; +} + +void onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) { + LOG_INFO << "FileServer - Sending file " << g_file << " to " + << conn->peerAddress().toIpPort(); + conn->setHighWaterMarkCallback(onHighWaterMark, 64 * 1024); + string fileContent = readFile(g_file); + conn->send(fileContent); + conn->shutdown(); + LOG_INFO << "FileServer - done"; + } +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + g_file = argv[1]; + + EventLoop loop; + InetAddress listenAddr(2021); + TcpServer server(&loop, listenAddr, "FileServer"); + server.setConnectionCallback(onConnection); + server.start(); + loop.loop(); + } else { + fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); + } +} diff --git a/examples/filetransfer/download2.cc b/examples/filetransfer/download2.cc new file mode 100644 index 0000000..a1c6c40 --- /dev/null +++ b/examples/filetransfer/download2.cc @@ -0,0 +1,81 @@ +#include +#include + +#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(nread)); + } else { + conn->shutdown(); + LOG_INFO << "FileServer - no such file"; + } + } else { + if (!conn->getContext().empty()) { + FILE* fp = boost::any_cast(conn->getContext()); + if (fp) { + ::fclose(fp); + } + } + } +} + +void onWriteComplete(const TcpConnectionPtr& conn) +{ + FILE* fp = boost::any_cast(conn->getContext()); + char buf[kBufSize]; + size_t nread = ::fread(buf, 1, sizeof buf, fp); + if (nread > 0) { + conn->send(buf, static_cast(nread)); + } else { + ::fclose(fp); + fp = NULL; + conn->setContext(fp); + conn->shutdown(); + LOG_INFO << "FileServer - done"; + } +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + g_file = argv[1]; + + EventLoop loop; + InetAddress listenAddr(2021); + TcpServer server(&loop, listenAddr, "FileServer"); + server.setConnectionCallback(onConnection); + server.setWriteCompleteCallback(onWriteComplete); + server.start(); + loop.loop(); + } else { + fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); + } +} diff --git a/examples/filetransfer/download3.cc b/examples/filetransfer/download3.cc new file mode 100644 index 0000000..6962e6a --- /dev/null +++ b/examples/filetransfer/download3.cc @@ -0,0 +1,73 @@ +#include +#include + +#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 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(nread)); + } else { + conn->shutdown(); + LOG_INFO << "FileServer - no such file"; + } + } +} + +void onWriteComplete(const TcpConnectionPtr& conn) +{ + const FilePtr& fp = boost::any_cast(conn->getContext()); + char buf[kBufSize]; + size_t nread = ::fread(buf, 1, sizeof buf, get_pointer(fp)); + if (nread > 0) { + conn->send(buf, static_cast(nread)); + } else { + conn->shutdown(); + LOG_INFO << "FileServer - done"; + } +} + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + if (argc > 1) { + g_file = argv[1]; + + EventLoop loop; + InetAddress listenAddr(2021); + TcpServer server(&loop, listenAddr, "FileServer"); + server.setConnectionCallback(onConnection); + server.setWriteCompleteCallback(onWriteComplete); + server.start(); + loop.loop(); + } else { + fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]); + } +} diff --git a/examples/filetransfer/loadtest/Client.java b/examples/filetransfer/loadtest/Client.java new file mode 100644 index 0000000..9ae0629 --- /dev/null +++ b/examples/filetransfer/loadtest/Client.java @@ -0,0 +1,62 @@ +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; + +public class Client { + + private static final class PipelineFactory implements ChannelPipelineFactory { + private final int kMinLength; + private final int kMaxLength; + private final CountDownLatch latch; + Random random = new Random(); + + private PipelineFactory(int kMinLength, int kMaxLength, CountDownLatch latch) { + this.kMinLength = kMinLength; + this.kMaxLength = kMaxLength; + this.latch = latch; + assert kMinLength <= kMaxLength; + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + int variance = random.nextInt(kMaxLength - kMinLength + 1); + int maxLength = kMinLength + variance; + return Channels.pipeline(new Handler(maxLength, latch)); + } + } + + static final int kClients = 500; + static final int kMB = 1024 * 1024; + static final int kMinLength = 1 * kMB; + static final int kMaxLength = 6 * kMB; + + public static void main(String[] args) throws Exception { + ChannelFactory channelFactory = new NioClientSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool()); + long start = System.currentTimeMillis(); + + final CountDownLatch latch = new CountDownLatch(kClients); + ChannelPipelineFactory pipelineFactory = new PipelineFactory(kMinLength, kMaxLength, latch); + for (int i = 0; i < kClients; ++i) { + ClientBootstrap bootstrap = new ClientBootstrap(channelFactory); + bootstrap.setPipelineFactory(pipelineFactory); + bootstrap.connect(new InetSocketAddress(args[0], 2021)); + } + + latch.await(); + + System.out.println(Thread.currentThread().getId() + " All done. " + + (System.currentTimeMillis() - start)); + System.exit(0); + } + +} diff --git a/examples/filetransfer/loadtest/Handler.java b/examples/filetransfer/loadtest/Handler.java new file mode 100644 index 0000000..115214d --- /dev/null +++ b/examples/filetransfer/loadtest/Handler.java @@ -0,0 +1,68 @@ +import java.math.BigInteger; +import java.security.MessageDigest; +import java.util.concurrent.CountDownLatch; + +import org.jboss.netty.buffer.BigEndianHeapChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; + +public class Handler extends SimpleChannelUpstreamHandler { + + private static int created = 0; + private int received = 0; + private final int maxLength; + private int id; + private CountDownLatch latch; + private MessageDigest digest; + + public Handler(int maxLength, CountDownLatch latch) throws Exception { + this.id = created++; + this.maxLength = maxLength; + this.latch = latch; + this.digest = MessageDigest.getInstance("MD5"); + System.out.println("Handler tid=" + Thread.currentThread().getId() + " " + id); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + System.out.println("channelConnected tid=" + Thread.currentThread().getId() + " " + id); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + byte[] md5 = digest.digest(); + BigInteger bigInt = new BigInteger(1, md5); + System.out.println("channelDisconnected tid=" + Thread.currentThread().getId() + " " + id + + " got " + + received + " " + bigInt.toString(16)); + latch.countDown(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + BigEndianHeapChannelBuffer message = (BigEndianHeapChannelBuffer) e.getMessage(); + // System.out.println("messageReceived " + ctx.getChannel() + message.readableBytes()); + received += message.readableBytes(); + digest.update(message.array(), message.readerIndex(), message.readableBytes()); + if (received > maxLength) { + System.out.println("messageReceived tid=" + Thread.currentThread().getId() + + " " + id + " got " + received); + ctx.getChannel().close(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + e.getCause().printStackTrace(); + + Channel ch = e.getChannel(); + ch.close(); + latch.countDown(); + } +} diff --git a/examples/hub/README b/examples/hub/README new file mode 100644 index 0000000..107c691 --- /dev/null +++ b/examples/hub/README @@ -0,0 +1,5 @@ +hub - a server for broadcasting +pubsub - a client library of hub +pub - a command line tool for publishing content on a topic +sub - a demo tool for subscribing a topic + diff --git a/examples/hub/codec.cc b/examples/hub/codec.cc new file mode 100644 index 0000000..67a462a --- /dev/null +++ b/examples/hub/codec.cc @@ -0,0 +1,53 @@ +#include "examples/hub/codec.h" + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +ParseResult pubsub::parseMessage(Buffer* buf, + string* cmd, + string* topic, + string* content) +{ + ParseResult result = kError; + const char* crlf = buf->findCRLF(); + if (crlf) + { + const char* space = std::find(buf->peek(), crlf, ' '); + if (space != crlf) + { + cmd->assign(buf->peek(), space); + topic->assign(space+1, crlf); + if (*cmd == "pub") + { + const char* start = crlf + 2; + crlf = buf->findCRLF(start); + if (crlf) + { + content->assign(start, crlf); + buf->retrieveUntil(crlf+2); + result = kSuccess; + } + else + { + result = kContinue; + } + } + else + { + buf->retrieveUntil(crlf+2); + result = kSuccess; + } + } + else + { + result = kError; + } + } + else + { + result = kContinue; + } + return result; +} + diff --git a/examples/hub/codec.h b/examples/hub/codec.h new file mode 100644 index 0000000..1fd3120 --- /dev/null +++ b/examples/hub/codec.h @@ -0,0 +1,27 @@ +#ifndef MUDUO_EXAMPLES_HUB_CODEC_H +#define MUDUO_EXAMPLES_HUB_CODEC_H + +// internal header file + +#include "muduo/base/Types.h" +#include "muduo/net/Buffer.h" + +namespace pubsub +{ +using muduo::string; + +enum ParseResult +{ + kError, + kSuccess, + kContinue, +}; + +ParseResult parseMessage(muduo::net::Buffer* buf, + string* cmd, + string* topic, + string* content); +} // namespace pubsub + +#endif // MUDUO_EXAMPLES_HUB_CODEC_H + diff --git a/examples/hub/hub.cc b/examples/hub/hub.cc new file mode 100644 index 0000000..a6faec2 --- /dev/null +++ b/examples/hub/hub.cc @@ -0,0 +1,217 @@ +#include "examples/hub/codec.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +namespace pubsub +{ + +typedef std::set 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::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 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(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(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(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::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 topics_; +}; + +} // namespace pubsub + +int main(int argc, char* argv[]) +{ + if (argc > 1) + { + uint16_t port = static_cast(atoi(argv[1])); + EventLoop loop; + if (argc > 2) + { + //int inspectPort = atoi(argv[2]); + } + pubsub::PubSubServer server(&loop, InetAddress(port)); + server.start(); + loop.loop(); + } + else + { + printf("Usage: %s pubsub_port [inspect_port]\n", argv[0]); + } +} + diff --git a/examples/hub/pub.cc b/examples/hub/pub.cc new file mode 100644 index 0000000..4a50e2d --- /dev/null +++ b/examples/hub/pub.cc @@ -0,0 +1,82 @@ +#include "examples/hub/pubsub.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" + +#include +#include + +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(atoi(hostport.c_str()+colon+1)); + g_topic = argv[2]; + g_content = argv[3]; + + string name = ProcessInfo::username()+"@"+ProcessInfo::hostname(); + name += ":" + ProcessInfo::pidString(); + + if (g_content == "-") + { + EventLoopThread loopThread; + g_loop = loopThread.startLoop(); + PubSubClient client(g_loop, InetAddress(hostip, port), name); + client.start(); + + string line; + while (getline(std::cin, line)) + { + client.publish(g_topic, line); + } + client.stop(); + CurrentThread::sleepUsec(1000*1000); + } + else + { + EventLoop loop; + g_loop = &loop; + PubSubClient client(g_loop, InetAddress(hostip, port), name); + client.setConnectionCallback(connection); + client.start(); + loop.loop(); + } + } + else + { + printf("Usage: %s hub_ip:port topic content\n", argv[0]); + } + } + else + { + printf("Usage: %s hub_ip:port topic content\n" + "Read contents from stdin:\n" + " %s hub_ip:port topic -\n", argv[0], argv[0]); + } +} diff --git a/examples/hub/pubsub.cc b/examples/hub/pubsub.cc new file mode 100644 index 0000000..1a6f1a7 --- /dev/null +++ b/examples/hub/pubsub.cc @@ -0,0 +1,106 @@ +#include "examples/hub/pubsub.h" +#include "examples/hub/codec.h" + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +PubSubClient::PubSubClient(EventLoop* loop, + const InetAddress& hubAddr, + const string& name) + : client_(loop, hubAddr, name) +{ + // FIXME: dtor is not thread safe + client_.setConnectionCallback( + std::bind(&PubSubClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&PubSubClient::onMessage, this, _1, _2, _3)); +} + +void PubSubClient::start() +{ + client_.connect(); +} + +void PubSubClient::stop() +{ + client_.disconnect(); +} + +bool PubSubClient::connected() const +{ + return conn_ && conn_->connected(); +} + +bool PubSubClient::subscribe(const string& topic, const SubscribeCallback& cb) +{ + string message = "sub " + topic + "\r\n"; + subscribeCallback_ = cb; + return send(message); +} + +void PubSubClient::unsubscribe(const string& topic) +{ + string message = "unsub " + topic + "\r\n"; + send(message); +} + + +bool PubSubClient::publish(const string& topic, const string& content) +{ + string message = "pub " + topic + "\r\n" + content + "\r\n"; + return send(message); +} + +void PubSubClient::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn_ = conn; + // FIXME: re-sub + } + else + { + conn_.reset(); + } + if (connectionCallback_) + { + connectionCallback_(this); + } +} + +void PubSubClient::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + ParseResult result = kSuccess; + while (result == kSuccess) + { + string cmd; + string topic; + string content; + result = parseMessage(buf, &cmd, &topic, &content); + if (result == kSuccess) + { + if (cmd == "pub" && subscribeCallback_) + { + subscribeCallback_(topic, content, receiveTime); + } + } + else if (result == kError) + { + conn->shutdown(); + } + } +} + +bool PubSubClient::send(const string& message) +{ + bool succeed = false; + if (conn_ && conn_->connected()) + { + conn_->send(message); + succeed = true; + } + return succeed; +} diff --git a/examples/hub/pubsub.h b/examples/hub/pubsub.h new file mode 100644 index 0000000..e6e5fc8 --- /dev/null +++ b/examples/hub/pubsub.h @@ -0,0 +1,47 @@ +#ifndef MUDUO_EXAMPLES_HUB_PUBSUB_H +#define MUDUO_EXAMPLES_HUB_PUBSUB_H + +#include "muduo/net/TcpClient.h" + +namespace pubsub +{ +using muduo::string; + +// FIXME: dtor is not thread safe +class PubSubClient : muduo::noncopyable +{ + public: + typedef std::function ConnectionCallback; + typedef std::function SubscribeCallback; + + PubSubClient(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& hubAddr, + const string& name); + void start(); + void stop(); + bool connected() const; + + void setConnectionCallback(const ConnectionCallback& cb) + { connectionCallback_ = cb; } + + bool subscribe(const string& topic, const SubscribeCallback& cb); + void unsubscribe(const string& topic); + bool publish(const string& topic, const string& content); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp receiveTime); + bool send(const string& message); + + muduo::net::TcpClient client_; + muduo::net::TcpConnectionPtr conn_; + ConnectionCallback connectionCallback_; + SubscribeCallback subscribeCallback_; +}; +} // namespace pubsub + +#endif // MUDUO_EXAMPLES_HUB_PUBSUB_H diff --git a/examples/hub/sub.cc b/examples/hub/sub.cc new file mode 100644 index 0000000..f5cabd2 --- /dev/null +++ b/examples/hub/sub.cc @@ -0,0 +1,69 @@ +#include "examples/hub/pubsub.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/net/EventLoop.h" + +#include +#include + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +EventLoop* g_loop = NULL; +std::vector 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::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(atoi(hostport.c_str()+colon+1)); + for (int i = 2; i < argc; ++i) + { + g_topics.push_back(argv[i]); + } + + EventLoop loop; + g_loop = &loop; + string name = ProcessInfo::username()+"@"+ProcessInfo::hostname(); + name += ":" + ProcessInfo::pidString(); + PubSubClient client(&loop, InetAddress(hostip, port), name); + client.setConnectionCallback(connection); + client.start(); + loop.loop(); + } + else + { + printf("Usage: %s hub_ip:port topic [topic ...]\n", argv[0]); + } + } + else + { + printf("Usage: %s hub_ip:port topic [topic ...]\n", argv[0]); + } +} diff --git a/examples/idleconnection/echo.cc b/examples/idleconnection/echo.cc new file mode 100644 index 0000000..48ccdad --- /dev/null +++ b/examples/idleconnection/echo.cc @@ -0,0 +1,99 @@ +#include "examples/idleconnection/echo.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include +#include + +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(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(conn->getContext())); + EntryPtr entry(weakEntry.lock()); + if (entry) + { + connectionBuckets_.back().insert(entry); + dumpConnectionBuckets(); + } +} + +void EchoServer::onTimer() +{ + connectionBuckets_.push_back(Bucket()); + dumpConnectionBuckets(); +} + +void EchoServer::dumpConnectionBuckets() const +{ + LOG_INFO << "size = " << connectionBuckets_.size(); + int idx = 0; + for (WeakConnectionList::const_iterator bucketI = connectionBuckets_.begin(); + bucketI != connectionBuckets_.end(); + ++bucketI, ++idx) + { + const Bucket& bucket = *bucketI; + printf("[%d] len = %zd : ", idx, bucket.size()); + for (const auto& it : bucket) + { + bool connectionDead = it->weakConn_.expired(); + printf("%p(%ld)%s, ", get_pointer(it), it.use_count(), + connectionDead ? " DEAD" : ""); + } + puts(""); + } +} + diff --git a/examples/idleconnection/echo.h b/examples/idleconnection/echo.h new file mode 100644 index 0000000..475bebb --- /dev/null +++ b/examples/idleconnection/echo.h @@ -0,0 +1,55 @@ +#ifndef MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H +#define MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H + +#include "muduo/net/TcpServer.h" +// #include + +#include +#include + +// 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 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 EntryPtr; + typedef std::weak_ptr WeakEntryPtr; + typedef std::unordered_set Bucket; + typedef boost::circular_buffer WeakConnectionList; + + muduo::net::TcpServer server_; + WeakConnectionList connectionBuckets_; +}; + +#endif // MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H diff --git a/examples/idleconnection/main.cc b/examples/idleconnection/main.cc new file mode 100644 index 0000000..86a5184 --- /dev/null +++ b/examples/idleconnection/main.cc @@ -0,0 +1,40 @@ +#include + +#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 > h; + std::shared_ptr x1(new int(10)); + std::shared_ptr x2(new int(10)); + h(x1); + assert(h(x1) != h(x2)); + x1 = x2; + assert(h(x1) == h(x2)); + x1.reset(); + assert(h(x1) != h(x2)); + x2.reset(); + assert(h(x1) == h(x2)); +} +*/ + +int main(int argc, char* argv[]) +{ + // testHash(); + EventLoop loop; + InetAddress listenAddr(2007); + int idleSeconds = 10; + if (argc > 1) { + idleSeconds = atoi(argv[1]); + } + LOG_INFO << "pid = " << getpid() << ", idle seconds = " << idleSeconds; + EchoServer server(&loop, listenAddr, idleSeconds); + server.start(); + loop.loop(); +} diff --git a/examples/idleconnection/sortedlist.cc b/examples/idleconnection/sortedlist.cc new file mode 100644 index 0000000..cc200e8 --- /dev/null +++ b/examples/idleconnection/sortedlist.cc @@ -0,0 +1,179 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/TcpServer.h" +#include +#include +#include + +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 WeakTcpConnectionPtr; + typedef std::list 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(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(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(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(conn->getContext()); + printf(" time %s\n", n.lastReceiveTime.toString().c_str()); + } + else + { + printf("expired\n"); + } + } +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + InetAddress listenAddr(2007); + int idleSeconds = 10; + if (argc > 1) + { + idleSeconds = atoi(argv[1]); + } + LOG_INFO << "pid = " << getpid() << ", idle seconds = " << idleSeconds; + EchoServer server(&loop, listenAddr, idleSeconds); + server.start(); + loop.loop(); +} + diff --git a/examples/maxconnection/echo.cc b/examples/maxconnection/echo.cc new file mode 100644 index 0000000..721190b --- /dev/null +++ b/examples/maxconnection/echo.cc @@ -0,0 +1,56 @@ +#include "examples/maxconnection/echo.h" + +#include "muduo/base/Logging.h" + +using namespace muduo; +using namespace muduo::net; + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int maxConnections) + : server_(loop, listenAddr, "EchoServer"), + numConnected_(0), + kMaxConnections_(maxConnections) +{ + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); +} + +void EchoServer::start() +{ + server_.start(); +} + +void EchoServer::onConnection(const TcpConnectionPtr& conn) +{ + LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + ++numConnected_; + if (numConnected_ > kMaxConnections_) + { + conn->shutdown(); + conn->forceCloseWithDelay(3.0); // > round trip of the whole Internet. + } + } + else + { + --numConnected_; + } + LOG_INFO << "numConnected = " << numConnected_; +} + +void EchoServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp time) +{ + string msg(buf->retrieveAllAsString()); + LOG_INFO << conn->name() << " echo " << msg.size() << " bytes at " << time.toString(); + conn->send(msg); +} + diff --git a/examples/maxconnection/echo.h b/examples/maxconnection/echo.h new file mode 100644 index 0000000..090cd6a --- /dev/null +++ b/examples/maxconnection/echo.h @@ -0,0 +1,28 @@ +#ifndef MUDUO_EXAMPLES_MAXCONNECTION_ECHO_H +#define MUDUO_EXAMPLES_MAXCONNECTION_ECHO_H + +#include "muduo/net/TcpServer.h" + +// RFC 862 +class EchoServer +{ + public: + EchoServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr, + int maxConnections); + + void start(); + + private: + void onConnection(const muduo::net::TcpConnectionPtr& conn); + + void onMessage(const muduo::net::TcpConnectionPtr& conn, + muduo::net::Buffer* buf, + muduo::Timestamp time); + + muduo::net::TcpServer server_; + int numConnected_; // should be atomic_int + const int kMaxConnections_; +}; + +#endif // MUDUO_EXAMPLES_MAXCONNECTION_ECHO_H diff --git a/examples/maxconnection/main.cc b/examples/maxconnection/main.cc new file mode 100644 index 0000000..25aaf9a --- /dev/null +++ b/examples/maxconnection/main.cc @@ -0,0 +1,26 @@ +#include "examples/maxconnection/echo.h" + +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +#include + +using namespace muduo; +using namespace muduo::net; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(2007); + int maxConnections = 5; + if (argc > 1) + { + maxConnections = atoi(argv[1]); + } + LOG_INFO << "maxConnections = " << maxConnections; + EchoServer server(&loop, listenAddr, maxConnections); + server.start(); + loop.loop(); +} + diff --git a/examples/memcached/README b/examples/memcached/README new file mode 100644 index 0000000..da9803b --- /dev/null +++ b/examples/memcached/README @@ -0,0 +1,19 @@ +Simple implementation of memcached protocol for both server and client side. +Not meant to replace memcached, but just sample code of network programming with muduo. + +Server limits: + - The memory management is not customized, just uses (tc)malloc. + - It doesn't control the memory footprint + - Unix domain socket is not supported + - Only listen on one TCP port + +Server goals: + - Pass as many feature tests as possible + - Prefer simplicity over performance + +TODO: + - incr/decr + - UDP + - Binary protocol + - expiration + - LRU diff --git a/examples/memcached/client/bench.cc b/examples/memcached/client/bench.cc new file mode 100644 index 0000000..c991898 --- /dev/null +++ b/examples/memcached/client/bench.cc @@ -0,0 +1,239 @@ +#include "muduo/base/CountDownLatch.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/TcpClient.h" + +#include +#include + +#include + +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(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(&tcpport), "TCP port") + ("ip,i", po::value(&hostIp), "Host IP") + ("threads,t", po::value(&threads), "Number of worker threads") + ("clients,c", po::value(&clients), "Number of concurrent clients") + ("requests,r", po::value(&requests), "Number of requests per clients") + ("keys,k", po::value(&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> holder; + for (int i = 0; i < clients; ++i) + { + snprintf(buf, sizeof buf, "%d-", i+1); + holder.emplace_back(new Client(buf, + pool.getNextLoop(), + serverAddr, + op, + requests, + keys, + valuelen, + &connected, + &finished)); + } + connected.wait(); + LOG_WARN << clients << " clients all connected"; + Timestamp start = Timestamp::now(); + for (int i = 0; i < clients; ++i) + { + holder[i]->send(); + } + finished.wait(); + Timestamp end = Timestamp::now(); + LOG_WARN << "All finished"; + double seconds = timeDifference(end, start); + LOG_WARN << seconds << " sec"; + LOG_WARN << 1.0 * clients * requests / seconds << " QPS"; +} diff --git a/examples/memcached/server/Item.cc b/examples/memcached/server/Item.cc new file mode 100644 index 0000000..f3cc089 --- /dev/null +++ b/examples/memcached/server/Item.cc @@ -0,0 +1,63 @@ +#include "examples/memcached/server/Item.h" + +#include "muduo/base/LogStream.h" +#include "muduo/net/Buffer.h" + +#include + +#include // memcpy +#include + +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(::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(len); + assert(receivedBytes_ <= totalLen()); +} + +void Item::output(Buffer* out, bool needCas) const +{ + out->append("VALUE "); + out->append(data_, keylen_); + LogStream buf; + buf << ' ' << flags_ << ' ' << valuelen_-2; + if (needCas) + { + buf << ' ' << cas_; + } + buf << "\r\n"; + out->append(buf.buffer().data(), buf.buffer().length()); + out->append(value(), valuelen_); +} + +void Item::resetKey(StringPiece k) +{ + assert(k.size() <= 250); + keylen_ = k.size(); + receivedBytes_ = 0; + append(k.data(), k.size()); + hash_ = boost::hash_range(k.begin(), k.end()); +} diff --git a/examples/memcached/server/Item.h b/examples/memcached/server/Item.h new file mode 100644 index 0000000..7fa865f --- /dev/null +++ b/examples/memcached/server/Item.h @@ -0,0 +1,129 @@ +#ifndef MUDUO_EXAMPLES_MEMCACHED_SERVER_ITEM_H +#define MUDUO_EXAMPLES_MEMCACHED_SERVER_ITEM_H + +#include "muduo/base/Atomic.h" +#include "muduo/base/StringPiece.h" +#include "muduo/base/Types.h" + +#include + +namespace muduo +{ +namespace net +{ +class Buffer; +} +} + +class Item; +typedef std::shared_ptr ItemPtr; // TODO: use unique_ptr +typedef std::shared_ptr 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(keyArg, flagsArg, exptimeArg, valuelen, casArg); + //return ItemPtr(new Item(keyArg, flagsArg, exptimeArg, valuelen, casArg)); + } + + Item(muduo::StringPiece keyArg, + uint32_t flagsArg, + int exptimeArg, + int valuelen, + uint64_t casArg); + + ~Item() + { + ::free(data_); + } + + muduo::StringPiece key() const + { + return muduo::StringPiece(data_, keylen_); + } + + uint32_t flags() const + { + return flags_; + } + + int rel_exptime() const + { + return rel_exptime_; + } + + const char* value() const + { + return data_+keylen_; + } + + size_t valueLength() const + { + return valuelen_; + } + + uint64_t cas() const + { + return cas_; + } + + size_t hash() const + { + return hash_; + } + + void setCas(uint64_t casArg) + { + cas_ = casArg; + } + + size_t neededBytes() const + { + return totalLen() - receivedBytes_; + } + + void append(const char* data, size_t len); + + bool endsWithCRLF() const + { + return receivedBytes_ == totalLen() + && data_[totalLen()-2] == '\r' + && data_[totalLen()-1] == '\n'; + } + + void output(muduo::net::Buffer* out, bool needCas = false) const; + + void resetKey(muduo::StringPiece k); + + private: + int totalLen() const { return keylen_ + valuelen_; } + + int keylen_; + const uint32_t flags_; + const int rel_exptime_; + const int valuelen_; + int receivedBytes_; // FIXME: remove this member + uint64_t cas_; + size_t hash_; + char* data_; +}; + +#endif // MUDUO_EXAMPLES_MEMCACHED_SERVER_ITEM_H diff --git a/examples/memcached/server/MemcacheServer.cc b/examples/memcached/server/MemcacheServer.cc new file mode 100644 index 0000000..cb874bf --- /dev/null +++ b/examples/memcached/server/MemcacheServer.cc @@ -0,0 +1,174 @@ +#include "examples/memcached/server/MemcacheServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" + +using namespace muduo; +using namespace muduo::net; + +muduo::AtomicInt64 g_cas; + +MemcacheServer::Options::Options() +{ + memZero(this, sizeof(*this)); +} + +struct MemcacheServer::Stats +{ +}; + +MemcacheServer::MemcacheServer(muduo::net::EventLoop* loop, const Options& options) + : loop_(loop), + options_(options), + startTime_(::time(NULL)-1), + server_(loop, InetAddress(options.tcpport), "muduo-memcached"), + stats_(new Stats) +{ + server_.setConnectionCallback( + std::bind(&MemcacheServer::onConnection, this, _1)); +} + +MemcacheServer::~MemcacheServer() = default; + +void MemcacheServer::start() +{ + server_.start(); +} + +void MemcacheServer::stop() +{ + loop_->runAfter(3.0, std::bind(&EventLoop::quit, loop_)); +} + +bool MemcacheServer::storeItem(const ItemPtr& item, const Item::UpdatePolicy policy, bool* exists) +{ + assert(item->neededBytes() == 0); + MutexLock& mutex = shards_[item->hash() % kShards].mutex; + ItemMap& items = shards_[item->hash() % kShards].items; + MutexLockGuard lock(mutex); + ItemMap::const_iterator it = items.find(item); + *exists = it != items.end(); + if (policy == Item::kSet) + { + item->setCas(g_cas.incrementAndGet()); + if (*exists) + { + items.erase(it); + } + items.insert(item); + } + else + { + if (policy == Item::kAdd) + { + if (*exists) + { + return false; + } + else + { + item->setCas(g_cas.incrementAndGet()); + items.insert(item); + } + } + else if (policy == Item::kReplace) + { + if (*exists) + { + item->setCas(g_cas.incrementAndGet()); + items.erase(it); + items.insert(item); + } + else + { + return false; + } + } + else if (policy == Item::kAppend || policy == Item::kPrepend) + { + if (*exists) + { + const ConstItemPtr& oldItem = *it; + int newLen = static_cast(item->valueLength() + oldItem->valueLength() - 2); + ItemPtr newItem(Item::makeItem(item->key(), + oldItem->flags(), + oldItem->rel_exptime(), + newLen, + g_cas.incrementAndGet())); + if (policy == Item::kAppend) + { + newItem->append(oldItem->value(), oldItem->valueLength() - 2); + newItem->append(item->value(), item->valueLength()); + } + else + { + newItem->append(item->value(), item->valueLength() - 2); + newItem->append(oldItem->value(), oldItem->valueLength()); + } + assert(newItem->neededBytes() == 0); + assert(newItem->endsWithCRLF()); + items.erase(it); + items.insert(newItem); + } + else + { + return false; + } + } + else if (policy == Item::kCas) + { + if (*exists && (*it)->cas() == item->cas()) + { + item->setCas(g_cas.incrementAndGet()); + items.erase(it); + items.insert(item); + } + else + { + return false; + } + } + else + { + assert(false); + } + } + return true; +} + +ConstItemPtr MemcacheServer::getItem(const ConstItemPtr& key) const +{ + MutexLock& mutex = shards_[key->hash() % kShards].mutex; + const ItemMap& items = shards_[key->hash() % kShards].items; + MutexLockGuard lock(mutex); + ItemMap::const_iterator it = items.find(key); + return it != items.end() ? *it : ConstItemPtr(); +} + +bool MemcacheServer::deleteItem(const ConstItemPtr& key) +{ + MutexLock& mutex = shards_[key->hash() % kShards].mutex; + ItemMap& items = shards_[key->hash() % kShards].items; + MutexLockGuard lock(mutex); + return items.erase(key) == 1; +} + +void MemcacheServer::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + SessionPtr session(new Session(this, conn)); + MutexLockGuard lock(mutex_); + assert(sessions_.find(conn->name()) == sessions_.end()); + sessions_[conn->name()] = session; + // assert(sessions_.size() == stats_.current_conns); + } + else + { + MutexLockGuard lock(mutex_); + assert(sessions_.find(conn->name()) != sessions_.end()); + sessions_.erase(conn->name()); + // assert(sessions_.size() == stats_.current_conns); + } +} diff --git a/examples/memcached/server/MemcacheServer.h b/examples/memcached/server/MemcacheServer.h new file mode 100644 index 0000000..a418f01 --- /dev/null +++ b/examples/memcached/server/MemcacheServer.h @@ -0,0 +1,87 @@ +#ifndef MUDUO_EXAMPLES_MEMCACHED_SERVER_MEMCACHESERVER_H +#define MUDUO_EXAMPLES_MEMCACHED_SERVER_MEMCACHESERVER_H + +#include "examples/memcached/server/Item.h" +#include "examples/memcached/server/Session.h" + +#include "muduo/base/Mutex.h" +#include "muduo/net/TcpServer.h" +#include "examples/wordcount/hash.h" + +#include +#include +#include + +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 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 ItemMap; + + struct MapWithLock + { + ItemMap items; + mutable muduo::MutexLock mutex; + }; + + const static int kShards = 4096; + + std::array shards_; + + // NOT guarded by mutex_, but here because server_ has to destructs before + // sessions_ + muduo::net::TcpServer server_; + std::unique_ptr stats_ PT_GUARDED_BY(mutex_); +}; + +#endif // MUDUO_EXAMPLES_MEMCACHED_SERVER_MEMCACHESERVER_H diff --git a/examples/memcached/server/Session.cc b/examples/memcached/server/Session.cc new file mode 100644 index 0000000..58a0aff --- /dev/null +++ b/examples/memcached/server/Session.cc @@ -0,0 +1,423 @@ +#include "examples/memcached/server/Session.h" +#include "examples/memcached/server/MemcacheServer.h" + +#ifdef HAVE_TCMALLOC +#include +#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 +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(memchr(start, ' ', end - start)); + if (sp) + { + tok.set(start, static_cast(sp - start)); + next = sp; + } + else + { + tok.set(start, static_cast(end - next)); + next = end; + } + return true; +} + +struct Session::Reader +{ + Reader(Tokenizer::iterator& beg, Tokenizer::iterator end) + : first_(beg), + last_(end) + { + } + + template + 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(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(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(exptime); + if (exptime > 60*60*24*30) + { + rel_exptime = static_cast(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 [noreply]\r\n"); + } + else + { + needle_->resetKey(key); + if (owner_->deleteItem(needle_)) + { + reply("DELETED\r\n"); + } + else + { + reply("NOT_FOUND\r\n"); + } + } +} diff --git a/examples/memcached/server/Session.h b/examples/memcached/server/Session.h new file mode 100644 index 0000000..82ef13c --- /dev/null +++ b/examples/memcached/server/Session.h @@ -0,0 +1,114 @@ +#ifndef MUDUO_EXAMPLES_MEMCACHED_SERVER_SESSION_H +#define MUDUO_EXAMPLES_MEMCACHED_SERVER_SESSION_H + +#include "examples/memcached/server/Item.h" + +#include "muduo/base/Logging.h" + +#include "muduo/net/TcpConnection.h" + +#include + +using muduo::string; + +class MemcacheServer; + +class Session : public std::enable_shared_from_this, + 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 + bool operator()(InputIterator& next, InputIterator end, Token& tok); + }; + + typedef boost::tokenizer 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 SessionPtr; + +#endif // MUDUO_EXAMPLES_MEMCACHED_SERVER_SESSION_H diff --git a/examples/memcached/server/footprint_test.cc b/examples/memcached/server/footprint_test.cc new file mode 100644 index 0000000..9f2bace --- /dev/null +++ b/examples/memcached/server/footprint_test.cc @@ -0,0 +1,66 @@ +#include "examples/memcached/server/MemcacheServer.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/inspect/ProcessInspector.h" + +#include +#ifdef HAVE_TCMALLOC +#include +#include +#endif + +using namespace muduo::net; + +int main(int argc, char* argv[]) +{ +#ifdef HAVE_TCMALLOC + MallocExtension::Initialize(); +#endif + int items = argc > 1 ? atoi(argv[1]) : 10000; + int keylen = argc > 2 ? atoi(argv[2]) : 10; + int valuelen = argc > 3 ? atoi(argv[3]) : 100; + EventLoop loop; + MemcacheServer::Options options; + MemcacheServer server(&loop, options); + + printf("sizeof(Item) = %zd\npid = %d\nitems = %d\nkeylen = %d\nvaluelen = %d\n", + sizeof(Item), getpid(), items, keylen, valuelen); + char key[256] = { 0 }; + string value; + for (int i = 0; i < items; ++i) + { + snprintf(key, sizeof key, "%0*d", keylen, i); + value.assign(valuelen, "0123456789"[i % 10]); + ItemPtr item(Item::makeItem(key, 0, 0, valuelen+2, 1)); + item->append(value.data(), value.size()); + item->append("\r\n", 2); + assert(item->endsWithCRLF()); + bool exists = false; + bool stored = server.storeItem(item, Item::kAdd, &exists); + assert(stored); (void) stored; + assert(!exists); + } + Inspector::ArgList arg; + printf("==========\n%s\n", + ProcessInspector::overview(HttpRequest::kGet, arg).c_str()); + // TODO: print bytes per item, overhead percent + fflush(stdout); +#ifdef HAVE_TCMALLOC + char buf[8192]; + MallocExtension::instance()->GetStats(buf, sizeof buf); + printf("%s\n", buf); + HeapProfilerDump("end"); + +/* + // only works for tcmalloc_debug + int blocks = 0; + size_t total = 0; + int histogram[kMallocHistogramSize] = { 0, }; + MallocExtension::instance()->MallocMemoryStats(&blocks, &total, histogram); + printf("==========\nblocks = %d\ntotal = %zd\n", blocks, total); + for (int i = 0; i < kMallocHistogramSize; ++i) + { + printf("%d = %d\n", i, histogram[i]); + } +*/ +#endif +} diff --git a/examples/memcached/server/server.cc b/examples/memcached/server/server.cc new file mode 100644 index 0000000..162597f --- /dev/null +++ b/examples/memcached/server/server.cc @@ -0,0 +1,55 @@ +#include "examples/memcached/server/MemcacheServer.h" + +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThread.h" +#include "muduo/net/inspect/Inspector.h" + +#include + +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(&options->tcpport), "TCP port") + ("udpport,U", po::value(&options->udpport), "UDP port") + ("gperf,g", po::value(&options->gperfport), "port for gperftools") + ("threads,t", po::value(&options->threads), "Number of worker threads") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) + { + //printf("memcached 1.1.0\n"); + return false; + } + return true; +} + +int main(int argc, char* argv[]) +{ + EventLoop loop; + EventLoopThread inspectThread; + MemcacheServer::Options options; + if (parseCommandLine(argc, argv, &options)) + { + // FIXME: how to destruct it safely ? + new Inspector(inspectThread.startLoop(), InetAddress(options.gperfport), "memcached-debug"); + + MemcacheServer server(&loop, options); + server.setThreadNum(options.threads); + server.start(); + loop.loop(); + } +} + diff --git a/examples/multiplexer/demux.cc b/examples/multiplexer/demux.cc new file mode 100644 index 0000000..3222254 --- /dev/null +++ b/examples/multiplexer/demux.cc @@ -0,0 +1,240 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +typedef std::shared_ptr 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(*buf->peek()); + if (buf->readableBytes() < len + kHeaderLen) + { + break; + } + else + { + int connId = static_cast(buf->peek()[1]); + connId |= (static_cast(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(len), + static_cast(connId & 0xFF), + static_cast((connId & 0xFF00) >> 8) + }; + buf->prepend(header, kHeaderLen); + if (serverConn_) + { + serverConn_->send(buf); + } + } + + EventLoop* loop_; + TcpServer server_; + TcpConnectionPtr serverConn_; + const InetAddress socksAddr_; + std::map socksConns_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(kListenPort); + if (argc > 1) + { + socksIp = argv[1]; + } + InetAddress socksAddr(socksIp, kSocksPort); + DemuxServer server(&loop, listenAddr, socksAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/multiplexer/harness/run.sh b/examples/multiplexer/harness/run.sh new file mode 100644 index 0000000..ce5194c --- /dev/null +++ b/examples/multiplexer/harness/run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +CLASSPATH=lib/netty-3.2.4.Final.jar:lib/slf4j-api-1.6.1.jar:lib/slf4j-simple-1.6.1.jar:./bin + +export CLASSPATH +mkdir -p bin +javac -d bin ./src/com/chenshuo/muduo/example/multiplexer/*.java ./src/com/chenshuo/muduo/example/multiplexer/testcase/*.java +java -ea -Djava.net.preferIPv4Stack=true com.chenshuo.muduo.example.multiplexer.MultiplexerTest localhost diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java new file mode 100644 index 0000000..5f91379 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/DataEvent.java @@ -0,0 +1,22 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.nio.charset.Charset; + +import org.jboss.netty.buffer.ChannelBuffer; + +public class DataEvent extends Event { + + public final EventSource source; + public final int whichClient; + public final ChannelBuffer data; + + public DataEvent(EventSource source, int whichClient, ChannelBuffer data) { + this.source = source; + this.whichClient = whichClient; + this.data = data; + } + + public String getString() { + return data.toString(Charset.defaultCharset()); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java new file mode 100644 index 0000000..f372d4b --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/Event.java @@ -0,0 +1,5 @@ +package com.chenshuo.muduo.example.multiplexer; + +public class Event { + +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java new file mode 100644 index 0000000..a375a50 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventQueue.java @@ -0,0 +1,25 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +public class EventQueue { + private BlockingDeque queue = new LinkedBlockingDeque(); + + public void put(Event e) { + queue.add(e); + } + + public Event take() { + try { + return queue.poll(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return null; + } + } + + public boolean isEmpty() { + return queue.isEmpty(); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java new file mode 100644 index 0000000..79e4a4e --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/EventSource.java @@ -0,0 +1,5 @@ +package com.chenshuo.muduo.example.multiplexer; + +public enum EventSource { + kBackend, kClient +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java new file mode 100644 index 0000000..1e33a06 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockBackendServer.java @@ -0,0 +1,126 @@ +package com.chenshuo.muduo.example.multiplexer; + +import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; + +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockBackendServer { + private static final Logger logger = LoggerFactory.getLogger("MockBackendServer"); + + private class Handler extends SimpleChannelHandler { + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelConnected {},, {}", ctx, e); + assert connection == null; + connection = e.getChannel(); + latch.countDown(); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelDisconnected {},, {}", ctx, e); + assert connection == e.getChannel(); + connection = null; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + logger.debug("messageReceived {},, {}", ctx, e); + assert connection == e.getChannel(); + ChannelBuffer input = (ChannelBuffer) e.getMessage(); + int len = input.readUnsignedByte(); + int whichClient = input.readUnsignedShort(); + assert len == input.readableBytes(); + logger.debug("From {}, '{}'", whichClient, input.toString(Charset.defaultCharset())); + queue.put(new DataEvent(EventSource.kBackend, whichClient, input)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + logger.error("exceptionCaught {},, {}", ctx, e); + } + } + + private final EventQueue queue; + private final int port; + private final Executor boss; + private final Executor worker; + private final CountDownLatch latch; + private Channel listener; + private volatile Channel connection; + + public MockBackendServer(EventQueue queue, int listeningPort, Executor boss, Executor worker, + CountDownLatch latch) { + this.queue = queue; + port = listeningPort; + this.boss = boss; + this.worker = worker; + this.latch = latch; + } + + public void start() { + ServerBootstrap bootstrap = getBootstrap(); + listener = bootstrap.bind(new InetSocketAddress(port)); + logger.debug("started"); + } + + public void sendToClient(int whichClient, ChannelBuffer data) { + ChannelBuffer output = data.factory().getBuffer(3); + output.writeByte(data.readableBytes()); + output.writeShort(whichClient); + connection.write(wrappedBuffer(output, data)); + } + + public ChannelBuffer sendToClient(int whichClient, String str) { + byte[] bytes = str.getBytes(); + ChannelBuffer data = MultiplexerTest.bufferFactory.getBuffer(bytes, 0, bytes.length); + sendToClient(whichClient, data); + return data; + } + + public void stop() { + listener.close(); + } + + private ServerBootstrap getBootstrap() { + ChannelFactory factory = new NioServerSocketChannelFactory(boss, worker); + ServerBootstrap bootstrap = new ServerBootstrap(factory); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline( + new LengthFieldBasedFrameDecoder(255 + 3, 0, 1, 2, 0), + new Handler()); + } + }); + bootstrap.setOption("reuseAddress", true); + bootstrap.setOption("child.tcpNoDelay", true); + bootstrap.setOption("child.bufferFactory", MultiplexerTest.bufferFactory); + return bootstrap; + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java new file mode 100644 index 0000000..4543f4a --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MockClient.java @@ -0,0 +1,143 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.Timer; +import org.jboss.netty.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockClient { + private static final Logger logger = LoggerFactory.getLogger("MockClient"); + + private class Handler extends SimpleChannelHandler { + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelConnected {},, {}", ctx, e); + assert connection == null; + connection = e.getChannel(); + if (latch != null) + latch.countDown(); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + logger.debug("channelDisconnected {},, {}", ctx, e); + assert connection == e.getChannel(); + connection = null; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + logger.debug("messageReceived {},, {}", ctx, e); + assert connection == e.getChannel(); + queue.put(new DataEvent(EventSource.kClient, connId, (ChannelBuffer) e.getMessage())); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + logger.error("exceptionCaught {},, {}", ctx, e); + // reconnect(); + } + + @SuppressWarnings("unused") + private void reconnect() { + timer.newTimeout(new TimerTask() { + @Override + public void run(Timeout timeout) throws Exception { + logger.info("Reconnecting"); + bootstrap.connect(); + + } + }, 5, TimeUnit.SECONDS); + } + } + + private final EventQueue queue; + private final InetSocketAddress remoteAddress; + private final Executor boss; + private final Executor worker; + private final Timer timer; + private volatile Channel connection; + private ClientBootstrap bootstrap; + private int connId; + private MyCountDownLatch latch; + + public MockClient(EventQueue queue, InetSocketAddress remoteAddress, Executor boss, + Executor worker) { + this.queue = queue; + this.remoteAddress = remoteAddress; + this.boss = boss; + this.worker = worker; + this.timer = new HashedWheelTimer(); + connId = -1; + } + + public ChannelFuture connect() { + assert bootstrap == null; + + ChannelFactory factory = new NioClientSocketChannelFactory(boss, worker); + bootstrap = new ClientBootstrap(factory); + + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() { + return Channels.pipeline(new Handler()); + } + }); + + bootstrap.setOption("tcpNoDelay", true); + bootstrap.setOption("remoteAddress", remoteAddress); + + return bootstrap.connect(); + } + + public void connectAndWait() { + latch = new MyCountDownLatch(1); + connect(); + latch.awaitUninterruptibly(500); + assert connection != null; + } + + public void send(ChannelBuffer buf) { + connection.write(buf); + } + + public ChannelBuffer send(String str) { + byte[] bytes = str.getBytes(); + ChannelBuffer buf = MultiplexerTest.bufferFactory.getBuffer(bytes, 0, bytes.length); + connection.write(buf); + return buf; + } + + public void disconnect() { + connection.close(); + } + + public void setId(int connId) { + assert this.connId == -1; + this.connId = connId; + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java new file mode 100644 index 0000000..bb5dd78 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MultiplexerTest.java @@ -0,0 +1,104 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.net.InetSocketAddress; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Pattern; + +import org.jboss.netty.buffer.ChannelBufferFactory; +import org.jboss.netty.buffer.HeapChannelBufferFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientBothSend; +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientNoData; +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientSend; +import com.chenshuo.muduo.example.multiplexer.testcase.TestOneClientBackendSend; +import com.chenshuo.muduo.example.multiplexer.testcase.TestTwoClients; + +public class MultiplexerTest { + private static final Logger logger = LoggerFactory.getLogger("MultiplexerTest"); + public static final ChannelBufferFactory bufferFactory = + HeapChannelBufferFactory.getInstance(ByteOrder.LITTLE_ENDIAN); + + public final Pattern commandChannel = Pattern.compile("CONN (\\d+) FROM [0-9.:]+ IS ([A-Z]+)\r\n"); + + private static final int kMultiplexerServerPort = 3333; + private static final int kLogicalServerPort = 9999; + private final InetSocketAddress multiplexerAddress; + private final ExecutorService boss; + private final ExecutorService worker; + private EventQueue queue; + private MyCountDownLatch latch; + private MockBackendServer backend; + private ArrayList testCases; + + public MultiplexerTest(String multiplexerHost) { + multiplexerAddress = new InetSocketAddress(multiplexerHost, kMultiplexerServerPort); + boss = Executors.newCachedThreadPool(); + worker = Executors.newCachedThreadPool(); + queue = new EventQueue(); + latch = new MyCountDownLatch(1); + backend = new MockBackendServer(queue, kLogicalServerPort, boss, worker, latch); + testCases = new ArrayList(); + } + + public static void main(String[] args) { + if (args.length >= 1) { + String multiplexerHost = args[0]; + MultiplexerTest test = new MultiplexerTest(multiplexerHost); + test.addTestCase(new TestOneClientNoData()); + test.addTestCase(new TestOneClientSend()); + test.addTestCase(new TestOneClientBackendSend()); + test.addTestCase(new TestOneClientBothSend()); + test.addTestCase(new TestTwoClients()); + test.run(); + } else { + System.out.println("Usage: ./run.sh path_to_test_data multiplexer_host"); + System.out.println("Example: ./run.sh localhost"); + } + } + + private void addTestCase(TestCase testCase) { + testCases.add(testCase); + testCase.setOwner(this); + } + + private void run() { + logger.info("Waiting for connection"); + backend.start(); + latch.awaitUninterruptibly(); + + logger.info("Ready"); + for (TestCase testCase : testCases) { + testCase.test(); + } + System.out.flush(); + sleep(500); + logger.info("Finished"); + System.exit(0); + } + + public MockClient newClient() { + MockClient client = new MockClient(queue, multiplexerAddress, boss, worker); + client.connectAndWait(); + return client; + } + + public EventQueue getEventQueue() { + return queue; + } + + public MockBackendServer getBackend() { + return backend; + } + + public void sleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + } + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java new file mode 100644 index 0000000..4492fe4 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/MyCountDownLatch.java @@ -0,0 +1,26 @@ +package com.chenshuo.muduo.example.multiplexer; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class MyCountDownLatch extends CountDownLatch { + + public MyCountDownLatch(int count) { + super(count); + } + + public void awaitUninterruptibly() { + try { + await(); + } catch (InterruptedException e) { + } + } + + public void awaitUninterruptibly(int millis) { + try { + await(millis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + } + } + +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java new file mode 100644 index 0000000..88f12db --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestCase.java @@ -0,0 +1,48 @@ +package com.chenshuo.muduo.example.multiplexer; + +import org.jboss.netty.buffer.ChannelBufferFactory; + +public abstract class TestCase { + protected static final ChannelBufferFactory bufferFactory = MultiplexerTest.bufferFactory; + + protected MultiplexerTest god; + protected EventQueue queue; + protected MockBackendServer backend; + + public void setOwner(MultiplexerTest god) { + this.god = god; + queue = god.getEventQueue(); + backend = god.getBackend(); + } + + public void test() { + try { + run(); + } catch (TestFailedException e) { + System.out.printf("%s FAILED: %s\n", this.getClass().getSimpleName(), e.getMessage()); + e.printStackTrace(); + return; + } catch (Exception e) { + System.out.printf("%s FATAL: %s\n", this.getClass().getSimpleName(), e.toString()); + e.printStackTrace(); + return; + } + System.out.printf("%s PASS\n", this.getClass().getSimpleName()); + } + + protected void assertEquals(Object expected, Object actual) { + if (!expected.equals(actual)) + fail("assertEquals failed"); + } + + protected void assertTrue(boolean yes) { + if (!yes) + fail("assertTrue failed"); + } + + protected void fail(String message) { + throw new TestFailedException(message); + } + + public abstract void run(); +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java new file mode 100644 index 0000000..383ea5f --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/TestFailedException.java @@ -0,0 +1,9 @@ +package com.chenshuo.muduo.example.multiplexer; + +public class TestFailedException extends RuntimeException { + private static final long serialVersionUID = 1982L; + + public TestFailedException(String message) { + super(message); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java new file mode 100644 index 0000000..5f96e21 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBackendSend.java @@ -0,0 +1,75 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientBackendSend extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + // step 2 + ChannelBuffer buf = backend.sendToClient(connId, "hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = backend.sendToClient(connId, "World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 255; ++i) + sb.append('H'); + + buf = backend.sendToClient(connId, sb.toString()); + + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + + // step 4 + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java new file mode 100644 index 0000000..6c71ee3 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientBothSend.java @@ -0,0 +1,64 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientBothSend extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + // step 2 + ChannelBuffer buf = client.send("hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = backend.sendToClient(connId, "World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 4 + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java new file mode 100644 index 0000000..cfe6eaf --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientNoData.java @@ -0,0 +1,43 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.util.regex.Matcher; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientNoData extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java new file mode 100644 index 0000000..68b2ff7 --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestOneClientSend.java @@ -0,0 +1,80 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestOneClientSend extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + MockClient client = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId = Integer.parseInt(m.group(1)); + assertTrue(connId > 0); + client.setId(connId); + + assertEquals("UP", m.group(2)); + + // step 2 + ChannelBuffer buf = client.send("hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = client.send("World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 500; ++i) + sb.append('H'); + + buf = client.send(sb.toString()); + + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf.copy(0, 255), de.data); + + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId, de.whichClient); + assertEquals(buf.copy(255, 245), de.data); + + // step 4 + client.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + assertEquals(connId, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + } +} diff --git a/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java new file mode 100644 index 0000000..2de3d6b --- /dev/null +++ b/examples/multiplexer/harness/src/com/chenshuo/muduo/example/multiplexer/testcase/TestTwoClients.java @@ -0,0 +1,85 @@ +package com.chenshuo.muduo.example.multiplexer.testcase; + +import java.nio.charset.Charset; +import java.util.regex.Matcher; + +import org.jboss.netty.buffer.ChannelBuffer; + +import com.chenshuo.muduo.example.multiplexer.DataEvent; +import com.chenshuo.muduo.example.multiplexer.Event; +import com.chenshuo.muduo.example.multiplexer.EventSource; +import com.chenshuo.muduo.example.multiplexer.MockClient; +import com.chenshuo.muduo.example.multiplexer.TestCase; + +public class TestTwoClients extends TestCase { + + @Override + public void run() { + if (!queue.isEmpty()) + fail("EventQueue is not empty"); + + // step 1 + final MockClient client1 = god.newClient(); + Event ev = queue.take(); + DataEvent de = (DataEvent) ev; + assertEquals(EventSource.kBackend, de.source); + + Matcher m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId1 = Integer.parseInt(m.group(1)); + assertTrue(connId1 > 0); + client1.setId(connId1); + assertEquals("UP", m.group(2)); + + // step 2 + final MockClient client2 = god.newClient(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + + final int connId2 = Integer.parseInt(m.group(1)); + assertTrue(connId2 > 0); + client2.setId(connId2); + assertEquals("UP", m.group(2)); + + ChannelBuffer buf = client1.send("hello"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + assertEquals(connId1, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 3 + buf = backend.sendToClient(connId2, "World!"); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kClient, de.source); + assertEquals(connId2, de.whichClient); + assertEquals(buf, de.data); + System.out.println(de.data.toString(Charset.defaultCharset())); + + // step 4 + client1.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + assertEquals(connId1, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + + client2.disconnect(); + de = (DataEvent) queue.take(); + assertEquals(EventSource.kBackend, de.source); + m = god.commandChannel.matcher(de.getString()); + if (!m.matches()) + fail("command channel message doesn't match."); + assertEquals(connId2, Integer.parseInt(m.group(1))); + assertEquals("DOWN", m.group(2)); + + } +} diff --git a/examples/multiplexer/multiplexer.cc b/examples/multiplexer/multiplexer.cc new file mode 100644 index 0000000..127ba4e --- /dev/null +++ b/examples/multiplexer/multiplexer.cc @@ -0,0 +1,315 @@ +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Mutex.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +const int kMaxConns = 10; // 65535 +const size_t kMaxPacketLen = 255; +const size_t kHeaderLen = 3; + +const uint16_t kClientPort = 3333; +const char* backendIp = "127.0.0.1"; +const uint16_t kBackendPort = 9999; + +class MultiplexServer +{ + public: + MultiplexServer(EventLoop* loop, + const InetAddress& listenAddr, + const InetAddress& backendAddr, + int numThreads) + : server_(loop, listenAddr, "MultiplexServer"), + backend_(loop, backendAddr, "MultiplexBackend"), + numThreads_(numThreads), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&MultiplexServer::onClientConnection, this, _1)); + server_.setMessageCallback( + std::bind(&MultiplexServer::onClientMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + + backend_.setConnectionCallback( + std::bind(&MultiplexServer::onBackendConnection, this, _1)); + backend_.setMessageCallback( + std::bind(&MultiplexServer::onBackendMessage, this, _1, _2, _3)); + backend_.enableRetry(); + + // loop->runEvery(10.0, std::bind(&MultiplexServer::printStatistics, this)); + + } + + void start() + { + LOG_INFO << "starting " << numThreads_ << " threads."; + backend_.connect(); + server_.start(); + } + + private: + void sendBackendPacket(int id, Buffer* buf) + { + size_t len = buf->readableBytes(); + assert(len <= kMaxPacketLen); + uint8_t header[kHeaderLen] = { + static_cast(len), + static_cast(id & 0xFF), + static_cast((id & 0xFF00) >> 8) + }; + buf->prepend(header, kHeaderLen); + TcpConnectionPtr backendConn; + { + MutexLockGuard lock(mutex_); + backendConn = backendConn_; + } + if (backendConn) + { + backendConn->send(buf); + } + } + + void sendBackendString(int id, const string& msg) + { + assert(msg.size() <= kMaxPacketLen); + Buffer buf; + buf.append(msg); + sendBackendPacket(id, &buf); + } + + void sendBackendBuffer(int id, Buffer* buf) + { + while (buf->readableBytes() > kMaxPacketLen) + { + Buffer packet; + packet.append(buf->peek(), kMaxPacketLen); + buf->retrieve(kMaxPacketLen); + sendBackendPacket(id, &packet); + } + if (buf->readableBytes() > 0) + { + sendBackendPacket(id, buf); + } + } + + void sendToClient(Buffer* buf) + { + while (buf->readableBytes() > kHeaderLen) + { + int len = static_cast(*buf->peek()); + if (buf->readableBytes() < len + kHeaderLen) + { + break; + } + else + { + int id = static_cast(buf->peek()[1]); + id |= (static_cast(buf->peek()[2]) << 8); + + TcpConnectionPtr clientConn; + { + MutexLockGuard lock(mutex_); + std::map::iterator it = clientConns_.find(id); + if (it != clientConns_.end()) + { + clientConn = it->second; + } + } + if (clientConn) + { + clientConn->send(buf->peek() + kHeaderLen, len); + } + buf->retrieve(len + kHeaderLen); + } + } + } + + void onClientConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Client " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + int id = -1; + { + MutexLockGuard lock(mutex_); + if (!availIds_.empty()) + { + id = availIds_.front(); + availIds_.pop(); + clientConns_[id] = conn; + } + } + + if (id <= 0) + { + conn->shutdown(); + } + else + { + conn->setContext(id); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS UP\r\n", id, + conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + } + } + else + { + if (!conn->getContext().empty()) + { + int id = boost::any_cast(conn->getContext()); + assert(id > 0 && id <= kMaxConns); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS DOWN\r\n", + id, conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + + MutexLockGuard lock(mutex_); + if (backendConn_) + { + availIds_.push(id); + clientConns_.erase(id); + } + else + { + assert(availIds_.empty()); + assert(clientConns_.empty()); + } + } + } + } + + void onClientMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.addAndGet(len); + receivedMessages_.incrementAndGet(); + if (!conn->getContext().empty()) + { + int id = boost::any_cast(conn->getContext()); + sendBackendBuffer(id, buf); + // assert(buf->readableBytes() == 0); + } + else + { + buf->retrieveAll(); + // FIXME: error handling + } + } + + void onBackendConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Backend " << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + std::vector connsToDestroy; + if (conn->connected()) + { + MutexLockGuard lock(mutex_); + backendConn_ = conn; + assert(availIds_.empty()); + for (int i = 1; i <= kMaxConns; ++i) + { + availIds_.push(i); + } + } + else + { + MutexLockGuard lock(mutex_); + backendConn_.reset(); + connsToDestroy.reserve(clientConns_.size()); + for (std::map::iterator it = clientConns_.begin(); + it != clientConns_.end(); + ++it) + { + connsToDestroy.push_back(it->second); + } + clientConns_.clear(); + while (!availIds_.empty()) + { + availIds_.pop(); + } + } + + for (std::vector::iterator it = connsToDestroy.begin(); + it != connsToDestroy.end(); + ++it) + { + (*it)->shutdown(); + } + } + + void onBackendMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.addAndGet(len); + receivedMessages_.incrementAndGet(); + sendToClient(buf); + } + + void printStatistics() + { + Timestamp endTime = Timestamp::now(); + int64_t newCounter = transferred_.get(); + int64_t bytes = newCounter - oldCounter_; + int64_t msgs = receivedMessages_.getAndSet(0); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n", + static_cast(bytes)/time/1024/1024, + static_cast(msgs)/time/1024, + static_cast(bytes)/static_cast(msgs)); + + oldCounter_ = newCounter; + startTime_ = endTime; + } + + TcpServer server_; + TcpClient backend_; + int numThreads_; + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; + MutexLock mutex_; + TcpConnectionPtr backendConn_ GUARDED_BY(mutex_); + std::map clientConns_ GUARDED_BY(mutex_); + std::queue availIds_ GUARDED_BY(mutex_); +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + int numThreads = 4; + if (argc > 1) + { + backendIp = argv[1]; + } + if (argc > 2) + { + numThreads = atoi(argv[2]); + } + EventLoop loop; + InetAddress listenAddr(kClientPort); + InetAddress backendAddr(backendIp, kBackendPort); + MultiplexServer server(&loop, listenAddr, backendAddr, numThreads); + + server.start(); + + loop.loop(); +} + diff --git a/examples/multiplexer/multiplexer_simple.cc b/examples/multiplexer/multiplexer_simple.cc new file mode 100644 index 0000000..ea3f766 --- /dev/null +++ b/examples/multiplexer/multiplexer_simple.cc @@ -0,0 +1,269 @@ +#include "muduo/base/Logging.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" +#include "muduo/net/TcpClient.h" +#include "muduo/net/TcpServer.h" + +#include +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +const int kMaxConns = 10; // 65535 +const size_t kMaxPacketLen = 255; +const size_t kHeaderLen = 3; + +const uint16_t kClientPort = 3333; +const char* backendIp = "127.0.0.1"; +const uint16_t kBackendPort = 9999; + +class MultiplexServer : noncopyable +{ + public: + MultiplexServer(EventLoop* loop, const InetAddress& listenAddr, const InetAddress& backendAddr) + : server_(loop, listenAddr, "MultiplexServer"), + backend_(loop, backendAddr, "MultiplexBackend") + { + server_.setConnectionCallback( + std::bind(&MultiplexServer::onClientConnection, this, _1)); + server_.setMessageCallback( + std::bind(&MultiplexServer::onClientMessage, this, _1, _2, _3)); + backend_.setConnectionCallback( + std::bind(&MultiplexServer::onBackendConnection, this, _1)); + backend_.setMessageCallback( + std::bind(&MultiplexServer::onBackendMessage, this, _1, _2, _3)); + backend_.enableRetry(); + } + + void start() + { + backend_.connect(); + server_.start(); + } + + private: + + void onClientConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Client " << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + if (conn->connected()) + { + int id = -1; + if (!availIds_.empty()) + { + id = availIds_.front(); + availIds_.pop(); + clientConns_[id] = conn; + } + + if (id <= 0) + { + // no client id available + conn->shutdown(); + } + else + { + conn->setContext(id); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS UP\r\n", + id, conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + } + } + else + { + if (!conn->getContext().empty()) + { + int id = boost::any_cast(conn->getContext()); + assert(id > 0 && id <= kMaxConns); + char buf[256]; + snprintf(buf, sizeof(buf), "CONN %d FROM %s IS DOWN\r\n", + id, conn->peerAddress().toIpPort().c_str()); + sendBackendString(0, buf); + + if (backendConn_) + { + // put client id back for reusing + availIds_.push(id); + clientConns_.erase(id); + } + else + { + assert(availIds_.empty()); + assert(clientConns_.empty()); + } + } + } + } + + void sendBackendString(int id, const string& msg) + { + assert(msg.size() <= kMaxPacketLen); + Buffer buf; + buf.append(msg); + sendBackendPacket(id, &buf); + } + + void onClientMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + if (!conn->getContext().empty()) + { + int id = boost::any_cast(conn->getContext()); + sendBackendBuffer(id, buf); + } + else + { + buf->retrieveAll(); + // FIXME: error handling + } + } + + void sendBackendBuffer(int id, Buffer* buf) + { + while (buf->readableBytes() > kMaxPacketLen) + { + Buffer packet; + packet.append(buf->peek(), kMaxPacketLen); + buf->retrieve(kMaxPacketLen); + sendBackendPacket(id, &packet); + } + if (buf->readableBytes() > 0) + { + sendBackendPacket(id, buf); + } + } + + void sendBackendPacket(int id, Buffer* buf) + { + size_t len = buf->readableBytes(); + LOG_DEBUG << "sendBackendPacket " << len; + assert(len <= kMaxPacketLen); + uint8_t header[kHeaderLen] = { + static_cast(len), + static_cast(id & 0xFF), + static_cast((id & 0xFF00) >> 8) + }; + buf->prepend(header, kHeaderLen); + if (backendConn_) + { + backendConn_->send(buf); + } + } + + void onBackendConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << "Backend " << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + backendConn_ = conn; + assert(availIds_.empty()); + for (int i = 1; i <= kMaxConns; ++i) + { + availIds_.push(i); + } + } + else + { + backendConn_.reset(); + for (std::map::iterator it = clientConns_.begin(); + it != clientConns_.end(); + ++it) + { + it->second->shutdown(); + } + clientConns_.clear(); + while (!availIds_.empty()) + { + availIds_.pop(); + } + } + } + + void onBackendMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + sendToClient(buf); + } + + void sendToClient(Buffer* buf) + { + while (buf->readableBytes() > kHeaderLen) + { + int len = static_cast(*buf->peek()); + if (buf->readableBytes() < len + kHeaderLen) + { + break; + } + else + { + int id = static_cast(buf->peek()[1]); + id |= (static_cast(buf->peek()[2]) << 8); + + if (id != 0) + { + std::map::iterator it = clientConns_.find(id); + if (it != clientConns_.end()) + { + it->second->send(buf->peek() + kHeaderLen, len); + } + } + else + { + string cmd(buf->peek() + kHeaderLen, len); + LOG_INFO << "Backend cmd " << cmd; + doCommand(cmd); + } + buf->retrieve(len + kHeaderLen); + } + } + } + + void doCommand(const string& cmd) + { + static const string kDisconnectCmd = "DISCONNECT "; + + if (cmd.size() > kDisconnectCmd.size() + && std::equal(kDisconnectCmd.begin(), kDisconnectCmd.end(), cmd.begin())) + { + int connId = atoi(&cmd[kDisconnectCmd.size()]); + std::map::iterator it = clientConns_.find(connId); + if (it != clientConns_.end()) + { + it->second->shutdown(); + } + } + } + + TcpServer server_; + TcpClient backend_; + // MutexLock mutex_; + TcpConnectionPtr backendConn_; + std::map clientConns_; + std::queue availIds_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid(); + EventLoop loop; + InetAddress listenAddr(kClientPort); + if (argc > 1) + { + backendIp = argv[1]; + } + InetAddress backendAddr(backendIp, kBackendPort); + MultiplexServer server(&loop, listenAddr, backendAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/discard/client.cc b/examples/netty/discard/client.cc new file mode 100644 index 0000000..3669f85 --- /dev/null +++ b/examples/netty/discard/client.cc @@ -0,0 +1,95 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class DiscardClient : noncopyable +{ + public: + DiscardClient(EventLoop* loop, const InetAddress& listenAddr, int size) + : loop_(loop), + client_(loop, listenAddr, "DiscardClient"), + message_(size, 'H') + { + client_.setConnectionCallback( + std::bind(&DiscardClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&DiscardClient::onMessage, this, _1, _2, _3)); + client_.setWriteCompleteCallback( + std::bind(&DiscardClient::onWriteComplete, this, _1)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->send(message_); + } + else + { + loop_->quit(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + buf->retrieveAll(); + } + + void onWriteComplete(const TcpConnectionPtr& conn) + { + LOG_INFO << "write complete " << message_.size(); + conn->send(message_); + } + + EventLoop* loop_; + TcpClient client_; + string message_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + EventLoop loop; + InetAddress serverAddr(argv[1], 2009); + + int size = 256; + if (argc > 2) + { + size = atoi(argv[2]); + } + + DiscardClient client(&loop, serverAddr, size); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip [msg_size]\n", argv[0]); + } +} + diff --git a/examples/netty/discard/server.cc b/examples/netty/discard/server.cc new file mode 100644 index 0000000..7b3083e --- /dev/null +++ b/examples/netty/discard/server.cc @@ -0,0 +1,96 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class DiscardServer +{ + public: + DiscardServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "DiscardServer"), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&DiscardServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&DiscardServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(3.0, std::bind(&DiscardServer::printThroughput, this)); + } + + void start() + { + LOG_INFO << "starting " << numThreads << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.add(len); + receivedMessages_.incrementAndGet(); + buf->retrieveAll(); + } + + void printThroughput() + { + Timestamp endTime = Timestamp::now(); + int64_t newCounter = transferred_.get(); + int64_t bytes = newCounter - oldCounter_; + int64_t msgs = receivedMessages_.getAndSet(0); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n", + static_cast(bytes)/time/1024/1024, + static_cast(msgs)/time/1024, + static_cast(bytes)/static_cast(msgs)); + + oldCounter_ = newCounter; + startTime_ = endTime; + } + + TcpServer server_; + + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(2009); + DiscardServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/echo/client.cc b/examples/netty/echo/client.cc new file mode 100644 index 0000000..debab4c --- /dev/null +++ b/examples/netty/echo/client.cc @@ -0,0 +1,87 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class EchoClient : noncopyable +{ + public: + EchoClient(EventLoop* loop, const InetAddress& listenAddr, int size) + : loop_(loop), + client_(loop, listenAddr, "EchoClient"), + message_(size, 'H') + { + client_.setConnectionCallback( + std::bind(&EchoClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&EchoClient::onMessage, this, _1, _2, _3)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->send(message_); + } + else + { + loop_->quit(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + conn->send(buf); + } + + EventLoop* loop_; + TcpClient client_; + string message_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + EventLoop loop; + InetAddress serverAddr(argv[1], 2007); + + int size = 256; + if (argc > 2) + { + size = atoi(argv[2]); + } + + EchoClient client(&loop, serverAddr, size); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip [msg_size]\n", argv[0]); + } +} + diff --git a/examples/netty/echo/server.cc b/examples/netty/echo/server.cc new file mode 100644 index 0000000..2c1f936 --- /dev/null +++ b/examples/netty/echo/server.cc @@ -0,0 +1,96 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class EchoServer +{ + public: + EchoServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "EchoServer"), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(3.0, std::bind(&EchoServer::printThroughput, this)); + } + + void start() + { + LOG_INFO << "starting " << numThreads << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + conn->setTcpNoDelay(true); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferred_.addAndGet(len); + receivedMessages_.incrementAndGet(); + conn->send(buf); + } + + void printThroughput() + { + Timestamp endTime = Timestamp::now(); + int64_t newCounter = transferred_.get(); + int64_t bytes = newCounter - oldCounter_; + int64_t msgs = receivedMessages_.getAndSet(0); + double time = timeDifference(endTime, startTime_); + printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n", + static_cast(bytes)/time/1024/1024, + static_cast(msgs)/time/1024, + static_cast(bytes)/static_cast(msgs)); + + oldCounter_ = newCounter; + startTime_ = endTime; + } + + TcpServer server_; + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(2007); + EchoServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/echo/server2.cc b/examples/netty/echo/server2.cc new file mode 100644 index 0000000..030943e --- /dev/null +++ b/examples/netty/echo/server2.cc @@ -0,0 +1,139 @@ +#include "muduo/net/TcpServer.h" + +#include "muduo/base/Atomic.h" +#include "muduo/base/FileUtil.h" +#include "muduo/base/Logging.h" +#include "muduo/base/ProcessInfo.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class EchoServer +{ + public: + EchoServer(EventLoop* loop, const InetAddress& listenAddr) + : server_(loop, listenAddr, "EchoServer"), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + std::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&EchoServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(5.0, std::bind(&EchoServer::printThroughput, this)); + } + + void start() + { + LOG_INFO << "starting " << numThreads << " threads."; + server_.start(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->peerAddress().toIpPort() << " -> " + << conn->localAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + conn->setTcpNoDelay(true); + if (conn->connected()) + { + connections_.increment(); + } + else + { + connections_.decrement(); + } + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + size_t len = buf->readableBytes(); + transferredBytes_.addAndGet(len); + receivedMessages_.incrementAndGet(); + conn->send(buf); + } + + void printThroughput() + { + Timestamp endTime = Timestamp::now(); + double bytes = static_cast(transferredBytes_.getAndSet(0)); + int msgs = receivedMessages_.getAndSet(0); + double bytesPerMsg = msgs > 0 ? bytes/msgs : 0; + double time = timeDifference(endTime, startTime_); + printf("%.3f MiB/s %.2f Kilo Msgs/s %.2f bytes per msg, ", + bytes/time/1024/1024, + static_cast(msgs)/time/1000, + bytesPerMsg); + + printConnection(); + fflush(stdout); + startTime_ = endTime; + } + + void printConnection() + { + string procStatus = ProcessInfo::procStatus(); + printf("%d conn, files %d , VmSize %ld KiB, RSS %ld KiB, ", + connections_.get(), + ProcessInfo::openedFiles(), + getLong(procStatus, "VmSize:"), + getLong(procStatus, "VmRSS:")); + + string meminfo; + FileUtil::readFile("/proc/meminfo", 65536, &meminfo); + long total_kb = getLong(meminfo, "MemTotal:"); + long free_kb = getLong(meminfo, "MemFree:"); + long buffers_kb = getLong(meminfo, "Buffers:"); + long cached_kb = getLong(meminfo, "Cached:"); + printf("system memory used %ld KiB\n", + total_kb - free_kb - buffers_kb - cached_kb); + } + + long getLong(const string& procStatus, const char* key) + { + long result = 0; + size_t pos = procStatus.find(key); + if (pos != string::npos) + { + result = ::atol(procStatus.c_str() + pos + strlen(key)); + } + return result; + } + + TcpServer server_; + AtomicInt32 connections_; + AtomicInt32 receivedMessages_; + AtomicInt64 transferredBytes_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() + << ", tid = " << CurrentThread::tid() + << ", max files = " << ProcessInfo::maxOpenFiles(); + Logger::setLogLevel(Logger::WARN); + if (argc > 1) + { + numThreads = atoi(argv[1]); + } + EventLoop loop; + InetAddress listenAddr(2007); + EchoServer server(&loop, listenAddr); + + server.start(); + + loop.loop(); +} + diff --git a/examples/netty/uptime/uptime.cc b/examples/netty/uptime/uptime.cc new file mode 100644 index 0000000..31ceac3 --- /dev/null +++ b/examples/netty/uptime/uptime.cc @@ -0,0 +1,67 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class UptimeClient : noncopyable +{ + public: + UptimeClient(EventLoop* loop, const InetAddress& listenAddr) + : client_(loop, listenAddr, "UptimeClient") + { + client_.setConnectionCallback( + std::bind(&UptimeClient::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&UptimeClient::onMessage, this, _1, _2, _3)); + //client_.enableRetry(); + } + + void connect() + { + client_.connect(); + } + + private: + void onConnection(const TcpConnectionPtr& conn) + { + LOG_TRACE << conn->localAddress().toIpPort() << " -> " + << conn->peerAddress().toIpPort() << " is " + << (conn->connected() ? "UP" : "DOWN"); + } + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) + { + } + + TcpClient client_; +}; + +int main(int argc, char* argv[]) +{ + LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid(); + if (argc > 2) + { + EventLoop loop; + uint16_t port = static_cast(atoi(argv[2])); + InetAddress serverAddr(argv[1], port); + + UptimeClient client(&loop, serverAddr); + client.connect(); + loop.loop(); + } + else + { + printf("Usage: %s host_ip port\n", argv[0]); + } +} + diff --git a/examples/pingpong/bench.cc b/examples/pingpong/bench.cc new file mode 100644 index 0000000..db8214e --- /dev/null +++ b/examples/pingpong/bench.cc @@ -0,0 +1,142 @@ +// Benchmark inspired by libevent/test/bench.c +// See also: http://libev.schmorp.de/bench.html + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/Channel.h" +#include "muduo/net/EventLoop.h" + +#include +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +std::vector g_pipes; +int numPipes; +int numActive; +int numWrites; +EventLoop* g_loop; +std::vector> g_channels; + +int g_reads, g_writes, g_fired; + +void readCallback(Timestamp, int fd, int idx) +{ + char ch; + + g_reads += static_cast(::recv(fd, &ch, sizeof(ch), 0)); + if (g_writes > 0) + { + int widx = idx+1; + if (widx >= numPipes) + { + widx -= numPipes; + } + ::send(g_pipes[2 * widx + 1], "m", 1, 0); + g_writes--; + g_fired++; + } + if (g_fired == g_reads) + { + g_loop->quit(); + } +} + +std::pair runOnce() +{ + Timestamp beforeInit(Timestamp::now()); + for (int i = 0; i < numPipes; ++i) + { + Channel& channel = *g_channels[i]; + channel.setReadCallback(std::bind(readCallback, _1, channel.fd(), i)); + channel.enableReading(); + } + + int space = numPipes / numActive; + space *= 2; + for (int i = 0; i < numActive; ++i) + { + ::send(g_pipes[i * space + 1], "m", 1, 0); + } + + g_fired = numActive; + g_reads = 0; + g_writes = numWrites; + Timestamp beforeLoop(Timestamp::now()); + g_loop->loop(); + + Timestamp end(Timestamp::now()); + + int iterTime = static_cast(end.microSecondsSinceEpoch() - beforeInit.microSecondsSinceEpoch()); + int loopTime = static_cast(end.microSecondsSinceEpoch() - beforeLoop.microSecondsSinceEpoch()); + return std::make_pair(iterTime, loopTime); +} + +int main(int argc, char* argv[]) +{ + numPipes = 100; + numActive = 1; + numWrites = 100; + int c; + while ((c = getopt(argc, argv, "n:a:w:")) != -1) + { + switch (c) + { + case 'n': + numPipes = atoi(optarg); + break; + case 'a': + numActive = atoi(optarg); + break; + case 'w': + numWrites = atoi(optarg); + break; + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + return 1; + } + } + + struct rlimit rl; + rl.rlim_cur = rl.rlim_max = numPipes * 2 + 50; + if (::setrlimit(RLIMIT_NOFILE, &rl) == -1) + { + perror("setrlimit"); + //return 1; // comment out this line if under valgrind + } + g_pipes.resize(2 * numPipes); + for (int i = 0; i < numPipes; ++i) + { + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, &g_pipes[i*2]) == -1) + { + perror("pipe"); + return 1; + } + } + + EventLoop loop; + g_loop = &loop; + + for (int i = 0; i < numPipes; ++i) + { + Channel* channel = new Channel(&loop, g_pipes[i*2]); + g_channels.emplace_back(channel); + } + + for (int i = 0; i < 25; ++i) + { + std::pair t = runOnce(); + printf("%8d %8d\n", t.first, t.second); + } + + for (const auto& channel : g_channels) + { + channel->disableAll(); + channel->remove(); + } + g_channels.clear(); +} + diff --git a/examples/pingpong/client.cc b/examples/pingpong/client.cc new file mode 100644 index 0000000..cc2c61f --- /dev/null +++ b/examples/pingpong/client.cc @@ -0,0 +1,214 @@ +#include "muduo/net/TcpClient.h" + +#include "muduo/base/Logging.h" +#include "muduo/base/Thread.h" +#include "muduo/net/EventLoop.h" +#include "muduo/net/EventLoopThreadPool.h" +#include "muduo/net/InetAddress.h" + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class Client; + +class Session : noncopyable +{ + public: + Session(EventLoop* loop, + const InetAddress& serverAddr, + const string& name, + Client* owner) + : client_(loop, serverAddr, name), + owner_(owner), + bytesRead_(0), + bytesWritten_(0), + messagesRead_(0) + { + client_.setConnectionCallback( + std::bind(&Session::onConnection, this, _1)); + client_.setMessageCallback( + std::bind(&Session::onMessage, this, _1, _2, _3)); + } + + void start() + { + client_.connect(); + } + + void stop() + { + client_.disconnect(); + } + + int64_t bytesRead() const + { + return bytesRead_; + } + + int64_t messagesRead() const + { + return messagesRead_; + } + + private: + + void onConnection(const TcpConnectionPtr& conn); + + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) + { + ++messagesRead_; + bytesRead_ += buf->readableBytes(); + bytesWritten_ += buf->readableBytes(); + conn->send(buf); + } + + TcpClient client_; + Client* owner_; + int64_t bytesRead_; + int64_t bytesWritten_; + int64_t messagesRead_; +}; + +class Client : noncopyable +{ + public: + Client(EventLoop* loop, + const InetAddress& serverAddr, + int blockSize, + int sessionCount, + int timeout, + int threadCount) + : loop_(loop), + threadPool_(loop, "pingpong-client"), + sessionCount_(sessionCount), + timeout_(timeout) + { + loop->runAfter(timeout, std::bind(&Client::handleTimeout, this)); + if (threadCount > 1) + { + threadPool_.setThreadNum(threadCount); + } + threadPool_.start(); + + for (int i = 0; i < blockSize; ++i) + { + message_.push_back(static_cast(i % 128)); + } + + for (int i = 0; i < sessionCount; ++i) + { + char buf[32]; + snprintf(buf, sizeof buf, "C%05d", i); + Session* session = new Session(threadPool_.getNextLoop(), serverAddr, buf, this); + session->start(); + sessions_.emplace_back(session); + } + } + + const string& message() const + { + return message_; + } + + void onConnect() + { + if (numConnected_.incrementAndGet() == sessionCount_) + { + LOG_WARN << "all connected"; + } + } + + void onDisconnect(const TcpConnectionPtr& conn) + { + if (numConnected_.decrementAndGet() == 0) + { + LOG_WARN << "all disconnected"; + + int64_t totalBytesRead = 0; + int64_t totalMessagesRead = 0; + for (const auto& session : sessions_) + { + totalBytesRead += session->bytesRead(); + totalMessagesRead += session->messagesRead(); + } + LOG_WARN << totalBytesRead << " total bytes read"; + LOG_WARN << totalMessagesRead << " total messages read"; + LOG_WARN << static_cast(totalBytesRead) / static_cast(totalMessagesRead) + << " average message size"; + LOG_WARN << static_cast(totalBytesRead) / (timeout_ * 1024 * 1024) + << " MiB/s throughput"; + conn->getLoop()->queueInLoop(std::bind(&Client::quit, this)); + } + } + + private: + + void quit() + { + loop_->queueInLoop(std::bind(&EventLoop::quit, loop_)); + } + + void handleTimeout() + { + LOG_WARN << "stop"; + for (auto& session : sessions_) + { + session->stop(); + } + } + + EventLoop* loop_; + EventLoopThreadPool threadPool_; + int sessionCount_; + int timeout_; + std::vector> sessions_; + string message_; + AtomicInt32 numConnected_; +}; + +void Session::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + conn->setTcpNoDelay(true); + conn->send(owner_->message()); + owner_->onConnect(); + } + else + { + owner_->onDisconnect(conn); + } +} + +int main(int argc, char* argv[]) +{ + if (argc != 7) + { + fprintf(stderr, "Usage: client "); + fprintf(stderr, "