diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1377554eb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..2c8880a20 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 2.6) + +project(muduo CXX) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() + +set(CXX_FLAGS + -g + # -DVALGRIND + # -DMUDUO_STD_STRING + -D_FILE_OFFSET_BITS=64 + -Wall + -Wextra + -Werror + -Wconversion + -Wno-unused-parameter + -Wold-style-cast + -Woverloaded-virtual + -Wpointer-arith + -Wshadow + -Wwrite-strings + -march=native + # -MMD + # -std=c++0x + -rdynamic + ) +if(CMAKE_BUILD_BITS EQUAL 32) + list(APPEND CXX_FLAGS "-m32") +endif() +string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") + +set(CMAKE_CXX_COMPILER "g++") +#set(CMAKE_CXX_COMPILER "icpc") +set(CMAKE_CXX_FLAGS_DEBUG "-O0") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -finline-limit=1000 -DNDEBUG") +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +find_package(Boost REQUIRED) +find_package(Protobuf) +find_package(CURL) +find_path(CARES_INCLUDE_DIR ares.h) +find_library(CARES_LIBRARY NAMES cares) +find_path(MHD_INCLUDE_DIR microhttpd.h) +find_library(MHD_LIBRARY NAMES microhttpd) +find_library(BOOSTTEST_LIBRARY NAMES boost_unit_test_framework) + +include_directories(${Boost_INCLUDE_DIRS}) + +include_directories(${PROJECT_SOURCE_DIR}) + +string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE) +message(STATUS "CXX_FLAGS = " ${CMAKE_CXX_FLAGS} " " ${CMAKE_CXX_FLAGS_${BUILD_TYPE}}) + +add_subdirectory(muduo/base) +add_subdirectory(muduo/net) + +if(NOT CMAKE_BUILD_NO_EXAMPLES) + add_subdirectory(examples) +else() + if(CARES_INCLUDE_DIR AND CARES_LIBRARY) + add_subdirectory(examples/cdns) + endif() +endif() + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..2f4fdcddc --- /dev/null +++ b/ChangeLog @@ -0,0 +1,312 @@ +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/Doxyfile b/Doxyfile new file mode 100644 index 000000000..b0bda9f26 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1514 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Muduo + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.1 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ../build + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = muduo + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = muduo + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */.svn/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = YES + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/License b/License new file mode 100644 index 000000000..a2ff61865 --- /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. +// * The name of the author may not 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 000000000..8edf1c9b3 --- /dev/null +++ b/README @@ -0,0 +1,17 @@ +Muduo is a multithreaded C++ network library based on +the reactor pattern. +It runs on Linux with kernel version >= 2.6.28. +http://code.google.com/p/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. + + __ __ _ + | \/ | | | + | \ / |_ _ __| |_ _ ___ + | |\/| | | | |/ _` | | | |/ _ \ + | | | | |_| | (_| | |_| | (_) | + |_| |_|\__,_|\__,_|\__,_|\___/ + diff --git a/TODO b/TODO new file mode 100644 index 000000000..1cc054330 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +Always processing timer before IO +Support string and line protocol +Add Benchmark +Support signal handling + diff --git a/armlinux.diff b/armlinux.diff new file mode 100644 index 000000000..9a396bf4b --- /dev/null +++ b/armlinux.diff @@ -0,0 +1,137 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2c8880a..af0d174 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -21,7 +21,7 @@ set(CXX_FLAGS + -Wpointer-arith + -Wshadow + -Wwrite-strings +- -march=native ++ -march=armv4 + # -MMD + # -std=c++0x + -rdynamic +@@ -31,7 +31,7 @@ if(CMAKE_BUILD_BITS EQUAL 32) + endif() + string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") + +-set(CMAKE_CXX_COMPILER "g++") ++set(CMAKE_CXX_COMPILER "arm-g++") + #set(CMAKE_CXX_COMPILER "icpc") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") + set(CMAKE_CXX_FLAGS_RELEASE "-O2 -finline-limit=1000 -DNDEBUG") +diff --git a/muduo/base/Atomic.h b/muduo/base/Atomic.h +index 3478da0..cc1dd45 100644 +--- a/muduo/base/Atomic.h ++++ b/muduo/base/Atomic.h +@@ -8,6 +8,7 @@ + + #include + #include ++#include + + namespace muduo + { +@@ -83,10 +84,88 @@ class AtomicIntegerT : boost::noncopyable + private: + volatile T value_; + }; ++ ++template ++class AtomicIntegerLock : boost::noncopyable ++{ ++ public: ++ AtomicIntegerLock() ++ : value_(0) ++ { ++ } ++ ++ // uncomment if you need copying and assignment ++ // ++ // AtomicIntegerT(const AtomicIntegerT& that) ++ // : value_(that.get()) ++ // {} ++ // ++ // AtomicIntegerT& operator=(const AtomicIntegerT& that) ++ // { ++ // getAndSet(that.get()); ++ // return *this; ++ // } ++ ++ T get() ++ { ++ MutexLockGuard lock(mutex_); ++ return value_; ++ } ++ ++ T getAndAdd(T x) ++ { ++ MutexLockGuard lock(mutex_); ++ T old = value_; ++ value_ += x; ++ return old; ++ } ++ ++ T addAndGet(T x) ++ { ++ return getAndAdd(x) + x; ++ } ++ ++ T incrementAndGet() ++ { ++ return addAndGet(1); ++ } ++ ++ T decrementAndGet() ++ { ++ return addAndGet(-1); ++ } ++ ++ void add(T x) ++ { ++ getAndAdd(x); ++ } ++ ++ void increment() ++ { ++ incrementAndGet(); ++ } ++ ++ void decrement() ++ { ++ decrementAndGet(); ++ } ++ ++ T getAndSet(T newValue) ++ { ++ MutexLockGuard lock(mutex_); ++ T old = value_; ++ value_ = newValue; ++ return old; ++ } ++ ++ private: ++ volatile T value_; ++ MutexLock mutex_; ++}; + } + + typedef detail::AtomicIntegerT AtomicInt32; +-typedef detail::AtomicIntegerT AtomicInt64; ++typedef detail::AtomicIntegerLock AtomicInt64; + } + + #endif // MUDUO_BASE_ATOMIC_H +diff --git a/muduo/base/tests/CMakeLists.txt b/muduo/base/tests/CMakeLists.txt +index 2c3f1c4..73d90fd 100644 +--- a/muduo/base/tests/CMakeLists.txt ++++ b/muduo/base/tests/CMakeLists.txt +@@ -2,7 +2,7 @@ + target_link_libraries(asynclogging_test muduo_base) + + add_executable(atomic_unittest Atomic_unittest.cc) +-# target_link_libraries(atomic_unittest muduo_base) ++target_link_libraries(atomic_unittest muduo_base) + + add_executable(blockingqueue_test BlockingQueue_test.cc) + target_link_libraries(blockingqueue_test muduo_base) diff --git a/backport.diff b/backport.diff new file mode 100644 index 000000000..4fb189b8d --- /dev/null +++ b/backport.diff @@ -0,0 +1,490 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -9,18 +9,18 @@ endif() + set(CXX_FLAGS + -g + # -DVALGRIND +- # -DMUDUO_STD_STRING ++ -DMUDUO_STD_STRING + -Wall + -Wextra + -Werror + -Wconversion + -Wno-unused-parameter +- -Wold-style-cast ++ # -Wold-style-cast + -Woverloaded-virtual + -Wpointer-arith +- -Wshadow ++ # -Wshadow + -Wwrite-strings +- -march=native ++ -march=nocona + # -MMD + # -std=c++0x + -rdynamic +diff --git a/examples/idleconnection/CMakeLists.txt b/examples/idleconnection/CMakeLists.txt +--- a/examples/idleconnection/CMakeLists.txt ++++ b/examples/idleconnection/CMakeLists.txt +@@ -1,2 +1,2 @@ +-add_executable(idleconnection_echo echo.cc main.cc) ++add_executable(idleconnection_echo EXCLUDE_FROM_ALL echo.cc main.cc) + target_link_libraries(idleconnection_echo muduo_net) +diff --git a/muduo/base/Timestamp.cc b/muduo/base/Timestamp.cc +--- a/muduo/base/Timestamp.cc ++++ b/muduo/base/Timestamp.cc +@@ -4,7 +4,7 @@ + #include + #define __STDC_FORMAT_MACROS + #include +-#undef __STDC_FORMAT_MACROS ++//#undef __STDC_FORMAT_MACROS + + #include + +diff --git a/muduo/base/tests/CMakeLists.txt b/muduo/base/tests/CMakeLists.txt +--- a/muduo/base/tests/CMakeLists.txt ++++ b/muduo/base/tests/CMakeLists.txt +@@ -4,7 +4,7 @@ add_executable(atomic_unittest Atomic_unittest.cc) + add_executable(blockingqueue_test BlockingQueue_test.cc) + target_link_libraries(blockingqueue_test muduo_base) + +-add_executable(boundedblockingqueue_test BoundedBlockingQueue_test.cc) ++add_executable(boundedblockingqueue_test EXCLUDE_FROM_ALL BoundedBlockingQueue_test.cc) + target_link_libraries(boundedblockingqueue_test muduo_base) + + add_executable(date_unittest Date_unittest.cc) +diff --git a/muduo/net/Channel.cc b/muduo/net/Channel.cc +--- a/muduo/net/Channel.cc ++++ b/muduo/net/Channel.cc +@@ -21,6 +21,10 @@ const int Channel::kNoneEvent = 0; + const int Channel::kReadEvent = POLLIN | POLLPRI; + const int Channel::kWriteEvent = POLLOUT; + ++#ifndef POLLRDHUP ++#define POLLRDHUP 0x2000 ++#endif ++ + Channel::Channel(EventLoop* loop, int fd__) + : loop_(loop), + fd_(fd__), +diff --git a/muduo/net/Endian.h b/muduo/net/Endian.h +--- a/muduo/net/Endian.h ++++ b/muduo/net/Endian.h +@@ -13,6 +13,7 @@ + + #include + #include ++#include + + namespace muduo + { +@@ -23,39 +24,37 @@ namespace sockets + + // the inline assembler code makes type blur, + // so we disable warnings for a while. +-#pragma GCC diagnostic ignored "-Wconversion" +-#pragma GCC diagnostic ignored "-Wold-style-cast" ++#if __BYTE_ORDER == __LITTLE_ENDIAN + inline uint64_t hostToNetwork64(uint64_t host64) + { +- return htobe64(host64); ++ return bswap_64(host64); + } + + inline uint32_t hostToNetwork32(uint32_t host32) + { +- return htobe32(host32); ++ return bswap_32(host32); + } + + inline uint16_t hostToNetwork16(uint16_t host16) + { +- return htobe16(host16); ++ return bswap_16(host16); + } + + inline uint64_t networkToHost64(uint64_t net64) + { +- return be64toh(net64); ++ return bswap_64(net64); + } + + inline uint32_t networkToHost32(uint32_t net32) + { +- return be32toh(net32); ++ return bswap_32(net32); + } + + inline uint16_t networkToHost16(uint16_t net16) + { +- return be16toh(net16); ++ return bswap_16(net16); + } +-#pragma GCC diagnostic error "-Wconversion" +-#pragma GCC diagnostic error "-Wold-style-cast" ++#endif + + } + } +diff --git a/muduo/net/EventLoop.cc b/muduo/net/EventLoop.cc +--- a/muduo/net/EventLoop.cc ++++ b/muduo/net/EventLoop.cc +@@ -19,7 +19,8 @@ + #include + + #include +-#include ++#include ++#include + + using namespace muduo; + using namespace muduo::net; +@@ -30,18 +31,6 @@ __thread EventLoop* t_loopInThisThread = 0; + + const int kPollTimeMs = 10000; + +-int createEventfd() +-{ +- int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); +- if (evtfd < 0) +- { +- LOG_SYSERR << "Failed in eventfd"; +- abort(); +- } +- return evtfd; +-} +- +-#pragma GCC diagnostic ignored "-Wold-style-cast" + class IgnoreSigPipe + { + public: +@@ -51,7 +40,6 @@ class IgnoreSigPipe + LOG_TRACE << "Ignore SIGPIPE"; + } + }; +-#pragma GCC diagnostic error "-Wold-style-cast" + + IgnoreSigPipe initObj; + } +@@ -63,11 +51,15 @@ EventLoop::EventLoop() + callingPendingFunctors_(false), + threadId_(CurrentThread::tid()), + poller_(Poller::newDefaultPoller(this)), +- timerQueue_(new TimerQueue(this)), +- wakeupFd_(createEventfd()), +- wakeupChannel_(new Channel(this, wakeupFd_)) ++ timerQueue_(new TimerQueue(this)) + { + LOG_TRACE << "EventLoop created " << this << " in thread " << threadId_; ++ if (::socketpair(AF_UNIX, SOCK_STREAM, 0, wakeupFd_) < 0) ++ { ++ LOG_SYSFATAL << "Failed in socketpair"; ++ } ++ wakeupChannel_.reset(new Channel(this, wakeupFd_[0])); ++ + if (t_loopInThisThread) + { + LOG_FATAL << "Another EventLoop " << t_loopInThisThread +@@ -85,7 +77,8 @@ EventLoop::EventLoop() + + EventLoop::~EventLoop() + { +- ::close(wakeupFd_); ++ ::close(wakeupFd_[0]); ++ ::close(wakeupFd_[1]); + t_loopInThisThread = NULL; + } + +@@ -100,11 +93,12 @@ void EventLoop::loop() + while (!quit_) + { + activeChannels_.clear(); +- pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); ++ pollReturnTime_ = poller_->poll(timerQueue_->getTimeout(), &activeChannels_); + if (Logger::logLevel() <= Logger::TRACE) + { + printActiveChannels(); + } ++ timerQueue_->processTimers(); + // TODO sort channel by priority + eventHandling_ = true; + for (ChannelList::iterator it = activeChannels_.begin(); +@@ -201,7 +195,7 @@ void EventLoop::abortNotInLoopThread() + void EventLoop::wakeup() + { + uint64_t one = 1; +- ssize_t n = sockets::write(wakeupFd_, &one, sizeof one); ++ ssize_t n = sockets::write(wakeupFd_[1], &one, sizeof one); + if (n != sizeof one) + { + LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8"; +@@ -211,7 +205,7 @@ void EventLoop::wakeup() + void EventLoop::handleRead() + { + uint64_t one = 1; +- ssize_t n = sockets::read(wakeupFd_, &one, sizeof one); ++ ssize_t n = sockets::read(wakeupFd_[0], &one, sizeof one); + if (n != sizeof one) + { + LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8"; +diff --git a/muduo/net/EventLoop.h b/muduo/net/EventLoop.h +--- a/muduo/net/EventLoop.h ++++ b/muduo/net/EventLoop.h +@@ -124,7 +124,7 @@ class EventLoop : boost::noncopyable + Timestamp pollReturnTime_; + boost::scoped_ptr poller_; + boost::scoped_ptr timerQueue_; +- int wakeupFd_; ++ int wakeupFd_[2]; + // unlike in TimerQueue, which is an internal class, + // we don't expose Channel to client. + boost::scoped_ptr wakeupChannel_; +diff --git a/muduo/net/InetAddress.cc b/muduo/net/InetAddress.cc +--- a/muduo/net/InetAddress.cc ++++ b/muduo/net/InetAddress.cc +@@ -17,9 +17,7 @@ + #include + + // INADDR_ANY use (type)value casting. +-#pragma GCC diagnostic ignored "-Wold-style-cast" + static const in_addr_t kInaddrAny = INADDR_ANY; +-#pragma GCC diagnostic error "-Wold-style-cast" + + // /* Structure describing an Internet socket address. */ + // struct sockaddr_in { +diff --git a/muduo/net/SocketsOps.cc b/muduo/net/SocketsOps.cc +--- a/muduo/net/SocketsOps.cc ++++ b/muduo/net/SocketsOps.cc +@@ -59,7 +59,6 @@ void setNonBlockAndCloseOnExec(int sockfd) + int sockets::createNonblockingOrDie() + { + // socket +-#if VALGRIND + int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) + { +@@ -67,13 +66,6 @@ int sockets::createNonblockingOrDie() + } + + setNonBlockAndCloseOnExec(sockfd); +-#else +- int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); +- if (sockfd < 0) +- { +- LOG_SYSFATAL << "sockets::createNonblockingOrDie"; +- } +-#endif + return sockfd; + } + +@@ -98,13 +90,8 @@ void sockets::listenOrDie(int sockfd) + int sockets::accept(int sockfd, struct sockaddr_in* addr) + { + socklen_t addrlen = sizeof *addr; +-#if VALGRIND + int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen); + setNonBlockAndCloseOnExec(connfd); +-#else +- int connfd = ::accept4(sockfd, sockaddr_cast(addr), +- &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); +-#endif + if (connfd < 0) + { + int savedErrno = errno; +diff --git a/muduo/net/TimerQueue.cc b/muduo/net/TimerQueue.cc +--- a/muduo/net/TimerQueue.cc ++++ b/muduo/net/TimerQueue.cc +@@ -16,8 +16,6 @@ + + #include + +-#include +- + namespace muduo + { + namespace net +@@ -25,57 +23,15 @@ namespace net + namespace detail + { + +-int createTimerfd() +-{ +- int timerfd = ::timerfd_create(CLOCK_MONOTONIC, +- TFD_NONBLOCK | TFD_CLOEXEC); +- if (timerfd < 0) +- { +- LOG_SYSFATAL << "Failed in timerfd_create"; +- } +- return timerfd; +-} +- +-struct timespec howMuchTimeFromNow(Timestamp when) ++int howMuchTimeFromNow(Timestamp when) + { + int64_t microseconds = when.microSecondsSinceEpoch() + - Timestamp::now().microSecondsSinceEpoch(); +- if (microseconds < 100) +- { +- microseconds = 100; +- } +- struct timespec ts; +- ts.tv_sec = static_cast( +- microseconds / Timestamp::kMicroSecondsPerSecond); +- ts.tv_nsec = static_cast( +- (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); +- return ts; +-} +- +-void readTimerfd(int timerfd, Timestamp now) +-{ +- uint64_t howmany; +- ssize_t n = ::read(timerfd, &howmany, sizeof howmany); +- LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString(); +- if (n != sizeof howmany) +- { +- LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8"; +- } +-} +- +-void resetTimerfd(int timerfd, Timestamp expiration) +-{ +- // wake up loop by timerfd_settime() +- struct itimerspec newValue; +- struct itimerspec oldValue; +- bzero(&newValue, sizeof newValue); +- bzero(&oldValue, sizeof oldValue); +- newValue.it_value = howMuchTimeFromNow(expiration); +- int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); +- if (ret) ++ if (microseconds < 1000) + { +- LOG_SYSERR << "timerfd_settime()"; ++ microseconds = 1000; + } ++ return static_cast(microseconds / 1000); + } + + } +@@ -88,20 +44,13 @@ using namespace muduo::net::detail; + + TimerQueue::TimerQueue(EventLoop* loop) + : loop_(loop), +- timerfd_(createTimerfd()), +- timerfdChannel_(loop, timerfd_), + timers_(), + callingExpiredTimers_(false) + { +- timerfdChannel_.setReadCallback( +- boost::bind(&TimerQueue::handleRead, this)); +- // we are always reading the timerfd, we disarm it with timerfd_settime. +- timerfdChannel_.enableReading(); + } + + TimerQueue::~TimerQueue() + { +- ::close(timerfd_); + // do not remove channel, since we're in EventLoop::dtor(); + for (TimerList::iterator it = timers_.begin(); + it != timers_.end(); ++it) +@@ -129,11 +78,19 @@ void TimerQueue::cancel(TimerId timerId) + void TimerQueue::scheduleInLoop(Timer* timer) + { + loop_->assertInLoopThread(); +- bool earliestChanged = insert(timer); ++ insert(timer); ++} + +- if (earliestChanged) ++int TimerQueue::getTimeout() const ++{ ++ loop_->assertInLoopThread(); ++ if (timers_.empty()) ++ { ++ return 10000; ++ } ++ else + { +- resetTimerfd(timerfd_, timer->expiration()); ++ return howMuchTimeFromNow(timers_.begin()->second->expiration()); + } + } + +@@ -157,11 +114,10 @@ void TimerQueue::cancelInLoop(TimerId ti + assert(timers_.size() == activeTimers_.size()); + } + +-void TimerQueue::handleRead() ++void TimerQueue::processTimers() + { + loop_->assertInLoopThread(); + Timestamp now(Timestamp::now()); +- readTimerfd(timerfd_, now); + + std::vector expired = getExpired(now); + +@@ -225,11 +181,6 @@ void TimerQueue::reset(const std::vector + { + nextExpire = timers_.begin()->second->expiration(); + } +- +- if (nextExpire.valid()) +- { +- resetTimerfd(timerfd_, nextExpire); +- } + } + + bool TimerQueue::insert(Timer* timer) +diff --git a/muduo/net/TimerQueue.h b/muduo/net/TimerQueue.h +--- a/muduo/net/TimerQueue.h ++++ b/muduo/net/TimerQueue.h +@@ -51,6 +51,9 @@ class TimerQueue : boost::noncopyable + + void cancel(TimerId timerId); + ++ int getTimeout() const; ++ void processTimers(); ++ + private: + + // FIXME: use unique_ptr instead of raw pointers. +@@ -61,8 +64,6 @@ class TimerQueue : boost::noncopyable + + void scheduleInLoop(Timer* timer); + void cancelInLoop(TimerId timerId); +- // called when timerfd arms +- void handleRead(); + // move out all expired timers + std::vector getExpired(Timestamp now); + void reset(const std::vector& expired, Timestamp now); +@@ -70,9 +71,6 @@ class TimerQueue : boost::noncopyable + bool insert(Timer* timer); + + EventLoop* loop_; +- const int timerfd_; +- Channel timerfdChannel_; +- // Timer list sorted by expiration + TimerList timers_; + + // for cancel() +diff --git a/muduo/net/poller/EPollPoller.cc b/muduo/net/poller/EPollPoller.cc +--- a/muduo/net/poller/EPollPoller.cc ++++ b/muduo/net/poller/EPollPoller.cc +@@ -26,7 +26,6 @@ using namespace muduo::net; + BOOST_STATIC_ASSERT(EPOLLIN == POLLIN); + BOOST_STATIC_ASSERT(EPOLLPRI == POLLPRI); + BOOST_STATIC_ASSERT(EPOLLOUT == POLLOUT); +-BOOST_STATIC_ASSERT(EPOLLRDHUP == POLLRDHUP); + BOOST_STATIC_ASSERT(EPOLLERR == POLLERR); + BOOST_STATIC_ASSERT(EPOLLHUP == POLLHUP); + +@@ -39,7 +38,7 @@ const int kDeleted = 2; + + EPollPoller::EPollPoller(EventLoop* loop) + : Poller(loop), +- epollfd_(::epoll_create1(EPOLL_CLOEXEC)), ++ epollfd_(::epoll_create(32)), + events_(kInitEventListSize) + { + if (epollfd_ < 0) diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..f751680f9 --- /dev/null +++ b/build.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +set -x + +SOURCE_DIR=`pwd` +BUILD_DIR=${BUILD_DIR:-../build} +BUILD_TYPE=${BUILD_TYPE:-debug} +INSTALL_DIR=${INSTALL_DIR:-../${BUILD_TYPE}-install} +BUILD_NO_EXAMPLES=${BUILD_NO_EXAMPLES:-0} + +mkdir -p $BUILD_DIR/$BUILD_TYPE \ + && cd $BUILD_DIR/$BUILD_TYPE \ + && cmake \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ + -DCMAKE_BUILD_NO_EXAMPLES=$BUILD_NO_EXAMPLES \ + $SOURCE_DIR \ + && make $* + +# cd $SOURCE_DIR && doxygen + diff --git a/clang.diff b/clang.diff new file mode 100644 index 000000000..bdf2a6645 --- /dev/null +++ b/clang.diff @@ -0,0 +1,53 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2c8880a..ea1432a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -9,13 +9,14 @@ endif() + set(CXX_FLAGS + -g + # -DVALGRIND +- # -DMUDUO_STD_STRING ++ -DMUDUO_STD_STRING + -D_FILE_OFFSET_BITS=64 + -Wall + -Wextra +- -Werror ++ # -Werror + -Wconversion + -Wno-unused-parameter ++ -Wno-sign-conversion + -Wold-style-cast + -Woverloaded-virtual + -Wpointer-arith +@@ -24,17 +25,19 @@ set(CXX_FLAGS + -march=native + # -MMD + # -std=c++0x +- -rdynamic ++ # -rdynamic + ) + if(CMAKE_BUILD_BITS EQUAL 32) + list(APPEND CXX_FLAGS "-m32") + endif() + string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") + +-set(CMAKE_CXX_COMPILER "g++") ++#set(CMAKE_CXX_COMPILER "g++") + #set(CMAKE_CXX_COMPILER "icpc") ++set(CMAKE_CXX_COMPILER "clang") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") +-set(CMAKE_CXX_FLAGS_RELEASE "-O2 -finline-limit=1000 -DNDEBUG") ++set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") ++set(CMAKE_EXE_LINKER_FLAGS -lstdc++) + set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) + set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +@@ -48,6 +51,8 @@ find_library(MHD_LIBRARY NAMES microhttpd) + find_library(BOOSTTEST_LIBRARY NAMES boost_unit_test_framework) + + include_directories(${Boost_INCLUDE_DIRS}) ++include_directories(/usr/lib/gcc/x86_64-linux-gnu/4.4/include/) ++include_directories(/usr/lib/gcc/x86_64-linux-gnu/4.4/include-fixed/) + + include_directories(${PROJECT_SOURCE_DIR}) + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..bbcd0f469 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,39 @@ +add_subdirectory(asio/chat) +add_subdirectory(asio/tutorial) +add_subdirectory(fastcgi) +add_subdirectory(filetransfer) +add_subdirectory(hub) +add_subdirectory(idleconnection) +add_subdirectory(maxconnection) +add_subdirectory(multiplexer) +add_subdirectory(netty/discard) +add_subdirectory(netty/echo) +add_subdirectory(netty/uptime) +add_subdirectory(pingpong) +add_subdirectory(roundtrip) +add_subdirectory(shorturl) +add_subdirectory(simple) +add_subdirectory(socks4a) +add_subdirectory(sudoku) +add_subdirectory(twisted/finger) +add_subdirectory(wordcount) +add_subdirectory(zeromq) + +if(CARES_INCLUDE_DIR AND CARES_LIBRARY) + add_subdirectory(cdns) +else() + add_subdirectory(cdns EXCLUDE_FROM_ALL) +endif() + +if(CURL_FOUND) + add_subdirectory(curl) +else() + add_subdirectory(curl EXCLUDE_FROM_ALL) +endif() + +if(PROTOBUF_FOUND) + add_subdirectory(protobuf) +else() + add_subdirectory(protobuf EXCLUDE_FROM_ALL) +endif() + diff --git a/examples/asio/chat/CMakeLists.txt b/examples/asio/chat/CMakeLists.txt new file mode 100644 index 000000000..f2b0dfc36 --- /dev/null +++ b/examples/asio/chat/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(asio_chat_client client.cc) +target_link_libraries(asio_chat_client muduo_net) + +add_executable(asio_chat_loadtest loadtest.cc) +target_link_libraries(asio_chat_loadtest muduo_net) + +add_executable(asio_chat_server server.cc) +target_link_libraries(asio_chat_server muduo_net) + +add_executable(asio_chat_server_threaded server_threaded.cc) +target_link_libraries(asio_chat_server_threaded muduo_net) + +add_executable(asio_chat_server_threaded_efficient server_threaded_efficient.cc) +target_link_libraries(asio_chat_server_threaded_efficient muduo_net) + +add_executable(asio_chat_server_threaded_highperformance server_threaded_highperformance.cc) +target_link_libraries(asio_chat_server_threaded_highperformance muduo_net) + diff --git a/examples/asio/chat/client.cc b/examples/asio/chat/client.cc new file mode 100644 index 000000000..2aee5a8ad --- /dev/null +++ b/examples/asio/chat/client.cc @@ -0,0 +1,106 @@ +#include "codec.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class ChatClient : boost::noncopyable +{ + public: + ChatClient(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + client_(loop, serverAddr, "ChatClient"), + codec_(boost::bind(&ChatClient::onStringMessage, this, _1, _2, _3)) + { + client_.setConnectionCallback( + boost::bind(&ChatClient::onConnection, this, _1)); + client_.setMessageCallback( + boost::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()); + } + + EventLoop* loop_; + TcpClient client_; + LengthHeaderCodec codec_; + MutexLock mutex_; + TcpConnectionPtr connection_; +}; + +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(); + } + 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 000000000..29f1d3dd0 --- /dev/null +++ b/examples/asio/chat/codec.h @@ -0,0 +1,71 @@ +#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H +#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H + +#include +#include +#include +#include + +#include +#include + +class LengthHeaderCodec : boost::noncopyable +{ + public: + typedef boost::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 000000000..ff4c2dc6f --- /dev/null +++ b/examples/asio/chat/loadtest.cc @@ -0,0 +1,171 @@ +#include "codec.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#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; +boost::function g_statistic; + +class ChatClient : boost::noncopyable +{ + public: + ChatClient(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + client_(loop, serverAddr, "LoadTestClient"), + codec_(boost::bind(&ChatClient::onStringMessage, this, _1, _2, _3)) + { + client_.setConnectionCallback( + boost::bind(&ChatClient::onConnection, this, _1)); + client_.setMessageCallback( + boost::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, boost::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 boost::ptr_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 += 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); + loopPool.setThreadNum(threads); + loopPool.start(); + + g_receiveTime.reserve(g_connections); + boost::ptr_vector clients(g_connections); + g_statistic = boost::bind(statistic, boost::ref(clients)); + + for (int i = 0; i < g_connections; ++i) + { + clients.push_back(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 000000000..9bc827023 --- /dev/null +++ b/examples/asio/chat/server.cc @@ -0,0 +1,89 @@ +#include "codec.h" + +#include +#include +#include +#include + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : boost::noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "ChatServer"), + codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) + { + server_.setConnectionCallback( + boost::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::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; + EventLoop* loop_; + 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 000000000..a79fba30d --- /dev/null +++ b/examples/asio/chat/server_threaded.cc @@ -0,0 +1,101 @@ +#include "codec.h" + +#include +#include +#include +#include + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : boost::noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "ChatServer"), + codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) + { + server_.setConnectionCallback( + boost::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::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; + EventLoop* loop_; + TcpServer server_; + LengthHeaderCodec codec_; + MutexLock mutex_; + 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); + 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 000000000..4621d7651 --- /dev/null +++ b/examples/asio/chat/server_threaded_efficient.cc @@ -0,0 +1,117 @@ +#include "codec.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : boost::noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "ChatServer"), + codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)), + connections_(new ConnectionList) + { + server_.setConnectionCallback( + boost::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::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 boost::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_; + } + + EventLoop* loop_; + TcpServer server_; + LengthHeaderCodec codec_; + MutexLock mutex_; + ConnectionListPtr 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); + 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 000000000..d1ec921e1 --- /dev/null +++ b/examples/asio/chat/server_threaded_highperformance.cc @@ -0,0 +1,132 @@ +#include "codec.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class ChatServer : boost::noncopyable +{ + public: + ChatServer(EventLoop* loop, + const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "ChatServer"), + codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)) + { + server_.setConnectionCallback( + boost::bind(&ChatServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3)); + } + + void setThreadNum(int numThreads) + { + server_.setThreadNum(numThreads); + } + + void start() + { + server_.setThreadInitCallback(boost::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 = boost::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); + } + + EventLoop* loop_; + TcpServer server_; + LengthHeaderCodec codec_; + typedef ThreadLocalSingleton LocalConnections; + + MutexLock mutex_; + std::set loops_; +}; + +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 000000000..e69de29bb diff --git a/examples/asio/tutorial/CMakeLists.txt b/examples/asio/tutorial/CMakeLists.txt new file mode 100644 index 000000000..e4d304113 --- /dev/null +++ b/examples/asio/tutorial/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(asio_tutorial_timer2 timer2/timer.cc) +target_link_libraries(asio_tutorial_timer2 muduo_net) + +add_executable(asio_tutorial_timer3 timer3/timer.cc) +target_link_libraries(asio_tutorial_timer3 muduo_net) + +add_executable(asio_tutorial_timer4 timer4/timer.cc) +target_link_libraries(asio_tutorial_timer4 muduo_net) + +add_executable(asio_tutorial_timer5 timer5/timer.cc) +target_link_libraries(asio_tutorial_timer5 muduo_net) + +add_executable(asio_tutorial_timer6 timer6/timer.cc) +target_link_libraries(asio_tutorial_timer6 muduo_net) + diff --git a/examples/asio/tutorial/daytime_see_simple b/examples/asio/tutorial/daytime_see_simple new file mode 100644 index 000000000..e69de29bb diff --git a/examples/asio/tutorial/there_is_no_timer1 b/examples/asio/tutorial/there_is_no_timer1 new file mode 100644 index 000000000..e69de29bb diff --git a/examples/asio/tutorial/timer2/timer.cc b/examples/asio/tutorial/timer2/timer.cc new file mode 100644 index 000000000..2376b8d36 --- /dev/null +++ b/examples/asio/tutorial/timer2/timer.cc @@ -0,0 +1,16 @@ +#include + +#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 000000000..98d609300 --- /dev/null +++ b/examples/asio/tutorial/timer3/timer.cc @@ -0,0 +1,29 @@ +#include + +#include +#include + +void print(muduo::net::EventLoop* loop, int* count) +{ + if (*count < 5) + { + std::cout << *count << "\n"; + ++(*count); + + loop->runAfter(1, boost::bind(print, loop, count)); + } + else + { + loop->quit(); + } +} + +int main() +{ + muduo::net::EventLoop loop; + int count = 0; + loop.runAfter(1, boost::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 000000000..2a2d8c329 --- /dev/null +++ b/examples/asio/tutorial/timer4/timer.cc @@ -0,0 +1,48 @@ +#include + +#include +#include +#include + +class Printer : boost::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop) + : loop_(loop), + count_(0) + { + loop_->runAfter(1, boost::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, boost::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 000000000..8e345c98e --- /dev/null +++ b/examples/asio/tutorial/timer5/timer.cc @@ -0,0 +1,74 @@ +#include +#include +#include + +#include +#include +#include + +class Printer : boost::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop1, muduo::net::EventLoop* loop2) + : loop1_(loop1), + loop2_(loop2), + count_(0) + { + loop1_->runAfter(1, boost::bind(&Printer::print1, this)); + loop2_->runAfter(1, boost::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, boost::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, boost::bind(&Printer::print2, this)); + } + else + { + loop2_->quit(); + } + } + +private: + + muduo::MutexLock mutex_; + muduo::net::EventLoop* loop1_; + muduo::net::EventLoop* loop2_; + int count_; +}; + +int main() +{ + muduo::net::EventLoop loop; + muduo::net::EventLoopThread loopThread; + muduo::net::EventLoop* loopInAnotherThread = loopThread.startLoop(); + Printer 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 000000000..95c4721d4 --- /dev/null +++ b/examples/asio/tutorial/timer6/timer.cc @@ -0,0 +1,112 @@ +#include +#include +#include + +#include +#include +#include + +// +// Minimize locking +// + +class Printer : boost::noncopyable +{ + public: + Printer(muduo::net::EventLoop* loop1, muduo::net::EventLoop* loop2) + : loop1_(loop1), + loop2_(loop2), + count_(0) + { + loop1_->runAfter(1, boost::bind(&Printer::print1, this)); + loop2_->runAfter(1, boost::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) + { + loop1_->quit(); + } + else + { + // cout is not thread safe + //std::cout << "Timer 1: " << count << "\n"; + printf("Timer 1: %d\n", count); + loop1_->runAfter(1, boost::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) + { + loop2_->quit(); + } + else + { + // cout is not thread safe + //std::cout << "Timer 2: " << count << "\n"; + printf("Timer 2: %d\n", count); + loop2_->runAfter(1, boost::bind(&Printer::print2, this)); + } + } + +private: + + muduo::MutexLock mutex_; + muduo::net::EventLoop* loop1_; + muduo::net::EventLoop* loop2_; + int count_; +}; + +int main() +{ + muduo::net::EventLoop loop; + muduo::net::EventLoopThread loopThread; + muduo::net::EventLoop* loopInAnotherThread = loopThread.startLoop(); + Printer printer(&loop, loopInAnotherThread); + loop.loop(); +} + diff --git a/examples/cdns/CMakeLists.txt b/examples/cdns/CMakeLists.txt new file mode 100644 index 000000000..27d8727d3 --- /dev/null +++ b/examples/cdns/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(muduo_cdns Resolver.cc) +target_link_libraries(muduo_cdns muduo_net) +target_link_libraries(muduo_cdns cares) + +install(TARGETS muduo_cdns DESTINATION lib) +install(FILES Resolver.h DESTINATION include/muduo/cdns) + +add_executable(cdns dns.cc) +target_link_libraries(cdns muduo_cdns) + diff --git a/examples/cdns/Resolver.cc b/examples/cdns/Resolver.cc new file mode 100644 index 000000000..1e56e282c --- /dev/null +++ b/examples/cdns/Resolver.cc @@ -0,0 +1,203 @@ +#include + +#include +#include +#include + +#include + +#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; +} + +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(const StringPiece& hostname, const Callback& cb) +{ + loop_->assertInLoopThread(); + QueryData* queryData = new QueryData(this, cb); + ares_gethostbyname(ctx_, hostname.data(), 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, boost::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, boost::bind(&Resolver::onTimer, this)); + } +} + +void Resolver::onQueryResult(int status, struct hostent* result, const Callback& callback) +{ + LOG_DEBUG << "onQueryResult " << status; + struct sockaddr_in addr; + bzero(&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(boost::bind(&Resolver::onRead, this, sockfd, _1)); + channel->enableReading(); + channels_.insert(sockfd, 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 000000000..5f15767cd --- /dev/null +++ b/examples/cdns/Resolver.h @@ -0,0 +1,76 @@ +#ifndef MUDUO_EXAMPLES_CDNS_RESOLVER_H +#define MUDUO_EXAMPLES_CDNS_RESOLVER_H + +#include +#include +#include + +#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 : boost::noncopyable +{ + public: + typedef boost::function Callback; + enum Option + { + kDNSandHostsFile, + kDNSonly, + }; + + explicit Resolver(muduo::net::EventLoop* loop, Option opt = kDNSandHostsFile); + ~Resolver(); + + bool resolve(const muduo::StringPiece& 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 boost::ptr_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); +}; +} + +#endif diff --git a/examples/cdns/dns.cc b/examples/cdns/dns.cc new file mode 100644 index 000000000..d25db3887 --- /dev/null +++ b/examples/cdns/dns.cc @@ -0,0 +1,52 @@ +#include +#include +#include +#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, boost::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/CMakeLists.txt b/examples/curl/CMakeLists.txt new file mode 100644 index 000000000..bf0b15554 --- /dev/null +++ b/examples/curl/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(muduo_curl Curl.cc) +target_link_libraries(muduo_curl muduo_net) +target_link_libraries(muduo_curl curl) + +install(TARGETS muduo_curl DESTINATION lib) +install(FILES Curl.h DESTINATION include/muduo/curl) + +add_executable(mcurl mcurl.cc) +target_link_libraries(mcurl muduo_curl) + +add_executable(curl_download download.cc) +target_link_libraries(curl_download muduo_curl) + diff --git a/examples/curl/Curl.cc b/examples/curl/Curl.cc new file mode 100644 index 000000000..2d843a3e0 --- /dev/null +++ b/examples/curl/Curl.cc @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include + +#include +#include + +using namespace curl; +using namespace muduo; +using namespace muduo::net; + +static void dummy(const boost::shared_ptr&) +{ +} + +Request::Request(Curl* owner, StringPiece url) + : owner_(owner), + curl_(CHECK_NOTNULL(curl_easy_init())) +{ + setopt(CURLOPT_URL, url.data()); + 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_); +} + +template +int Request::setopt(OPT opt, long p) +{ + return curl_easy_setopt(curl_, opt, p); +} + +template +int Request::setopt(OPT opt, const char* p) +{ + return curl_easy_setopt(curl_, opt, p); +} + +template +int Request::setopt(OPT opt, void* p) +{ + return curl_easy_setopt(curl_, opt, p); +} + +template +int Request::setopt(OPT opt, size_t (*p)(char*, size_t , size_t , void*)) +{ + return curl_easy_setopt(curl_, opt, p); +} + +// 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(muduo::StringPiece range) +{ + setopt(CURLOPT_RANGE, range.data()); +} + +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(boost::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(boost::bind(&Curl::onRead, curl, fd)); + ch->setWriteCallback(boost::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, boost::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(muduo::StringPiece url) +{ + RequestPtr req(new Request(this, url)); + 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 000000000..cd9697610 --- /dev/null +++ b/examples/curl/Curl.h @@ -0,0 +1,134 @@ +#ifndef MUDUO_EXAMPLES_CURL_CURL_H +#define MUDUO_EXAMPLES_CURL_CURL_H + +#include + +#include +#include +#include +#include +#include + +extern "C" +{ +typedef void CURLM; +typedef void CURL; +} + +namespace muduo +{ +namespace net +{ +class Channel; +class EventLoop; +} +} + +namespace curl +{ + +class Curl; + +class Request : public boost::enable_shared_from_this, + boost::noncopyable +{ + public: + typedef boost::function DataCallback; + typedef boost::function DoneCallback; + + Request(Curl*, muduo::StringPiece 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(muduo::StringPiece range); + + template + int setopt(OPT opt, long); + + template + int setopt(OPT opt, const char*); + + template + int setopt(OPT opt, void*); + + template + int setopt(OPT opt, size_t (*)(char *, size_t , size_t , void *)); + + 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 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_; + boost::shared_ptr channel_; + DataCallback dataCb_; + DataCallback headerCb_; + DoneCallback doneCb_; +}; + +typedef boost::shared_ptr RequestPtr; + +class Curl : boost::noncopyable +{ + public: + + enum Option + { + kCURLnossl = 0, + kCURLssl = 1, + }; + + explicit Curl(muduo::net::EventLoop* loop); + ~Curl(); + + RequestPtr getUrl(muduo::StringPiece url); // must be null-terminated string + + 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_; +}; + +} + +#endif diff --git a/examples/curl/README b/examples/curl/README new file mode 100644 index 000000000..8ddbcf80c --- /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 000000000..6cf505d2f --- /dev/null +++ b/examples/curl/download.cc @@ -0,0 +1,209 @@ +// Concurrent downloading one file from HTTP + +#include +#include +#include +#include +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +typedef boost::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 : boost::noncopyable +{ + public: + Piece(const curl::RequestPtr& req, + const FilePtr& out, + const std::string& range, + const boost::function done) + : req_(req), + out_(out), + range_(range), + doneCb_(done) + { + LOG_INFO << "range: " << range; + req->setRange(range); + req_->setDataCallback( + boost::bind(&Piece::onData, this, _1, _2)); + req_->setDoneCallback( + boost::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_; + std::string range_; + boost::function doneCb_; +}; + +class Downloader : boost::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( + boost::bind(&Downloader::onHeader, this, _1, _2)); + req_->setDoneCallback( + boost::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( + boost::bind(&Downloader::onData, this, _1, _2)); + req2_->setDoneCallback( + boost::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_.push_back(new Piece(req, + out, + range.str(), + boost::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_; + boost::ptr_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] : "http://chenshuo.com/pdf/MuduoManual.pdf"; + Downloader d(&loop, url); + loop.loop(); +} diff --git a/examples/curl/mcurl.cc b/examples/curl/mcurl.cc new file mode 100644 index 000000000..170ad2997 --- /dev/null +++ b/examples/curl/mcurl.cc @@ -0,0 +1,49 @@ +#include +#include +#include +#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, boost::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/CMakeLists.txt b/examples/fastcgi/CMakeLists.txt new file mode 100644 index 000000000..4bade6194 --- /dev/null +++ b/examples/fastcgi/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(fastcgi_test fastcgi.cc fastcgi_test.cc) +target_link_libraries(fastcgi_test muduo_net) + diff --git a/examples/fastcgi/fastcgi.cc b/examples/fastcgi/fastcgi.cc new file mode 100644 index 000000000..cbd2a26cf --- /dev/null +++ b/examples/fastcgi/fastcgi.cc @@ -0,0 +1,247 @@ +#include +#include +#include + +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) + { + 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 000000000..7c309c6f2 --- /dev/null +++ b/examples/fastcgi/fastcgi.h @@ -0,0 +1,70 @@ +#ifndef MUDUO_EXAMPLES_FASTCGI_FASTCGI_H +#define MUDUO_EXAMPLES_FASTCGI_FASTCGI_H + +#include +#include + +using muduo::string; + +// one FastCgiCodec per TcpConnection +// both lighttpd and nginx do not implement multiplexing, +// so there is no concurrent requests of one connection. +class FastCgiCodec : boost::noncopyable +{ + public: + typedef std::map ParamMap; + typedef boost::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 000000000..93dac6f03 --- /dev/null +++ b/examples/fastcgi/fastcgi_test.cc @@ -0,0 +1,48 @@ +#include + +#include +#include +#include + +#include + +using namespace muduo::net; + +void onRequest(const TcpConnectionPtr& conn, + FastCgiCodec::ParamMap& params, + Buffer* in) +{ + LOG_INFO << params["REQUEST_URI"]; + + for (FastCgiCodec::ParamMap::const_iterator it = params.begin(); + it != params.end(); ++it) + { + LOG_DEBUG << it->first << " = " << it->second; + } + Buffer response; + response.append("Context-Type: text/plain\r\n\r\n"); + response.append("Hello FastCGI."); + FastCgiCodec::respond(&response); + conn->send(&response); +} + +typedef boost::shared_ptr CodecPtr; +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->connected()) + { + CodecPtr codec(new FastCgiCodec(onRequest)); + conn->setContext(codec); + conn->setMessageCallback( + boost::bind(&FastCgiCodec::onMessage, codec, _1, _2, _3)); + } +} + +int main() +{ + muduo::net::EventLoop loop; + TcpServer server(&loop, InetAddress(9000), "FastCGI"); + server.setConnectionCallback(onConnection); + server.start(); + loop.loop(); +} diff --git a/examples/filetransfer/CMakeLists.txt b/examples/filetransfer/CMakeLists.txt new file mode 100644 index 000000000..bb8b1ce4b --- /dev/null +++ b/examples/filetransfer/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(filetransfer_download download.cc) +target_link_libraries(filetransfer_download muduo_net) + +add_executable(filetransfer_download2 download2.cc) +target_link_libraries(filetransfer_download2 muduo_net) + +add_executable(filetransfer_download3 download3.cc) +target_link_libraries(filetransfer_download3 muduo_net) + diff --git a/examples/filetransfer/download.cc b/examples/filetransfer/download.cc new file mode 100644 index 000000000..21c312095 --- /dev/null +++ b/examples/filetransfer/download.cc @@ -0,0 +1,76 @@ +#include +#include +#include + +#include + +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 000000000..1695662bd --- /dev/null +++ b/examples/filetransfer/download2.cc @@ -0,0 +1,95 @@ +#include +#include +#include + +#include + +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, 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, 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 000000000..5200f4767 --- /dev/null +++ b/examples/filetransfer/download3.cc @@ -0,0 +1,85 @@ +#include +#include +#include + +#include + +#include + +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 boost::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, 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, 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 000000000..9ae0629da --- /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 000000000..115214def --- /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/CMakeLists.txt b/examples/hub/CMakeLists.txt new file mode 100644 index 000000000..19a36f33d --- /dev/null +++ b/examples/hub/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(hub hub.cc codec.cc) +target_link_libraries(hub muduo_inspect) + +add_library(muduo_pubsub pubsub.cc codec.cc) +target_link_libraries(muduo_pubsub muduo_inspect) + +add_executable(pub pub.cc) +target_link_libraries(pub muduo_pubsub) + +add_executable(sub sub.cc) +target_link_libraries(sub muduo_pubsub) + diff --git a/examples/hub/README b/examples/hub/README new file mode 100644 index 000000000..107c691e6 --- /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 000000000..fc6e8d3d0 --- /dev/null +++ b/examples/hub/codec.cc @@ -0,0 +1,53 @@ +#include "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 000000000..e6ba63d8a --- /dev/null +++ b/examples/hub/codec.h @@ -0,0 +1,29 @@ +#ifndef MUDUO_EXAMPLES_HUB_CODEC_H +#define MUDUO_EXAMPLES_HUB_CODEC_H + +// internal header file + +#include +#include + +#include + +namespace pubsub +{ +using muduo::string; + +enum ParseResult +{ + kError, + kSuccess, + kContinue, +}; + +ParseResult parseMessage(muduo::net::Buffer* buf, + string* cmd, + string* topic, + string* content); +} + +#endif // MUDUO_EXAMPLES_HUB_CODEC_H + diff --git a/examples/hub/hub.cc b/examples/hub/hub.cc new file mode 100644 index 000000000..ad0c4b2a7 --- /dev/null +++ b/examples/hub/hub.cc @@ -0,0 +1,218 @@ +#include "codec.h" + +#include +#include +#include + +#include + +#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 : boost::noncopyable +{ + public: + PubSubServer(muduo::net::EventLoop* loop, + const muduo::net::InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "PubSubServer") + { + server_.setConnectionCallback( + boost::bind(&PubSubServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&PubSubServer::onMessage, this, _1, _2, _3)); + loop_->runEvery(1.0, boost::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()); + for (ConnectionSubscription::const_iterator it = connSub.begin(); + it != connSub.end(); + ++it) + { + 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; + ConnectionSubscription* connSub + = boost::any_cast(conn->getMutableContext()); + connSub->erase(topic); + getTopic(topic).remove(conn); + } + + 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_; +}; + +} + +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 000000000..22a6acb1a --- /dev/null +++ b/examples/hub/pub.cc @@ -0,0 +1,81 @@ +#include "pubsub.h" +#include +#include +#include + +#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(); + } + 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 000000000..7772a3b38 --- /dev/null +++ b/examples/hub/pubsub.cc @@ -0,0 +1,109 @@ +#include "pubsub.h" +#include "codec.h" + +#include + +using namespace muduo; +using namespace muduo::net; +using namespace pubsub; + +PubSubClient::PubSubClient(EventLoop* loop, + const InetAddress& hubAddr, + const string& name) + : loop_(loop), + client_(loop, hubAddr, name) +{ + // FIXME: dtor is not thread safe + client_.setConnectionCallback( + boost::bind(&PubSubClient::onConnection, this, _1)); + client_.setMessageCallback( + boost::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 000000000..32d5dc0d1 --- /dev/null +++ b/examples/hub/pubsub.h @@ -0,0 +1,49 @@ +#ifndef MUDUO_EXAMPLES_HUB_PUBSUB_H +#define MUDUO_EXAMPLES_HUB_PUBSUB_H + +#include + +namespace pubsub +{ +using muduo::string; +using muduo::Timestamp; + +// FIXME: dtor is not thread safe +class PubSubClient : boost::noncopyable +{ + public: + typedef boost::function ConnectionCallback; + typedef boost::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::EventLoop* loop_; + muduo::net::TcpClient client_; + muduo::net::TcpConnectionPtr conn_; + ConnectionCallback connectionCallback_; + SubscribeCallback subscribeCallback_; +}; +} + +#endif // MUDUO_EXAMPLES_HUB_PUBSUB_H diff --git a/examples/hub/sub.cc b/examples/hub/sub.cc new file mode 100644 index 000000000..2e8ff6ccb --- /dev/null +++ b/examples/hub/sub.cc @@ -0,0 +1,70 @@ +#include "pubsub.h" +#include +#include + +#include +#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/CMakeLists.txt b/examples/idleconnection/CMakeLists.txt new file mode 100644 index 000000000..75ada4f98 --- /dev/null +++ b/examples/idleconnection/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(idleconnection_echo echo.cc main.cc) +target_link_libraries(idleconnection_echo muduo_net) + +add_executable(idleconnection_echo2 sortedlist.cc) +target_link_libraries(idleconnection_echo2 muduo_net) diff --git a/examples/idleconnection/echo.cc b/examples/idleconnection/echo.cc new file mode 100644 index 000000000..1d620657d --- /dev/null +++ b/examples/idleconnection/echo.cc @@ -0,0 +1,104 @@ +#include "echo.h" + +#include +#include + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int idleSeconds) + : loop_(loop), + server_(loop, listenAddr, "EchoServer"), + connectionBuckets_(idleSeconds) +{ + server_.setConnectionCallback( + boost::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); + loop->runEvery(1.0, boost::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 (Bucket::const_iterator it = bucket.begin(); + it != bucket.end(); + ++it) + { + 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 000000000..f8e847086 --- /dev/null +++ b/examples/idleconnection/echo.h @@ -0,0 +1,73 @@ +#ifndef MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H +#define MUDUO_EXAMPLES_IDLECONNECTION_ECHO_H + +#include +//#include + +#include +#include +#include + +#if BOOST_VERSION < 104700 +namespace boost +{ +template +inline size_t hash_value(const boost::shared_ptr& x) +{ + return boost::hash_value(x.get()); +} +} +#endif + +// 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 boost::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 boost::shared_ptr EntryPtr; + typedef boost::weak_ptr WeakEntryPtr; + typedef boost::unordered_set Bucket; + typedef boost::circular_buffer WeakConnectionList; + + muduo::net::EventLoop* loop_; + 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 000000000..5d7fb74a8 --- /dev/null +++ b/examples/idleconnection/main.cc @@ -0,0 +1,40 @@ +#include "echo.h" +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +void testHash() +{ + boost::hash > h; + boost::shared_ptr x1(new int(10)); + boost::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 000000000..be13c096e --- /dev/null +++ b/examples/idleconnection/sortedlist.cc @@ -0,0 +1,178 @@ +#include +#include +#include +#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 boost::weak_ptr WeakTcpConnectionPtr; + typedef std::list WeakConnectionList; + + struct Node : public muduo::copyable + { + Timestamp lastReceiveTime; + WeakConnectionList::iterator position; + }; + + EventLoop* loop_; + TcpServer server_; + int idleSeconds_; + WeakConnectionList connectionList_; +}; + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int idleSeconds) + : loop_(loop), + server_(loop, listenAddr, "EchoServer"), + idleSeconds_(idleSeconds) +{ + server_.setConnectionCallback( + boost::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); + loop->runEvery(1.0, boost::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; + // move node inside list with list::splice() + connectionList_.erase(node->position); + connectionList_.push_back(conn); + 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_) + { + conn->shutdown(); + } + 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/CMakeLists.txt b/examples/maxconnection/CMakeLists.txt new file mode 100644 index 000000000..52f881d50 --- /dev/null +++ b/examples/maxconnection/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(maxconnection_echo echo.cc main.cc) +target_link_libraries(maxconnection_echo muduo_net) diff --git a/examples/maxconnection/echo.cc b/examples/maxconnection/echo.cc new file mode 100644 index 000000000..c8418af2a --- /dev/null +++ b/examples/maxconnection/echo.cc @@ -0,0 +1,58 @@ +#include "echo.h" + +#include + +#include + +using namespace muduo; +using namespace muduo::net; + +EchoServer::EchoServer(EventLoop* loop, + const InetAddress& listenAddr, + int maxConnections) + : loop_(loop), + server_(loop, listenAddr, "EchoServer"), + numConnected_(0), + kMaxConnections_(maxConnections) +{ + server_.setConnectionCallback( + boost::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::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(); + } + } + 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 000000000..deb2453e1 --- /dev/null +++ b/examples/maxconnection/echo.h @@ -0,0 +1,29 @@ +#ifndef MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H +#define MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H + +#include + +// 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::EventLoop* loop_; + muduo::net::TcpServer server_; + int numConnected_; // should be atomic_int + const int kMaxConnections_; +}; + +#endif // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H diff --git a/examples/maxconnection/main.cc b/examples/maxconnection/main.cc new file mode 100644 index 000000000..dab32fbac --- /dev/null +++ b/examples/maxconnection/main.cc @@ -0,0 +1,24 @@ +#include "echo.h" + +#include +#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/multiplexer/CMakeLists.txt b/examples/multiplexer/CMakeLists.txt new file mode 100644 index 000000000..acf14d3bf --- /dev/null +++ b/examples/multiplexer/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(multiplex_server multiplexer.cc) +target_link_libraries(multiplex_server muduo_net) + +add_executable(multiplex_server_simple multiplexer_simple.cc) +target_link_libraries(multiplex_server_simple muduo_net) + +add_executable(multiplex_demux demux.cc) +target_link_libraries(multiplex_demux muduo_net) + diff --git a/examples/multiplexer/demux.cc b/examples/multiplexer/demux.cc new file mode 100644 index 000000000..4a7ff2732 --- /dev/null +++ b/examples/multiplexer/demux.cc @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +typedef boost::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 : boost::noncopyable +{ + public: + DemuxServer(EventLoop* loop, const InetAddress& listenAddr, const InetAddress& socksAddr) + : loop_(loop), + server_(loop, listenAddr, "DemuxServer"), + socksAddr_(socksAddr) + { + server_.setConnectionCallback( + boost::bind(&DemuxServer::onServerConnection, this, _1)); + server_.setMessageCallback( + boost::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) + { + size_t 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( + boost::bind(&DemuxServer::onSocksConnection, this, connId, _1)); + entry.client->setMessageCallback( + boost::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 100755 index 000000000..ce5194cdc --- /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 000000000..5f91379ad --- /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 000000000..f372d4bf1 --- /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 000000000..a375a5039 --- /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 000000000..79e4a4eff --- /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 000000000..1e33a06a1 --- /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 000000000..4543f4a61 --- /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 000000000..bb5dd786a --- /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 000000000..4492fe43f --- /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 000000000..88f12db1b --- /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 000000000..383ea5faa --- /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 000000000..5f96e217a --- /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 000000000..6c71ee3a2 --- /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 000000000..cfe6eaf2c --- /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 000000000..68b2ff7d5 --- /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 000000000..2de3d6b2e --- /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 000000000..b2e613042 --- /dev/null +++ b/examples/multiplexer/multiplexer.cc @@ -0,0 +1,320 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#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) + : loop_(loop), + server_(loop, listenAddr, "MultiplexServer"), + backend_(loop, backendAddr, "MultiplexBackend"), + numThreads_(numThreads), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + boost::bind(&MultiplexServer::onClientConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&MultiplexServer::onClientMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + + backend_.setConnectionCallback( + boost::bind(&MultiplexServer::onBackendConnection, this, _1)); + backend_.setMessageCallback( + boost::bind(&MultiplexServer::onBackendMessage, this, _1, _2, _3)); + backend_.enableRetry(); + + // loop->runEvery(10.0, boost::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) + { + size_t 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; + } + + EventLoop* loop_; + TcpServer server_; + TcpClient backend_; + int numThreads_; + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; + MutexLock mutex_; + TcpConnectionPtr backendConn_; + std::map clientConns_; + std::queue availIds_; +}; + +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 000000000..9d6a36113 --- /dev/null +++ b/examples/multiplexer/multiplexer_simple.cc @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include + +#include + +#include +#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 : boost::noncopyable +{ + public: + MultiplexServer(EventLoop* loop, const InetAddress& listenAddr, const InetAddress& backendAddr) + : loop_(loop), + server_(loop, listenAddr, "MultiplexServer"), + backend_(loop, backendAddr, "MultiplexBackend") + { + server_.setConnectionCallback( + boost::bind(&MultiplexServer::onClientConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&MultiplexServer::onClientMessage, this, _1, _2, _3)); + backend_.setConnectionCallback( + boost::bind(&MultiplexServer::onBackendConnection, this, _1)); + backend_.setMessageCallback( + boost::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) + { + size_t 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(); + } + } + } + + EventLoop* loop_; + 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/CMakeLists.txt b/examples/netty/discard/CMakeLists.txt new file mode 100644 index 000000000..830f7e470 --- /dev/null +++ b/examples/netty/discard/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(netty_discard_client client.cc) +target_link_libraries(netty_discard_client muduo_net) + +add_executable(netty_discard_server server.cc) +target_link_libraries(netty_discard_server muduo_net) + diff --git a/examples/netty/discard/client.cc b/examples/netty/discard/client.cc new file mode 100644 index 000000000..12fa03bdf --- /dev/null +++ b/examples/netty/discard/client.cc @@ -0,0 +1,98 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class DiscardClient : boost::noncopyable +{ + public: + DiscardClient(EventLoop* loop, const InetAddress& listenAddr, int size) + : loop_(loop), + client_(loop, listenAddr, "DiscardClient"), + message_(size, 'H') + { + client_.setConnectionCallback( + boost::bind(&DiscardClient::onConnection, this, _1)); + client_.setMessageCallback( + boost::bind(&DiscardClient::onMessage, this, _1, _2, _3)); + client_.setWriteCompleteCallback( + boost::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 000000000..75818a05d --- /dev/null +++ b/examples/netty/discard/server.cc @@ -0,0 +1,100 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class DiscardServer +{ + public: + DiscardServer(EventLoop* loop, const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "DiscardServer"), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + boost::bind(&DiscardServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&DiscardServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(3.0, boost::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; + } + + EventLoop* loop_; + 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/CMakeLists.txt b/examples/netty/echo/CMakeLists.txt new file mode 100644 index 000000000..b387037bf --- /dev/null +++ b/examples/netty/echo/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(netty_echo_client client.cc) +target_link_libraries(netty_echo_client muduo_net) + +add_executable(netty_echo_server server.cc) +target_link_libraries(netty_echo_server muduo_net) + diff --git a/examples/netty/echo/client.cc b/examples/netty/echo/client.cc new file mode 100644 index 000000000..8785bfd47 --- /dev/null +++ b/examples/netty/echo/client.cc @@ -0,0 +1,90 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class EchoClient : boost::noncopyable +{ + public: + EchoClient(EventLoop* loop, const InetAddress& listenAddr, int size) + : loop_(loop), + client_(loop, listenAddr, "EchoClient"), + message_(size, 'H') + { + client_.setConnectionCallback( + boost::bind(&EchoClient::onConnection, this, _1)); + client_.setMessageCallback( + boost::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 000000000..54a2bb01c --- /dev/null +++ b/examples/netty/echo/server.cc @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +int numThreads = 0; + +class EchoServer +{ + public: + EchoServer(EventLoop* loop, const InetAddress& listenAddr) + : loop_(loop), + server_(loop, listenAddr, "EchoServer"), + oldCounter_(0), + startTime_(Timestamp::now()) + { + server_.setConnectionCallback( + boost::bind(&EchoServer::onConnection, this, _1)); + server_.setMessageCallback( + boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); + server_.setThreadNum(numThreads); + loop->runEvery(3.0, boost::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; + } + + EventLoop* loop_; + TcpServer server_; + AtomicInt64 transferred_; + AtomicInt64 receivedMessages_; + int64_t oldCounter_; + Timestamp startTime_; +}; + +int main(int argc, char* argv[]) +{ + mtrace(); + 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/uptime/CMakeLists.txt b/examples/netty/uptime/CMakeLists.txt new file mode 100644 index 000000000..415458c8d --- /dev/null +++ b/examples/netty/uptime/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(netty_uptime uptime.cc) +target_link_libraries(netty_uptime muduo_net) + diff --git a/examples/netty/uptime/uptime.cc b/examples/netty/uptime/uptime.cc new file mode 100644 index 000000000..f171351bd --- /dev/null +++ b/examples/netty/uptime/uptime.cc @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class UptimeClient : boost::noncopyable +{ + public: + UptimeClient(EventLoop* loop, const InetAddress& listenAddr) + : loop_(loop), + client_(loop, listenAddr, "UptimeClient") + { + client_.setConnectionCallback( + boost::bind(&UptimeClient::onConnection, this, _1)); + client_.setMessageCallback( + boost::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) + { + } + + EventLoop* loop_; + 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/CMakeLists.txt b/examples/pingpong/CMakeLists.txt new file mode 100644 index 000000000..6ad1dbf5c --- /dev/null +++ b/examples/pingpong/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(pingpong_client client.cc) +target_link_libraries(pingpong_client muduo_net) + +add_executable(pingpong_server server.cc) +target_link_libraries(pingpong_server muduo_net) + +add_executable(pingpong_bench bench.cc) +target_link_libraries(pingpong_bench muduo_net) + diff --git a/examples/pingpong/bench.cc b/examples/pingpong/bench.cc new file mode 100644 index 000000000..9a7e7eab2 --- /dev/null +++ b/examples/pingpong/bench.cc @@ -0,0 +1,136 @@ +// Benchmark inspired by libevent/test/bench.c +// See also: http://libev.schmorp.de/bench.html + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +std::vector g_pipes; +int numPipes; +int numActive; +int numWrites; +EventLoop* g_loop; +boost::ptr_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(boost::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.push_back(channel); + } + + for (int i = 0; i < 25; ++i) + { + std::pair t = runOnce(); + printf("%8d %8d\n", t.first, t.second); + } +} diff --git a/examples/pingpong/client.cc b/examples/pingpong/client.cc new file mode 100644 index 000000000..622b903b1 --- /dev/null +++ b/examples/pingpong/client.cc @@ -0,0 +1,217 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +using namespace muduo; +using namespace muduo::net; + +class Client; + +class Session : boost::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( + boost::bind(&Session::onConnection, this, _1)); + client_.setMessageCallback( + boost::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 : boost::noncopyable +{ + public: + Client(EventLoop* loop, + const InetAddress& serverAddr, + int blockSize, + int sessionCount, + int timeout, + int threadCount) + : loop_(loop), + threadPool_(loop), + sessionCount_(sessionCount), + timeout_(timeout) + { + loop->runAfter(timeout, boost::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_.push_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 (boost::ptr_vector::iterator it = sessions_.begin(); + it != sessions_.end(); ++it) + { + totalBytesRead += it->bytesRead(); + totalMessagesRead += it->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(boost::bind(&Client::quit, this)); + } + } + + private: + + void quit() + { + loop_->queueInLoop(boost::bind(&EventLoop::quit, loop_)); + } + + void handleTimeout() + { + LOG_WARN << "stop"; + std::for_each(sessions_.begin(), sessions_.end(), + boost::mem_fn(&Session::stop)); + } + + EventLoop* loop_; + EventLoopThreadPool threadPool_; + int sessionCount_; + int timeout_; + boost::ptr_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, "