├── .clang-format ├── .gitignore ├── .travis.yml ├── .travis ├── check-git-clang-format.sh └── git-clang-format ├── 10m ├── 10m-cli.cc ├── 10m-svr.cc └── Makefile ├── CMakeLists.txt ├── ChangeLog ├── LICENSE ├── Makefile ├── README-en.md ├── README.md ├── build_config ├── doc-en.md ├── doc.md ├── examples ├── Makefile ├── chat.cc ├── codec-cli.cc ├── codec-svr.cc ├── daemon.cc ├── daemon.conf ├── echo.cc ├── hsha.cc ├── http-hello.cc ├── idle-close.cc ├── reconnect.cc ├── safe-close.cc ├── stat.cc ├── timer.cc ├── udp-cli.cc ├── udp-hsha.cc ├── udp-svr.cc └── write-on-empty.cc ├── handy ├── codec.cc ├── codec.h ├── conf.cc ├── conf.h ├── conn.cc ├── conn.h ├── daemon.cc ├── daemon.h ├── event_base.cc ├── event_base.h ├── file.cc ├── file.h ├── handy-imp.h ├── handy.h ├── http.cc ├── http.h ├── logging.cc ├── logging.h ├── net.cc ├── net.h ├── poller.cc ├── poller.h ├── port_posix.cc ├── port_posix.h ├── slice.h ├── stat-svr.cc ├── stat-svr.h ├── status.h ├── threads.cc ├── threads.h ├── udp.cc ├── udp.h ├── util.cc └── util.h ├── protobuf ├── Makefile ├── msg.proto ├── proto_msg.cc ├── proto_msg.h └── test.cc ├── raw-examples ├── Makefile ├── epoll-et.cc ├── epoll.cc └── kqueue.cc └── test ├── conf.ut.cc ├── files ├── bad_comment.ini ├── bad_multi.ini ├── bad_section.ini ├── multi_line.ini ├── normal.ini └── user_error.ini ├── handy.ut.cc ├── tcpcli.ut.cc ├── test_harness.cc ├── test_harness.h ├── threads.ut.cc ├── ut.cc └── util.ut.cc /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | ColumnLimit: 160 3 | IndentWidth: 4 4 | DerivePointerAlignment: false 5 | IndentCaseLabels: false 6 | PointerAlignment: Right 7 | SpaceAfterCStyleCast: true 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | handy.* 2 | *.cbp 3 | *.o 4 | *.a 5 | a* 6 | *.mk 7 | *.pb.* 8 | ssl 9 | openssl-example 10 | */*.dSYM 11 | .* 12 | CMake* 13 | 10m/10m-cli 14 | 10m/10m-svr 15 | examples/chat 16 | examples/cli 17 | examples/codec-cli 18 | examples/codec-svr 19 | examples/daemon 20 | examples/echo 21 | examples/hsha 22 | examples/http-echo 23 | examples/log 24 | examples/stat 25 | examples/write-on-empty 26 | examples/http-hello 27 | examples/idle-close 28 | examples/reconnect 29 | examples/safe-close 30 | examples/timer 31 | examples/udp-cli 32 | examples/udp-svr 33 | examples/udp-hsha 34 | raw-examples/epoll 35 | raw-examples/epoll-et 36 | raw-examples/kqueue 37 | handy_test 38 | protobuf/middle 39 | protobuf/test 40 | build/ 41 | # Clion 42 | cmake-build-debug/ 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: cpp 4 | compiler: g++ 5 | 6 | matrix: 7 | include: 8 | # Job1: This is the job of building project in linux os. 9 | - os: linux 10 | dist: jammy 11 | before_install: 12 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 13 | - sudo apt-get update -qq 14 | install: 15 | - sudo apt-get install -qq g++-4.8 16 | - export CXX="g++-4.8" 17 | script: make && sudo ./handy_test 18 | notifications: 19 | email: true 20 | 21 | # Job2: This is the job of checking code style. 22 | - os: linux 23 | dist: focal 24 | env: LINT=1 25 | before_install: 26 | - sudo apt-get update 27 | - sudo apt-get install clang-format-11 28 | install: [] 29 | script: 30 | - sudo bash .travis/check-git-clang-format.sh 31 | -------------------------------------------------------------------------------- /.travis/check-git-clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_PULL_REQUEST" == "true" ] ; then 4 | base_commit="$TRAVIS_BRANCH" 5 | else 6 | base_commit="HEAD^" 7 | fi 8 | 9 | output="$(sudo python .travis/git-clang-format --binary clang-format-11 --commit $base_commit --diff)" 10 | 11 | if [ "$output" == "no modified files to format" ] || [ "$output" == "clang-format did not modify any files" ] ; then 12 | echo "clang-format passed." 13 | exit 0 14 | else 15 | echo "clang-format failed." 16 | echo "$output" 17 | exit 1 18 | fi 19 | -------------------------------------------------------------------------------- /10m/10m-cli.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | using namespace handy; 6 | 7 | struct Report { 8 | long connected; 9 | long retry; 10 | long sended; 11 | long recved; 12 | Report() { memset(this, 0, sizeof(*this)); } 13 | }; 14 | 15 | int main(int argc, const char *argv[]) { 16 | if (argc < 9) { 17 | printf("usage %s \n", 18 | argv[0]); 19 | return 1; 20 | } 21 | int c = 1; 22 | string host = argv[c++]; 23 | int begin_port = atoi(argv[c++]); 24 | int end_port = atoi(argv[c++]); 25 | int conn_count = atoi(argv[c++]); 26 | float create_seconds = atof(argv[c++]); 27 | int processes = atoi(argv[c++]); 28 | conn_count = conn_count / processes; 29 | int heartbeat_interval = atoi(argv[c++]); 30 | int bsz = atoi(argv[c++]); 31 | int man_port = atoi(argv[c++]); 32 | 33 | int pid = 1; 34 | for (int i = 0; i < processes; i++) { 35 | pid = fork(); 36 | if (pid == 0) { // a child process, break 37 | sleep(1); 38 | break; 39 | } 40 | } 41 | Signal::signal(SIGPIPE, [] {}); 42 | EventBase base; 43 | if (pid == 0) { // child process 44 | char *buf = new char[bsz]; 45 | ExitCaller ec1([=] { delete[] buf; }); 46 | Slice msg(buf, bsz); 47 | char heartbeat[] = "heartbeat"; 48 | int send = 0; 49 | int connected = 0; 50 | int retry = 0; 51 | int recved = 0; 52 | 53 | vector allConns; 54 | info("creating %d connections", conn_count); 55 | for (int k = 0; k < create_seconds * 10; k++) { 56 | base.runAfter(100 * k, [&] { 57 | int c = conn_count / create_seconds / 10; 58 | for (int i = 0; i < c; i++) { 59 | unsigned short port = begin_port + (i % (end_port - begin_port)); 60 | auto con = TcpConn::createConnection(&base, host, port, 20 * 1000); 61 | allConns.push_back(con); 62 | con->setReconnectInterval(20 * 1000); 63 | con->onMsg(new LengthCodec, [&](const TcpConnPtr &con, const Slice &msg) { 64 | if (heartbeat_interval == 0) { // echo the msg if no interval 65 | con->sendMsg(msg); 66 | send++; 67 | } 68 | recved++; 69 | }); 70 | con->onState([&, i](const TcpConnPtr &con) { 71 | TcpConn::State st = con->getState(); 72 | if (st == TcpConn::Connected) { 73 | connected++; 74 | // send ++; 75 | // con->sendMsg(msg); 76 | } else if (st == TcpConn::Failed || st == TcpConn::Closed) { //连接出错 77 | if (st == TcpConn::Closed) { 78 | connected--; 79 | } 80 | retry++; 81 | } 82 | }); 83 | } 84 | }); 85 | } 86 | if (heartbeat_interval) { 87 | base.runAfter(heartbeat_interval * 1000, 88 | [&] { 89 | for (int i = 0; i < heartbeat_interval * 10; i++) { 90 | base.runAfter(i * 100, [&, i] { 91 | size_t block = allConns.size() / heartbeat_interval / 10; 92 | for (size_t j = i * block; j < (i + 1) * block && j < allConns.size(); j++) { 93 | if (allConns[j]->getState() == TcpConn::Connected) { 94 | allConns[j]->sendMsg(msg); 95 | send++; 96 | } 97 | } 98 | }); 99 | } 100 | }, 101 | heartbeat_interval * 1000); 102 | } 103 | TcpConnPtr report = TcpConn::createConnection(&base, "127.0.0.1", man_port, 3000); 104 | report->onMsg(new LineCodec, [&](const TcpConnPtr &con, Slice msg) { 105 | if (msg == "exit") { 106 | info("recv exit msg from master, so exit"); 107 | base.exit(); 108 | } 109 | }); 110 | report->onState([&](const TcpConnPtr &con) { 111 | if (con->getState() == TcpConn::Closed) { 112 | base.exit(); 113 | } 114 | }); 115 | base.runAfter(2000, 116 | [&]() { report->sendMsg(util::format("%d connected: %ld retry: %ld send: %ld recved: %ld", getpid(), connected, retry, send, recved)); }, 117 | 100); 118 | base.loop(); 119 | } else { // master process 120 | map subs; 121 | TcpServerPtr master = TcpServer::startServer(&base, "127.0.0.1", man_port); 122 | master->onConnMsg(new LineCodec, [&](const TcpConnPtr &con, Slice msg) { 123 | auto fs = msg.split(' '); 124 | if (fs.size() != 9) { 125 | error("number of fields is %lu expected 7", fs.size()); 126 | return; 127 | } 128 | Report &c = subs[atoi(fs[0].data())]; 129 | c.connected = atoi(fs[2].data()); 130 | c.retry = atoi(fs[4].data()); 131 | c.sended = atoi(fs[6].data()); 132 | c.recved = atoi(fs[8].data()); 133 | }); 134 | base.runAfter(3000, 135 | [&]() { 136 | for (auto &s : subs) { 137 | Report &r = s.second; 138 | printf("pid: %6ld connected %6ld retry %6ld sended %6ld recved %6ld\n", (long) s.first, r.connected, r.retry, r.sended, r.recved); 139 | } 140 | printf("\n"); 141 | }, 142 | 3000); 143 | Signal::signal(SIGCHLD, [] { 144 | int status = 0; 145 | wait(&status); 146 | error("wait result: status: %d is signaled: %d signal: %d", status, WIFSIGNALED(status), WTERMSIG(status)); 147 | }); 148 | base.loop(); 149 | } 150 | info("program exited"); 151 | } 152 | -------------------------------------------------------------------------------- /10m/10m-svr.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | struct Report { 7 | long connected; 8 | long closed; 9 | long recved; 10 | Report() { memset(this, 0, sizeof(*this)); } 11 | }; 12 | 13 | int main(int argc, const char *argv[]) { 14 | if (argc < 5) { 15 | printf("usage: %s \n", argv[0]); 16 | return 1; 17 | } 18 | int begin_port = atoi(argv[1]); 19 | int end_port = atoi(argv[2]); 20 | int processes = atoi(argv[3]); 21 | int man_port = atoi(argv[4]); 22 | int pid = 1; 23 | for (int i = 0; i < processes; i++) { 24 | pid = fork(); 25 | if (pid == 0) { // a child process, break 26 | break; 27 | } 28 | } 29 | EventBase base; 30 | if (pid == 0) { // child process 31 | usleep(100 * 1000); // wait master to listen management port 32 | vector svrs; 33 | long connected = 0, closed = 0, recved = 0; 34 | for (int i = 0; i < end_port - begin_port; i++) { 35 | TcpServerPtr p = TcpServer::startServer(&base, "", begin_port + i, true); 36 | p->onConnCreate([&] { 37 | TcpConnPtr con(new TcpConn); 38 | con->onState([&](const TcpConnPtr &con) { 39 | auto st = con->getState(); 40 | if (st == TcpConn::Connected) { 41 | connected++; 42 | } else if (st == TcpConn::Closed || st == TcpConn::Failed) { 43 | closed++; 44 | connected--; 45 | } 46 | }); 47 | con->onMsg(new LengthCodec, [&](const TcpConnPtr &con, Slice msg) { 48 | recved++; 49 | con->sendMsg(msg); 50 | }); 51 | return con; 52 | }); 53 | svrs.push_back(p); 54 | } 55 | TcpConnPtr report = TcpConn::createConnection(&base, "127.0.0.1", man_port, 3000); 56 | report->onMsg(new LineCodec, [&](const TcpConnPtr &con, Slice msg) { 57 | if (msg == "exit") { 58 | info("recv exit msg from master, so exit"); 59 | base.exit(); 60 | } 61 | }); 62 | report->onState([&](const TcpConnPtr &con) { 63 | if (con->getState() == TcpConn::Closed) { 64 | base.exit(); 65 | } 66 | }); 67 | base.runAfter(100, [&]() { report->sendMsg(util::format("%d connected: %ld closed: %ld recved: %ld", getpid(), connected, closed, recved)); }, 100); 68 | base.loop(); 69 | } else { 70 | map subs; 71 | TcpServerPtr master = TcpServer::startServer(&base, "127.0.0.1", man_port); 72 | master->onConnMsg(new LineCodec, [&](const TcpConnPtr &con, Slice msg) { 73 | auto fs = msg.split(' '); 74 | if (fs.size() != 7) { 75 | error("number of fields is %lu expected 7", fs.size()); 76 | return; 77 | } 78 | Report &c = subs[atoi(fs[0].data())]; 79 | c.connected = atoi(fs[2].data()); 80 | c.closed = atoi(fs[4].data()); 81 | c.recved = atoi(fs[6].data()); 82 | }); 83 | base.runAfter(3000, 84 | [&]() { 85 | for (auto &s : subs) { 86 | Report r = s.second; 87 | printf("pid: %6d connected %6ld closed: %6ld recved %6ld\n", s.first, r.connected, r.closed, r.recved); 88 | } 89 | printf("\n"); 90 | }, 91 | 3000); 92 | base.loop(); 93 | } 94 | info("program exited"); 95 | } 96 | -------------------------------------------------------------------------------- /10m/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | CXXFLAGS += $(PLATFORM_CXXFLAGS) -lhandy $(PLATFORM_LDFLAGS) 3 | 4 | SRCS=$(wildcard *.cc) 5 | PROGS=$(SRCS:.cc=) 6 | 7 | all:$(PROGS) 8 | $(PROGS):%:%.cc 9 | $(CXX) $^ -o $@ $(CXXFLAGS) 10 | 11 | .PHONY:clean 12 | clean: 13 | rm -f $(PROGS) 14 | 15 | 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(handy) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | include(GNUInstallDirs) 7 | 8 | find_package(Threads REQUIRED) 9 | 10 | list(APPEND HANDY_SRCS 11 | ${PROJECT_SOURCE_DIR}/handy/daemon.cc 12 | ${PROJECT_SOURCE_DIR}/handy/net.cc 13 | ${PROJECT_SOURCE_DIR}/handy/codec.cc 14 | ${PROJECT_SOURCE_DIR}/handy/http.cc 15 | ${PROJECT_SOURCE_DIR}/handy/conn.cc 16 | ${PROJECT_SOURCE_DIR}/handy/poller.cc 17 | ${PROJECT_SOURCE_DIR}/handy/udp.cc 18 | ${PROJECT_SOURCE_DIR}/handy/threads.cc 19 | ${PROJECT_SOURCE_DIR}/handy/file.cc 20 | ${PROJECT_SOURCE_DIR}/handy/util.cc 21 | ${PROJECT_SOURCE_DIR}/handy/conf.cc 22 | ${PROJECT_SOURCE_DIR}/handy/stat-svr.cc 23 | ${PROJECT_SOURCE_DIR}/handy/port_posix.cc 24 | ${PROJECT_SOURCE_DIR}/handy/event_base.cc 25 | ${PROJECT_SOURCE_DIR}/handy/logging.cc) 26 | 27 | if(CMAKE_HOST_APPLE) 28 | add_definitions(-DOS_MACOSX) 29 | elseif(CMAKE_HOST_UNIX) 30 | add_definitions(-DOS_LINUX) 31 | else(CMAKE_HOST_APPLE) 32 | message(FATAL_ERROR "Platform not supported") 33 | endif(CMAKE_HOST_APPLE) 34 | 35 | option(BUILD_HANDY_SHARED_LIBRARY "Build Handy Shared Library" OFF) 36 | option(BUILD_HANDY_STATIC_LIBRARY "Build Handy Shared Library" ON) 37 | option(BUILD_HANDY_EXAMPLES "Build Handy Examples" OFF) 38 | 39 | ##Handy Shared Library 40 | if(BUILD_HANDY_SHARED_LIBRARY) 41 | add_library(handy SHARED ${HANDY_SRCS}) 42 | target_include_directories(handy PUBLIC ${PROJECT_SOURCE_DIR}/handy) 43 | target_link_libraries(handy Threads::Threads) 44 | install(TARGETS handy DESTINATION ${CMAKE_INSTALL_LIBDIR}) 45 | endif(BUILD_HANDY_SHARED_LIBRARY) 46 | 47 | #Handy Static library 48 | if(BUILD_HANDY_STATIC_LIBRARY) 49 | add_library(handy_s STATIC ${HANDY_SRCS}) 50 | target_include_directories(handy_s PUBLIC ${PROJECT_SOURCE_DIR}/handy/) 51 | target_link_libraries(handy_s Threads::Threads) 52 | install(TARGETS handy_s DESTINATION ${CMAKE_INSTALL_LIBDIR}) 53 | endif(BUILD_HANDY_STATIC_LIBRARY) 54 | 55 | if(BUILD_HANDY_SHARED_LIBRARY OR BUILD_HANDY_STATIC_LIBRARY) 56 | install(FILES 57 | ${PROJECT_SOURCE_DIR}/handy/codec.h 58 | ${PROJECT_SOURCE_DIR}/handy/conf.h 59 | ${PROJECT_SOURCE_DIR}/handy/conn.h 60 | ${PROJECT_SOURCE_DIR}/handy/daemon.h 61 | ${PROJECT_SOURCE_DIR}/handy/event_base.h 62 | ${PROJECT_SOURCE_DIR}/handy/file.h 63 | ${PROJECT_SOURCE_DIR}/handy/handy.h 64 | ${PROJECT_SOURCE_DIR}/handy/handy-imp.h 65 | ${PROJECT_SOURCE_DIR}/handy/http.h 66 | ${PROJECT_SOURCE_DIR}/handy/logging.h 67 | ${PROJECT_SOURCE_DIR}/handy/net.h 68 | ${PROJECT_SOURCE_DIR}/handy/poller.h 69 | ${PROJECT_SOURCE_DIR}/handy/port_posix.h 70 | ${PROJECT_SOURCE_DIR}/handy/slice.h 71 | ${PROJECT_SOURCE_DIR}/handy/stat-svr.h 72 | ${PROJECT_SOURCE_DIR}/handy/status.h 73 | ${PROJECT_SOURCE_DIR}/handy/threads.h 74 | ${PROJECT_SOURCE_DIR}/handy/udp.h 75 | ${PROJECT_SOURCE_DIR}/handy/util.h 76 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/handy) 77 | endif(BUILD_HANDY_SHARED_LIBRARY OR BUILD_HANDY_STATIC_LIBRARY) 78 | 79 | 80 | function(add_handy_executable EXECUTABLE_NAME EXECUTABLE_SOURCES) 81 | add_executable(${EXECUTABLE_NAME} ${EXECUTABLE_SOURCES}) 82 | target_link_libraries(${EXECUTABLE_NAME} handy_s) 83 | target_include_directories(${EXECUTABLE_NAME} PUBLIC ${PROJECT_SOURCE_DIR}) 84 | install(TARGETS ${EXECUTABLE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) 85 | endfunction(add_handy_executable) 86 | 87 | 88 | if(BUILD_HANDY_EXAMPLES) 89 | add_handy_executable(codec-cli examples/codec-cli.cc) 90 | add_handy_executable(codec-svr examples/codec-svr.cc) 91 | add_handy_executable(daemon examples/daemon.cc) 92 | add_handy_executable(echo examples/echo.cc) 93 | add_handy_executable(hsha examples/hsha.cc) 94 | add_handy_executable(http-hello examples/http-hello.cc) 95 | add_handy_executable(idle-close examples/idle-close.cc) 96 | add_handy_executable(reconnect examples/reconnect.cc) 97 | add_handy_executable(safe-close examples/safe-close.cc) 98 | add_handy_executable(stat examples/stat.cc) 99 | add_handy_executable(timer examples/timer.cc) 100 | add_handy_executable(udp-cli examples/udp-cli.cc) 101 | add_handy_executable(udp-hsha examples/udp-hsha.cc) 102 | add_handy_executable(udp-svr examples/udp-svr.cc) 103 | add_handy_executable(write-on-empty examples/write-on-empty.cc) 104 | add_handy_executable(10m-cli 10m/10m-cli.cc) 105 | add_handy_executable(10m-svr 10m/10m-svr.cc) 106 | 107 | if(CMAKE_HOST_APPLE) 108 | add_handy_executable(kqueue raw-examples/kqueue.cc) 109 | endif(CMAKE_HOST_APPLE) 110 | endif(BUILD_HANDY_EXAMPLES) 111 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2014-08-03 yedongfu dongfuye@163.com 2 | * start to work 3 | * version 0.1.0 4 | 5 | 2019-05-17 Qing Wang kingchin1218@gmail.com 6 | * Some bug fix 7 | * version 0.2.0 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, yedf 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #OPT ?= -O2 -DNDEBUG 2 | # (B) Debug mode, w/ full line-level debugging symbols 3 | OPT ?= -g2 4 | # (C) Profiling mode: opt, but w/debugging symbols 5 | # OPT ?= -O2 -g2 -DNDEBUG 6 | $(shell CC="$(CC)" CXX="$(CXX)" TARGET_OS="$(TARGET_OS)" ./build_config 1>&2) 7 | include config.mk 8 | 9 | CFLAGS += -I. $(PLATFORM_CCFLAGS) $(OPT) 10 | CXXFLAGS += -I. $(PLATFORM_CXXFLAGS) $(OPT) 11 | 12 | LDFLAGS += $(PLATFORM_LDFLAGS) 13 | LIBS += $(PLATFORM_LIBS) 14 | 15 | HANDY_SOURCES += $(shell find handy -name '*.cc') 16 | HANDY_OBJECTS = $(HANDY_SOURCES:.cc=.o) 17 | 18 | TEST_SOURCES = $(shell find test -name '*.cc') 19 | TEST_OBJECTS = $(TEST_SOURCES:.cc=.o) 20 | 21 | EXAMPLE_SOURCES += $(shell find examples -name '*.cc') 22 | EXAMPLES = $(EXAMPLE_SOURCES:.cc=) 23 | 24 | KW_SOURCES += $(shell find 10m -name '*.cc') 25 | KW = $(KW_SOURCES:.cc=) 26 | 27 | LIBRARY = libhandy.a 28 | 29 | TARGETS = $(LIBRARY) handy_test $(EXAMPLES) $(KW) 30 | 31 | default: $(TARGETS) 32 | handy_examples: $(EXAMPLES) 33 | $(EXAMPLES): $(LIBRARY) 34 | $(KW): $(LIBRARY) 35 | 36 | install: libhandy.a 37 | mkdir -p $(PREFIX)/usr/local/include/handy 38 | cp -f handy/*.h $(PREFIX)/usr/local/include/handy 39 | cp -f libhandy.a $(PREFIX)/usr/local/lib 40 | 41 | uninstall: 42 | rm -rf $(PREFIX)/usr/local/include/handy $(PREFIX)/usr/local/lib/libhandy.a 43 | clean: 44 | -rm -f $(TARGETS) 45 | -rm -f */*.o 46 | 47 | $(LIBRARY): $(HANDY_OBJECTS) 48 | rm -f $@ 49 | $(AR) -rs $@ $(HANDY_OBJECTS) 50 | 51 | handy_test: $(TEST_OBJECTS) $(LIBRARY) 52 | $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS) 53 | 54 | .cc.o: 55 | $(CXX) $(CXXFLAGS) -c $< -o $@ 56 | 57 | .c.o: 58 | $(CC) $(CFLAGS) -c $< -o $@ 59 | 60 | .cc: 61 | $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) $(LIBRARY) $(LIBS) 62 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | handy[![Build Status](https://travis-ci.org/yedf2/handy.png)](https://travis-ci.org/yedf2/handy) 2 | ==== 3 | [中文版](https://github.com/yedf/handy/blob/master/README.md) 4 | ## A C++11 non-blocking network library 5 | 6 | ### multi platform support 7 | 8 | * Linux: ubuntu14 64bit g++4.8.1 tested 9 | 10 | * MacOSX: LLVM version 6.1.0 tested 11 | 12 | ### elegant program exit 13 | 14 | programmer can write operations for exit 15 | 16 | can use valgrind to check memory leak 17 | 18 | ### high performance 19 | 20 | * use epoll on Linux 21 | 22 | * use kqueue on MacOSX 23 | 24 | [performance report](http://www.oschina.net/p/c11-handy) 25 | ### elegant 26 | 27 | only 10 lines can finish a complete server 28 | 29 | ## Usage 30 | 31 | ### Quick start 32 | ``` 33 | make && make install 34 | ``` 35 | 36 | ### sample --echo-server 37 | 38 | ```c 39 | #include 40 | using namespace handy; 41 | 42 | int main(int argc, const char* argv[]) { 43 | EventBase base; 44 | Signal::signal(SIGINT, [&]{ base.exit(); }); 45 | TcpServerPtr svr = TcpServer::startServer(&base, "", 2099); 46 | exitif(svr == NULL, "start tcp server failed"); 47 | svr->onConnRead([](const TcpConnPtr& con) { 48 | con->send(con->getInput()); 49 | }); 50 | base.loop(); 51 | } 52 | ``` 53 | 54 | ### half sync half async pattern 55 | 56 | processing I/O asynchronously and Request synchronously can greatly simplify the coding of business processing 57 | 58 | example can be found examples/hsha.cc 59 | 60 | ### openssl supported 61 | 62 | asynchronously handle the openssl connection. if you have installed openssl, then make will automatically download handy-ssl. 63 | ssl support files are in [handy-ssl](https://github.com/yedf/handy-ssl.git) because of license. 64 | 65 | ### protobuf supported 66 | 67 | examples can be found in directory protobuf 68 | 69 | ### contents 70 | 71 | * handy--------handy library 72 | * examples---- 73 | * ssl------------openssl support and examples 74 | * protobuf-----protobuf support and examples 75 | * test-----------handy test case 76 | 77 | ### [hand book](https://github.com/yedf/handy/blob/master/doc-cn.md) 78 | 79 | ## Advanced build option 80 | 81 | ### Build handy shared library and examples: 82 | ``` 83 | $ git clone https://github.com/yedf/handy 84 | $ cd handy && mkdir build && cd build 85 | $ cmake -DBUILD_HANDY_SHARED_LIBRARY=ON -DBUILD_HANDY_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=/tmp/handy .. 86 | $ make -j4 87 | $ make install 88 | $ ls /tmp/handy 89 | bin include lib64 90 | $ ls /tmp/handy/bin/ 91 | 10m-cli 10m-svr codec-cli codec-svr daemon echo hsha http-hello idle-close reconnect safe-close stat timer udp-cli udp-hsha udp-svr write-on-empty 92 | $ ls /tmp/handy/lib64/ 93 | libhandy_s.a libhandy.so 94 | ``` 95 | 96 | ### As a static library in your own programs: 97 | * add handy as a git submodule to say a folder called vendor 98 | * in your CMakeLists.txt 99 | 100 | ``` 101 | add_subdirectory("vendor/handy" EXCLUDE_FROM_ALL) 102 | 103 | add_executable(${PROJECT_NAME} main.cpp) 104 | 105 | target_include_directories(${PROJECT_NAME} PUBLIC 106 | "vendor/handy" 107 | ) 108 | 109 | target_link_libraries(${PROJECT_NAME} PUBLIC 110 | handy_s 111 | ) 112 | ``` 113 | 114 | license 115 | ==== 116 | Use of this source code is governed by a BSD-style 117 | license that can be found in the License file. 118 | 119 | email 120 | ==== 121 | dongfuye@163.com 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | handy[![Build Status](https://travis-ci.org/yedf2/handy.png)](https://travis-ci.org/yedf2/handy) 2 | ==== 3 | [English](https://github.com/yedf/handy/blob/master/README-en.md) 4 | 5 | ## 简洁易用的C++11网络库 6 | 7 | ### 多平台支持 8 | 9 | * Linux: ubuntu14 64bit g++4.8.1 上测试通过 10 | 11 | * MacOSX: LLVM version 6.1.0 上测试通过 12 | 13 | * MacOSX: 支持CLion IDE 14 | 15 | ### 支持优雅退出 16 | 17 | 优雅退出可以让程序员更好的定义自己程序的退出行为 18 | 19 | 能够更好的借助valgrind等工具检查内存泄露。 20 | 21 | ### 高性能 22 | 23 | * linux上使用epoll 24 | * MacOSX上使用kqueue 25 | * [性能测试报告](http://www.oschina.net/p/c11-handy) 26 | * [单机千万并发连接](https://zhuanlan.zhihu.com/p/21378825) 27 | 28 | ### 简洁 29 | 30 | 10行代码能够编写一个完整的服务器 31 | 32 | ### 代码示例--echo-server 33 | 34 | ```c 35 | #include 36 | using namespace handy; 37 | 38 | int main(int argc, const char* argv[]) { 39 | EventBase base; 40 | Signal::signal(SIGINT, [&]{ base.exit(); }); 41 | TcpServerPtr svr = TcpServer::startServer(&base, "", 2099); 42 | exitif(svr == NULL, "start tcp server failed"); 43 | svr->onConnRead([](const TcpConnPtr& con) { 44 | con->send(con->getInput()); 45 | }); 46 | base.loop(); 47 | } 48 | ``` 49 | 50 | ### 支持半同步半异步处理 51 | 52 | 异步管理网络I/O,同步处理请求,可以简化服务器处理逻辑的编写,示例参见examples/hsha.cc 53 | 54 | ### openssl支持 55 | 56 | 异步连接管理,支持openssl连接,如果实现安装了openssl,能够找到,项目会自动下载handy-ssl 57 | 由于openssl的开源协议与此不兼容,所以项目文件单独放在[handy-ssl](https://github.com/yedf/handy-ssl.git) 58 | 59 | ### protobuf支持 60 | 61 | 使用protobuf的消息encode/decode示例在protobuf下 62 | 63 | ### udp支持 64 | 65 | 支持udp,udp的客户端采用connect方式使用,类似tcp 66 | 67 | ### 安装与使用 68 | 69 | make && make install 70 | 71 | ### 目录结构 72 | 73 | * handy--------handy库 74 | * 10m----------进行千万并发连接测试所使用的程序 75 | * examples----示例 76 | * raw-examples--原生api使用示例,包括了epoll,epoll ET模式,kqueue示例 77 | * ssl------------openssl相关的代码与示例 78 | * protobuf-----handy使用protobuf的示例 79 | * test-----------handy相关的测试 80 | 81 | ### [使用文档](https://github.com/yedf/handy/blob/master/doc.md) 82 | 83 | ### raw-examples 84 | 使用os提供的api如epoll,kqueue编写并发应用程序 85 | * epoll.cc,演示了epoll的通常用法,使用epoll的LT模式 86 | * epoll-et.cc,演示了epoll的ET模式,与LT模式非常像,区别主要体现在不需要手动开关EPOLLOUT事件 87 | 88 | ### examples 89 | 使用handy的示例 90 | * echo.cc 简单的回显服务 91 | * timer.cc 使用定时器来管理定时任务 92 | * idle-close.cc 关闭一个空闲的连接 93 | * reconnect.cc 设置连接关闭后自动重连 94 | * safe-close.cc 在其他线程中安全操作连接 95 | * chat.cc 简单的聊天应用,用户使用telnet登陆后,系统分配一个用户id,用户可以发送消息给某个用户,也可以发送消息给所有用户 96 | * codec-cli.cc 发送消息给服务器,使用的消息格式为mBdT开始,紧接着4字节的长度,然后是消息内容 97 | * codec-svr.cc 见上 98 | * hsha.cc 半同步半异步示例,用户可以把IO交给handy框架进行处理,自己同步处理用户请求 99 | * http-hello.cc 一个简单的http服务器程序 100 | * stat.cc 一个简单的状态服务器示例,一个内嵌的http服务器,方便外部的工具查看应用程序的状态 101 | * write-on-empty.cc 这个例子演示了需要写出大量数据,例如1G文件这种情景中的使用技巧 102 | * daemon.cc 程序已以daemon方式启动,从conf文件中获取日志相关的配置,并初始化日志参数 103 | * udp-cli.cc udp的客户端 104 | * udp-svr.cc udp服务器 105 | * udp-hsha.cc udp的半同步半异步服务器 106 | 107 | license 108 | ==== 109 | Use of this source code is governed by a BSD-style 110 | license that can be found in the License file. 111 | 112 | email 113 | ==== 114 | dongfuye@163.com 115 | 116 | 微信交流群 117 | ==== 118 | 如果您希望更快的获得反馈,或者更多的了解其他用户在使用过程中的各种反馈,欢迎加入我们的微信交流群 119 | 120 | 请加作者的微信 yedf2008 好友或者扫码加好友,备注 `handy` 按照指引进群 121 | 122 | ![yedf2008](http://service.ivydad.com/cover/dubbingb6b5e2c0-2d2a-cd59-f7c5-c6b90aceb6f1.jpeg) 123 | 124 | 如果您觉得此项目不错,或者对您有帮助,请赏颗星吧! 125 | -------------------------------------------------------------------------------- /build_config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Detects OS we're compiling on and outputs a file specified by the first 4 | # argument, which in turn gets read while processing Makefile. 5 | # 6 | # The output will set the following variables: 7 | # CC C Compiler path 8 | # CXX C++ Compiler path 9 | # PLATFORM_LDFLAGS Linker flags 10 | # PLATFORM_LIBS Libraries flags 11 | # PLATFORM_SHARED_EXT Extension for shared libraries 12 | # PLATFORM_SHARED_LDFLAGS Flags for building shared library 13 | # This flag is embedded just before the name 14 | # of the shared library without intervening spaces 15 | # PLATFORM_SHARED_CFLAGS Flags for compiling objects for shared library 16 | # PLATFORM_CCFLAGS C compiler flags 17 | # PLATFORM_CXXFLAGS C++ compiler flags. Will contain: 18 | # PLATFORM_SHARED_VERSIONED Set to 'true' if platform supports versioned 19 | # shared libraries, empty otherwise. 20 | # 21 | 22 | OUTPUT=config.mk 23 | # Delete existing output, if it exists 24 | rm -f $OUTPUT 25 | touch $OUTPUT 26 | 27 | if test -z "$CC"; then 28 | CC=cc 29 | fi 30 | 31 | if test -z "$CXX"; then 32 | CXX=g++ 33 | fi 34 | 35 | if test -z "$TMPDIR"; then 36 | TMPDIR=/tmp 37 | fi 38 | 39 | # Detect OS 40 | if test -z "$TARGET_OS"; then 41 | TARGET_OS=`uname -s` 42 | fi 43 | COMMON_FLAGS= 44 | CROSS_COMPILE= 45 | PLATFORM_CCFLAGS= 46 | PLATFORM_CXXFLAGS= 47 | PLATFORM_LDFLAGS= 48 | PLATFORM_LIBS= 49 | PLATFORM_SHARED_EXT="so" 50 | PLATFORM_SHARED_LDFLAGS="-shared -Wl,-soname -Wl," 51 | PLATFORM_SHARED_CFLAGS="-fPIC" 52 | PLATFORM_SHARED_VERSIONED=true 53 | 54 | case "$TARGET_OS" in 55 | CYGWIN_*) 56 | PLATFORM=OS_LINUX 57 | COMMON_FLAGS="$MEMCMP_FLAG -lpthread -DOS_LINUX -DCYGWIN" 58 | PLATFORM_LDFLAGS="-lpthread" 59 | PORT_FILE=port/port_posix.cc 60 | ;; 61 | Darwin) 62 | PLATFORM=OS_MACOSX 63 | COMMON_FLAGS="$MEMCMP_FLAG -DOS_MACOSX -Dthread_local=__thread -Wno-deprecated-declarations" 64 | PLATFORM_SHARED_EXT=dylib 65 | [ -z "$INSTALL_PATH" ] && INSTALL_PATH=`pwd` 66 | PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name $INSTALL_PATH/" 67 | PORT_FILE=port/port_posix.cc 68 | ;; 69 | Linux) 70 | PLATFORM=OS_LINUX 71 | COMMON_FLAGS="$MEMCMP_FLAG -pthread -DOS_LINUX" 72 | PLATFORM_LDFLAGS="-pthread" 73 | PORT_FILE=port/port_posix.cc 74 | ;; 75 | SunOS) 76 | PLATFORM=OS_SOLARIS 77 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_SOLARIS" 78 | PLATFORM_LIBS="-lpthread -lrt" 79 | PORT_FILE=port/port_posix.cc 80 | ;; 81 | FreeBSD) 82 | PLATFORM=OS_FREEBSD 83 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_FREEBSD" 84 | PLATFORM_LIBS="-lpthread" 85 | PORT_FILE=port/port_posix.cc 86 | ;; 87 | NetBSD) 88 | PLATFORM=OS_NETBSD 89 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_NETBSD" 90 | PLATFORM_LIBS="-lpthread -lgcc_s" 91 | PORT_FILE=port/port_posix.cc 92 | ;; 93 | OpenBSD) 94 | PLATFORM=OS_OPENBSD 95 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_OPENBSD" 96 | PLATFORM_LDFLAGS="-pthread" 97 | PORT_FILE=port/port_posix.cc 98 | ;; 99 | DragonFly) 100 | PLATFORM=OS_DRAGONFLYBSD 101 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_DRAGONFLYBSD" 102 | PLATFORM_LIBS="-lpthread" 103 | PORT_FILE=port/port_posix.cc 104 | ;; 105 | OS_ANDROID_CROSSCOMPILE) 106 | PLATFORM=OS_ANDROID 107 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX" 108 | PLATFORM_LDFLAGS="" # All pthread features are in the Android C library 109 | PORT_FILE=port/port_posix.cc 110 | CROSS_COMPILE=true 111 | ;; 112 | HP-UX) 113 | PLATFORM=OS_HPUX 114 | COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_HPUX" 115 | PLATFORM_LDFLAGS="-pthread" 116 | PORT_FILE=port/port_posix.cc 117 | # man ld: +h internal_name 118 | PLATFORM_SHARED_LDFLAGS="-shared -Wl,+h -Wl," 119 | ;; 120 | IOS) 121 | PLATFORM=IOS 122 | COMMON_FLAGS="$MEMCMP_FLAG -DOS_MACOSX" 123 | [ -z "$INSTALL_PATH" ] && INSTALL_PATH=`pwd` 124 | PORT_FILE=port/port_posix.cc 125 | PLATFORM_SHARED_EXT= 126 | PLATFORM_SHARED_LDFLAGS= 127 | PLATFORM_SHARED_CFLAGS= 128 | PLATFORM_SHARED_VERSIONED= 129 | ;; 130 | *) 131 | echo "Unknown platform!" >&2 132 | exit 1 133 | esac 134 | 135 | $CXX -x c++ - -o $TMPDIR/handy_build_config.out </dev/null 2>&1 < 146 | #int main() {} 147 | #EOF 148 | #[ $? = 0 ] && COMMON_FLAGS="$COMMON_FLAGS -DUSE_EPOLL" 149 | 150 | $CXX -x c++ - -o $TMPDIR/handy_build_config.out >/dev/null 2>&1 < 152 | int main() {} 153 | EOF 154 | [ $? = 0 ] && SSL=1 && ! [ -e ssl ] && git clone https://github.com/yedf/handy-ssl.git ssl 155 | [ x$SSL = x1 ] && PLATFORM_LIBS="$PLATFORM_LIBS -lssl -lcrypto" 156 | 157 | PWD=`pwd` 158 | COMMON_FLAGS="$COMMON_FLAGS -DLITTLE_ENDIAN=$PLATFORM_IS_LITTLE_ENDIAN -std=c++11 -I$PWD" 159 | PLATFORM_CCFLAGS="$PLATFORM_CCFLAGS $COMMON_FLAGS" 160 | PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS $COMMON_FLAGS" 161 | PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -L$PWD" 162 | 163 | echo "CC=$CC" >> $OUTPUT 164 | echo "CXX=$CXX" >> $OUTPUT 165 | echo "PLATFORM=$PLATFORM" >> $OUTPUT 166 | echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> $OUTPUT 167 | echo "PLATFORM_LIBS=$PLATFORM_LIBS" >> $OUTPUT 168 | echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> $OUTPUT 169 | echo "PLATFORM_CXXFLAGS=$PLATFORM_CXXFLAGS" >> $OUTPUT 170 | echo "PLATFORM_SHARED_CFLAGS=$PLATFORM_SHARED_CFLAGS" >> $OUTPUT 171 | echo "PLATFORM_SHARED_EXT=$PLATFORM_SHARED_EXT" >> $OUTPUT 172 | echo "PLATFORM_SHARED_LDFLAGS=$PLATFORM_SHARED_LDFLAGS" >> $OUTPUT 173 | echo "PLATFORM_SHARED_VERSIONED=$PLATFORM_SHARED_VERSIONED" >> $OUTPUT 174 | [ x$SSL = x1 ] && echo "HANDY_SOURCES=ssl/ssl-conn.cc" >> $OUTPUT 175 | [ x$SSL = x1 ] && echo "EXAMPLE_SOURCES=ssl/ssl-cli.cc ssl/ssl-svr.cc ssl/https-svr.cc" >> $OUTPUT 176 | 177 | -------------------------------------------------------------------------------- /doc-en.md: -------------------------------------------------------------------------------- 1 | # handy 2 | yedongfu 3 | 4 | A C++11 non-blocking network library 5 | 6 | [Example](#sample) 7 | [EventBase events dispatcher](#event-base) 8 | [tcp connection](#tcp-conn) 9 | [tcp server](#tcp-server) 10 | [http server](#http-server) 11 | [half sync half async server](#hsha) 12 |

example--echo

13 | 14 | ```c 15 | #include 16 | 17 | using namespace std; 18 | using namespace handy; 19 | 20 | 21 | int main(int argc, const char* argv[]) { 22 | EventBase base; 23 | //handle ctrl+c 24 | Signal::signal(SIGINT, [&]{ base.exit(); }); 25 | TcpServer echo(&base); 26 | int r = echo.bind("", 2099); 27 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 28 | echo.onConnRead([](const TcpConnPtr& con) { 29 | con->send(con->getInput()); 30 | }); 31 | base.loop(); //enter events loop 32 | } 33 | ``` 34 |

EventBase: events dispatcher

35 | EventBase is an events dispatcher,use epoll/kqueue to handle non-blocking I/O 36 | 37 | ```c 38 | EventBase base; 39 | ``` 40 | 41 | ### events loop 42 | 43 | ```c 44 | //call epoll_wait repeatedly, handle I/O events 45 | base.loop(); 46 | ``` 47 | 48 | ### exit events loop 49 | 50 | ```c 51 | //exit events loop, can be called from other threads 52 | base.exit(); 53 | ``` 54 | 55 | ### is events loop exited? 56 | 57 | ```c 58 | bool exited(); 59 | ``` 60 | 61 | ### add tcp connection 62 | 63 | ```c 64 | TcpConnPtr con = TcpConn::createConnection(&base, host, port); 65 | ``` 66 | 67 | ### add listen fd, and will add all socket return by accept(listen_fd) 68 | 69 | ```c 70 | TcpServer echo(&base); 71 | ``` 72 | 73 | ### perform tasks in I/O thread 74 | Some tasks must be called from I/O thread, for example writing some data to connection. 75 | In order to avoid conflicting read/write, the operation should be performed in a single thread. 76 | 77 | ```c 78 | void safeCall(const Task& task); 79 | 80 | base.safeCall([](con){con->send("OK");}); 81 | ``` 82 | 83 | ### manage timeout tasks 84 | EventBase will make itself return by setting a wait time form epoll_wait/kevent. 85 | It will check and call timeout tasks. The precision rely on epoll_wait/kevent 86 | 87 | ```c 88 | //interval: 0:once task;>0:repeated task, task will be execute every interval milliseconds 89 | TimerId runAfter(int64_t milli, const Task& task, int64_t interval=0); 90 | //runAt will specify the absolute time 91 | TimerId runAt(int64_t milli, const Task& task, int64_t interval=0) 92 | //cancel Task, Ignore if task is already removed or expired. 93 | bool cancel(TimerId timerid); 94 | 95 | TimerId tid = base.runAfter(1000, []{ info("a second passed"); }); 96 | base.cancel(tid); 97 | ``` 98 | 99 |

TcpConn tcp connection

100 | use shared_ptr to manage connection, no need to release manually 101 | 102 | ### reference count 103 | 104 | ```c 105 | typedef std::shared_ptr TcpConnPtr; 106 | ``` 107 | 108 | ### state 109 | 110 | ```c 111 | enum State { Invalid=1, Handshaking, Connected, Closed, Failed, }; 112 | ``` 113 | 114 | ### example 115 | 116 | ```c 117 | TcpConnPtr con = TcpConn::createConnection(base, host, port); 118 | con->onState([=](const TcpConnPtr& con) { 119 | info("onState called state: %d", con->getState()); 120 | }); 121 | con->onRead([](const TcpConnPtr& con){ 122 | info("recv %lu bytes", con->getInput().size()); 123 | con->send("ok"); 124 | con->getInput().clear(); 125 | }); 126 | 127 | ``` 128 | 129 | ### reconnect setting 130 | 131 | ```c 132 | //set reconnect. -1: no reconnect; 0 :reconnect now; other: wait millisecond; default -1 133 | void setReconnectInterval(int milli); 134 | ``` 135 | 136 | ### callback for idle 137 | 138 | ```c 139 | void addIdleCB(int idle, const TcpCallBack& cb); 140 | 141 | //close connection if idle for 30 seconds 142 | con->addIdleCB(30, [](const TcpConnPtr& con)) { con->close(); }); 143 | ``` 144 | 145 | ### Message mode 146 | you can onRead or onMsg to handle message 147 | 148 | ```c 149 | //message callback, confict with onRead callback. You should set only one of these 150 | //codec will be released when connection destroyed 151 | void onMsg(CodecBase* codec, const MsgCallBack& cb); 152 | //send message 153 | void sendMsg(Slice msg); 154 | 155 | con->onMsg(new LineCodec, [](const TcpConnPtr& con, Slice msg) { 156 | info("recv msg: %.*s", (int)msg.size(), msg.data()); 157 | con->sendMsg("hello"); 158 | }); 159 | 160 | ``` 161 | 162 | ### store you own data 163 | 164 | ```c 165 | template T& context(); 166 | 167 | con->context() = "user defined data"; 168 | ``` 169 | 170 |

TcpServer

171 | ### example 172 | 173 | ```c 174 | TcpServer echo(&base); 175 | int r = echo.bind("", 2099); 176 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 177 | echo.onConnRead([](const TcpConnPtr& con) { 178 | con->send(con->getInput()); // echo data read 179 | }); 180 | ``` 181 | 182 | ### customize your connection 183 | when TcpServer accept a connection, it will call this to create an TcpConn 184 | 185 | ``` 186 | void onConnCreate(const std::function& cb); 187 | 188 | chat.onConnCreate([&]{ 189 | TcpConnPtr con(new TcpConn); 190 | con->onState([&](const TcpConnPtr& con) { 191 | if (con->getState() == TcpConn::Connected) { 192 | con->context() = 1; 193 | } 194 | } 195 | return con; 196 | }); 197 | ``` 198 | 199 |

HttpServer

200 | 201 | ```c 202 | //example 203 | HttpServer sample(&base); 204 | int r = sample.bind("", 8081); 205 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 206 | sample.onGet("/hello", [](const HttpConnPtr& con) { 207 | HttpResponse resp; 208 | resp.body = Slice("hello world"); 209 | con.sendResponse(resp); 210 | }); 211 | 212 | ``` 213 |

half sync half async server

214 | 215 | ```c 216 | // empty string indicates unfinished handling of request. You may operate on con as you like. 217 | void onMsg(CodecBase* codec, const RetMsgCallBack& cb); 218 | 219 | hsha.onMsg(new LineCodec, [](const TcpConnPtr& con, const string& input){ 220 | int ms = rand() % 1000; 221 | info("processing a msg"); 222 | usleep(ms * 1000); 223 | return util::format("%s used %d ms", input.c_str(), ms); 224 | }); 225 | 226 | ``` 227 | updating....... 228 | -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | # handy 2 | yedongfu 3 | 4 | Handy是一个简洁高效的C++11网络库,支持linux与mac平台,使用异步IO模型 5 | 6 | [使用示例](#sample) 7 | [EventBase事件分发器](#event-base) 8 | [tcp连接](#tcp-conn) 9 | [tcp服务器](#tcp-server) 10 | [http服务器](#http-server) 11 | [半同步半异步服务器](#hsha) 12 |

使用示例--echo

13 | 14 | ```c 15 | 16 | #include 17 | 18 | using namespace std; 19 | using namespace handy; 20 | 21 | 22 | int main(int argc, const char* argv[]) { 23 | EventBase base; //事件分发器 24 | //注册Ctrl+C的信号处理器--退出事件分发循环 25 | Signal::signal(SIGINT, [&]{ base.exit(); }); 26 | TcpServer echo(&base); //创建服务器 27 | int r = echo.bind("", 2099); //绑定端口 28 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 29 | echo.onConnRead([](const TcpConnPtr& con) { 30 | con->send(con->getInput()); // echo 读取的数据 31 | }); 32 | base.loop(); //进入事件分发循环 33 | } 34 | ``` 35 |

EventBase事件分发器

36 | EventBase是事件分发器,内部使用epoll/kqueue来管理非阻塞IO 37 | 38 | ```c 39 | EventBase base; 40 | ``` 41 | ### 事件分发循环 42 | 43 | ```c 44 | //不断调用epoll_wait,处理IO事件 45 | base.loop(); 46 | ``` 47 | ### 退出事件循环 48 | 49 | ```c 50 | //退出事件循环,线程安全,可在其他线程中调用 51 | base.exit(); 52 | ``` 53 | ### 是否已退出 54 | 55 | ```c 56 | bool exited(); 57 | ``` 58 | 59 | ### 在IO线程中执行任务 60 | 一些任务必须在IO线程中完成,例如往连接中写入数据。非IO线程需要往连接中写入数据时,必须把任务交由IO线程进行处理 61 | 62 | ```c 63 | void safeCall(const Task& task); 64 | 65 | base.safeCall([con](){con->send("OK");}); 66 | ``` 67 | [例子程序](examples/safe-close.cc) 68 | ### 管理定时任务 69 | EventBase通过设定epoll_wait/kevent的等待时间让自己及时返回,然后检查是否有到期的任务,因此时间精度依赖于epoll_wait/kevent的精度 70 | 71 | ```c 72 | //interval: 0:一次性任务;>0:重复任务,每隔interval毫秒,任务被执行一次 73 | TimerId runAfter(int64_t milli, const Task& task, int64_t interval=0); 74 | //runAt则指定执行时刻 75 | TimerId runAt(int64_t milli, const Task& task, int64_t interval=0) 76 | //取消定时任务,若timer已经过期,则忽略 77 | bool cancel(TimerId timerid); 78 | 79 | TimerId tid = base.runAfter(1000, []{ info("a second passed"); }); 80 | base.cancel(tid); 81 | ``` 82 | [例子程序](examples/timer.cc) 83 |

TcpConn tcp连接

84 | 连接采用引用计数的方式进行管理,因此用户无需手动释放连接 85 | ### 引用计数 86 | 87 | ```c 88 | typedef std::shared_ptr TcpConnPtr; 89 | ``` 90 | ### 状态 91 | 92 | ```c 93 | 94 | enum State { Invalid=1, Handshaking, Connected, Closed, Failed, }; 95 | ``` 96 | 97 | ### 创建连接 98 | 99 | ```c 100 | TcpConnPtr con = TcpConn::createConnection(&base, host, port); #第一个参数为前面的EventBase* 101 | ``` 102 | ### 使用示例 103 | 104 | ```c 105 | TcpConnPtr con = TcpConn::createConnection(&base, host, port); 106 | con->onState([=](const TcpConnPtr& con) { 107 | info("onState called state: %d", con->getState()); 108 | }); 109 | con->onRead([](const TcpConnPtr& con){ 110 | info("recv %lu bytes", con->getInput().size()); 111 | con->send("ok"); 112 | con->getInput().clear(); 113 | }); 114 | ``` 115 | [例子程序](examples/echo.cc) 116 | 117 | ### 设置重连 118 | 119 | ```c 120 | //设置重连时间间隔,-1: 不重连,0:立即重连,其它:等待毫秒数,未设置不重连 121 | void setReconnectInterval(int milli); 122 | ``` 123 | [例子程序](examples/reconnect.cc) 124 | ### 连接空闲回调 125 | 126 | ```c 127 | void addIdleCB(int idle, const TcpCallBack& cb); 128 | 129 | //连接空闲30s关闭连接 130 | con->addIdleCB(30, [](const TcpConnPtr& con)) { con->close(); }); 131 | ``` 132 | [例子程序](examples/idle-close.cc) 133 | 134 | ### 消息模式 135 | 可以使用onRead处理消息,也可以选用onMsg方式处理消息 136 | 137 | ```c 138 | //消息回调,此回调与onRead回调只有一个生效,后设置的生效 139 | //codec所有权交给onMsg 140 | void onMsg(CodecBase* codec, const MsgCallBack& cb); 141 | //发送消息 142 | void sendMsg(Slice msg); 143 | 144 | con->onMsg(new LineCodec, [](const TcpConnPtr& con, Slice msg) { 145 | info("recv msg: %.*s", (int)msg.size(), msg.data()); 146 | con->sendMsg("hello"); 147 | }); 148 | ``` 149 | [例子程序](examples/codec-svr.cc) 150 | ### 存放自定义数据 151 | 152 | ```c 153 | template T& context(); 154 | 155 | con->context() = "user defined data"; 156 | ``` 157 | 158 |

TcpServer tcp服务器

159 | 160 | ### 创建tcp服务器 161 | 162 | ```c 163 | TcpServer echo(&base); 164 | ``` 165 | 166 | ### 使用示例 167 | 168 | ```c 169 | TcpServer echo(&base); //创建服务器 170 | int r = echo.bind("", 2099); //绑定端口 171 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 172 | echo.onConnRead([](const TcpConnPtr& con) { 173 | con->send(con->getInput()); // echo 读取的数据 174 | }); 175 | ``` 176 | [例子程序](examples/echo.cc) 177 | ### 自定义创建的连接 178 | 当服务器accept一个连接时,调用此函数 179 | 180 | ``` 181 | void onConnCreate(const std::function& cb); 182 | 183 | chat.onConnCreate([&]{ 184 | TcpConnPtr con(new TcpConn); 185 | con->onState([&](const TcpConnPtr& con) { 186 | if (con->getState() == TcpConn::Connected) { 187 | con->context() = 1; 188 | } 189 | } 190 | return con; 191 | }); 192 | ``` 193 | 194 | [例子程序](examples/codec-svr.cc) 195 | 196 |

HttpServer http服务器

197 | 198 | ```c 199 | //使用示例 200 | HttpServer sample(&base); 201 | int r = sample.bind("", 8081); 202 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 203 | sample.onGet("/hello", [](const HttpConnPtr& con) { 204 | HttpResponse resp; 205 | resp.body = Slice("hello world"); 206 | con.sendResponse(resp); 207 | }); 208 | ``` 209 | 210 | [例子程序](examples/http-hello.cc) 211 |

半同步半异步服务器

212 | 213 | ```c 214 | //cb返回空string,表示无需返回数据。如果用户需要更灵活的控制,可以直接操作cb的con参数 215 | void onMsg(CodecBase* codec, const RetMsgCallBack& cb); 216 | 217 | hsha.onMsg(new LineCodec, [](const TcpConnPtr& con, const string& input){ 218 | int ms = rand() % 1000; 219 | info("processing a msg"); 220 | usleep(ms * 1000); 221 | return util::format("%s used %d ms", input.c_str(), ms); 222 | }); 223 | ``` 224 | 225 | [例子程序](examples/hsha.cc) 226 | 227 | 持续更新中...... 228 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | CXXFLAGS += $(PLATFORM_CXXFLAGS) -lhandy $(PLATFORM_LDFLAGS) 3 | 4 | SRCS=$(wildcard *.cc) 5 | PROGS=$(SRCS:.cc=) 6 | 7 | all:$(PROGS) 8 | $(PROGS):%:%.cc 9 | $(CXX) $^ -o $@ $(CXXFLAGS) 10 | 11 | .PHONY:clean 12 | clean: 13 | rm -f $(PROGS) 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/chat.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | using namespace handy; 6 | 7 | int main(int argc, const char *argv[]) { 8 | setloglevel("TRACE"); 9 | map users; //生命周期比连接更长,必须放在Base前 10 | EventBase base; 11 | Signal::signal(SIGINT, [&] { base.exit(); }); 12 | 13 | int userid = 1; 14 | TcpServerPtr chat = TcpServer::startServer(&base, "", 2099); 15 | exitif(chat == NULL, "start tcpserver failed"); 16 | chat->onConnCreate([&] { 17 | TcpConnPtr con(new TcpConn); 18 | con->onState([&](const TcpConnPtr &con) { 19 | if (con->getState() == TcpConn::Connected) { 20 | con->context() = userid; 21 | const char *welcome = " : send msg to \n: send msg to all\n\nhello %d"; 22 | con->sendMsg(util::format(welcome, userid)); 23 | users[userid] = con; 24 | userid++; 25 | } else if (con->getState() == TcpConn::Closed) { 26 | users.erase(con->context()); 27 | } 28 | }); 29 | con->onMsg(new LineCodec, [&](const TcpConnPtr &con, Slice msg) { 30 | if (msg.size() == 0) { //忽略空消息 31 | return; 32 | } 33 | int cid = con->context(); 34 | char *p = (char *) msg.data(); 35 | intptr_t id = strtol(p, &p, 10); 36 | p += *p == ' '; //忽略一个空格 37 | string resp = util::format("%ld# %.*s", cid, msg.end() - p, p); 38 | 39 | int sended = 0; 40 | if (id == 0) { //发给其他所有用户 41 | for (auto &pc : users) { 42 | if (pc.first != cid) { 43 | sended++; 44 | pc.second->sendMsg(resp); 45 | } 46 | } 47 | } else { //发给特定用户 48 | auto p1 = users.find(id); 49 | if (p1 != users.end()) { 50 | sended++; 51 | p1->second->sendMsg(resp); 52 | } 53 | } 54 | con->sendMsg(util::format("#sended to %d users", sended)); 55 | }); 56 | return con; 57 | }); 58 | base.loop(); 59 | info("program exited"); 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /examples/codec-cli.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | int main(int argc, const char *argv[]) { 7 | setloglevel("TRACE"); 8 | EventBase base; 9 | Signal::signal(SIGINT, [&] { base.exit(); }); 10 | TcpConnPtr con = TcpConn::createConnection(&base, "127.0.0.1", 2099, 3000); 11 | con->setReconnectInterval(3000); 12 | con->onMsg(new LengthCodec, [](const TcpConnPtr &con, Slice msg) { info("recv msg: %.*s", (int) msg.size(), msg.data()); }); 13 | con->onState([=](const TcpConnPtr &con) { 14 | info("onState called state: %d", con->getState()); 15 | if (con->getState() == TcpConn::Connected) { 16 | con->sendMsg("hello"); 17 | } 18 | }); 19 | base.loop(); 20 | info("program exited"); 21 | } -------------------------------------------------------------------------------- /examples/codec-svr.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | int main(int argc, const char *argv[]) { 7 | Logger::getLogger().setLogLevel(Logger::LTRACE); 8 | EventBase base; 9 | Signal::signal(SIGINT, [&] { base.exit(); }); 10 | 11 | TcpServerPtr echo = TcpServer::startServer(&base, "", 2099); 12 | exitif(echo == NULL, "start tcp server failed"); 13 | echo->onConnCreate([] { 14 | TcpConnPtr con(new TcpConn); 15 | con->onMsg(new LengthCodec, [](const TcpConnPtr &con, Slice msg) { 16 | info("recv msg: %.*s", (int) msg.size(), msg.data()); 17 | con->sendMsg(msg); 18 | }); 19 | return con; 20 | }); 21 | base.loop(); 22 | info("program exited"); 23 | } -------------------------------------------------------------------------------- /examples/daemon.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | int main(int argc, const char *argv[]) { 7 | if (argc != 2) { 8 | printf("usage: %s \n", argv[0]); 9 | return 1; 10 | } 11 | string program = argv[0]; 12 | string pidfile = program + ".pid"; 13 | string conffile = program + ".conf"; 14 | Daemon::daemonProcess(argv[1], pidfile.c_str()); 15 | Conf conf; 16 | int r = conf.parse(conffile.c_str()); 17 | fatalif(r, "config file parse failed %s", conffile.c_str()); 18 | string logfile = conf.get("", "logfile", program + ".log"); 19 | string loglevel = conf.get("", "loglevel", "INFO"); 20 | long rotateInterval = conf.getInteger("", "log_rotate_interval", 86400); 21 | fprintf(stderr, "conf: file: %s level: %s interval: %ld\n", logfile.c_str(), loglevel.c_str(), rotateInterval); 22 | Logger::getLogger().setFileName(logfile.c_str()); 23 | Logger::getLogger().setLogLevel(loglevel.c_str()); 24 | Logger::getLogger().setRotateInterval(rotateInterval); 25 | 26 | EventBase base; 27 | Signal::signal(SIGINT, [&] { base.exit(); }); 28 | TcpServerPtr echo = TcpServer::startServer(&base, "", 2099); 29 | exitif(echo == NULL, "start tcp server failed"); 30 | echo->onConnRead([](const TcpConnPtr &con) { con->send(con->getInput()); }); 31 | base.runAfter(1000, [] { info("log"); }, 1000); 32 | base.loop(); 33 | info("program exited"); 34 | } -------------------------------------------------------------------------------- /examples/daemon.conf: -------------------------------------------------------------------------------- 1 | logfile=daemon.log 2 | loglevel=debug 3 | log_rotate_interval=120 4 | -------------------------------------------------------------------------------- /examples/echo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace handy; 3 | 4 | int main(int argc, const char *argv[]) { 5 | EventBase base; 6 | Signal::signal(SIGINT, [&] { base.exit(); }); 7 | TcpServerPtr svr = TcpServer::startServer(&base, "", 2099); 8 | exitif(svr == NULL, "start tcp server failed"); 9 | svr->onConnRead([](const TcpConnPtr &con) { con->send(con->getInput()); }); 10 | base.loop(); 11 | } -------------------------------------------------------------------------------- /examples/hsha.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | int main(int argc, const char *argv[]) { 7 | setloglevel("TRACE"); 8 | EventBase base; 9 | HSHAPtr hsha = HSHA::startServer(&base, "", 2099, 4); 10 | exitif(!hsha, "bind failed"); 11 | Signal::signal(SIGINT, [&, hsha] { 12 | base.exit(); 13 | hsha->exit(); 14 | signal(SIGINT, SIG_DFL); 15 | }); 16 | 17 | hsha->onMsg(new LineCodec, [](const TcpConnPtr &con, const string &input) { 18 | int ms = rand() % 1000; 19 | info("processing a msg"); 20 | usleep(ms * 1000); 21 | return util::format("%s used %d ms", input.c_str(), ms); 22 | }); 23 | for (int i = 0; i < 5; i++) { 24 | TcpConnPtr con = TcpConn::createConnection(&base, "localhost", 2099); 25 | con->onMsg(new LineCodec, [](const TcpConnPtr &con, Slice msg) { 26 | info("%.*s recved", (int) msg.size(), msg.data()); 27 | con->close(); 28 | }); 29 | con->onState([](const TcpConnPtr &con) { 30 | if (con->getState() == TcpConn::Connected) { 31 | con->sendMsg("hello"); 32 | } 33 | }); 34 | } 35 | base.runAfter(1000, [&, hsha] { 36 | base.exit(); 37 | hsha->exit(); 38 | }); 39 | base.loop(); 40 | info("program exited"); 41 | } 42 | -------------------------------------------------------------------------------- /examples/http-hello.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | int main(int argc, const char *argv[]) { 7 | int threads = 1; 8 | if (argc > 1) { 9 | threads = atoi(argv[1]); 10 | } 11 | setloglevel("TRACE"); 12 | MultiBase base(threads); 13 | HttpServer sample(&base); 14 | int r = sample.bind("", 8081); 15 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 16 | sample.onGet("/hello", [](const HttpConnPtr &con) { 17 | string v = con.getRequest().version; 18 | HttpResponse resp; 19 | resp.body = Slice("hello world"); 20 | con.sendResponse(resp); 21 | if (v == "HTTP/1.0") { 22 | con->close(); 23 | } 24 | }); 25 | Signal::signal(SIGINT, [&] { base.exit(); }); 26 | base.loop(); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /examples/idle-close.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace handy; 3 | 4 | int main(int argc, const char *argv[]) { 5 | setloglevel("TRACE"); 6 | EventBase base; 7 | Signal::signal(SIGINT, [&] { base.exit(); }); 8 | TcpServerPtr svr = TcpServer::startServer(&base, "", 2099); 9 | exitif(svr == NULL, "start tcp server failed"); 10 | svr->onConnState([](const TcpConnPtr &con) { 11 | if (con->getState() == TcpConn::Connected) { 12 | con->addIdleCB(2, [](const TcpConnPtr &con) { 13 | info("idle for 2 seconds, close connection"); 14 | con->close(); 15 | }); 16 | } 17 | }); 18 | auto con = TcpConn::createConnection(&base, "localhost", 2099); 19 | base.runAfter(3000, [&]() { base.exit(); }); 20 | base.loop(); 21 | } -------------------------------------------------------------------------------- /examples/reconnect.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace handy; 3 | 4 | int main(int argc, const char *argv[]) { 5 | setloglevel("TRACE"); 6 | EventBase base; 7 | Signal::signal(SIGINT, [&] { base.exit(); }); 8 | TcpServerPtr svr = TcpServer::startServer(&base, "", 2099); 9 | exitif(svr == NULL, "start tcp server failed"); 10 | svr->onConnState([&](const TcpConnPtr &con) { // 200ms后关闭连接 11 | if (con->getState() == TcpConn::Connected) 12 | base.runAfter(200, [con]() { 13 | info("close con after 200ms"); 14 | con->close(); 15 | }); 16 | }); 17 | TcpConnPtr con1 = TcpConn::createConnection(&base, "localhost", 2099); 18 | con1->setReconnectInterval(300); 19 | // TcpConnPtr con2 = TcpConn::createConnection(&base, "localhost", 1, 100); 20 | // con2->setReconnectInterval(200); 21 | base.runAfter(600, [&]() { base.exit(); }); 22 | base.loop(); 23 | } -------------------------------------------------------------------------------- /examples/safe-close.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace handy; 3 | 4 | int main(int argc, const char *argv[]) { 5 | EventBase base; 6 | Signal::signal(SIGINT, [&] { base.exit(); }); 7 | TcpServerPtr svr = TcpServer::startServer(&base, "", 2099); 8 | exitif(svr == NULL, "start tcp server failed"); 9 | TcpConnPtr con = TcpConn::createConnection(&base, "localhost", 2099); 10 | std::thread th([con, &base]() { 11 | sleep(1); 12 | info("thread want to close an connection"); 13 | base.safeCall([con]() { con->close(); }); //其他线程需要操作连接,应当通过safeCall把操作交给io线程来做 14 | }); 15 | base.runAfter(1500, [&base]() { base.exit(); }); 16 | base.loop(); 17 | th.join(); 18 | } -------------------------------------------------------------------------------- /examples/stat.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | using namespace handy; 6 | 7 | int main(int argc, const char *argv[]) { 8 | Logger::getLogger().setLogLevel("DEBUG"); 9 | EventBase base; 10 | StatServer sample(&base); 11 | int r = sample.bind("", 80); 12 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 13 | sample.onState("loglevel", "log level for server", [] { return Logger::getLogger().getLogLevelStr(); }); 14 | sample.onState("pid", "process id of server", [] { return util::format("%d", getpid()); }); 15 | sample.onCmd("lesslog", "set log to less detail", [] { 16 | Logger::getLogger().adjustLogLevel(-1); 17 | return "OK"; 18 | }); 19 | sample.onCmd("morelog", "set log to more detail", [] { 20 | Logger::getLogger().adjustLogLevel(1); 21 | return "OK"; 22 | }); 23 | sample.onCmd("restart", "restart program", [&] { 24 | base.safeCall([&] { 25 | base.exit(); 26 | Daemon::changeTo(argv); 27 | }); 28 | return "restarting"; 29 | }); 30 | sample.onCmd("stop", "stop program", [&] { 31 | base.safeCall([&] { base.exit(); }); 32 | return "stoping"; 33 | }); 34 | sample.onPage("page", "show page content", [] { return "this is a page"; }); 35 | Signal::signal(SIGINT, [&] { base.exit(); }); 36 | base.loop(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /examples/timer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace handy; 3 | 4 | int main(int argc, const char *argv[]) { 5 | EventBase base; 6 | Signal::signal(SIGINT, [&] { base.exit(); }); 7 | info("program begin"); 8 | base.runAfter(200, []() { info("a task in runAfter 200ms"); }); 9 | base.runAfter(100, []() { info("a task in runAfter 100ms interval 1000ms"); }, 1000); 10 | TimerId id = base.runAt(time(NULL) * 1000 + 300, []() { info("a task in runAt now+300 interval 500ms"); }, 500); 11 | base.runAfter(2000, [&]() { 12 | info("cancel task of interval 500ms"); 13 | base.cancel(id); 14 | }); 15 | base.runAfter(3000, [&]() { base.exit(); }); 16 | base.loop(); 17 | } -------------------------------------------------------------------------------- /examples/udp-cli.cc: -------------------------------------------------------------------------------- 1 | // echo client 2 | #include 3 | using namespace handy; 4 | 5 | int main(int argc, const char *argv[]) { 6 | setloglevel("TRACE"); 7 | EventBase base; 8 | Signal::signal(SIGINT, [&] { base.exit(); }); 9 | UdpConnPtr con = UdpConn::createConnection(&base, "127.0.0.1", 2099); 10 | exitif(!con, "start udp conn failed"); 11 | con->onMsg([](const UdpConnPtr &p, Buffer buf) { info("udp recved %lu bytes", buf.size()); }); 12 | base.runAfter(0, [=]() { con->send("hello"); }, 1000); 13 | base.loop(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/udp-hsha.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | int main(int argc, const char *argv[]) { 7 | setloglevel("TRACE"); 8 | EventBase base; 9 | HSHAUPtr hsha = HSHAU::startServer(&base, "", 2099, 1); 10 | exitif(!hsha, "bind failed"); 11 | Signal::signal(SIGINT, [&, hsha] { 12 | base.exit(); 13 | hsha->exit(); 14 | signal(SIGINT, SIG_DFL); 15 | }); 16 | 17 | hsha->onMsg([](const UdpServerPtr &con, const string &input, Ip4Addr addr) { 18 | int ms = rand() % 1000 + 500; 19 | info("processing a msg: %.*s will using %d ms", (int) input.length(), input.data(), ms); 20 | usleep(ms * 1000); 21 | info("msg processed"); 22 | return util::format("%s used %d ms", input.c_str(), ms); 23 | }); 24 | for (int i = 0; i < 1; i++) { 25 | UdpConnPtr con = UdpConn::createConnection(&base, "localhost", 2099); 26 | con->onMsg([](const UdpConnPtr &con, Buffer buf) { 27 | info("%.*s recved", (int) buf.size(), buf.data()); 28 | con->close(); 29 | }); 30 | con->send("hello"); 31 | } 32 | base.runAfter(500, [&, hsha] { 33 | info("exiting"); 34 | base.exit(); 35 | hsha->exit(); 36 | }); 37 | base.loop(); 38 | info("program exited"); 39 | } 40 | -------------------------------------------------------------------------------- /examples/udp-svr.cc: -------------------------------------------------------------------------------- 1 | // echo server 2 | #include 3 | using namespace handy; 4 | 5 | int main(int argc, const char *argv[]) { 6 | setloglevel("TRACE"); 7 | EventBase base; 8 | Signal::signal(SIGINT, [&] { base.exit(); }); 9 | UdpServerPtr svr = UdpServer::startServer(&base, "", 2099); 10 | exitif(!svr, "start udp server failed"); 11 | svr->onMsg([](const UdpServerPtr &p, Buffer buf, Ip4Addr peer) { 12 | info("echo msg: %s to %s", buf.data(), peer.toString().c_str()); 13 | p->sendTo(buf, peer); 14 | }); 15 | base.loop(); 16 | } 17 | -------------------------------------------------------------------------------- /examples/write-on-empty.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | using namespace handy; 5 | 6 | char buf[20 * 1024 * 1024]; 7 | 8 | int main(int argc, const char *argv[]) { 9 | setloglevel("TRACE"); 10 | int sended = 0, total = 1054768 * 100; 11 | memset(buf, 'a', sizeof buf); 12 | EventBase bases; 13 | Signal::signal(SIGINT, [&] { bases.exit(); }); 14 | TcpServer echo(&bases); 15 | int r = echo.bind("", 2099); 16 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 17 | auto sendcb = [&](const TcpConnPtr &con) { 18 | while (con->getOutput().size() == 0 && sended < total) { 19 | con->send(buf, sizeof buf); 20 | sended += sizeof buf; 21 | info("%d bytes sended output size: %lu", sended, con->getOutput().size()); 22 | } 23 | if (sended >= total) { 24 | con->close(); 25 | bases.exit(); 26 | } 27 | }; 28 | echo.onConnCreate([sendcb]() { 29 | TcpConnPtr con(new TcpConn); 30 | con->onState([sendcb](const TcpConnPtr &con) { 31 | if (con->getState() == TcpConn::Connected) { 32 | con->onWritable(sendcb); 33 | } 34 | sendcb(con); 35 | }); 36 | return con; 37 | }); 38 | thread th([] { //模拟了一个客户端,连接服务器后,接收服务器发送过来的数据 39 | EventBase base2; 40 | TcpConnPtr con = TcpConn::createConnection(&base2, "127.0.0.1", 2099); 41 | con->onRead([](const TcpConnPtr &con) { 42 | info("recv %lu bytes", con->getInput().size()); 43 | con->getInput().clear(); 44 | sleep(1); 45 | }); 46 | con->onState([&](const TcpConnPtr &con) { 47 | if (con->getState() == TcpConn::Closed || con->getState() == TcpConn::Failed) { 48 | base2.exit(); 49 | } 50 | }); 51 | base2.loop(); 52 | }); 53 | bases.loop(); 54 | th.join(); 55 | info("program exited"); 56 | } -------------------------------------------------------------------------------- /handy/codec.cc: -------------------------------------------------------------------------------- 1 | #include "codec.h" 2 | 3 | using namespace std; 4 | 5 | namespace handy { 6 | 7 | int LineCodec::tryDecode(Slice data, Slice &msg) { 8 | if (data.size() == 1 && data[0] == 0x04) { 9 | msg = data; 10 | return 1; 11 | } 12 | for (size_t i = 0; i < data.size(); i++) { 13 | if (data[i] == '\n') { 14 | if (i > 0 && data[i - 1] == '\r') { 15 | msg = Slice(data.data(), i - 1); 16 | return static_cast(i + 1); 17 | } else { 18 | msg = Slice(data.data(), i); 19 | return static_cast(i + 1); 20 | } 21 | } 22 | } 23 | return 0; 24 | } 25 | void LineCodec::encode(Slice msg, Buffer &buf) { 26 | buf.append(msg).append("\r\n"); 27 | } 28 | 29 | int LengthCodec::tryDecode(Slice data, Slice &msg) { 30 | if (data.size() < 8) { 31 | return 0; 32 | } 33 | int len = net::ntoh(*(int32_t *) (data.data() + 4)); 34 | if (len > 1024 * 1024 || memcmp(data.data(), "mBdT", 4) != 0) { 35 | return -1; 36 | } 37 | if ((int) data.size() >= len + 8) { 38 | msg = Slice(data.data() + 8, len); 39 | return len + 8; 40 | } 41 | return 0; 42 | } 43 | void LengthCodec::encode(Slice msg, Buffer &buf) { 44 | buf.append("mBdT").appendValue(net::hton((int32_t) msg.size())).append(msg); 45 | } 46 | 47 | } // namespace handy -------------------------------------------------------------------------------- /handy/codec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "net.h" 3 | #include "slice.h" 4 | namespace handy { 5 | 6 | struct CodecBase { 7 | // > 0 解析出完整消息,消息放在msg中,返回已扫描的字节数 8 | // == 0 解析部分消息 9 | // < 0 解析错误 10 | virtual int tryDecode(Slice data, Slice &msg) = 0; 11 | virtual void encode(Slice msg, Buffer &buf) = 0; 12 | virtual CodecBase *clone() = 0; 13 | virtual ~CodecBase() = default; 14 | }; 15 | 16 | //以\r\n结尾的消息 17 | struct LineCodec : public CodecBase { 18 | int tryDecode(Slice data, Slice &msg) override; 19 | void encode(Slice msg, Buffer &buf) override; 20 | CodecBase *clone() override { return new LineCodec(); } 21 | }; 22 | 23 | //给出长度的消息 24 | struct LengthCodec : public CodecBase { 25 | int tryDecode(Slice data, Slice &msg) override; 26 | void encode(Slice msg, Buffer &buf) override; 27 | CodecBase *clone() override { return new LengthCodec(); } 28 | }; 29 | 30 | } // namespace handy 31 | -------------------------------------------------------------------------------- /handy/conf.cc: -------------------------------------------------------------------------------- 1 | #include "conf.h" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | namespace handy { 9 | 10 | static string makeKey(const string& section, const string& name) { 11 | string key = section + "." + name; 12 | // Convert to lower case to make section/name lookups case-insensitive 13 | std::transform(key.begin(), key.end(), key.begin(), ::tolower); 14 | return key; 15 | } 16 | 17 | string Conf::get(const string& section, const string& name, const string& default_value) { 18 | string key = makeKey(section, name); 19 | auto p = values_.find(key); 20 | return p == values_.end() ? default_value : p->second.back(); 21 | } 22 | 23 | list Conf::getStrings(const string& section, const string& name) { 24 | string key = makeKey(section, name); 25 | auto p = values_.find(key); 26 | return p == values_.end() ? list() : p->second; 27 | } 28 | 29 | long Conf::getInteger(const string& section, const string& name, long default_value) { 30 | string valstr = get(section, name, ""); 31 | const char *value = valstr.c_str(); 32 | char *end; 33 | // This parses "1234" (decimal) and also "0x4D2" (hex) 34 | long n = strtol(value, &end, 0); 35 | return end > value ? n : default_value; 36 | } 37 | 38 | double Conf::getReal(const string& section, const string& name, double default_value) { 39 | string valstr = get(section, name, ""); 40 | const char *value = valstr.c_str(); 41 | char *end; 42 | double n = strtod(value, &end); 43 | return end > value ? n : default_value; 44 | } 45 | 46 | bool Conf::getBoolean(const string& section, const string& name, bool default_value) { 47 | string valstr = get(section, name, ""); 48 | // Convert to lower case to make string comparisons case-insensitive 49 | std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); 50 | if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") 51 | return true; 52 | else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") 53 | return false; 54 | else 55 | return default_value; 56 | } 57 | 58 | namespace { 59 | struct LineScanner { 60 | char *p; 61 | int err; 62 | LineScanner(char *ln) : p(ln), err(0) {} 63 | LineScanner &skipSpaces() { 64 | while (!err && *p && isspace(*p)) { 65 | p++; 66 | } 67 | return *this; 68 | } 69 | static string rstrip(char *s, char *e) { 70 | while (e > s && isspace(e[-1])) { 71 | e--; 72 | } 73 | return {s, e}; 74 | } 75 | int peekChar() { 76 | skipSpaces(); 77 | return *p; 78 | } 79 | LineScanner &skip(int i) { 80 | p += i; 81 | return *this; 82 | } 83 | LineScanner &match(char c) { 84 | skipSpaces(); 85 | err = *p++ != c; 86 | return *this; 87 | } 88 | string consumeTill(char c) { 89 | skipSpaces(); 90 | char *e = p; 91 | while (!err && *e && *e != c) { 92 | e++; 93 | } 94 | if (*e != c) { 95 | err = 1; 96 | return ""; 97 | } 98 | char *s = p; 99 | p = e; 100 | return rstrip(s, e); 101 | } 102 | string consumeTillEnd() { 103 | skipSpaces(); 104 | char *e = p; 105 | int wasspace = 0; 106 | while (!err && *e && *e != ';' && *e != '#') { 107 | if (wasspace) { 108 | break; 109 | } 110 | wasspace = isspace(*e); 111 | e++; 112 | } 113 | char *s = p; 114 | p = e; 115 | return rstrip(s, e); 116 | } 117 | }; 118 | } // namespace 119 | 120 | int Conf::parse(const string &filename) { 121 | this->filename = filename; 122 | FILE *file = fopen(this->filename.c_str(), "r"); 123 | if (!file) 124 | return -1; 125 | unique_ptr release2(file, fclose); 126 | static const int MAX_LINE = 16 * 1024; 127 | char *ln = new char[MAX_LINE]; 128 | unique_ptr release1(ln); 129 | int lineno = 0; 130 | string section, key; 131 | int err = 0; 132 | while (!err && fgets(ln, MAX_LINE, file) != nullptr) { 133 | lineno++; 134 | LineScanner ls(ln); 135 | int c = ls.peekChar(); 136 | if (c == ';' || c == '#' || c == '\0') { 137 | continue; 138 | } else if (c == '[') { 139 | section = ls.skip(1).consumeTill(']'); 140 | err = ls.match(']').err; 141 | key = ""; 142 | } else if (isspace(ln[0])) { 143 | /* Non-black line with leading whitespace, treat as continuation 144 | of previous name's value (as per Python ConfigParser). */ 145 | if (!key.empty()) { 146 | values_[makeKey(section, key)].push_back(ls.consumeTill('\0')); 147 | } else { 148 | err = 1; 149 | } 150 | } else { 151 | LineScanner lsc = ls; 152 | key = ls.consumeTill('='); 153 | if (ls.peekChar() == '=') { 154 | ls.skip(1); 155 | } else { 156 | ls = lsc; 157 | key = ls.consumeTill(':'); 158 | err = ls.match(':').err; 159 | } 160 | string value = ls.consumeTillEnd(); 161 | values_[makeKey(section, key)].push_back(value); 162 | } 163 | } 164 | return err ? lineno : 0; 165 | } 166 | 167 | } // namespace handy 168 | -------------------------------------------------------------------------------- /handy/conf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace handy { 8 | 9 | struct Conf { 10 | // 0 success 11 | // -1 IO ERROR 12 | // >0 line no of error 13 | int parse(const std::string &filename); 14 | 15 | // Get a string value from INI file, returning default_value if not found. 16 | std::string get(const std::string& section, const std::string& name, const std::string& default_value); 17 | 18 | // Get an integer (long) value from INI file, returning default_value if 19 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). 20 | long getInteger(const std::string& section, const std::string& name, long default_value); 21 | 22 | // Get a real (floating point double) value from INI file, returning 23 | // default_value if not found or not a valid floating point value 24 | // according to strtod(). 25 | double getReal(const std::string& section, const std::string& name, double default_value); 26 | 27 | // Get a boolean value from INI file, returning default_value if not found or if 28 | // not a valid true/false value. Valid true values are "true", "yes", "on", "1", 29 | // and valid false values are "false", "no", "off", "0" (not case sensitive). 30 | bool getBoolean(const std::string& section, const std::string& name, bool default_value); 31 | 32 | // Get a string value from INI file, returning empty list if not found. 33 | std::list getStrings(const std::string& section, const std::string& name); 34 | 35 | std::map> values_; 36 | std::string filename; 37 | }; 38 | 39 | } // namespace handy -------------------------------------------------------------------------------- /handy/conn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "event_base.h" 3 | 4 | namespace handy { 5 | 6 | // Tcp连接,使用引用计数 7 | struct TcpConn : public std::enable_shared_from_this, private noncopyable { 8 | // Tcp连接的个状态 9 | enum State { 10 | Invalid = 1, 11 | Handshaking, 12 | Connected, 13 | Closed, 14 | Failed, 15 | }; 16 | // Tcp构造函数,实际可用的连接应当通过createConnection创建 17 | TcpConn(); 18 | virtual ~TcpConn(); 19 | //可传入连接类型,返回智能指针 20 | template 21 | static TcpConnPtr createConnection(EventBase *base, const std::string &host, unsigned short port, int timeout = 0, const std::string &localip = "") { 22 | TcpConnPtr con(new C); 23 | con->connect(base, host, port, timeout, localip); 24 | return con; 25 | } 26 | template 27 | static TcpConnPtr createConnection(EventBase *base, int fd, Ip4Addr local, Ip4Addr peer) { 28 | TcpConnPtr con(new C); 29 | con->attach(base, fd, local, peer); 30 | return con; 31 | } 32 | 33 | bool isClient() { return destPort_ > 0; } 34 | // automatically managed context. allocated when first used, deleted when destruct 35 | template 36 | T &context() { 37 | return ctx_.context(); 38 | } 39 | 40 | EventBase *getBase() { return base_; } 41 | State getState() { return state_; } 42 | // TcpConn的输入输出缓冲区 43 | Buffer &getInput() { return input_; } 44 | Buffer &getOutput() { return output_; } 45 | 46 | Channel *getChannel() { return channel_; } 47 | bool writable() { return channel_ ? channel_->writeEnabled() : false; } 48 | 49 | //发送数据 50 | void sendOutput() { send(output_); } 51 | void send(Buffer &msg); 52 | void send(const char *buf, size_t len); 53 | void send(const std::string &s) { send(s.data(), s.size()); } 54 | void send(const char *s) { send(s, strlen(s)); } 55 | 56 | //数据到达时回调 57 | void onRead(const TcpCallBack &cb) { 58 | assert(!readcb_); 59 | readcb_ = cb; 60 | }; 61 | //当tcp缓冲区可写时回调 62 | void onWritable(const TcpCallBack &cb) { writablecb_ = cb; } 63 | // tcp状态改变时回调 64 | void onState(const TcpCallBack &cb) { statecb_ = cb; } 65 | // tcp空闲回调 66 | void addIdleCB(int idle, const TcpCallBack &cb); 67 | 68 | //消息回调,此回调与onRead回调冲突,只能够调用一个 69 | // codec所有权交给onMsg 70 | void onMsg(CodecBase *codec, const MsgCallBack &cb); 71 | //发送消息 72 | void sendMsg(Slice msg); 73 | 74 | // conn会在下个事件周期进行处理 75 | void close(); 76 | //设置重连时间间隔,-1: 不重连,0:立即重连,其它:等待毫秒数,未设置不重连 77 | void setReconnectInterval(int milli) { reconnectInterval_ = milli; } 78 | 79 | //!慎用。立即关闭连接,清理相关资源,可能导致该连接的引用计数变为0,从而使当前调用者引用的连接被析构 80 | void closeNow() { 81 | if (channel_) 82 | channel_->close(); 83 | } 84 | 85 | //远程地址的字符串 86 | std::string str() { return peer_.toString(); } 87 | 88 | public: 89 | EventBase *base_; 90 | Channel *channel_; 91 | Buffer input_, output_; 92 | Ip4Addr local_, peer_; 93 | State state_; 94 | TcpCallBack readcb_, writablecb_, statecb_; 95 | std::list idleIds_; 96 | TimerId timeoutId_; 97 | AutoContext ctx_, internalCtx_; 98 | std::string destHost_, localIp_; 99 | int destPort_, connectTimeout_, reconnectInterval_; 100 | int64_t connectedTime_; 101 | std::unique_ptr codec_; 102 | void handleRead(const TcpConnPtr &con); 103 | void handleWrite(const TcpConnPtr &con); 104 | ssize_t isend(const char *buf, size_t len); 105 | void cleanup(const TcpConnPtr &con); 106 | void connect(EventBase *base, const std::string &host, unsigned short port, int timeout, const std::string &localip); 107 | void reconnect(); 108 | void attach(EventBase *base, int fd, Ip4Addr local, Ip4Addr peer); 109 | virtual int readImp(int fd, void *buf, size_t bytes) { return ::read(fd, buf, bytes); } 110 | virtual int writeImp(int fd, const void *buf, size_t bytes) { return ::write(fd, buf, bytes); } 111 | virtual int handleHandshake(const TcpConnPtr &con); 112 | }; 113 | 114 | // Tcp服务器 115 | struct TcpServer : private noncopyable { 116 | TcpServer(EventBases *bases); 117 | // return 0 on sucess, errno on error 118 | int bind(const std::string &host, unsigned short port, bool reusePort = false); 119 | static TcpServerPtr startServer(EventBases *bases, const std::string &host, unsigned short port, bool reusePort = false); 120 | ~TcpServer() { delete listen_channel_; } 121 | Ip4Addr getAddr() { return addr_; } 122 | EventBase *getBase() { return base_; } 123 | void onConnCreate(const std::function &cb) { createcb_ = cb; } 124 | void onConnState(const TcpCallBack &cb) { statecb_ = cb; } 125 | void onConnRead(const TcpCallBack &cb) { 126 | readcb_ = cb; 127 | assert(!msgcb_); 128 | } 129 | // 消息处理与Read回调冲突,只能调用一个 130 | void onConnMsg(CodecBase *codec, const MsgCallBack &cb) { 131 | codec_.reset(codec); 132 | msgcb_ = cb; 133 | assert(!readcb_); 134 | } 135 | 136 | private: 137 | EventBase *base_; 138 | EventBases *bases_; 139 | Ip4Addr addr_; 140 | Channel *listen_channel_; 141 | TcpCallBack statecb_, readcb_; 142 | MsgCallBack msgcb_; 143 | std::function createcb_; 144 | std::unique_ptr codec_; 145 | void handleAccept(); 146 | }; 147 | 148 | typedef std::function RetMsgCallBack; 149 | //半同步半异步服务器 150 | struct HSHA; 151 | typedef std::shared_ptr HSHAPtr; 152 | struct HSHA { 153 | static HSHAPtr startServer(EventBase *base, const std::string &host, unsigned short port, int threads); 154 | HSHA(int threads) : threadPool_(threads) {} 155 | void exit() { 156 | threadPool_.exit(); 157 | threadPool_.join(); 158 | } 159 | void onMsg(CodecBase *codec, const RetMsgCallBack &cb); 160 | TcpServerPtr server_; 161 | ThreadPool threadPool_; 162 | }; 163 | 164 | } // namespace handy 165 | -------------------------------------------------------------------------------- /handy/daemon.cc: -------------------------------------------------------------------------------- 1 | #include "daemon.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | namespace handy { 18 | 19 | namespace { 20 | 21 | struct ExitCaller { 22 | ~ExitCaller() { functor_(); } 23 | ExitCaller(std::function &&functor) : functor_(std::move(functor)) {} 24 | 25 | private: 26 | std::function functor_; 27 | }; 28 | 29 | } // namespace 30 | 31 | static int writePidFile(const char *pidfile) { 32 | char str[32]; 33 | int lfp = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC, 0600); 34 | if (lfp < 0 || lockf(lfp, F_TLOCK, 0) < 0) { 35 | fprintf(stderr, "Can't write Pid File: %s", pidfile); 36 | return -1; 37 | } 38 | ExitCaller call1([=] { close(lfp); }); 39 | sprintf(str, "%d\n", getpid()); 40 | ssize_t len = strlen(str); 41 | ssize_t ret = write(lfp, str, len); 42 | if (ret != len) { 43 | fprintf(stderr, "Can't Write Pid File: %s", pidfile); 44 | return -1; 45 | } 46 | return 0; 47 | } 48 | 49 | int Daemon::getPidFromFile(const char *pidfile) { 50 | char buffer[64], *p; 51 | int lfp = open(pidfile, O_RDONLY, 0); 52 | if (lfp < 0) { 53 | return lfp; 54 | } 55 | ssize_t rd = read(lfp, buffer, 64); 56 | close(lfp); 57 | if (rd <= 0) { 58 | return -1; 59 | } 60 | buffer[63] = '\0'; 61 | p = strchr(buffer, '\n'); 62 | if (p != NULL) 63 | *p = '\0'; 64 | return atoi(buffer); 65 | } 66 | 67 | int Daemon::daemonStart(const char *pidfile) { 68 | int pid = getPidFromFile(pidfile); 69 | if (pid > 0) { 70 | if (kill(pid, 0) == 0 || errno == EPERM) { 71 | fprintf(stderr, "daemon exists, use restart\n"); 72 | return -1; 73 | } 74 | } 75 | if (getppid() == 1) { 76 | fprintf(stderr, "already daemon, can't start\n"); 77 | return -1; 78 | } 79 | 80 | pid = fork(); 81 | if (pid < 0) { 82 | fprintf(stderr, "fork error: %d\n", pid); 83 | return -1; 84 | } 85 | if (pid > 0) { 86 | exit(0); // parent exit 87 | } 88 | setsid(); 89 | int r = writePidFile(pidfile); 90 | if (r != 0) { 91 | return r; 92 | } 93 | int fd = open("/dev/null", 0); 94 | if (fd >= 0) { 95 | close(0); 96 | dup2(fd, 0); 97 | dup2(fd, 1); 98 | close(fd); 99 | string pfile = pidfile; 100 | static ExitCaller del([=] { unlink(pfile.c_str()); }); 101 | return 0; 102 | } 103 | return -1; 104 | } 105 | 106 | int Daemon::daemonStop(const char *pidfile) { 107 | int pid = getPidFromFile(pidfile); 108 | if (pid <= 0) { 109 | fprintf(stderr, "%s not exists or not valid\n", pidfile); 110 | return -1; 111 | } 112 | int r = kill(pid, SIGQUIT); 113 | if (r < 0) { 114 | fprintf(stderr, "program %d not exists\n", pid); 115 | return r; 116 | } 117 | for (int i = 0; i < 300; i++) { 118 | usleep(10 * 1000); 119 | r = kill(pid, SIGQUIT); 120 | if (r != 0) { 121 | fprintf(stderr, "program %d exited\n", pid); 122 | unlink(pidfile); 123 | return 0; 124 | } 125 | } 126 | fprintf(stderr, "signal sended to process, but still exists after 3 seconds\n"); 127 | return -1; 128 | } 129 | 130 | int Daemon::daemonRestart(const char *pidfile) { 131 | int pid = getPidFromFile(pidfile); 132 | if (pid > 0) { 133 | if (kill(pid, 0) == 0) { 134 | int r = daemonStop(pidfile); 135 | if (r < 0) { 136 | return r; 137 | } 138 | } else if (errno == EPERM) { 139 | fprintf(stderr, "do not have permission to kill process: %d\n", pid); 140 | return -1; 141 | } 142 | } else { 143 | fprintf(stderr, "pid file not valid, just ignore\n"); 144 | } 145 | return daemonStart(pidfile); 146 | } 147 | 148 | void Daemon::daemonProcess(const char *cmd, const char *pidfile) { 149 | int r = 0; 150 | if (cmd == NULL || strcmp(cmd, "start") == 0) { 151 | r = daemonStart(pidfile); 152 | } else if (strcmp(cmd, "stop") == 0) { 153 | r = daemonStop(pidfile); 154 | if (r == 0) { 155 | exit(0); 156 | } 157 | } else if (strcmp(cmd, "restart") == 0) { 158 | r = daemonRestart(pidfile); 159 | } else { 160 | fprintf(stderr, "ERROR: bad daemon command. exit\n"); 161 | r = -1; 162 | } 163 | if (r) { 164 | // exit on error 165 | exit(1); 166 | } 167 | } 168 | 169 | void Daemon::changeTo(const char *argv[]) { 170 | int pid = getpid(); 171 | int r = fork(); 172 | if (r < 0) { 173 | fprintf(stderr, "fork error %d %s", errno, strerror(errno)); 174 | } else if (r > 0) { // parent; 175 | return; 176 | } else { // child 177 | // wait parent to exit 178 | while (kill(pid, 0) == 0) { 179 | usleep(10 * 1000); 180 | } 181 | if (errno != ESRCH) { 182 | const char *msg = "kill error\n"; 183 | ssize_t w1 = write(2, msg, strlen(msg)); 184 | (void) w1; 185 | _exit(1); 186 | } 187 | execvp(argv[0], (char *const *) argv); 188 | } 189 | } 190 | 191 | namespace { 192 | map> handlers; 193 | void signal_handler(int sig) { 194 | handlers[sig](); 195 | } 196 | } // namespace 197 | 198 | void Signal::signal(int sig, const function &handler) { 199 | handlers[sig] = handler; 200 | ::signal(sig, signal_handler); 201 | } 202 | 203 | } // namespace handy -------------------------------------------------------------------------------- /handy/daemon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | namespace handy { 5 | 6 | struct Daemon { 7 | // exit in parent 8 | static int daemonStart(const char *pidfile); 9 | // exit in parent 10 | static int daemonRestart(const char *pidfile); 11 | static int daemonStop(const char *pidfile); 12 | static int getPidFromFile(const char *pidfile); 13 | 14 | // cmd: start stop restart 15 | // exit(1) when error 16 | // exit(0) when start or restart in parent 17 | // return when start or restart in child 18 | static void daemonProcess(const char *cmd, const char *pidfile); 19 | // fork; wait for parent to exit; exec argv 20 | // you may use it to implement restart in program 21 | static void changeTo(const char *argv[]); 22 | }; 23 | 24 | struct Signal { 25 | static void signal(int sig, const std::function &handler); 26 | }; 27 | } // namespace handy 28 | -------------------------------------------------------------------------------- /handy/event_base.cc: -------------------------------------------------------------------------------- 1 | #include "event_base.h" 2 | #include 3 | #include 4 | #include 5 | #include "conn.h" 6 | #include "logging.h" 7 | #include "poller.h" 8 | #include "util.h" 9 | using namespace std; 10 | 11 | namespace handy { 12 | 13 | namespace { 14 | 15 | struct TimerRepeatable { 16 | int64_t at; // current timer timeout timestamp 17 | int64_t interval; 18 | TimerId timerid; 19 | Task cb; 20 | }; 21 | 22 | struct IdleNode { 23 | TcpConnPtr con_; 24 | int64_t updated_; 25 | TcpCallBack cb_; 26 | }; 27 | 28 | } // namespace 29 | 30 | struct IdleIdImp { 31 | IdleIdImp() {} 32 | typedef list::iterator Iter; 33 | IdleIdImp(list *lst, Iter iter) : lst_(lst), iter_(iter) {} 34 | list *lst_; 35 | Iter iter_; 36 | }; 37 | 38 | struct EventsImp { 39 | EventBase *base_; 40 | PollerBase *poller_; 41 | std::atomic exit_; 42 | int wakeupFds_[2]; 43 | int nextTimeout_; 44 | SafeQueue tasks_; 45 | 46 | std::map timerReps_; 47 | std::map timers_; 48 | std::atomic timerSeq_; 49 | // 记录每个idle时间(单位秒)下所有的连接。链表中的所有连接,最新的插入到链表末尾。连接若有活动,会把连接从链表中移到链表尾部,做法参考memcache 50 | std::map> idleConns_; 51 | std::set reconnectConns_; 52 | bool idleEnabled; 53 | 54 | EventsImp(EventBase *base, int taskCap); 55 | ~EventsImp(); 56 | void init(); 57 | void callIdles(); 58 | IdleId registerIdle(int idle, const TcpConnPtr &con, const TcpCallBack &cb); 59 | void unregisterIdle(const IdleId &id); 60 | void updateIdle(const IdleId &id); 61 | void handleTimeouts(); 62 | void refreshNearest(const TimerId *tid = NULL); 63 | void repeatableTimeout(TimerRepeatable *tr); 64 | 65 | // eventbase functions 66 | EventBase &exit() { 67 | exit_ = true; 68 | wakeup(); 69 | return *base_; 70 | } 71 | bool exited() { return exit_; } 72 | void safeCall(Task &&task) { 73 | tasks_.push(move(task)); 74 | wakeup(); 75 | } 76 | void loop(); 77 | void loop_once(int waitMs) { 78 | poller_->loop_once(std::min(waitMs, nextTimeout_)); 79 | handleTimeouts(); 80 | } 81 | void wakeup() { 82 | int r = write(wakeupFds_[1], "", 1); 83 | fatalif(r <= 0, "write error wd %d %d %s", r, errno, strerror(errno)); 84 | } 85 | 86 | bool cancel(TimerId timerid); 87 | TimerId runAt(int64_t milli, Task &&task, int64_t interval); 88 | }; 89 | 90 | EventBase::EventBase(int taskCapacity) { 91 | imp_.reset(new EventsImp(this, taskCapacity)); 92 | imp_->init(); 93 | } 94 | 95 | EventBase::~EventBase() {} 96 | 97 | EventBase &EventBase::exit() { 98 | return imp_->exit(); 99 | } 100 | 101 | bool EventBase::exited() { 102 | return imp_->exited(); 103 | } 104 | 105 | void EventBase::safeCall(Task &&task) { 106 | imp_->safeCall(move(task)); 107 | } 108 | 109 | void EventBase::wakeup() { 110 | imp_->wakeup(); 111 | } 112 | 113 | void EventBase::loop() { 114 | imp_->loop(); 115 | } 116 | 117 | void EventBase::loop_once(int waitMs) { 118 | imp_->loop_once(waitMs); 119 | } 120 | 121 | bool EventBase::cancel(TimerId timerid) { 122 | return imp_ && imp_->cancel(timerid); 123 | } 124 | 125 | TimerId EventBase::runAt(int64_t milli, Task &&task, int64_t interval) { 126 | return imp_->runAt(milli, std::move(task), interval); 127 | } 128 | 129 | EventsImp::EventsImp(EventBase *base, int taskCap) 130 | : base_(base), poller_(createPoller()), exit_(false), nextTimeout_(1 << 30), tasks_(taskCap), timerSeq_(0), idleEnabled(false) {} 131 | 132 | void EventsImp::loop() { 133 | while (!exit_) 134 | loop_once(10000); 135 | timerReps_.clear(); 136 | timers_.clear(); 137 | idleConns_.clear(); 138 | for (auto recon : reconnectConns_) { //重连的连接无法通过channel清理,因此单独清理 139 | recon->cleanup(recon); 140 | } 141 | loop_once(0); 142 | } 143 | 144 | void EventsImp::init() { 145 | int r = pipe(wakeupFds_); 146 | fatalif(r, "pipe failed %d %s", errno, strerror(errno)); 147 | r = util::addFdFlag(wakeupFds_[0], FD_CLOEXEC); 148 | fatalif(r, "addFdFlag failed %d %s", errno, strerror(errno)); 149 | r = util::addFdFlag(wakeupFds_[1], FD_CLOEXEC); 150 | fatalif(r, "addFdFlag failed %d %s", errno, strerror(errno)); 151 | trace("wakeup pipe created %d %d", wakeupFds_[0], wakeupFds_[1]); 152 | Channel *ch = new Channel(base_, wakeupFds_[0], kReadEvent); 153 | ch->onRead([=] { 154 | char buf[1024]; 155 | int r = ch->fd() >= 0 ? ::read(ch->fd(), buf, sizeof buf) : 0; 156 | if (r > 0) { 157 | Task task; 158 | while (tasks_.pop_wait(&task, 0)) { 159 | task(); 160 | } 161 | } else if (r == 0) { 162 | delete ch; 163 | } else if (errno == EINTR) { 164 | } else { 165 | fatal("wakeup channel read error %d %d %s", r, errno, strerror(errno)); 166 | } 167 | }); 168 | } 169 | 170 | void EventsImp::handleTimeouts() { 171 | int64_t now = util::timeMilli(); 172 | TimerId tid{now, 1L << 62}; 173 | while (timers_.size() && timers_.begin()->first < tid) { 174 | Task task = move(timers_.begin()->second); 175 | timers_.erase(timers_.begin()); 176 | task(); 177 | } 178 | refreshNearest(); 179 | } 180 | 181 | EventsImp::~EventsImp() { 182 | delete poller_; 183 | ::close(wakeupFds_[1]); 184 | } 185 | 186 | void EventsImp::callIdles() { 187 | int64_t now = util::timeMilli() / 1000; 188 | for (auto &l : idleConns_) { 189 | int idle = l.first; 190 | auto& lst = l.second; 191 | while (lst.size()) { 192 | IdleNode &node = lst.front(); 193 | if (node.updated_ + idle > now) { 194 | break; 195 | } 196 | node.updated_ = now; 197 | lst.splice(lst.end(), lst, lst.begin()); 198 | node.cb_(node.con_); 199 | } 200 | } 201 | } 202 | 203 | IdleId EventsImp::registerIdle(int idle, const TcpConnPtr &con, const TcpCallBack &cb) { 204 | if (!idleEnabled) { 205 | base_->runAfter(1000, [this] { callIdles(); }, 1000); 206 | idleEnabled = true; 207 | } 208 | auto &lst = idleConns_[idle]; 209 | lst.push_back(IdleNode{con, util::timeMilli() / 1000, move(cb)}); 210 | trace("register idle"); 211 | return IdleId(new IdleIdImp(&lst, --lst.end())); 212 | } 213 | 214 | void EventsImp::unregisterIdle(const IdleId &id) { 215 | trace("unregister idle"); 216 | id->lst_->erase(id->iter_); 217 | } 218 | 219 | void EventsImp::updateIdle(const IdleId &id) { 220 | trace("update idle"); 221 | id->iter_->updated_ = util::timeMilli() / 1000; 222 | id->lst_->splice(id->lst_->end(), *id->lst_, id->iter_); 223 | } 224 | 225 | void EventsImp::refreshNearest(const TimerId *tid) { 226 | if (timers_.empty()) { 227 | nextTimeout_ = 1 << 30; 228 | } else { 229 | const TimerId &t = timers_.begin()->first; 230 | nextTimeout_ = t.first - util::timeMilli(); 231 | nextTimeout_ = nextTimeout_ < 0 ? 0 : nextTimeout_; 232 | } 233 | } 234 | 235 | void EventsImp::repeatableTimeout(TimerRepeatable *tr) { 236 | tr->at += tr->interval; 237 | tr->timerid = {tr->at, ++timerSeq_}; 238 | timers_[tr->timerid] = [this, tr] { repeatableTimeout(tr); }; 239 | refreshNearest(&tr->timerid); 240 | tr->cb(); 241 | } 242 | 243 | TimerId EventsImp::runAt(int64_t milli, Task &&task, int64_t interval) { 244 | if (exit_) { 245 | return TimerId(); 246 | } 247 | if (interval) { 248 | TimerId tid{-milli, ++timerSeq_}; 249 | TimerRepeatable &rtr = timerReps_[tid]; 250 | rtr = {milli, interval, {milli, ++timerSeq_}, move(task)}; 251 | TimerRepeatable *tr = &rtr; 252 | timers_[tr->timerid] = [this, tr] { repeatableTimeout(tr); }; 253 | refreshNearest(&tr->timerid); 254 | return tid; 255 | } else { 256 | TimerId tid{milli, ++timerSeq_}; 257 | timers_.insert({tid, move(task)}); 258 | refreshNearest(&tid); 259 | return tid; 260 | } 261 | } 262 | 263 | bool EventsImp::cancel(TimerId timerid) { 264 | if (timerid.first < 0) { 265 | auto p = timerReps_.find(timerid); 266 | auto ptimer = timers_.find(p->second.timerid); 267 | if (ptimer != timers_.end()) { 268 | timers_.erase(ptimer); 269 | } 270 | timerReps_.erase(p); 271 | return true; 272 | } else { 273 | auto p = timers_.find(timerid); 274 | if (p != timers_.end()) { 275 | timers_.erase(p); 276 | return true; 277 | } 278 | return false; 279 | } 280 | } 281 | 282 | void MultiBase::loop() { 283 | int sz = bases_.size(); 284 | vector ths(sz - 1); 285 | for (int i = 0; i < sz - 1; i++) { 286 | thread t([this, i] { bases_[i].loop(); }); 287 | ths[i].swap(t); 288 | } 289 | bases_.back().loop(); 290 | for (int i = 0; i < sz - 1; i++) { 291 | ths[i].join(); 292 | } 293 | } 294 | 295 | Channel::Channel(EventBase *base, int fd, int events) : base_(base), fd_(fd), events_(events) { 296 | fatalif(net::setNonBlock(fd_) < 0, "channel set non block failed"); 297 | static atomic id(0); 298 | id_ = ++id; 299 | poller_ = base_->imp_->poller_; 300 | poller_->addChannel(this); 301 | } 302 | 303 | Channel::~Channel() { 304 | close(); 305 | } 306 | 307 | void Channel::enableRead(bool enable) { 308 | if (enable) { 309 | events_ |= kReadEvent; 310 | } else { 311 | events_ &= ~kReadEvent; 312 | } 313 | poller_->updateChannel(this); 314 | } 315 | 316 | void Channel::enableWrite(bool enable) { 317 | if (enable) { 318 | events_ |= kWriteEvent; 319 | } else { 320 | events_ &= ~kWriteEvent; 321 | } 322 | poller_->updateChannel(this); 323 | } 324 | 325 | void Channel::enableReadWrite(bool readable, bool writable) { 326 | if (readable) { 327 | events_ |= kReadEvent; 328 | } else { 329 | events_ &= ~kReadEvent; 330 | } 331 | if (writable) { 332 | events_ |= kWriteEvent; 333 | } else { 334 | events_ &= ~kWriteEvent; 335 | } 336 | poller_->updateChannel(this); 337 | } 338 | 339 | void Channel::close() { 340 | if (fd_ >= 0) { 341 | trace("close channel %ld fd %d", (long) id_, fd_); 342 | poller_->removeChannel(this); 343 | ::close(fd_); 344 | fd_ = -1; 345 | handleRead(); 346 | } 347 | } 348 | 349 | bool Channel::readEnabled() { 350 | return events_ & kReadEvent; 351 | } 352 | bool Channel::writeEnabled() { 353 | return events_ & kWriteEvent; 354 | } 355 | 356 | void handyUnregisterIdle(EventBase *base, const IdleId &idle) { 357 | base->imp_->unregisterIdle(idle); 358 | } 359 | 360 | void handyUpdateIdle(EventBase *base, const IdleId &idle) { 361 | base->imp_->updateIdle(idle); 362 | } 363 | 364 | TcpConn::TcpConn() 365 | : base_(NULL), channel_(NULL), state_(State::Invalid), destPort_(-1), connectTimeout_(0), reconnectInterval_(-1), connectedTime_(util::timeMilli()) {} 366 | 367 | TcpConn::~TcpConn() { 368 | trace("tcp destroyed %s - %s", local_.toString().c_str(), peer_.toString().c_str()); 369 | delete channel_; 370 | } 371 | 372 | void TcpConn::addIdleCB(int idle, const TcpCallBack &cb) { 373 | if (channel_) { 374 | idleIds_.push_back(getBase()->imp_->registerIdle(idle, shared_from_this(), cb)); 375 | } 376 | } 377 | 378 | void TcpConn::reconnect() { 379 | auto con = shared_from_this(); 380 | getBase()->imp_->reconnectConns_.insert(con); 381 | long long interval = reconnectInterval_ - (util::timeMilli() - connectedTime_); 382 | interval = interval > 0 ? interval : 0; 383 | info("reconnect interval: %d will reconnect after %lld ms", reconnectInterval_, interval); 384 | getBase()->runAfter(interval, [this, con]() { 385 | getBase()->imp_->reconnectConns_.erase(con); 386 | connect(getBase(), destHost_, (unsigned short) destPort_, connectTimeout_, localIp_); 387 | }); 388 | delete channel_; 389 | channel_ = NULL; 390 | } 391 | 392 | } // namespace handy -------------------------------------------------------------------------------- /handy/event_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "handy-imp.h" 3 | #include "poller.h" 4 | 5 | namespace handy { 6 | 7 | typedef std::shared_ptr TcpConnPtr; 8 | typedef std::shared_ptr TcpServerPtr; 9 | typedef std::function TcpCallBack; 10 | typedef std::function MsgCallBack; 11 | 12 | struct EventBases : private noncopyable { 13 | virtual EventBase *allocBase() = 0; 14 | }; 15 | 16 | //事件派发器,可管理定时器,连接,超时连接 17 | struct EventBase : public EventBases { 18 | // taskCapacity指定任务队列的大小,0无限制 19 | EventBase(int taskCapacity = 0); 20 | ~EventBase(); 21 | //处理已到期的事件,waitMs表示若无当前需要处理的任务,需要等待的时间 22 | void loop_once(int waitMs); 23 | //进入事件处理循环 24 | void loop(); 25 | //取消定时任务,若timer已经过期,则忽略 26 | bool cancel(TimerId timerid); 27 | //添加定时任务,interval=0表示一次性任务,否则为重复任务,时间为毫秒 28 | TimerId runAt(int64_t milli, const Task &task, int64_t interval = 0) { return runAt(milli, Task(task), interval); } 29 | TimerId runAt(int64_t milli, Task &&task, int64_t interval = 0); 30 | TimerId runAfter(int64_t milli, const Task &task, int64_t interval = 0) { return runAt(util::timeMilli() + milli, Task(task), interval); } 31 | TimerId runAfter(int64_t milli, Task &&task, int64_t interval = 0) { return runAt(util::timeMilli() + milli, std::move(task), interval); } 32 | 33 | //下列函数为线程安全的 34 | 35 | //退出事件循环 36 | EventBase &exit(); 37 | //是否已退出 38 | bool exited(); 39 | //唤醒事件处理 40 | void wakeup(); 41 | //添加任务 42 | void safeCall(Task &&task); 43 | void safeCall(const Task &task) { safeCall(Task(task)); } 44 | //分配一个事件派发器 45 | virtual EventBase *allocBase() { return this; } 46 | 47 | public: 48 | std::unique_ptr imp_; 49 | }; 50 | 51 | //多线程的事件派发器 52 | struct MultiBase : public EventBases { 53 | MultiBase(int sz) : id_(0), bases_(sz) {} 54 | virtual EventBase *allocBase() { 55 | int c = id_++; 56 | return &bases_[c % bases_.size()]; 57 | } 58 | void loop(); 59 | MultiBase &exit() { 60 | for (auto &b : bases_) { 61 | b.exit(); 62 | } 63 | return *this; 64 | } 65 | 66 | private: 67 | std::atomic id_; 68 | std::vector bases_; 69 | }; 70 | 71 | //通道,封装了可以进行epoll的一个fd 72 | struct Channel : private noncopyable { 73 | // base为事件管理器,fd为通道内部的fd,events为通道关心的事件 74 | Channel(EventBase *base, int fd, int events); 75 | ~Channel(); 76 | EventBase *getBase() { return base_; } 77 | int fd() { return fd_; } 78 | //通道id 79 | int64_t id() { return id_; } 80 | short events() { return events_; } 81 | //关闭通道 82 | void close(); 83 | 84 | //挂接事件处理器 85 | void onRead(const Task &readcb) { readcb_ = readcb; } 86 | void onWrite(const Task &writecb) { writecb_ = writecb; } 87 | void onRead(Task &&readcb) { readcb_ = std::move(readcb); } 88 | void onWrite(Task &&writecb) { writecb_ = std::move(writecb); } 89 | 90 | //启用读写监听 91 | void enableRead(bool enable); 92 | void enableWrite(bool enable); 93 | void enableReadWrite(bool readable, bool writable); 94 | bool readEnabled(); 95 | bool writeEnabled(); 96 | 97 | //处理读写事件 98 | void handleRead() { readcb_(); } 99 | void handleWrite() { writecb_(); } 100 | 101 | protected: 102 | EventBase *base_; 103 | PollerBase *poller_; 104 | int fd_; 105 | short events_; 106 | int64_t id_; 107 | std::function readcb_, writecb_, errorcb_; 108 | }; 109 | 110 | } // namespace handy 111 | -------------------------------------------------------------------------------- /handy/file.cc: -------------------------------------------------------------------------------- 1 | #include "file.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | namespace handy { 9 | 10 | Status file::getContent(const std::string &filename, std::string &cont) { 11 | int fd = open(filename.c_str(), O_RDONLY); 12 | if (fd < 0) { 13 | return Status::ioError("open", filename); 14 | } 15 | ExitCaller ec1([=] { close(fd); }); 16 | char buf[4096]; 17 | for (;;) { 18 | int r = read(fd, buf, sizeof buf); 19 | if (r < 0) { 20 | return Status::ioError("read", filename); 21 | } else if (r == 0) { 22 | break; 23 | } 24 | cont.append(buf, r); 25 | } 26 | return Status(); 27 | } 28 | 29 | Status file::writeContent(const std::string &filename, const std::string &cont) { 30 | int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); 31 | if (fd < 0) { 32 | return Status::ioError("open", filename); 33 | } 34 | ExitCaller ec1([=] { close(fd); }); 35 | int r = write(fd, cont.data(), cont.size()); 36 | if (r < 0) { 37 | return Status::ioError("write", filename); 38 | } 39 | return Status(); 40 | } 41 | 42 | Status file::renameSave(const string &name, const string &tmpName, const string &cont) { 43 | Status s = writeContent(tmpName, cont); 44 | if (s.ok()) { 45 | unlink(name.c_str()); 46 | s = renameFile(tmpName, name); 47 | } 48 | return s; 49 | } 50 | 51 | Status file::getChildren(const std::string &dir, std::vector *result) { 52 | result->clear(); 53 | DIR *d = opendir(dir.c_str()); 54 | if (d == NULL) { 55 | return Status::ioError("opendir", dir); 56 | } 57 | struct dirent *entry; 58 | while ((entry = readdir(d)) != NULL) { 59 | result->push_back(entry->d_name); 60 | } 61 | closedir(d); 62 | return Status(); 63 | } 64 | 65 | Status file::deleteFile(const string &fname) { 66 | if (unlink(fname.c_str()) != 0) { 67 | return Status::ioError("unlink", fname); 68 | } 69 | return Status(); 70 | } 71 | 72 | Status file::createDir(const std::string &name) { 73 | if (mkdir(name.c_str(), 0755) != 0) { 74 | return Status::ioError("mkdir", name); 75 | } 76 | return Status(); 77 | } 78 | 79 | Status file::deleteDir(const std::string &name) { 80 | if (rmdir(name.c_str()) != 0) { 81 | return Status::ioError("rmdir", name); 82 | } 83 | return Status(); 84 | } 85 | 86 | Status file::getFileSize(const std::string &fname, uint64_t *size) { 87 | struct stat sbuf; 88 | if (stat(fname.c_str(), &sbuf) != 0) { 89 | *size = 0; 90 | return Status::ioError("stat", fname); 91 | } else { 92 | *size = sbuf.st_size; 93 | } 94 | return Status(); 95 | } 96 | 97 | Status file::renameFile(const std::string &src, const std::string &target) { 98 | if (rename(src.c_str(), target.c_str()) != 0) { 99 | return Status::ioError("rename", src + " " + target); 100 | } 101 | return Status(); 102 | } 103 | 104 | bool file::fileExists(const std::string &fname) { 105 | return access(fname.c_str(), F_OK) == 0; 106 | } 107 | 108 | } // namespace handy 109 | -------------------------------------------------------------------------------- /handy/file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "status.h" 5 | 6 | namespace handy { 7 | 8 | struct file { 9 | static Status getContent(const std::string &filename, std::string &cont); 10 | static Status writeContent(const std::string &filename, const std::string &cont); 11 | static Status renameSave(const std::string &name, const std::string &tmpName, const std::string &cont); 12 | static Status getChildren(const std::string &dir, std::vector *result); 13 | static Status deleteFile(const std::string &fname); 14 | static Status createDir(const std::string &name); 15 | static Status deleteDir(const std::string &name); 16 | static Status getFileSize(const std::string &fname, uint64_t *size); 17 | static Status renameFile(const std::string &src, const std::string &target); 18 | static bool fileExists(const std::string &fname); 19 | }; 20 | 21 | } // namespace handy 22 | -------------------------------------------------------------------------------- /handy/handy-imp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "codec.h" 7 | #include "logging.h" 8 | #include "net.h" 9 | #include "threads.h" 10 | #include "util.h" 11 | 12 | namespace handy { 13 | struct Channel; 14 | struct TcpConn; 15 | struct TcpServer; 16 | struct IdleIdImp; 17 | struct EventsImp; 18 | struct EventBase; 19 | typedef std::unique_ptr IdleId; 20 | typedef std::pair TimerId; 21 | 22 | struct AutoContext : noncopyable { 23 | void *ctx; 24 | Task ctxDel; 25 | AutoContext() : ctx(0) {} 26 | template 27 | T &context() { 28 | if (ctx == NULL) { 29 | ctx = new T(); 30 | ctxDel = [this] { delete (T *) ctx; }; 31 | } 32 | return *(T *) ctx; 33 | } 34 | ~AutoContext() { 35 | if (ctx) 36 | ctxDel(); 37 | } 38 | }; 39 | 40 | } // namespace handy -------------------------------------------------------------------------------- /handy/handy.h: -------------------------------------------------------------------------------- 1 | #include "conf.h" 2 | #include "daemon.h" 3 | #include "file.h" 4 | #include "http.h" 5 | #include "logging.h" 6 | #include "slice.h" 7 | #include "threads.h" 8 | #include "udp.h" 9 | #include "util.h" 10 | -------------------------------------------------------------------------------- /handy/http.cc: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "file.h" 3 | #include "logging.h" 4 | #include "status.h" 5 | 6 | using namespace std; 7 | 8 | namespace handy { 9 | 10 | void HttpMsg::clear() { 11 | headers.clear(); 12 | version = "HTTP/1.1"; 13 | body.clear(); 14 | body2.clear(); 15 | complete_ = 0; 16 | contentLen_ = 0; 17 | scanned_ = 0; 18 | } 19 | 20 | string HttpMsg::getValueFromMap_(map &m, const string &n) { 21 | auto p = m.find(n); 22 | return p == m.end() ? "" : p->second; 23 | } 24 | 25 | HttpMsg::Result HttpMsg::tryDecode_(Slice buf, bool copyBody, Slice *line1) { 26 | if (complete_) { 27 | return Complete; 28 | } 29 | if (!contentLen_) { 30 | const char *p = buf.begin(); 31 | Slice req; 32 | // scanned at most points to first when empty line 33 | while (buf.size() >= scanned_ + 4) { 34 | if (p[scanned_] == '\r' && memcmp(p + scanned_, "\r\n\r\n", 4) == 0) { 35 | req = Slice(p, p + scanned_); 36 | break; 37 | } 38 | scanned_++; 39 | } 40 | if (req.empty()) { // header not complete 41 | return NotComplete; 42 | } 43 | 44 | *line1 = req.eatLine(); 45 | while (req.size()) { 46 | req.eat(2); 47 | Slice ln = req.eatLine(); 48 | Slice k = ln.eatWord(); 49 | ln.trimSpace(); 50 | if (k.size() && ln.size() && k.back() == ':') { 51 | for (size_t i = 0; i < k.size(); i++) { 52 | ((char *) k.data())[i] = tolower(k[i]); 53 | } 54 | headers[k.sub(0, -1)] = ln; 55 | } else if (k.empty() && ln.empty() && req.empty()) { 56 | break; 57 | } else { 58 | error("bad http line: %.*s %.*s", (int) k.size(), k.data(), (int) ln.size(), ln.data()); 59 | return Error; 60 | } 61 | } 62 | scanned_ += 4; 63 | contentLen_ = atoi(getHeader("content-length").c_str()); 64 | if (buf.size() < contentLen_ + scanned_ && getHeader("Expect").size()) { 65 | return Continue100; 66 | } 67 | } 68 | if (!complete_ && buf.size() >= contentLen_ + scanned_) { 69 | if (copyBody) { 70 | body.assign(buf.data() + scanned_, contentLen_); 71 | } else { 72 | body2 = Slice(buf.data() + scanned_, contentLen_); 73 | } 74 | complete_ = true; 75 | scanned_ += contentLen_; 76 | } 77 | return complete_ ? Complete : NotComplete; 78 | } 79 | 80 | int HttpRequest::encode(Buffer &buf) { 81 | size_t osz = buf.size(); 82 | char conlen[1024], reqln[4096]; 83 | snprintf(reqln, sizeof reqln, "%s %s %s\r\n", method.c_str(), query_uri.c_str(), version.c_str()); 84 | buf.append(reqln); 85 | for (auto &hd : headers) { 86 | buf.append(hd.first).append(": ").append(hd.second).append("\r\n"); 87 | } 88 | buf.append("Connection: Keep-Alive\r\n"); 89 | snprintf(conlen, sizeof conlen, "Content-Length: %lu\r\n", getBody().size()); 90 | buf.append(conlen); 91 | buf.append("\r\n").append(getBody()); 92 | return buf.size() - osz; 93 | } 94 | 95 | HttpMsg::Result HttpRequest::tryDecode(Slice buf, bool copyBody) { 96 | Slice ln1; 97 | Result r = tryDecode_(buf, copyBody, &ln1); 98 | if (ln1.size()) { 99 | method = ln1.eatWord(); 100 | query_uri = ln1.eatWord(); 101 | version = ln1.eatWord(); 102 | if (query_uri.size() == 0 || query_uri[0] != '/') { 103 | error("query uri '%.*s' should begin with /", (int) query_uri.size(), query_uri.data()); 104 | return Error; 105 | } 106 | for (size_t i = 0; i < query_uri.size(); i++) { 107 | if (query_uri[i] == '?') { 108 | uri = Slice(query_uri.data(), i); 109 | Slice qs = Slice(query_uri.data() + i + 1, query_uri.size() - i - 1); 110 | size_t c, kb, ke, vb, ve; 111 | ve = vb = ke = kb = c = 0; 112 | while (c < qs.size()) { 113 | while (c < qs.size() && qs[c] != '=' && qs[c] != '&') 114 | c++; 115 | ke = c; 116 | if (c < qs.size() && qs[c] == '=') 117 | c++; 118 | vb = c; 119 | while (c < qs.size() && qs[c] != '&') 120 | c++; 121 | ve = c; 122 | if (c < qs.size() && qs[c] == '&') 123 | c++; 124 | if (kb != ke) { 125 | args[string(qs.data() + kb, qs.data() + ke)] = string(qs.data() + vb, qs.data() + ve); 126 | } 127 | ve = vb = ke = kb = c; 128 | } 129 | break; 130 | } 131 | if (i == query_uri.size() - 1) { 132 | uri = query_uri; 133 | } 134 | } 135 | } 136 | return r; 137 | } 138 | 139 | int HttpResponse::encode(Buffer &buf) { 140 | size_t osz = buf.size(); 141 | char conlen[1024], statusln[1024]; 142 | snprintf(statusln, sizeof statusln, "%s %d %s\r\n", version.c_str(), status, statusWord.c_str()); 143 | buf.append(statusln); 144 | for (auto &hd : headers) { 145 | buf.append(hd.first).append(": ").append(hd.second).append("\r\n"); 146 | } 147 | buf.append("Connection: Keep-Alive\r\n"); 148 | snprintf(conlen, sizeof conlen, "Content-Length: %lu\r\n", getBody().size()); 149 | buf.append(conlen); 150 | buf.append("\r\n").append(getBody()); 151 | return buf.size() - osz; 152 | } 153 | 154 | HttpMsg::Result HttpResponse::tryDecode(Slice buf, bool copyBody) { 155 | Slice ln1; 156 | Result r = tryDecode_(buf, copyBody, &ln1); 157 | if (ln1.size()) { 158 | version = ln1.eatWord(); 159 | status = atoi(ln1.eatWord().data()); 160 | statusWord = ln1.trimSpace(); 161 | } 162 | return r; 163 | } 164 | 165 | void HttpConnPtr::sendFile(const string &filename) const { 166 | string cont; 167 | Status st = file::getContent(filename, cont); 168 | HttpResponse &resp = getResponse(); 169 | if (st.code() == ENOENT) { 170 | resp.setNotFound(); 171 | } else if (st.code()) { 172 | resp.setStatus(500, st.msg()); 173 | } else { 174 | resp.body2 = cont; 175 | } 176 | sendResponse(); 177 | } 178 | 179 | void HttpConnPtr::onHttpMsg(const HttpCallBack &cb) const { 180 | tcp->onRead([cb](const TcpConnPtr &con) { 181 | HttpConnPtr hcon(con); 182 | hcon.handleRead(cb); 183 | }); 184 | } 185 | 186 | void HttpConnPtr::handleRead(const HttpCallBack &cb) const { 187 | if (!tcp->isClient()) { // server 188 | HttpRequest &req = getRequest(); 189 | HttpMsg::Result r = req.tryDecode(tcp->getInput()); 190 | if (r == HttpMsg::Error) { 191 | tcp->close(); 192 | return; 193 | } 194 | if (r == HttpMsg::Continue100) { 195 | tcp->send("HTTP/1.1 100 Continue\n\r\n"); 196 | } else if (r == HttpMsg::Complete) { 197 | info("http request: %s %s %s", req.method.c_str(), req.query_uri.c_str(), req.version.c_str()); 198 | trace("http request:\n%.*s", (int) tcp->input_.size(), tcp->input_.data()); 199 | cb(*this); 200 | } 201 | } else { 202 | HttpResponse &resp = getResponse(); 203 | HttpMsg::Result r = resp.tryDecode(tcp->getInput()); 204 | if (r == HttpMsg::Error) { 205 | tcp->close(); 206 | return; 207 | } 208 | if (r == HttpMsg::Complete) { 209 | info("http response: %d %s", resp.status, resp.statusWord.c_str()); 210 | trace("http response:\n%.*s", (int) tcp->input_.size(), tcp->input_.data()); 211 | cb(tcp); 212 | } 213 | } 214 | } 215 | 216 | void HttpConnPtr::clearData() const { 217 | if (tcp->isClient()) { 218 | tcp->getInput().consume(getResponse().getByte()); 219 | getResponse().clear(); 220 | } else { 221 | tcp->getInput().consume(getRequest().getByte()); 222 | getRequest().clear(); 223 | } 224 | } 225 | 226 | void HttpConnPtr::logOutput(const char *title) const { 227 | Buffer &o = tcp->getOutput(); 228 | trace("%s:\n%.*s", title, (int) o.size(), o.data()); 229 | } 230 | 231 | HttpServer::HttpServer(EventBases *bases) : TcpServer(bases) { 232 | defcb_ = [](const HttpConnPtr &con) { 233 | HttpResponse &resp = con.getResponse(); 234 | resp.status = 404; 235 | resp.statusWord = "Not Found"; 236 | resp.body = "Not Found"; 237 | con.sendResponse(); 238 | }; 239 | conncb_ = [] { return TcpConnPtr(new TcpConn); }; 240 | onConnCreate([this]() { 241 | HttpConnPtr hcon(conncb_()); 242 | hcon.onHttpMsg([this](const HttpConnPtr &hcon) { 243 | HttpRequest &req = hcon.getRequest(); 244 | auto p = cbs_.find(req.method); 245 | if (p != cbs_.end()) { 246 | auto p2 = p->second.find(req.uri); 247 | if (p2 != p->second.end()) { 248 | p2->second(hcon); 249 | return; 250 | } 251 | } 252 | defcb_(hcon); 253 | }); 254 | return hcon.tcp; 255 | }); 256 | } 257 | 258 | } // namespace handy 259 | -------------------------------------------------------------------------------- /handy/http.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "conn.h" 5 | #include "slice.h" 6 | 7 | namespace handy { 8 | 9 | // base class for HttpRequest and HttpResponse 10 | struct HttpMsg { 11 | enum Result { 12 | Error, 13 | Complete, 14 | NotComplete, 15 | Continue100, 16 | }; 17 | HttpMsg() { HttpMsg::clear(); }; 18 | 19 | //内容添加到buf,返回写入的字节数 20 | virtual int encode(Buffer &buf) = 0; 21 | //尝试从buf中解析,默认复制body内容 22 | virtual Result tryDecode(Slice buf, bool copyBody = true) = 0; 23 | //清空消息相关的字段 24 | virtual void clear(); 25 | 26 | std::map headers; 27 | std::string version, body; 28 | // body可能较大,为了避免数据复制,加入body2 29 | Slice body2; 30 | 31 | std::string getHeader(const std::string &n) { return getValueFromMap_(headers, n); } 32 | Slice getBody() { return body2.size() ? body2 : (Slice) body; } 33 | 34 | //如果tryDecode返回Complete,则返回已解析的字节数 35 | int getByte() { return scanned_; } 36 | 37 | protected: 38 | bool complete_; 39 | size_t contentLen_; 40 | size_t scanned_; 41 | Result tryDecode_(Slice buf, bool copyBody, Slice *line1); 42 | std::string getValueFromMap_(std::map &m, const std::string &n); 43 | }; 44 | 45 | struct HttpRequest : public HttpMsg { 46 | HttpRequest() { clear(); } 47 | std::map args; 48 | std::string method, uri, query_uri; 49 | std::string getArg(const std::string &n) { return getValueFromMap_(args, n); } 50 | 51 | // override 52 | virtual int encode(Buffer &buf); 53 | virtual Result tryDecode(Slice buf, bool copyBody = true); 54 | virtual void clear() { 55 | HttpMsg::clear(); 56 | args.clear(); 57 | method = "GET"; 58 | query_uri = uri = ""; 59 | } 60 | }; 61 | 62 | struct HttpResponse : public HttpMsg { 63 | HttpResponse() { clear(); } 64 | std::string statusWord; 65 | int status; 66 | void setNotFound() { setStatus(404, "Not Found"); } 67 | void setStatus(int st, const std::string &msg = "") { 68 | status = st; 69 | statusWord = msg; 70 | body = msg; 71 | } 72 | 73 | // override 74 | virtual int encode(Buffer &buf); 75 | virtual Result tryDecode(Slice buf, bool copyBody = true); 76 | virtual void clear() { 77 | HttpMsg::clear(); 78 | status = 200; 79 | statusWord = "OK"; 80 | } 81 | }; 82 | 83 | // Http连接本质上是一条Tcp连接,下面的封装主要是加入了HttpRequest,HttpResponse的处理 84 | struct HttpConnPtr { 85 | TcpConnPtr tcp; 86 | HttpConnPtr(const TcpConnPtr &con) : tcp(con) {} 87 | operator TcpConnPtr() const { return tcp; } 88 | TcpConn *operator->() const { return tcp.get(); } 89 | bool operator<(const HttpConnPtr &con) const { return tcp < con.tcp; } 90 | 91 | typedef std::function HttpCallBack; 92 | 93 | HttpRequest &getRequest() const { return tcp->internalCtx_.context().req; } 94 | HttpResponse &getResponse() const { return tcp->internalCtx_.context().resp; } 95 | 96 | void sendRequest() const { sendRequest(getRequest()); } 97 | void sendResponse() const { sendResponse(getResponse()); } 98 | void sendRequest(HttpRequest &req) const { 99 | req.encode(tcp->getOutput()); 100 | logOutput("http req"); 101 | clearData(); 102 | tcp->sendOutput(); 103 | } 104 | void sendResponse(HttpResponse &resp) const { 105 | resp.encode(tcp->getOutput()); 106 | logOutput("http resp"); 107 | clearData(); 108 | tcp->sendOutput(); 109 | } 110 | //文件作为Response 111 | void sendFile(const std::string &filename) const; 112 | void clearData() const; 113 | 114 | void onHttpMsg(const HttpCallBack &cb) const; 115 | 116 | protected: 117 | struct HttpContext { 118 | HttpRequest req; 119 | HttpResponse resp; 120 | }; 121 | void handleRead(const HttpCallBack &cb) const; 122 | void logOutput(const char *title) const; 123 | }; 124 | 125 | typedef HttpConnPtr::HttpCallBack HttpCallBack; 126 | 127 | // http服务器 128 | struct HttpServer : public TcpServer { 129 | HttpServer(EventBases *base); 130 | template 131 | void setConnType() { 132 | conncb_ = [] { return TcpConnPtr(new Conn); }; 133 | } 134 | void onGet(const std::string &uri, const HttpCallBack &cb) { cbs_["GET"][uri] = cb; } 135 | void onRequest(const std::string &method, const std::string &uri, const HttpCallBack &cb) { cbs_[method][uri] = cb; } 136 | void onDefault(const HttpCallBack &cb) { defcb_ = cb; } 137 | 138 | private: 139 | HttpCallBack defcb_; 140 | std::function conncb_; 141 | std::map> cbs_; 142 | }; 143 | 144 | } // namespace handy 145 | -------------------------------------------------------------------------------- /handy/logging.cc: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "port_posix.h" 14 | 15 | using namespace std; 16 | 17 | namespace handy { 18 | 19 | Logger::Logger() : level_(LINFO), lastRotate_(time(NULL)), rotateInterval_(86400) { 20 | tzset(); 21 | fd_ = -1; 22 | realRotate_ = lastRotate_; 23 | } 24 | 25 | Logger::~Logger() { 26 | if (fd_ != -1) { 27 | close(fd_); 28 | } 29 | } 30 | 31 | const char *Logger::levelStrs_[LALL + 1] = { 32 | "FATAL", "ERROR", "UERR", "WARN", "INFO", "DEBUG", "TRACE", "ALL", 33 | }; 34 | 35 | Logger &Logger::getLogger() { 36 | static Logger logger; 37 | return logger; 38 | } 39 | 40 | void Logger::setLogLevel(const string &level) { 41 | LogLevel ilevel = LINFO; 42 | for (size_t i = 0; i < sizeof(levelStrs_) / sizeof(const char *); i++) { 43 | if (strcasecmp(levelStrs_[i], level.c_str()) == 0) { 44 | ilevel = (LogLevel) i; 45 | break; 46 | } 47 | } 48 | setLogLevel(ilevel); 49 | } 50 | 51 | void Logger::setFileName(const string &filename) { 52 | int fd = open(filename.c_str(), O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, DEFFILEMODE); 53 | if (fd < 0) { 54 | fprintf(stderr, "open log file %s failed. msg: %s ignored\n", filename.c_str(), strerror(errno)); 55 | return; 56 | } 57 | filename_ = filename; 58 | if (fd_ == -1) { 59 | fd_ = fd; 60 | } else { 61 | int r = dup2(fd, fd_); 62 | fatalif(r < 0, "dup2 failed"); 63 | close(fd); 64 | } 65 | } 66 | 67 | void Logger::maybeRotate() { 68 | time_t now = time(NULL); 69 | if (filename_.empty() || (now - timezone) / rotateInterval_ == (lastRotate_ - timezone) / rotateInterval_) { 70 | return; 71 | } 72 | lastRotate_ = now; 73 | long old = realRotate_.exchange(now); 74 | //如果realRotate的值是新的,那么返回,否则,获得了旧值,进行rotate 75 | if ((old - timezone) / rotateInterval_ == (lastRotate_ - timezone) / rotateInterval_) { 76 | return; 77 | } 78 | struct tm ntm; 79 | localtime_r(&now, &ntm); 80 | char newname[4096]; 81 | snprintf(newname, sizeof(newname), "%s.%d%02d%02d%02d%02d", filename_.c_str(), ntm.tm_year + 1900, ntm.tm_mon + 1, ntm.tm_mday, ntm.tm_hour, ntm.tm_min); 82 | const char *oldname = filename_.c_str(); 83 | int err = rename(oldname, newname); 84 | if (err != 0) { 85 | fprintf(stderr, "rename logfile %s -> %s failed msg: %s\n", oldname, newname, strerror(errno)); 86 | return; 87 | } 88 | int fd = open(filename_.c_str(), O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, DEFFILEMODE); 89 | if (fd < 0) { 90 | fprintf(stderr, "open log file %s failed. msg: %s ignored\n", newname, strerror(errno)); 91 | return; 92 | } 93 | dup2(fd, fd_); 94 | thread t([=]{ 95 | usleep(200 * 1000); // 睡眠200ms,参考leveldb做法 96 | close(fd); 97 | }); 98 | t.detach(); 99 | } 100 | 101 | static thread_local uint64_t tid; 102 | void Logger::logv(int level, const char *file, int line, const char *func, const char *fmt...) { 103 | if (tid == 0) { 104 | tid = port::gettid(); 105 | } 106 | if (level > level_) { 107 | return; 108 | } 109 | maybeRotate(); 110 | char buffer[4 * 1024]; 111 | char *p = buffer; 112 | char *limit = buffer + sizeof(buffer); 113 | 114 | struct timeval now_tv; 115 | gettimeofday(&now_tv, NULL); 116 | const time_t seconds = now_tv.tv_sec; 117 | struct tm t; 118 | localtime_r(&seconds, &t); 119 | p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %lx %s %s:%d ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, 120 | static_cast(now_tv.tv_usec), (long) tid, levelStrs_[level], file, line); 121 | va_list args; 122 | va_start(args, fmt); 123 | p += vsnprintf(p, limit - p, fmt, args); 124 | va_end(args); 125 | p = std::min(p, limit - 2); 126 | // trim the ending \n 127 | while (*--p == '\n') { 128 | } 129 | *++p = '\n'; 130 | *++p = '\0'; 131 | int fd = fd_ == -1 ? 1 : fd_; 132 | int err = ::write(fd, buffer, p - buffer); 133 | if (err != p - buffer) { 134 | fprintf(stderr, "write log file %s failed. written %d errmsg: %s\n", filename_.c_str(), err, strerror(errno)); 135 | } 136 | if (level <= LERROR) { 137 | syslog(LOG_ERR, "%s", buffer + 27); 138 | } 139 | if (level == LFATAL) { 140 | fprintf(stderr, "%s", buffer); 141 | assert(0); 142 | } 143 | } 144 | 145 | } // namespace handy -------------------------------------------------------------------------------- /handy/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "util.h" 6 | 7 | #ifdef NDEBUG 8 | #define hlog(level, ...) \ 9 | do { \ 10 | if (level <= Logger::getLogger().getLogLevel()) { \ 11 | Logger::getLogger().logv(level, __FILE__, __LINE__, __func__, __VA_ARGS__); \ 12 | } \ 13 | } while (0) 14 | #else 15 | #define hlog(level, ...) \ 16 | do { \ 17 | if (level <= Logger::getLogger().getLogLevel()) { \ 18 | snprintf(0, 0, __VA_ARGS__); \ 19 | Logger::getLogger().logv(level, __FILE__, __LINE__, __func__, __VA_ARGS__); \ 20 | } \ 21 | } while (0) 22 | 23 | #endif 24 | 25 | #define trace(...) hlog(Logger::LTRACE, __VA_ARGS__) 26 | #define debug(...) hlog(Logger::LDEBUG, __VA_ARGS__) 27 | #define info(...) hlog(Logger::LINFO, __VA_ARGS__) 28 | #define warn(...) hlog(Logger::LWARN, __VA_ARGS__) 29 | #define error(...) hlog(Logger::LERROR, __VA_ARGS__) 30 | #define fatal(...) hlog(Logger::LFATAL, __VA_ARGS__) 31 | #define fatalif(b, ...) \ 32 | do { \ 33 | if ((b)) { \ 34 | hlog(Logger::LFATAL, __VA_ARGS__); \ 35 | } \ 36 | } while (0) 37 | #define check(b, ...) \ 38 | do { \ 39 | if ((b)) { \ 40 | hlog(Logger::LFATAL, __VA_ARGS__); \ 41 | } \ 42 | } while (0) 43 | #define exitif(b, ...) \ 44 | do { \ 45 | if ((b)) { \ 46 | hlog(Logger::LERROR, __VA_ARGS__); \ 47 | _exit(1); \ 48 | } \ 49 | } while (0) 50 | 51 | #define setloglevel(l) Logger::getLogger().setLogLevel(l) 52 | #define setlogfile(n) Logger::getLogger().setFileName(n) 53 | 54 | namespace handy { 55 | 56 | struct Logger : private noncopyable { 57 | enum LogLevel { LFATAL = 0, LERROR, LUERR, LWARN, LINFO, LDEBUG, LTRACE, LALL }; 58 | Logger(); 59 | ~Logger(); 60 | void logv(int level, const char *file, int line, const char *func, const char *fmt...); 61 | 62 | void setFileName(const std::string &filename); 63 | void setLogLevel(const std::string &level); 64 | void setLogLevel(LogLevel level) { level_ = std::min(LALL, std::max(LFATAL, level)); } 65 | 66 | LogLevel getLogLevel() { return level_; } 67 | const char *getLogLevelStr() { return levelStrs_[level_]; } 68 | int getFd() { return fd_; } 69 | 70 | void adjustLogLevel(int adjust) { setLogLevel(LogLevel(level_ + adjust)); } 71 | void setRotateInterval(long rotateInterval) { rotateInterval_ = rotateInterval; } 72 | static Logger &getLogger(); 73 | 74 | private: 75 | void maybeRotate(); 76 | static const char *levelStrs_[LALL + 1]; 77 | int fd_; 78 | LogLevel level_; 79 | long lastRotate_; 80 | std::atomic realRotate_; 81 | long rotateInterval_; 82 | std::string filename_; 83 | }; 84 | 85 | } // namespace handy 86 | -------------------------------------------------------------------------------- /handy/net.cc: -------------------------------------------------------------------------------- 1 | #include "net.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "logging.h" 8 | #include "util.h" 9 | 10 | using namespace std; 11 | namespace handy { 12 | 13 | int net::setNonBlock(int fd, bool value) { 14 | int flags = fcntl(fd, F_GETFL, 0); 15 | if (flags < 0) { 16 | return errno; 17 | } 18 | if (value) { 19 | return fcntl(fd, F_SETFL, flags | O_NONBLOCK); 20 | } 21 | return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); 22 | } 23 | 24 | int net::setReuseAddr(int fd, bool value) { 25 | int flag = value; 26 | int len = sizeof flag; 27 | return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len); 28 | } 29 | 30 | int net::setReusePort(int fd, bool value) { 31 | #ifndef SO_REUSEPORT 32 | fatalif(value, "SO_REUSEPORT not supported"); 33 | return 0; 34 | #else 35 | int flag = value; 36 | int len = sizeof flag; 37 | return setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &flag, len); 38 | #endif 39 | } 40 | 41 | int net::setNoDelay(int fd, bool value) { 42 | int flag = value; 43 | int len = sizeof flag; 44 | return setsockopt(fd, SOL_SOCKET, TCP_NODELAY, &flag, len); 45 | } 46 | 47 | Ip4Addr::Ip4Addr(const string &host, unsigned short port) { 48 | memset(&addr_, 0, sizeof addr_); 49 | addr_.sin_family = AF_INET; 50 | addr_.sin_port = htons(port); 51 | if (host.size()) { 52 | addr_.sin_addr = port::getHostByName(host); 53 | } else { 54 | addr_.sin_addr.s_addr = INADDR_ANY; 55 | } 56 | if (addr_.sin_addr.s_addr == INADDR_NONE) { 57 | error("cannot resove %s to ip", host.c_str()); 58 | } 59 | } 60 | 61 | string Ip4Addr::toString() const { 62 | uint32_t uip = addr_.sin_addr.s_addr; 63 | return util::format("%d.%d.%d.%d:%d", (uip >> 0) & 0xff, (uip >> 8) & 0xff, (uip >> 16) & 0xff, (uip >> 24) & 0xff, ntohs(addr_.sin_port)); 64 | } 65 | 66 | string Ip4Addr::ip() const { 67 | uint32_t uip = addr_.sin_addr.s_addr; 68 | return util::format("%d.%d.%d.%d", (uip >> 0) & 0xff, (uip >> 8) & 0xff, (uip >> 16) & 0xff, (uip >> 24) & 0xff); 69 | } 70 | 71 | unsigned short Ip4Addr::port() const { 72 | return (unsigned short)ntohs(addr_.sin_port); 73 | } 74 | 75 | unsigned int Ip4Addr::ipInt() const { 76 | return ntohl(addr_.sin_addr.s_addr); 77 | } 78 | bool Ip4Addr::isIpValid() const { 79 | return addr_.sin_addr.s_addr != INADDR_NONE; 80 | } 81 | 82 | char *Buffer::makeRoom(size_t len) { 83 | if (e_ + len <= cap_) { 84 | } else if (size() + len < cap_ / 2) { 85 | moveHead(); 86 | } else { 87 | expand(len); 88 | } 89 | return end(); 90 | } 91 | 92 | void Buffer::expand(size_t len) { 93 | size_t ncap = std::max(exp_, std::max(2 * cap_, size() + len)); 94 | char *p = new char[ncap]; 95 | std::copy(begin(), end(), p); 96 | e_ -= b_; 97 | b_ = 0; 98 | delete[] buf_; 99 | buf_ = p; 100 | cap_ = ncap; 101 | } 102 | 103 | void Buffer::copyFrom(const Buffer &b) { 104 | memcpy(this, &b, sizeof b); 105 | if (b.buf_) { 106 | buf_ = new char[cap_]; 107 | memcpy(data(), b.begin(), b.size()); 108 | } 109 | } 110 | 111 | Buffer &Buffer::absorb(Buffer &buf) { 112 | if (&buf != this) { 113 | if (size() == 0) { 114 | char b[sizeof buf]; 115 | memcpy(b, this, sizeof b); 116 | memcpy(this, &buf, sizeof b); 117 | memcpy(&buf, b, sizeof b); 118 | std::swap(exp_, buf.exp_); // keep the origin exp_ 119 | } else { 120 | append(buf.begin(), buf.size()); 121 | buf.clear(); 122 | } 123 | } 124 | return *this; 125 | } 126 | 127 | } // namespace handy -------------------------------------------------------------------------------- /handy/net.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "port_posix.h" 7 | #include "slice.h" 8 | 9 | namespace handy { 10 | 11 | struct net { 12 | template 13 | static T hton(T v) { 14 | return port::htobe(v); 15 | } 16 | template 17 | static T ntoh(T v) { 18 | return port::htobe(v); 19 | } 20 | static int setNonBlock(int fd, bool value = true); 21 | static int setReuseAddr(int fd, bool value = true); 22 | static int setReusePort(int fd, bool value = true); 23 | static int setNoDelay(int fd, bool value = true); 24 | }; 25 | 26 | struct Ip4Addr { 27 | Ip4Addr(const std::string &host, unsigned short port); 28 | Ip4Addr(unsigned short port = 0) : Ip4Addr("", port) {} 29 | Ip4Addr(const struct sockaddr_in &addr) : addr_(addr){}; 30 | std::string toString() const; 31 | std::string ip() const; 32 | unsigned short port() const; 33 | unsigned int ipInt() const; 34 | // if you pass a hostname to constructor, then use this to check error 35 | bool isIpValid() const; 36 | struct sockaddr_in &getAddr() { 37 | return addr_; 38 | } 39 | static std::string hostToIp(const std::string &host) { 40 | Ip4Addr addr(host, 0); 41 | return addr.ip(); 42 | } 43 | 44 | private: 45 | struct sockaddr_in addr_; 46 | }; 47 | 48 | struct Buffer { 49 | Buffer() : buf_(NULL), b_(0), e_(0), cap_(0), exp_(512) {} 50 | ~Buffer() { delete[] buf_; } 51 | void clear() { 52 | delete[] buf_; 53 | buf_ = NULL; 54 | cap_ = 0; 55 | b_ = e_ = 0; 56 | } 57 | size_t size() const { return e_ - b_; } 58 | bool empty() const { return e_ == b_; } 59 | char *data() const { return buf_ + b_; } 60 | char *begin() const { return buf_ + b_; } 61 | char *end() const { return buf_ + e_; } 62 | char *makeRoom(size_t len); 63 | void makeRoom() { 64 | if (space() < exp_) 65 | expand(0); 66 | } 67 | size_t space() const { return cap_ - e_; } 68 | void addSize(size_t len) { e_ += len; } 69 | char *allocRoom(size_t len) { 70 | char *p = makeRoom(len); 71 | addSize(len); 72 | return p; 73 | } 74 | Buffer &append(const char *p, size_t len) { 75 | memcpy(allocRoom(len), p, len); 76 | return *this; 77 | } 78 | Buffer &append(Slice slice) { return append(slice.data(), slice.size()); } 79 | Buffer &append(const char *p) { return append(p, strlen(p)); } 80 | template 81 | Buffer &appendValue(const T &v) { 82 | append((const char *) &v, sizeof v); 83 | return *this; 84 | } 85 | Buffer &consume(size_t len) { 86 | b_ += len; 87 | if (size() == 0) 88 | clear(); 89 | return *this; 90 | } 91 | Buffer &absorb(Buffer &buf); 92 | void setSuggestSize(size_t sz) { exp_ = sz; } 93 | Buffer(const Buffer &b) { copyFrom(b); } 94 | Buffer &operator=(const Buffer &b) { 95 | if (this == &b) 96 | return *this; 97 | delete[] buf_; 98 | buf_ = NULL; 99 | copyFrom(b); 100 | return *this; 101 | } 102 | operator Slice() { return Slice(data(), size()); } 103 | 104 | private: 105 | char *buf_; 106 | size_t b_, e_, cap_, exp_; 107 | void moveHead() { 108 | std::copy(begin(), end(), buf_); 109 | e_ -= b_; 110 | b_ = 0; 111 | } 112 | void expand(size_t len); 113 | void copyFrom(const Buffer &b); 114 | }; 115 | 116 | } // namespace handy 117 | -------------------------------------------------------------------------------- /handy/poller.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "event_base.h" 3 | #include "logging.h" 4 | #include "util.h" 5 | #include "poller.h" 6 | 7 | #ifdef OS_LINUX 8 | #include 9 | #elif defined(OS_MACOSX) 10 | #include 11 | #else 12 | #error "platform unsupported" 13 | #endif 14 | 15 | namespace handy { 16 | 17 | #ifdef OS_LINUX 18 | 19 | struct PollerEpoll : public PollerBase { 20 | int fd_; 21 | std::set liveChannels_; 22 | // for epoll selected active events 23 | struct epoll_event activeEvs_[kMaxEvents]; 24 | PollerEpoll(); 25 | ~PollerEpoll(); 26 | void addChannel(Channel *ch) override; 27 | void removeChannel(Channel *ch) override; 28 | void updateChannel(Channel *ch) override; 29 | void loop_once(int waitMs) override; 30 | }; 31 | 32 | PollerBase *createPoller() { 33 | return new PollerEpoll(); 34 | } 35 | 36 | PollerEpoll::PollerEpoll() { 37 | fd_ = epoll_create1(EPOLL_CLOEXEC); 38 | fatalif(fd_ < 0, "epoll_create error %d %s", errno, strerror(errno)); 39 | info("poller epoll %d created", fd_); 40 | } 41 | 42 | PollerEpoll::~PollerEpoll() { 43 | info("destroying poller %d", fd_); 44 | while (liveChannels_.size()) { 45 | (*liveChannels_.begin())->close(); 46 | } 47 | ::close(fd_); 48 | info("poller %d destroyed", fd_); 49 | } 50 | 51 | void PollerEpoll::addChannel(Channel *ch) { 52 | struct epoll_event ev; 53 | memset(&ev, 0, sizeof(ev)); 54 | ev.events = ch->events(); 55 | ev.data.ptr = ch; 56 | trace("adding channel %lld fd %d events %d epoll %d", (long long) ch->id(), ch->fd(), ev.events, fd_); 57 | int r = epoll_ctl(fd_, EPOLL_CTL_ADD, ch->fd(), &ev); 58 | fatalif(r, "epoll_ctl add failed %d %s", errno, strerror(errno)); 59 | liveChannels_.insert(ch); 60 | } 61 | 62 | void PollerEpoll::updateChannel(Channel *ch) { 63 | struct epoll_event ev; 64 | memset(&ev, 0, sizeof(ev)); 65 | ev.events = ch->events(); 66 | ev.data.ptr = ch; 67 | trace("modifying channel %lld fd %d events read %d write %d epoll %d", (long long) ch->id(), ch->fd(), ev.events & POLLIN, ev.events & POLLOUT, fd_); 68 | int r = epoll_ctl(fd_, EPOLL_CTL_MOD, ch->fd(), &ev); 69 | fatalif(r, "epoll_ctl mod failed %d %s", errno, strerror(errno)); 70 | } 71 | 72 | void PollerEpoll::removeChannel(Channel *ch) { 73 | trace("deleting channel %lld fd %d epoll %d", (long long) ch->id(), ch->fd(), fd_); 74 | liveChannels_.erase(ch); 75 | for (int i = lastActive_; i >= 0; i--) { 76 | if (ch == activeEvs_[i].data.ptr) { 77 | activeEvs_[i].data.ptr = NULL; 78 | break; 79 | } 80 | } 81 | } 82 | 83 | void PollerEpoll::loop_once(int waitMs) { 84 | int64_t ticks = util::timeMilli(); 85 | lastActive_ = epoll_wait(fd_, activeEvs_, kMaxEvents, waitMs); 86 | int64_t used = util::timeMilli() - ticks; 87 | trace("epoll wait %d return %d errno %d used %lld millsecond", waitMs, lastActive_, errno, (long long) used); 88 | fatalif(lastActive_ == -1 && errno != EINTR, "epoll return error %d %s", errno, strerror(errno)); 89 | while (--lastActive_ >= 0) { 90 | int i = lastActive_; 91 | Channel *ch = (Channel *) activeEvs_[i].data.ptr; 92 | int events = activeEvs_[i].events; 93 | if (ch) { 94 | if (events & (kReadEvent | POLLERR)) { 95 | trace("channel %lld fd %d handle read", (long long) ch->id(), ch->fd()); 96 | ch->handleRead(); 97 | } else if (events & kWriteEvent) { 98 | trace("channel %lld fd %d handle write", (long long) ch->id(), ch->fd()); 99 | ch->handleWrite(); 100 | } else { 101 | fatal("unexpected poller events"); 102 | } 103 | } 104 | } 105 | } 106 | 107 | #elif defined(OS_MACOSX) 108 | 109 | struct PollerKqueue : public PollerBase { 110 | int fd_; 111 | std::set liveChannels_; 112 | // for epoll selected active events 113 | struct kevent activeEvs_[kMaxEvents]; 114 | PollerKqueue(); 115 | ~PollerKqueue(); 116 | void addChannel(Channel *ch) override; 117 | void removeChannel(Channel *ch) override; 118 | void updateChannel(Channel *ch) override; 119 | void loop_once(int waitMs) override; 120 | }; 121 | 122 | PollerBase *createPoller() { 123 | return new PollerKqueue(); 124 | } 125 | 126 | PollerKqueue::PollerKqueue() { 127 | fd_ = kqueue(); 128 | fatalif(fd_ < 0, "kqueue error %d %s", errno, strerror(errno)); 129 | info("poller kqueue %d created", fd_); 130 | } 131 | 132 | PollerKqueue::~PollerKqueue() { 133 | info("destroying poller %d", fd_); 134 | while (liveChannels_.size()) { 135 | (*liveChannels_.begin())->close(); 136 | } 137 | ::close(fd_); 138 | info("poller %d destroyed", fd_); 139 | } 140 | 141 | void PollerKqueue::addChannel(Channel *ch) { 142 | struct timespec now; 143 | now.tv_nsec = 0; 144 | now.tv_sec = 0; 145 | struct kevent ev[2]; 146 | int n = 0; 147 | if (ch->readEnabled()) { 148 | EV_SET(&ev[n++], ch->fd(), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, ch); 149 | } 150 | if (ch->writeEnabled()) { 151 | EV_SET(&ev[n++], ch->fd(), EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, ch); 152 | } 153 | trace("adding channel %lld fd %d events read %d write %d epoll %d", (long long) ch->id(), ch->fd(), ch->events() & POLLIN, ch->events() & POLLOUT, fd_); 154 | int r = kevent(fd_, ev, n, NULL, 0, &now); 155 | fatalif(r, "kevent add failed %d %s", errno, strerror(errno)); 156 | liveChannels_.insert(ch); 157 | } 158 | 159 | void PollerKqueue::updateChannel(Channel *ch) { 160 | struct timespec now; 161 | now.tv_nsec = 0; 162 | now.tv_sec = 0; 163 | struct kevent ev[2]; 164 | int n = 0; 165 | if (ch->readEnabled()) { 166 | EV_SET(&ev[n++], ch->fd(), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, ch); 167 | } else { 168 | EV_SET(&ev[n++], ch->fd(), EVFILT_READ, EV_DELETE, 0, 0, ch); 169 | } 170 | if (ch->writeEnabled()) { 171 | EV_SET(&ev[n++], ch->fd(), EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, ch); 172 | } else { 173 | EV_SET(&ev[n++], ch->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, ch); 174 | } 175 | trace("modifying channel %lld fd %d events read %d write %d epoll %d", (long long) ch->id(), ch->fd(), ch->events() & POLLIN, ch->events() & POLLOUT, fd_); 176 | int r = kevent(fd_, ev, n, NULL, 0, &now); 177 | fatalif(r, "kevent mod failed %d %s", errno, strerror(errno)); 178 | } 179 | 180 | void PollerKqueue::removeChannel(Channel *ch) { 181 | trace("deleting channel %lld fd %d epoll %d", (long long) ch->id(), ch->fd(), fd_); 182 | liveChannels_.erase(ch); 183 | // remove channel if in ready stat 184 | for (int i = lastActive_; i >= 0; i--) { 185 | if (ch == activeEvs_[i].udata) { 186 | activeEvs_[i].udata = NULL; 187 | break; 188 | } 189 | } 190 | } 191 | 192 | void PollerKqueue::loop_once(int waitMs) { 193 | struct timespec timeout; 194 | timeout.tv_sec = waitMs / 1000; 195 | timeout.tv_nsec = (waitMs % 1000) * 1000 * 1000; 196 | long ticks = util::timeMilli(); 197 | lastActive_ = kevent(fd_, NULL, 0, activeEvs_, kMaxEvents, &timeout); 198 | trace("kevent wait %d return %d errno %d used %lld millsecond", waitMs, lastActive_, errno, util::timeMilli() - ticks); 199 | fatalif(lastActive_ == -1 && errno != EINTR, "kevent return error %d %s", errno, strerror(errno)); 200 | while (--lastActive_ >= 0) { 201 | int i = lastActive_; 202 | Channel *ch = (Channel *) activeEvs_[i].udata; 203 | struct kevent &ke = activeEvs_[i]; 204 | if (ch) { 205 | // only handle write if read and write are enabled 206 | if (!(ke.flags & EV_EOF) && ch->writeEnabled()) { 207 | trace("channel %lld fd %d handle write", (long long) ch->id(), ch->fd()); 208 | ch->handleWrite(); 209 | } else if ((ke.flags & EV_EOF) || ch->readEnabled()) { 210 | trace("channel %lld fd %d handle read", (long long) ch->id(), ch->fd()); 211 | ch->handleRead(); 212 | } else { 213 | fatal("unexpected epoll events %d", ch->events()); 214 | } 215 | } 216 | } 217 | } 218 | 219 | #endif 220 | } // namespace handy -------------------------------------------------------------------------------- /handy/poller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace handy { 11 | 12 | const int kMaxEvents = 2000; 13 | const int kReadEvent = POLLIN; 14 | const int kWriteEvent = POLLOUT; 15 | 16 | struct PollerBase : private noncopyable { 17 | int64_t id_; 18 | int lastActive_; 19 | PollerBase() : lastActive_(-1) { 20 | static std::atomic id(0); 21 | id_ = ++id; 22 | } 23 | virtual void addChannel(Channel *ch) = 0; 24 | virtual void removeChannel(Channel *ch) = 0; 25 | virtual void updateChannel(Channel *ch) = 0; 26 | virtual void loop_once(int waitMs) = 0; 27 | virtual ~PollerBase(){}; 28 | }; 29 | 30 | PollerBase *createPoller(); 31 | 32 | } // namespace handy -------------------------------------------------------------------------------- /handy/port_posix.cc: -------------------------------------------------------------------------------- 1 | #include "port_posix.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace handy { 8 | namespace port { 9 | #ifdef OS_LINUX 10 | struct in_addr getHostByName(const std::string &host) { 11 | struct in_addr addr; 12 | char buf[1024]; 13 | struct hostent hent; 14 | struct hostent *he = NULL; 15 | int herrno = 0; 16 | memset(&hent, 0, sizeof hent); 17 | int r = gethostbyname_r(host.c_str(), &hent, buf, sizeof buf, &he, &herrno); 18 | if (r == 0 && he && he->h_addrtype == AF_INET) { 19 | addr = *reinterpret_cast(he->h_addr); 20 | } else { 21 | addr.s_addr = INADDR_NONE; 22 | } 23 | return addr; 24 | } 25 | uint64_t gettid() { 26 | return syscall(SYS_gettid); 27 | } 28 | #elif defined(OS_MACOSX) 29 | struct in_addr getHostByName(const std::string &host) { 30 | struct in_addr addr; 31 | struct hostent *he = gethostbyname(host.c_str()); 32 | if (he && he->h_addrtype == AF_INET) { 33 | addr = *reinterpret_cast(he->h_addr); 34 | } else { 35 | addr.s_addr = INADDR_NONE; 36 | } 37 | return addr; 38 | } 39 | uint64_t gettid() { 40 | pthread_t tid = pthread_self(); 41 | uint64_t uid = 0; 42 | memcpy(&uid, &tid, std::min(sizeof(tid), sizeof(uid))); 43 | return uid; 44 | } 45 | #endif 46 | 47 | } // namespace port 48 | } // namespace handy -------------------------------------------------------------------------------- /handy/port_posix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | namespace handy { 5 | namespace port { 6 | static const int kLittleEndian = LITTLE_ENDIAN; 7 | inline uint16_t htobe(uint16_t v) { 8 | if (!kLittleEndian) { 9 | return v; 10 | } 11 | unsigned char *pv = (unsigned char *) &v; 12 | return uint16_t(pv[0]) << 8 | uint16_t(pv[1]); 13 | } 14 | inline uint32_t htobe(uint32_t v) { 15 | if (!kLittleEndian) { 16 | return v; 17 | } 18 | unsigned char *pv = (unsigned char *) &v; 19 | return uint32_t(pv[0]) << 24 | uint32_t(pv[1]) << 16 | uint32_t(pv[2]) << 8 | uint32_t(pv[3]); 20 | } 21 | inline uint64_t htobe(uint64_t v) { 22 | if (!kLittleEndian) { 23 | return v; 24 | } 25 | unsigned char *pv = (unsigned char *) &v; 26 | return uint64_t(pv[0]) << 56 | uint64_t(pv[1]) << 48 | uint64_t(pv[2]) << 40 | uint64_t(pv[3]) << 32 | uint64_t(pv[4]) << 24 | uint64_t(pv[5]) << 16 | 27 | uint64_t(pv[6]) << 8 | uint64_t(pv[7]); 28 | } 29 | inline int16_t htobe(int16_t v) { 30 | return (int16_t) htobe((uint16_t) v); 31 | } 32 | inline int32_t htobe(int32_t v) { 33 | return (int32_t) htobe((uint32_t) v); 34 | } 35 | inline int64_t htobe(int64_t v) { 36 | return (int64_t) htobe((uint64_t) v); 37 | } 38 | struct in_addr getHostByName(const std::string &host); 39 | uint64_t gettid(); 40 | } // namespace port 41 | } // namespace handy -------------------------------------------------------------------------------- /handy/slice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace handy { 7 | 8 | class Slice { 9 | public: 10 | Slice() : pb_("") { pe_ = pb_; } 11 | Slice(const char *b, const char *e) : pb_(b), pe_(e) {} 12 | Slice(const char *d, size_t n) : pb_(d), pe_(d + n) {} 13 | Slice(const std::string &s) : pb_(s.data()), pe_(s.data() + s.size()) {} 14 | Slice(const char *s) : pb_(s), pe_(s + strlen(s)) {} 15 | 16 | const char *data() const { return pb_; } 17 | const char *begin() const { return pb_; } 18 | const char *end() const { return pe_; } 19 | char front() { return *pb_; } 20 | char back() { return pe_[-1]; } 21 | size_t size() const { return pe_ - pb_; } 22 | void resize(size_t sz) { pe_ = pb_ + sz; } 23 | inline bool empty() const { return pe_ == pb_; } 24 | void clear() { pe_ = pb_ = ""; } 25 | 26 | // return the eated data 27 | Slice eatWord(); 28 | Slice eatLine(); 29 | Slice eat(int sz) { 30 | Slice s(pb_, sz); 31 | pb_ += sz; 32 | return s; 33 | } 34 | Slice sub(int boff, int eoff = 0) const { 35 | Slice s(*this); 36 | s.pb_ += boff; 37 | s.pe_ += eoff; 38 | return s; 39 | } 40 | Slice &trimSpace(); 41 | 42 | inline char operator[](size_t n) const { return pb_[n]; } 43 | 44 | std::string toString() const { return std::string(pb_, pe_); } 45 | // Three-way comparison. Returns value: 46 | int compare(const Slice &b) const; 47 | 48 | // Return true if "x" is a prefix of "*this" 49 | bool starts_with(const Slice &x) const { return (size() >= x.size() && memcmp(pb_, x.pb_, x.size()) == 0); } 50 | 51 | bool end_with(const Slice &x) const { return (size() >= x.size() && memcmp(pe_ - x.size(), x.pb_, x.size()) == 0); } 52 | operator std::string() const { return std::string(pb_, pe_); } 53 | std::vector split(char ch) const; 54 | 55 | private: 56 | const char *pb_; 57 | const char *pe_; 58 | }; 59 | 60 | inline Slice Slice::eatWord() { 61 | const char *b = pb_; 62 | while (b < pe_ && isspace(*b)) { 63 | b++; 64 | } 65 | const char *e = b; 66 | while (e < pe_ && !isspace(*e)) { 67 | e++; 68 | } 69 | pb_ = e; 70 | return Slice(b, e - b); 71 | } 72 | 73 | inline Slice Slice::eatLine() { 74 | const char *p = pb_; 75 | while (pb_ < pe_ && *pb_ != '\n' && *pb_ != '\r') { 76 | pb_++; 77 | } 78 | return Slice(p, pb_ - p); 79 | } 80 | 81 | inline Slice &Slice::trimSpace() { 82 | while (pb_ < pe_ && isspace(*pb_)) 83 | pb_++; 84 | while (pb_ < pe_ && isspace(pe_[-1])) 85 | pe_--; 86 | return *this; 87 | } 88 | 89 | inline bool operator<(const Slice &x, const Slice &y) { 90 | return x.compare(y) < 0; 91 | } 92 | 93 | inline bool operator==(const Slice &x, const Slice &y) { 94 | return ((x.size() == y.size()) && (memcmp(x.data(), y.data(), x.size()) == 0)); 95 | } 96 | 97 | inline bool operator!=(const Slice &x, const Slice &y) { 98 | return !(x == y); 99 | } 100 | 101 | inline int Slice::compare(const Slice &b) const { 102 | size_t sz = size(), bsz = b.size(); 103 | const int min_len = (sz < bsz) ? sz : bsz; 104 | int r = memcmp(pb_, b.pb_, min_len); 105 | if (r == 0) { 106 | if (sz < bsz) 107 | r = -1; 108 | else if (sz > bsz) 109 | r = +1; 110 | } 111 | return r; 112 | } 113 | 114 | inline std::vector Slice::split(char ch) const { 115 | std::vector r; 116 | const char *pb = pb_; 117 | for (const char *p = pb_; p < pe_; p++) { 118 | if (*p == ch) { 119 | r.push_back(Slice(pb, p)); 120 | pb = p + 1; 121 | } 122 | } 123 | if (pe_ != pb_) 124 | r.push_back(Slice(pb, pe_)); 125 | return r; 126 | } 127 | 128 | } // namespace handy 129 | -------------------------------------------------------------------------------- /handy/stat-svr.cc: -------------------------------------------------------------------------------- 1 | #include "stat-svr.h" 2 | #include "file.h" 3 | #include "http.h" 4 | #include "logging.h" 5 | 6 | using namespace std; 7 | 8 | namespace handy { 9 | 10 | static string query_link(const string &path) { 11 | return util::format("%s", path.c_str(), path.c_str()); 12 | } 13 | 14 | static string page_link(const string &path) { 15 | return util::format("%s", path.c_str(), path.c_str()); 16 | } 17 | 18 | StatServer::StatServer(EventBase *base) : server_(base) { 19 | server_.onDefault([this](const HttpConnPtr &con) { 20 | HttpRequest &req = con.getRequest(); 21 | HttpResponse &resp = con.getResponse(); 22 | Buffer buf; 23 | string query = req.getArg("stat"); 24 | if (query.empty()) { 25 | query.assign(req.uri.data() + 1, req.uri.size() - 1); 26 | } 27 | if (query.size()) { 28 | auto p = allcbs_.find(query); 29 | if (p != allcbs_.end()) { 30 | p->second(req, resp); 31 | } else { 32 | resp.setNotFound(); 33 | } 34 | } 35 | if (req.uri == "/") { 36 | buf.append("refresh
\n"); 37 | buf.append("\n"); 38 | buf.append("\n"); 39 | for (auto &stat : statcbs_) { 40 | HttpResponse r; 41 | req.uri = stat.first; 42 | stat.second.second(req, r); 43 | buf.append("\n"); 50 | } 51 | buf.append("
StatDescValue
") 44 | .append(page_link(stat.first)) 45 | .append("") 46 | .append(stat.second.first) 47 | .append("") 48 | .append(r.body) 49 | .append("
\n
\n\n").append("\n"); 52 | for (auto &stat : pagecbs_) { 53 | buf.append("\n"); 54 | } 55 | buf.append("
PageDesc
").append(page_link(stat.first)).append("").append(stat.second.first).append("
\n
\n\n").append("\n"); 56 | for (auto &stat : cmdcbs_) { 57 | buf.append("\n"); 58 | } 59 | buf.append("
CmdDesc
").append(query_link(stat.first)).append("").append(stat.second.first).append("
\n"); 60 | if (resp.body.size()) { 61 | buf.append(util::format("
SubQuery %s:
%s", query.c_str(), resp.body.c_str())); 62 | } 63 | resp.body = Slice(buf.data(), buf.size()); 64 | } 65 | info("response is: %d \n%.*s", resp.status, (int) resp.body.size(), resp.body.data()); 66 | con.sendResponse(); 67 | }); 68 | } 69 | 70 | void StatServer::onRequest(StatType type, const string &key, const string &desc, const StatCallBack &cb) { 71 | if (type == STATE) { 72 | statcbs_[key] = {desc, cb}; 73 | } else if (type == PAGE) { 74 | pagecbs_[key] = {desc, cb}; 75 | } else if (type == CMD) { 76 | cmdcbs_[key] = {desc, cb}; 77 | } else { 78 | error("unknow state type: %d", type); 79 | return; 80 | } 81 | allcbs_[key] = cb; 82 | } 83 | 84 | void StatServer::onRequest(StatType type, const string &key, const string &desc, const InfoCallBack &cb) { 85 | onRequest(type, key, desc, [cb](const HttpRequest &, HttpResponse &r) { r.body = cb(); }); 86 | } 87 | 88 | void StatServer::onPageFile(const string &page, const string &desc, const string &file) { 89 | return onRequest(PAGE, page, desc, [file](const HttpRequest &req, HttpResponse &resp) { 90 | Status st = file::getContent(file, resp.body); 91 | if (!st.ok()) { 92 | error("get file %s failed %s", file.c_str(), st.toString().c_str()); 93 | resp.setNotFound(); 94 | } else { 95 | resp.headers["Content-Type"] = "text/plain; charset=utf-8"; 96 | } 97 | }); 98 | } 99 | 100 | } // namespace handy -------------------------------------------------------------------------------- /handy/stat-svr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "event_base.h" 6 | #include "http.h" 7 | #include "slice.h" 8 | 9 | namespace handy { 10 | 11 | typedef std::function StatCallBack; 12 | typedef std::function InfoCallBack; 13 | typedef std::function IntCallBack; 14 | 15 | struct StatServer : private noncopyable { 16 | enum StatType { 17 | STATE, 18 | PAGE, 19 | CMD, 20 | }; 21 | StatServer(EventBase *base); 22 | int bind(const std::string &host, unsigned short port) { return server_.bind(host, port); } 23 | typedef std::string string; 24 | void onRequest(StatType type, const string &key, const string &desc, const StatCallBack &cb); 25 | void onRequest(StatType type, const string &key, const string &desc, const InfoCallBack &cb); 26 | //用于展示一个简单的state 27 | void onState(const string &state, const string &desc, const InfoCallBack &cb) { onRequest(STATE, state, desc, cb); } 28 | void onState(const string &state, const string &desc, const IntCallBack &cb) { 29 | onRequest(STATE, state, desc, [cb] { return util::format("%ld", cb()); }); 30 | } 31 | //用于展示一个页面 32 | void onPage(const string &page, const string &desc, const InfoCallBack &cb) { onRequest(PAGE, page, desc, cb); } 33 | void onPageFile(const string &page, const string &desc, const string &file); 34 | //用于发送一个命令 35 | void onCmd(const string &cmd, const string &desc, const InfoCallBack &cb) { onRequest(CMD, cmd, desc, cb); } 36 | void onCmd(const string &cmd, const string &desc, const IntCallBack &cb) { 37 | onRequest(CMD, cmd, desc, [cb] { return util::format("%ld", cb()); }); 38 | } 39 | 40 | private: 41 | HttpServer server_; 42 | typedef std::pair DescState; 43 | std::map statcbs_, pagecbs_, cmdcbs_; 44 | std::map allcbs_; 45 | }; 46 | 47 | } // namespace handy 48 | -------------------------------------------------------------------------------- /handy/status.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "util.h" 7 | 8 | namespace handy { 9 | 10 | inline const char *errstr() { 11 | return strerror(errno); 12 | } 13 | 14 | struct Status { 15 | // Create a success status. 16 | Status() : state_(NULL) {} 17 | Status(int code, const char *msg); 18 | Status(int code, const std::string &msg) : Status(code, msg.c_str()) {} 19 | ~Status() { delete[] state_; } 20 | 21 | // Copy the specified status. 22 | Status(const Status &s) { state_ = copyState(s.state_); } 23 | void operator=(const Status &s) { 24 | delete[] state_; 25 | state_ = copyState(s.state_); 26 | } 27 | Status(Status &&s) { 28 | state_ = s.state_; 29 | s.state_ = NULL; 30 | } 31 | void operator=(Status &&s) { 32 | delete[] state_; 33 | state_ = s.state_; 34 | s.state_ = NULL; 35 | } 36 | 37 | static Status fromSystem() { return Status(errno, strerror(errno)); } 38 | static Status fromSystem(int err) { return Status(err, strerror(err)); } 39 | static Status fromFormat(int code, const char *fmt, ...); 40 | static Status ioError(const std::string &op, const std::string &name) { return Status::fromFormat(errno, "%s %s %s", op.c_str(), name.c_str(), errstr()); } 41 | 42 | int code() { return state_ ? *(int32_t *) (state_ + 4) : 0; } 43 | const char *msg() { return state_ ? state_ + 8 : ""; } 44 | bool ok() { return code() == 0; } 45 | std::string toString() { return util::format("%d %s", code(), msg()); } 46 | 47 | private: 48 | // state_[0..3] == length of message 49 | // state_[4..7] == code 50 | // state_[8..] == message 51 | const char *state_; 52 | const char *copyState(const char *state); 53 | }; 54 | 55 | inline const char *Status::copyState(const char *state) { 56 | if (state == NULL) { 57 | return state; 58 | } 59 | uint32_t size = *(uint32_t *) state; 60 | char *res = new char[size]; 61 | memcpy(res, state, size); 62 | return res; 63 | } 64 | 65 | inline Status::Status(int code, const char *msg) { 66 | uint32_t sz = strlen(msg) + 8; 67 | char *p = new char[sz]; 68 | state_ = p; 69 | *(uint32_t *) p = sz; 70 | *(int32_t *) (p + 4) = code; 71 | memcpy(p + 8, msg, sz - 8); 72 | } 73 | 74 | inline Status Status::fromFormat(int code, const char *fmt, ...) { 75 | va_list ap; 76 | va_start(ap, fmt); 77 | uint32_t size = 8 + vsnprintf(NULL, 0, fmt, ap) + 1; 78 | va_end(ap); 79 | Status r; 80 | r.state_ = new char[size]; 81 | *(uint32_t *) r.state_ = size; 82 | *(int32_t *) (r.state_ + 4) = code; 83 | va_start(ap, fmt); 84 | vsnprintf((char *) r.state_ + 8, size - 8, fmt, ap); 85 | va_end(ap); 86 | return r; 87 | } 88 | 89 | } // namespace handy 90 | -------------------------------------------------------------------------------- /handy/threads.cc: -------------------------------------------------------------------------------- 1 | #include "threads.h" 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | namespace handy { 8 | 9 | template class SafeQueue; 10 | 11 | ThreadPool::ThreadPool(int threads, int maxWaiting, bool start) : tasks_(maxWaiting), threads_(threads) { 12 | if (start) { 13 | this->start(); 14 | } 15 | } 16 | 17 | ThreadPool::~ThreadPool() { 18 | assert(tasks_.exited()); 19 | if (tasks_.size()) { 20 | fprintf(stderr, "%lu tasks not processed when thread pool exited\n", tasks_.size()); 21 | } 22 | } 23 | 24 | void ThreadPool::start() { 25 | for (auto &th : threads_) { 26 | thread t([this] { 27 | while (!tasks_.exited()) { 28 | Task task; 29 | if (tasks_.pop_wait(&task)) { 30 | task(); 31 | } 32 | } 33 | }); 34 | th.swap(t); 35 | } 36 | } 37 | 38 | void ThreadPool::join() { 39 | for (auto &t : threads_) { 40 | t.join(); 41 | } 42 | } 43 | 44 | bool ThreadPool::addTask(Task &&task) { 45 | return tasks_.push(move(task)); 46 | } 47 | 48 | } // namespace handy 49 | -------------------------------------------------------------------------------- /handy/threads.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "util.h" 11 | 12 | namespace handy { 13 | 14 | template 15 | struct SafeQueue : private std::mutex, private noncopyable { 16 | static const int wait_infinite = std::numeric_limits::max(); 17 | // 0 不限制队列中的任务数 18 | SafeQueue(size_t capacity = 0) : capacity_(capacity), exit_(false) {} 19 | //队列满则返回false 20 | bool push(T &&v); 21 | //超时则返回T() 22 | T pop_wait(int waitMs = wait_infinite); 23 | //超时返回false 24 | bool pop_wait(T *v, int waitMs = wait_infinite); 25 | 26 | size_t size(); 27 | void exit(); 28 | bool exited() { return exit_; } 29 | 30 | private: 31 | std::list items_; 32 | std::condition_variable ready_; 33 | size_t capacity_; 34 | std::atomic exit_; 35 | void wait_ready(std::unique_lock &lk, int waitMs); 36 | }; 37 | 38 | typedef std::function Task; 39 | extern template class SafeQueue; 40 | 41 | struct ThreadPool : private noncopyable { 42 | //创建线程池 43 | ThreadPool(int threads, int taskCapacity = 0, bool start = true); 44 | ~ThreadPool(); 45 | void start(); 46 | ThreadPool &exit() { 47 | tasks_.exit(); 48 | return *this; 49 | } 50 | void join(); 51 | 52 | //队列满返回false 53 | bool addTask(Task &&task); 54 | bool addTask(Task &task) { return addTask(Task(task)); } 55 | size_t taskSize() { return tasks_.size(); } 56 | 57 | private: 58 | SafeQueue tasks_; 59 | std::vector threads_; 60 | }; 61 | 62 | //以下为实现代码,不必关心 63 | template 64 | size_t SafeQueue::size() { 65 | std::lock_guard lk(*this); 66 | return items_.size(); 67 | } 68 | 69 | template 70 | void SafeQueue::exit() { 71 | exit_ = true; 72 | std::lock_guard lk(*this); 73 | ready_.notify_all(); 74 | } 75 | 76 | template 77 | bool SafeQueue::push(T &&v) { 78 | std::lock_guard lk(*this); 79 | if (exit_ || (capacity_ && items_.size() >= capacity_)) { 80 | return false; 81 | } 82 | items_.push_back(std::move(v)); 83 | ready_.notify_one(); 84 | return true; 85 | } 86 | template 87 | void SafeQueue::wait_ready(std::unique_lock &lk, int waitMs) { 88 | if (exit_ || !items_.empty()) { 89 | return; 90 | } 91 | if (waitMs == wait_infinite) { 92 | ready_.wait(lk, [this] { return exit_ || !items_.empty(); }); 93 | } else if (waitMs > 0) { 94 | auto tp = std::chrono::steady_clock::now() + std::chrono::milliseconds(waitMs); 95 | while (ready_.wait_until(lk, tp) != std::cv_status::timeout && items_.empty() && !exit_) { 96 | } 97 | } 98 | } 99 | 100 | template 101 | bool SafeQueue::pop_wait(T *v, int waitMs) { 102 | std::unique_lock lk(*this); 103 | wait_ready(lk, waitMs); 104 | if (items_.empty()) { 105 | return false; 106 | } 107 | *v = std::move(items_.front()); 108 | items_.pop_front(); 109 | return true; 110 | } 111 | 112 | template 113 | T SafeQueue::pop_wait(int waitMs) { 114 | std::unique_lock lk(*this); 115 | wait_ready(lk, waitMs); 116 | if (items_.empty()) { 117 | return T(); 118 | } 119 | T r = std::move(items_.front()); 120 | items_.pop_front(); 121 | return r; 122 | } 123 | 124 | } // namespace handy 125 | -------------------------------------------------------------------------------- /handy/udp.cc: -------------------------------------------------------------------------------- 1 | #include "udp.h" 2 | #include 3 | 4 | using namespace std; 5 | 6 | namespace handy { 7 | 8 | UdpServer::UdpServer(EventBases *bases) : base_(bases->allocBase()), bases_(bases), channel_(NULL) {} 9 | 10 | int UdpServer::bind(const std::string &host, unsigned short port, bool reusePort) { 11 | addr_ = Ip4Addr(host, port); 12 | int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 | int r = net::setReuseAddr(fd); 14 | fatalif(r, "set socket reuse option failed"); 15 | r = net::setReusePort(fd, reusePort); 16 | fatalif(r, "set socket reuse port option failed"); 17 | r = util::addFdFlag(fd, FD_CLOEXEC); 18 | fatalif(r, "addFdFlag FD_CLOEXEC failed"); 19 | r = ::bind(fd, (struct sockaddr *) &addr_.getAddr(), sizeof(struct sockaddr)); 20 | if (r) { 21 | close(fd); 22 | error("bind to %s failed %d %s", addr_.toString().c_str(), errno, strerror(errno)); 23 | return errno; 24 | } 25 | net::setNonBlock(fd); 26 | info("udp fd %d bind to %s", fd, addr_.toString().c_str()); 27 | channel_ = new Channel(base_, fd, kReadEvent); 28 | channel_->onRead([this] { 29 | Buffer buf; 30 | struct sockaddr_in raddr; 31 | socklen_t rsz = sizeof(raddr); 32 | if (!channel_ || channel_->fd() < 0) { 33 | return; 34 | } 35 | int fd = channel_->fd(); 36 | ssize_t rn = recvfrom(fd, buf.makeRoom(kUdpPacketSize), kUdpPacketSize, 0, (sockaddr *) &raddr, &rsz); 37 | if (rn < 0) { 38 | error("udp %d recv failed: %d %s", fd, errno, strerror(errno)); 39 | return; 40 | } 41 | buf.addSize(rn); 42 | trace("udp %d recv %ld bytes from %s", fd, rn, Ip4Addr(raddr).toString().data()); 43 | this->msgcb_(shared_from_this(), buf, raddr); 44 | }); 45 | return 0; 46 | } 47 | 48 | UdpServerPtr UdpServer::startServer(EventBases *bases, const std::string &host, unsigned short port, bool reusePort) { 49 | UdpServerPtr p(new UdpServer(bases)); 50 | int r = p->bind(host, port, reusePort); 51 | if (r) { 52 | error("bind to %s:%d failed %d %s", host.c_str(), port, errno, strerror(errno)); 53 | } 54 | return r == 0 ? p : NULL; 55 | } 56 | 57 | void UdpServer::sendTo(const char *buf, size_t len, Ip4Addr addr) { 58 | if (!channel_ || channel_->fd() < 0) { 59 | warn("udp sending %lu bytes to %s after channel closed", len, addr.toString().data()); 60 | return; 61 | } 62 | int fd = channel_->fd(); 63 | int wn = ::sendto(fd, buf, len, 0, (sockaddr *) &addr.getAddr(), sizeof(sockaddr)); 64 | if (wn < 0) { 65 | error("udp %d sendto %s error: %d %s", fd, addr.toString().c_str(), errno, strerror(errno)); 66 | return; 67 | } 68 | trace("udp %d sendto %s %d bytes", fd, addr.toString().c_str(), wn); 69 | } 70 | 71 | UdpConnPtr UdpConn::createConnection(EventBase *base, const string &host, unsigned short port) { 72 | Ip4Addr addr(host, port); 73 | int fd = socket(AF_INET, SOCK_DGRAM, 0); 74 | fatalif(fd < 0, "socket failed %d %s", errno, strerror(errno)); 75 | net::setNonBlock(fd); 76 | int t = util::addFdFlag(fd, FD_CLOEXEC); 77 | fatalif(t, "addFdFlag FD_CLOEXEC failed %d %s", t, strerror(t)); 78 | int r = ::connect(fd, (sockaddr *) &addr.getAddr(), sizeof(sockaddr_in)); 79 | if (r != 0 && errno != EINPROGRESS) { 80 | error("connect to %s error %d %s", addr.toString().c_str(), errno, strerror(errno)); 81 | return NULL; 82 | } 83 | trace("udp fd %d connecting to %s ok", fd, addr.toString().data()); 84 | UdpConnPtr con(new UdpConn); 85 | con->destHost_ = host; 86 | con->destPort_ = port; 87 | con->peer_ = addr; 88 | con->base_ = base; 89 | Channel *ch = new Channel(base, fd, kReadEvent); 90 | con->channel_ = ch; 91 | ch->onRead([con] { 92 | if (!con->channel_ || con->channel_->fd() < 0) { 93 | return con->close(); 94 | } 95 | Buffer input; 96 | int fd = con->channel_->fd(); 97 | int rn = ::read(fd, input.makeRoom(kUdpPacketSize), kUdpPacketSize); 98 | if (rn < 0) { 99 | error("udp read from %d error %d %s", fd, errno, strerror(errno)); 100 | return; 101 | } 102 | trace("udp %d read %d bytes", fd, rn); 103 | input.addSize(rn); 104 | con->cb_(con, input); 105 | }); 106 | return con; 107 | } 108 | 109 | void UdpConn::close() { 110 | if (!channel_) 111 | return; 112 | auto p = channel_; 113 | channel_ = NULL; 114 | base_->safeCall([p]() { delete p; }); 115 | } 116 | 117 | void UdpConn::send(const char *buf, size_t len) { 118 | if (!channel_ || channel_->fd() < 0) { 119 | warn("udp sending %lu bytes to %s after channel closed", len, peer_.toString().data()); 120 | return; 121 | } 122 | int fd = channel_->fd(); 123 | int wn = ::write(fd, buf, len); 124 | if (wn < 0) { 125 | error("udp %d write error %d %s", fd, errno, strerror(errno)); 126 | return; 127 | } 128 | trace("udp %d write %d bytes", fd, wn); 129 | } 130 | 131 | HSHAUPtr HSHAU::startServer(EventBase *base, const std::string &host, unsigned short port, int threads) { 132 | HSHAUPtr p = HSHAUPtr(new HSHAU(threads)); 133 | p->server_ = UdpServer::startServer(base, host, port); 134 | return p->server_ ? p : NULL; 135 | } 136 | 137 | void HSHAU::onMsg(const RetMsgUdpCallBack &cb) { 138 | server_->onMsg([this, cb](const UdpServerPtr &con, Buffer buf, Ip4Addr addr) { 139 | std::string input(buf.data(), buf.size()); 140 | threadPool_.addTask([=] { 141 | std::string output = cb(con, input, addr); 142 | server_->getBase()->safeCall([=] { 143 | if (output.size()) 144 | con->sendTo(output, addr); 145 | }); 146 | }); 147 | }); 148 | } 149 | 150 | } // namespace handy -------------------------------------------------------------------------------- /handy/udp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "event_base.h" 4 | 5 | namespace handy { 6 | 7 | struct UdpServer; 8 | struct UdpConn; 9 | typedef std::shared_ptr UdpConnPtr; 10 | typedef std::shared_ptr UdpServerPtr; 11 | typedef std::function UdpCallBack; 12 | typedef std::function UdpSvrCallBack; 13 | const int kUdpPacketSize = 4096; 14 | // Udp服务器 15 | struct UdpServer : public std::enable_shared_from_this, private noncopyable { 16 | UdpServer(EventBases *bases); 17 | // return 0 on sucess, errno on error 18 | int bind(const std::string &host, unsigned short port, bool reusePort = false); 19 | static UdpServerPtr startServer(EventBases *bases, const std::string &host, unsigned short port, bool reusePort = false); 20 | ~UdpServer() { delete channel_; } 21 | Ip4Addr getAddr() { return addr_; } 22 | EventBase *getBase() { return base_; } 23 | void sendTo(Buffer msg, Ip4Addr addr) { 24 | sendTo(msg.data(), msg.size(), addr); 25 | msg.clear(); 26 | } 27 | void sendTo(const char *buf, size_t len, Ip4Addr addr); 28 | void sendTo(const std::string &s, Ip4Addr addr) { sendTo(s.data(), s.size(), addr); } 29 | void sendTo(const char *s, Ip4Addr addr) { sendTo(s, strlen(s), addr); } 30 | 31 | //消息的处理 32 | void onMsg(const UdpSvrCallBack &cb) { msgcb_ = cb; } 33 | 34 | private: 35 | EventBase *base_; 36 | EventBases *bases_; 37 | Ip4Addr addr_; 38 | Channel *channel_; 39 | UdpSvrCallBack msgcb_; 40 | }; 41 | 42 | // Udp连接,使用引用计数 43 | struct UdpConn : public std::enable_shared_from_this, private noncopyable { 44 | // Udp构造函数,实际可用的连接应当通过createConnection创建 45 | UdpConn(){}; 46 | virtual ~UdpConn() { close(); }; 47 | static UdpConnPtr createConnection(EventBase *base, const std::string &host, unsigned short port); 48 | // automatically managed context. allocated when first used, deleted when destruct 49 | template 50 | T &context() { 51 | return ctx_.context(); 52 | } 53 | 54 | EventBase *getBase() { return base_; } 55 | Channel *getChannel() { return channel_; } 56 | 57 | //发送数据 58 | void send(Buffer msg) { 59 | send(msg.data(), msg.size()); 60 | msg.clear(); 61 | } 62 | void send(const char *buf, size_t len); 63 | void send(const std::string &s) { send(s.data(), s.size()); } 64 | void send(const char *s) { send(s, strlen(s)); } 65 | void onMsg(const UdpCallBack &cb) { cb_ = cb; } 66 | void close(); 67 | //远程地址的字符串 68 | std::string str() { return peer_.toString(); } 69 | 70 | public: 71 | void handleRead(const UdpConnPtr &); 72 | EventBase *base_; 73 | Channel *channel_; 74 | Ip4Addr local_, peer_; 75 | AutoContext ctx_; 76 | std::string destHost_; 77 | int destPort_; 78 | UdpCallBack cb_; 79 | }; 80 | 81 | typedef std::function RetMsgUdpCallBack; 82 | //半同步半异步服务器 83 | struct HSHAU; 84 | typedef std::shared_ptr HSHAUPtr; 85 | struct HSHAU { 86 | static HSHAUPtr startServer(EventBase *base, const std::string &host, unsigned short port, int threads); 87 | HSHAU(int threads) : threadPool_(threads) {} 88 | void exit() { 89 | threadPool_.exit(); 90 | threadPool_.join(); 91 | } 92 | void onMsg(const RetMsgUdpCallBack &cb); 93 | UdpServerPtr server_; 94 | ThreadPool threadPool_; 95 | }; 96 | 97 | } // namespace handy -------------------------------------------------------------------------------- /handy/util.cc: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | namespace handy { 11 | 12 | string util::format(const char *fmt, ...) { 13 | char buffer[500]; 14 | unique_ptr release1; 15 | char *base; 16 | for (int iter = 0; iter < 2; iter++) { 17 | int bufsize; 18 | if (iter == 0) { 19 | bufsize = sizeof(buffer); 20 | base = buffer; 21 | } else { 22 | bufsize = 30000; 23 | base = new char[bufsize]; 24 | release1.reset(base); 25 | } 26 | char *p = base; 27 | char *limit = base + bufsize; 28 | if (p < limit) { 29 | va_list ap; 30 | va_start(ap, fmt); 31 | p += vsnprintf(p, limit - p, fmt, ap); 32 | va_end(ap); 33 | } 34 | // Truncate to available space if necessary 35 | if (p >= limit) { 36 | if (iter == 0) { 37 | continue; // Try again with larger buffer 38 | } else { 39 | p = limit - 1; 40 | *p = '\0'; 41 | } 42 | } 43 | break; 44 | } 45 | return base; 46 | } 47 | 48 | int64_t util::timeMicro() { 49 | chrono::time_point p = chrono::system_clock::now(); 50 | return chrono::duration_cast(p.time_since_epoch()).count(); 51 | } 52 | int64_t util::steadyMicro() { 53 | chrono::time_point p = chrono::steady_clock::now(); 54 | return chrono::duration_cast(p.time_since_epoch()).count(); 55 | } 56 | 57 | std::string util::readableTime(time_t t) { 58 | struct tm tm1; 59 | localtime_r(&t, &tm1); 60 | return format("%04d-%02d-%02d %02d:%02d:%02d", tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday, tm1.tm_hour, tm1.tm_min, tm1.tm_sec); 61 | } 62 | 63 | int util::addFdFlag(int fd, int flag) { 64 | int ret = fcntl(fd, F_GETFD); 65 | return fcntl(fd, F_SETFD, ret | flag); 66 | } 67 | 68 | } // namespace handy 69 | -------------------------------------------------------------------------------- /handy/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace handy { 10 | 11 | struct noncopyable { 12 | protected: 13 | noncopyable() = default; 14 | virtual ~noncopyable() = default; 15 | 16 | noncopyable(const noncopyable &) = delete; 17 | noncopyable &operator=(const noncopyable &) = delete; 18 | }; 19 | 20 | struct util { 21 | static std::string format(const char *fmt, ...); 22 | static int64_t timeMicro(); 23 | static int64_t timeMilli() { return timeMicro() / 1000; } 24 | static int64_t steadyMicro(); 25 | static int64_t steadyMilli() { return steadyMicro() / 1000; } 26 | static std::string readableTime(time_t t); 27 | static int64_t atoi(const char *b, const char *e) { return strtol(b, (char **) &e, 10); } 28 | static int64_t atoi2(const char *b, const char *e) { 29 | char* ne = nullptr; 30 | int64_t v = strtol(b, &ne, 10); 31 | return ne == e ? v : -1; 32 | } 33 | static int64_t atoi(const char *b) { return atoi(b, b + strlen(b)); } 34 | static int addFdFlag(int fd, int flag); 35 | }; 36 | 37 | struct ExitCaller : private noncopyable { 38 | ~ExitCaller() { functor_(); } 39 | ExitCaller(std::function &&functor) : functor_(std::move(functor)) {} 40 | 41 | private: 42 | std::function functor_; 43 | }; 44 | 45 | } // namespace handy 46 | -------------------------------------------------------------------------------- /protobuf/Makefile: -------------------------------------------------------------------------------- 1 | # OPT ?= -O2 -DNDEBUG 2 | # (B) Debug mode, w/ full line-level debugging symbols 3 | OPT ?= -g2 4 | # (C) Profiling mode: opt, but w/debugging symbols 5 | # OPT ?= -O2 -g2 -DNDEBUG 6 | include ../config.mk 7 | 8 | CFLAGS += -I.. $(PLATFORM_CCFLAGS) $(OPT) 9 | CXXFLAGS += -I.. $(PLATFORM_CXXFLAGS) $(OPT) 10 | 11 | LDFLAGS += $(PLATFORM_LDFLAGS) 12 | LIBS += $(PLATFORM_LIBS) 13 | 14 | 15 | SOURCES=proto_msg.cc test.cc 16 | 17 | OBJS = $(SOURCES:.cc=.o) 18 | 19 | PROGRAMS = test 20 | 21 | default: $(PROGRAMS) 22 | 23 | $(PROGRAMS): $(OBJS) 24 | $(CXX) -o $@ $^ msg.pb.cc $(LDFLAGS) `pkg-config --libs protobuf` -lhandy 25 | 26 | $(OBJS): middle 27 | 28 | middle: msg.proto 29 | protoc --cpp_out=. $< 30 | @touch $@ 31 | 32 | clean: 33 | -rm -f $(PROGRAMS) middle 34 | -rm -f *.o *.pb.* 35 | 36 | .cc.o: 37 | $(CXX) $(CXXFLAGS) -c $< -o $@ `pkg-config --cflags protobuf` 38 | 39 | .c.o: 40 | $(CC) $(CFLAGS) -c $< -o $@ 41 | 42 | -------------------------------------------------------------------------------- /protobuf/msg.proto: -------------------------------------------------------------------------------- 1 | package handy; 2 | 3 | message Query { 4 | required string name = 1; 5 | required int32 id = 2; 6 | } 7 | -------------------------------------------------------------------------------- /protobuf/proto_msg.cc: -------------------------------------------------------------------------------- 1 | #include "proto_msg.h" 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | using namespace google::protobuf; 7 | 8 | namespace handy { 9 | void ProtoMsgDispatcher::handle(TcpConnPtr con, Message *msg) { 10 | auto p = protocbs_.find(msg->GetDescriptor()); 11 | if (p != protocbs_.end()) { 12 | p->second(con, msg); 13 | } else { 14 | error("unknown message type %s", msg->GetTypeName().c_str()); 15 | } 16 | } 17 | 18 | // 4 byte total msg len, including this 4 bytes 19 | // 4 byte name len 20 | // name string not null ended 21 | // protobuf data 22 | Message *ProtoMsgCodec::decode(Buffer &s) { 23 | if (s.size() < 8) { 24 | error("buffer is too small size: %lu", s.size()); 25 | return NULL; 26 | } 27 | char *p = s.data(); 28 | uint32_t msglen = *(uint32_t *) p; 29 | uint32_t namelen = *(uint32_t *) (p + 4); 30 | if (s.size() < msglen || s.size() < 4 + namelen) { 31 | error("buf format error size %lu msglen %d namelen %d", s.size(), msglen, namelen); 32 | return NULL; 33 | } 34 | string typeName(p + 8, namelen); 35 | Message *msg = NULL; 36 | const Descriptor *des = DescriptorPool::generated_pool()->FindMessageTypeByName(typeName); 37 | if (des) { 38 | const Message *proto = MessageFactory::generated_factory()->GetPrototype(des); 39 | if (proto) { 40 | msg = proto->New(); 41 | } 42 | } 43 | if (msg == NULL) { 44 | error("cannot create Message for %s", typeName.c_str()); 45 | return NULL; 46 | } 47 | int r = msg->ParseFromArray(p + 8 + namelen, msglen - 8 - namelen); 48 | if (!r) { 49 | error("bad msg for protobuf"); 50 | delete msg; 51 | return NULL; 52 | } 53 | s.consume(msglen); 54 | return msg; 55 | } 56 | 57 | void ProtoMsgCodec::encode(Message *msg, Buffer &buf) { 58 | size_t offset = buf.size(); 59 | buf.appendValue((uint32_t) 0); 60 | const string &typeName = msg->GetDescriptor()->full_name(); 61 | buf.appendValue((uint32_t) typeName.size()); 62 | buf.append(typeName.data(), typeName.size()); 63 | msg->SerializeToArray(buf.allocRoom(msg->ByteSize()), msg->ByteSize()); 64 | *(uint32_t *) (buf.begin() + offset) = buf.size() - offset; 65 | } 66 | 67 | } // namespace handy 68 | -------------------------------------------------------------------------------- /protobuf/proto_msg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace handy { 7 | 8 | typedef ::google::protobuf::Message Message; 9 | typedef ::google::protobuf::Descriptor Descriptor; 10 | typedef std::function ProtoCallBack; 11 | 12 | struct ProtoMsgCodec { 13 | static void encode(Message *msg, Buffer &buf); 14 | static Message *decode(Buffer &buf); 15 | static bool msgComplete(Buffer &buf); 16 | }; 17 | 18 | struct ProtoMsgDispatcher { 19 | void handle(TcpConnPtr con, Message *msg); 20 | template 21 | void onMsg(std::function cb) { 22 | protocbs_[M::descriptor()] = [cb](TcpConnPtr con, Message *msg) { cb(con, dynamic_cast(msg)); }; 23 | } 24 | 25 | private: 26 | std::map protocbs_; 27 | }; 28 | 29 | inline bool ProtoMsgCodec::msgComplete(Buffer &buf) { 30 | return buf.size() >= 4 && buf.size() >= *(uint32_t *) buf.begin(); 31 | } 32 | 33 | } // namespace handy 34 | -------------------------------------------------------------------------------- /protobuf/test.cc: -------------------------------------------------------------------------------- 1 | #include "msg.pb.h" 2 | #include "proto_msg.h" 3 | 4 | using namespace std; 5 | using namespace handy; 6 | 7 | void handleQuery(TcpConnPtr con, Query *query) { 8 | info("query recved name %s id %d", query->name().c_str(), query->id()); 9 | delete query; 10 | con->getBase()->exit(); 11 | } 12 | 13 | void testencode() { 14 | Query q; 15 | q.set_name("hello"); 16 | q.set_id(123); 17 | Buffer b; 18 | ProtoMsgCodec dis; 19 | dis.encode(&q, b); 20 | Query *p = dynamic_cast(dis.decode(b)); 21 | info("p name %s id %d", p->name().c_str(), p->id()); 22 | delete p; 23 | } 24 | int main() { 25 | Logger::getLogger().setLogLevel(Logger::LDEBUG); 26 | testencode(); 27 | 28 | EventBase base; 29 | TcpServer echo(&base); 30 | int r = echo.bind("", 2099); 31 | exitif(r, "bind failed %d %s", errno, strerror(errno)); 32 | ProtoMsgDispatcher dispatch; 33 | echo.onConnRead([&](TcpConnPtr con) { 34 | if (ProtoMsgCodec::msgComplete(con->getInput())) { 35 | Message *msg = ProtoMsgCodec::decode(con->getInput()); 36 | if (msg) { 37 | dispatch.handle(con, msg); 38 | } else { 39 | error("bad msg from connection data"); 40 | con->close(); 41 | } 42 | } 43 | }); 44 | 45 | dispatch.onMsg(handleQuery); 46 | TcpConnPtr cmd = TcpConn::createConnection(&base, "localhost", 2099); 47 | cmd->onState([](const TcpConnPtr &con) { 48 | if (con->getState() == TcpConn::Connected) { 49 | Query query; 50 | query.set_name("hello", 5); 51 | query.set_id(123); 52 | ProtoMsgCodec::encode(&query, con->getOutput()); 53 | con->sendOutput(); 54 | } 55 | }); 56 | base.loop(); 57 | } 58 | -------------------------------------------------------------------------------- /raw-examples/Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = $(shell ls *.cc) 2 | 3 | PROGRAMS = $(SOURCES:.cc=) 4 | 5 | default: 6 | @echo make \ [ program can be \"$(PROGRAMS)\" ] 7 | 8 | clean: 9 | -rm -f $(PROGRAMS) 10 | -rm -f *.o 11 | 12 | .cc.o: 13 | $(CXX) $(CXXFLAGS) -c $< -o $@ 14 | 15 | .c.o: 16 | $(CC) $(CFLAGS) -c $< -o $@ 17 | 18 | .cc: 19 | $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) 20 | 21 | -------------------------------------------------------------------------------- /raw-examples/epoll-et.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 编译:c++ -o epoll-et epoll-et.cc 3 | * 运行: ./epoll-et 4 | * 测试:curl -v localhost 5 | * 客户端发送GET请求后,服务器返回1M的数据,会触发EPOLLOUT,从epoll-et输出的日志看,EPOLLOUT事件得到了正确的处理 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | using namespace std; 21 | 22 | bool output_log = true; 23 | 24 | #define exit_if(r, ...) \ 25 | if (r) { \ 26 | printf(__VA_ARGS__); \ 27 | printf("%s:%d error no: %d error msg %s\n", __FILE__, __LINE__, errno, strerror(errno)); \ 28 | exit(1); \ 29 | } 30 | 31 | void setNonBlock(int fd) { 32 | int flags = fcntl(fd, F_GETFL, 0); 33 | exit_if(flags < 0, "fcntl failed"); 34 | int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); 35 | exit_if(r < 0, "fcntl failed"); 36 | } 37 | 38 | void updateEvents(int efd, int fd, int events, int op) { 39 | struct epoll_event ev; 40 | memset(&ev, 0, sizeof(ev)); 41 | ev.events = events; 42 | ev.data.fd = fd; 43 | printf("%s fd %d events read %d write %d\n", op == EPOLL_CTL_MOD ? "mod" : "add", fd, ev.events & EPOLLIN, ev.events & EPOLLOUT); 44 | int r = epoll_ctl(efd, op, fd, &ev); 45 | exit_if(r, "epoll_ctl failed"); 46 | } 47 | 48 | void handleAccept(int efd, int fd) { 49 | struct sockaddr_in raddr; 50 | socklen_t rsz = sizeof(raddr); 51 | int cfd = accept(fd, (struct sockaddr *) &raddr, &rsz); 52 | exit_if(cfd < 0, "accept failed"); 53 | sockaddr_in peer, local; 54 | socklen_t alen = sizeof(peer); 55 | int r = getpeername(cfd, (sockaddr *) &peer, &alen); 56 | exit_if(r < 0, "getpeername failed"); 57 | printf("accept a connection from %s\n", inet_ntoa(raddr.sin_addr)); 58 | setNonBlock(cfd); 59 | updateEvents(efd, cfd, EPOLLIN | EPOLLOUT | EPOLLET, EPOLL_CTL_ADD); 60 | } 61 | struct Con { 62 | string readed; 63 | size_t written; 64 | Con() : written(0) {} 65 | }; 66 | map cons; 67 | 68 | string httpRes; 69 | void sendRes(int fd) { 70 | Con &con = cons[fd]; 71 | if (!con.readed.length()) 72 | return; 73 | size_t left = httpRes.length() - con.written; 74 | int wd = 0; 75 | while ((wd = ::write(fd, httpRes.data() + con.written, left)) > 0) { 76 | con.written += wd; 77 | left -= wd; 78 | if (output_log) 79 | printf("write %d bytes left: %lu\n", wd, left); 80 | }; 81 | if (left == 0) { 82 | // close(fd); // 测试中使用了keepalive,因此不关闭连接。连接会在read事件中关闭 83 | cons.erase(fd); 84 | return; 85 | } 86 | if (wd < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 87 | return; 88 | if (wd <= 0) { 89 | printf("write error for %d: %d %s\n", fd, errno, strerror(errno)); 90 | close(fd); 91 | cons.erase(fd); 92 | } 93 | } 94 | 95 | void handleRead(int efd, int fd) { 96 | char buf[4096]; 97 | int n = 0; 98 | while ((n = ::read(fd, buf, sizeof buf)) > 0) { 99 | if (output_log) 100 | printf("read %d bytes\n", n); 101 | string &readed = cons[fd].readed; 102 | readed.append(buf, n); 103 | if (readed.length() > 4) { 104 | if (readed.substr(readed.length() - 2, 2) == "\n\n" || readed.substr(readed.length() - 4, 4) == "\r\n\r\n") { 105 | //当读取到一个完整的http请求,测试发送响应 106 | sendRes(fd); 107 | } 108 | } 109 | } 110 | if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 111 | return; 112 | //实际应用中,n<0应当检查各类错误,如EINTR 113 | if (n < 0) { 114 | printf("read %d error: %d %s\n", fd, errno, strerror(errno)); 115 | } 116 | close(fd); 117 | cons.erase(fd); 118 | } 119 | 120 | void handleWrite(int efd, int fd) { 121 | sendRes(fd); 122 | } 123 | 124 | void loop_once(int efd, int lfd, int waitms) { 125 | const int kMaxEvents = 20; 126 | struct epoll_event activeEvs[100]; 127 | int n = epoll_wait(efd, activeEvs, kMaxEvents, waitms); 128 | if (output_log) 129 | printf("epoll_wait return %d\n", n); 130 | for (int i = 0; i < n; i++) { 131 | int fd = activeEvs[i].data.fd; 132 | int events = activeEvs[i].events; 133 | if (events & (EPOLLIN | EPOLLERR)) { 134 | if (fd == lfd) { 135 | handleAccept(efd, fd); 136 | } else { 137 | handleRead(efd, fd); 138 | } 139 | } else if (events & EPOLLOUT) { // 请注意,例子为了保持简洁性,没有很好的处理极端情况,例如EPOLLIN和EPOLLOUT同时到达的情况 140 | if (output_log) 141 | printf("handling epollout\n"); 142 | handleWrite(efd, fd); 143 | } else { 144 | exit_if(1, "unknown event"); 145 | } 146 | } 147 | } 148 | 149 | int main(int argc, const char *argv[]) { 150 | if (argc > 1) { 151 | output_log = false; 152 | } 153 | ::signal(SIGPIPE, SIG_IGN); 154 | httpRes = "HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 1048576\r\n\r\n123456"; 155 | for (int i = 0; i < 1048570; i++) { 156 | httpRes += '\0'; 157 | } 158 | unsigned short port = 80; 159 | int epollfd = epoll_create(1); 160 | exit_if(epollfd < 0, "epoll_create failed"); 161 | int listenfd = socket(AF_INET, SOCK_STREAM, 0); 162 | exit_if(listenfd < 0, "socket failed"); 163 | struct sockaddr_in addr; 164 | memset(&addr, 0, sizeof addr); 165 | addr.sin_family = AF_INET; 166 | addr.sin_port = htons(port); 167 | addr.sin_addr.s_addr = INADDR_ANY; 168 | int r = ::bind(listenfd, (struct sockaddr *) &addr, sizeof(struct sockaddr)); 169 | exit_if(r, "bind to 0.0.0.0:%d failed %d %s", port, errno, strerror(errno)); 170 | r = listen(listenfd, 20); 171 | exit_if(r, "listen failed %d %s", errno, strerror(errno)); 172 | printf("fd %d listening at %d\n", listenfd, port); 173 | setNonBlock(listenfd); 174 | updateEvents(epollfd, listenfd, EPOLLIN, EPOLL_CTL_ADD); 175 | for (;;) { //实际应用应当注册信号处理函数,退出时清理资源 176 | loop_once(epollfd, listenfd, 10000); 177 | } 178 | return 0; 179 | } 180 | -------------------------------------------------------------------------------- /raw-examples/epoll.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 编译:c++ -o epoll epoll.cc 3 | * 运行: ./epoll 4 | * 测试:curl -v localhost 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | using namespace std; 20 | 21 | bool output_log = true; 22 | 23 | #define exit_if(r, ...) \ 24 | if (r) { \ 25 | printf(__VA_ARGS__); \ 26 | printf("%s:%d error no: %d error msg %s\n", __FILE__, __LINE__, errno, strerror(errno)); \ 27 | exit(1); \ 28 | } 29 | 30 | void setNonBlock(int fd) { 31 | int flags = fcntl(fd, F_GETFL, 0); 32 | exit_if(flags < 0, "fcntl failed"); 33 | int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); 34 | exit_if(r < 0, "fcntl failed"); 35 | } 36 | 37 | void updateEvents(int efd, int fd, int events, int op) { 38 | struct epoll_event ev; 39 | memset(&ev, 0, sizeof(ev)); 40 | ev.events = events; 41 | ev.data.fd = fd; 42 | printf("%s fd %d events read %d write %d\n", op == EPOLL_CTL_MOD ? "mod" : "add", fd, ev.events & EPOLLIN, ev.events & EPOLLOUT); 43 | int r = epoll_ctl(efd, op, fd, &ev); 44 | exit_if(r, "epoll_ctl failed"); 45 | } 46 | 47 | void handleAccept(int efd, int fd) { 48 | struct sockaddr_in raddr; 49 | socklen_t rsz = sizeof(raddr); 50 | int cfd = accept(fd, (struct sockaddr *) &raddr, &rsz); 51 | exit_if(cfd < 0, "accept failed"); 52 | sockaddr_in peer, local; 53 | socklen_t alen = sizeof(peer); 54 | int r = getpeername(cfd, (sockaddr *) &peer, &alen); 55 | exit_if(r < 0, "getpeername failed"); 56 | printf("accept a connection from %s\n", inet_ntoa(raddr.sin_addr)); 57 | setNonBlock(cfd); 58 | updateEvents(efd, cfd, EPOLLIN, EPOLL_CTL_ADD); 59 | } 60 | struct Con { 61 | string readed; 62 | size_t written; 63 | bool writeEnabled; 64 | Con() : written(0), writeEnabled(false) {} 65 | }; 66 | map cons; 67 | 68 | string httpRes; 69 | void sendRes(int efd, int fd) { 70 | Con &con = cons[fd]; 71 | size_t left = httpRes.length() - con.written; 72 | int wd = 0; 73 | while ((wd = ::write(fd, httpRes.data() + con.written, left)) > 0) { 74 | con.written += wd; 75 | left -= wd; 76 | if (output_log) 77 | printf("write %d bytes left: %lu\n", wd, left); 78 | }; 79 | if (left == 0) { 80 | // close(fd); // 测试中使用了keepalive,因此不关闭连接。连接会在read事件中关闭 81 | if (con.writeEnabled) { 82 | updateEvents(efd, fd, EPOLLIN, EPOLL_CTL_MOD); // 当所有数据发送结束后,不再关注其缓冲区可写事件 83 | con.writeEnabled = false; 84 | } 85 | cons.erase(fd); 86 | return; 87 | } 88 | if (wd < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 89 | if (!con.writeEnabled) { 90 | updateEvents(efd, fd, EPOLLIN | EPOLLOUT, EPOLL_CTL_MOD); 91 | con.writeEnabled = true; 92 | } 93 | return; 94 | } 95 | if (wd <= 0) { 96 | printf("write error for %d: %d %s\n", fd, errno, strerror(errno)); 97 | close(fd); 98 | cons.erase(fd); 99 | } 100 | } 101 | 102 | void handleRead(int efd, int fd) { 103 | char buf[4096]; 104 | int n = 0; 105 | while ((n = ::read(fd, buf, sizeof buf)) > 0) { 106 | if (output_log) 107 | printf("read %d bytes\n", n); 108 | string &readed = cons[fd].readed; 109 | readed.append(buf, n); 110 | if (readed.length() > 4) { 111 | if (readed.substr(readed.length() - 2, 2) == "\n\n" || readed.substr(readed.length() - 4, 4) == "\r\n\r\n") { 112 | //当读取到一个完整的http请求,测试发送响应 113 | sendRes(efd, fd); 114 | } 115 | } 116 | } 117 | if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 118 | return; 119 | //实际应用中,n<0应当检查各类错误,如EINTR 120 | if (n < 0) { 121 | printf("read %d error: %d %s\n", fd, errno, strerror(errno)); 122 | } 123 | close(fd); 124 | cons.erase(fd); 125 | } 126 | 127 | void handleWrite(int efd, int fd) { 128 | sendRes(efd, fd); 129 | } 130 | 131 | void loop_once(int efd, int lfd, int waitms) { 132 | const int kMaxEvents = 20; 133 | struct epoll_event activeEvs[100]; 134 | int n = epoll_wait(efd, activeEvs, kMaxEvents, waitms); 135 | if (output_log) 136 | printf("epoll_wait return %d\n", n); 137 | for (int i = 0; i < n; i++) { 138 | int fd = activeEvs[i].data.fd; 139 | int events = activeEvs[i].events; 140 | if (events & (EPOLLIN | EPOLLERR)) { 141 | if (fd == lfd) { 142 | handleAccept(efd, fd); 143 | } else { 144 | handleRead(efd, fd); 145 | } 146 | } else if (events & EPOLLOUT) { 147 | if (output_log) 148 | printf("handling epollout\n"); 149 | handleWrite(efd, fd); 150 | } else { 151 | exit_if(1, "unknown event"); 152 | } 153 | } 154 | } 155 | 156 | int main(int argc, const char *argv[]) { 157 | if (argc > 1) { 158 | output_log = false; 159 | } 160 | ::signal(SIGPIPE, SIG_IGN); 161 | httpRes = "HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 1048576\r\n\r\n123456"; 162 | for (int i = 0; i < 1048570; i++) { 163 | httpRes += '\0'; 164 | } 165 | unsigned short port = 80; 166 | int epollfd = epoll_create(1); 167 | exit_if(epollfd < 0, "epoll_create failed"); 168 | int listenfd = socket(AF_INET, SOCK_STREAM, 0); 169 | exit_if(listenfd < 0, "socket failed"); 170 | struct sockaddr_in addr; 171 | memset(&addr, 0, sizeof addr); 172 | addr.sin_family = AF_INET; 173 | addr.sin_port = htons(port); 174 | addr.sin_addr.s_addr = INADDR_ANY; 175 | int r = ::bind(listenfd, (struct sockaddr *) &addr, sizeof(struct sockaddr)); 176 | exit_if(r, "bind to 0.0.0.0:%d failed %d %s", port, errno, strerror(errno)); 177 | r = listen(listenfd, 20); 178 | exit_if(r, "listen failed %d %s", errno, strerror(errno)); 179 | printf("fd %d listening at %d\n", listenfd, port); 180 | setNonBlock(listenfd); 181 | updateEvents(epollfd, listenfd, EPOLLIN, EPOLL_CTL_ADD); 182 | for (;;) { //实际应用应当注册信号处理函数,退出时清理资源 183 | loop_once(epollfd, listenfd, 10000); 184 | } 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /raw-examples/kqueue.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 编译:c++ -o kqueue kqueue.cc 3 | * 运行: ./kqueue 4 | * 测试:echo abc | nc localhost 2099 5 | * 结果:abc 6 | * 例子的echo返回了 abc 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define exit_if(r, ...) \ 20 | if (r) { \ 21 | printf(__VA_ARGS__); \ 22 | printf("error no: %d error msg %s\n", errno, strerror(errno)); \ 23 | exit(1); \ 24 | } 25 | 26 | const int kReadEvent = 1; 27 | const int kWriteEvent = 2; 28 | 29 | void setNonBlock(int fd) { 30 | int flags = fcntl(fd, F_GETFL, 0); 31 | exit_if(flags < 0, "fcntl failed"); 32 | int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); 33 | exit_if(r < 0, "fcntl failed"); 34 | } 35 | 36 | void updateEvents(int efd, int fd, int events, bool modify) { 37 | struct kevent ev[2]; 38 | int n = 0; 39 | if (events & kReadEvent) { 40 | EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, (void *) (intptr_t) fd); 41 | } else if (modify) { 42 | EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, (void *) (intptr_t) fd); 43 | } 44 | if (events & kWriteEvent) { 45 | EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, (void *) (intptr_t) fd); 46 | } else if (modify) { 47 | EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, (void *) (intptr_t) fd); 48 | } 49 | printf("%s fd %d events read %d write %d\n", modify ? "mod" : "add", fd, events & kReadEvent, events & kWriteEvent); 50 | int r = kevent(efd, ev, n, NULL, 0, NULL); 51 | exit_if(r, "kevent failed "); 52 | } 53 | 54 | void handleAccept(int efd, int fd) { 55 | struct sockaddr_in raddr; 56 | socklen_t rsz = sizeof(raddr); 57 | int cfd = accept(fd, (struct sockaddr *) &raddr, &rsz); 58 | exit_if(cfd < 0, "accept failed"); 59 | sockaddr_in peer, local; 60 | socklen_t alen = sizeof(peer); 61 | int r = getpeername(cfd, (sockaddr *) &peer, &alen); 62 | exit_if(r < 0, "getpeername failed"); 63 | printf("accept a connection from %s\n", inet_ntoa(raddr.sin_addr)); 64 | setNonBlock(cfd); 65 | updateEvents(efd, cfd, kReadEvent | kWriteEvent, false); 66 | } 67 | 68 | void handleRead(int efd, int fd) { 69 | char buf[4096]; 70 | int n = 0; 71 | while ((n = ::read(fd, buf, sizeof buf)) > 0) { 72 | printf("read %d bytes\n", n); 73 | int r = ::write(fd, buf, n); //写出读取的数据 74 | //实际应用中,写出数据可能会返回EAGAIN,此时应当监听可写事件,当可写时再把数据写出 75 | exit_if(r <= 0, "write error"); 76 | } 77 | if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 78 | return; 79 | exit_if(n < 0, "read error"); //实际应用中,n<0应当检查各类错误,如EINTR 80 | printf("fd %d closed\n", fd); 81 | close(fd); 82 | } 83 | 84 | void handleWrite(int efd, int fd) { 85 | //实际应用应当实现可写时写出数据,无数据可写才关闭可写事件 86 | updateEvents(efd, fd, kReadEvent, true); 87 | } 88 | 89 | void loop_once(int efd, int lfd, int waitms) { 90 | struct timespec timeout; 91 | timeout.tv_sec = waitms / 1000; 92 | timeout.tv_nsec = (waitms % 1000) * 1000 * 1000; 93 | const int kMaxEvents = 20; 94 | struct kevent activeEvs[kMaxEvents]; 95 | int n = kevent(efd, NULL, 0, activeEvs, kMaxEvents, &timeout); 96 | printf("kqueue return %d\n", n); 97 | for (int i = 0; i < n; i++) { 98 | int fd = (int) (intptr_t) activeEvs[i].udata; 99 | int events = activeEvs[i].filter; 100 | if (events == EVFILT_READ) { 101 | if (fd == lfd) { 102 | handleAccept(efd, fd); 103 | } else { 104 | handleRead(efd, fd); 105 | } 106 | } else if (events == EVFILT_WRITE) { 107 | handleWrite(efd, fd); 108 | } else { 109 | exit_if(1, "unknown event"); 110 | } 111 | } 112 | } 113 | 114 | int main() { 115 | unsigned short port = 2099; 116 | int epollfd = kqueue(); 117 | exit_if(epollfd < 0, "kqueue failed"); 118 | int listenfd = socket(AF_INET, SOCK_STREAM, 0); 119 | exit_if(listenfd < 0, "socket failed"); 120 | struct sockaddr_in addr; 121 | memset(&addr, 0, sizeof addr); 122 | addr.sin_family = AF_INET; 123 | addr.sin_port = htons(port); 124 | addr.sin_addr.s_addr = INADDR_ANY; 125 | int r = ::bind(listenfd, (struct sockaddr *) &addr, sizeof(struct sockaddr)); 126 | exit_if(r, "bind to 0.0.0.0:%d failed %d %s", port, errno, strerror(errno)); 127 | r = listen(listenfd, 20); 128 | exit_if(r, "listen failed %d %s", errno, strerror(errno)); 129 | printf("fd %d listening at %d\n", listenfd, port); 130 | setNonBlock(listenfd); 131 | updateEvents(epollfd, listenfd, kReadEvent, false); 132 | for (;;) { //实际应用应当注册信号处理函数,退出时清理资源 133 | loop_once(epollfd, listenfd, 10000); 134 | } 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /test/conf.ut.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test_harness.h" 4 | 5 | using namespace std; 6 | using namespace handy; 7 | 8 | int dump_ini(const char *dir, const char *inifile) { 9 | Conf conf; 10 | char buf[4096]; 11 | snprintf(buf, sizeof buf, "%s/%s", dir, inifile); 12 | int err = conf.parse(buf); 13 | if (err) { 14 | // printf("parse error in %s err: %d\n", inifile, err); 15 | return err; 16 | } 17 | // printf("file %s:\n", inifile); 18 | // for (auto& kv : conf.values_) { 19 | // for(auto& v : kv.second) { 20 | // printf("%s=%s\n", kv.first.c_str(), v.c_str()); 21 | // } 22 | //} 23 | return 0; 24 | } 25 | 26 | TEST(test::TestBase, allFiles) { 27 | const char *dir = "test/"; 28 | ASSERT_EQ(1, dump_ini(dir, "files/bad_comment.ini")); 29 | ASSERT_EQ(1, dump_ini(dir, "files/bad_multi.ini")); 30 | ASSERT_EQ(3, dump_ini(dir, "files/bad_section.ini")); 31 | ASSERT_EQ(0, dump_ini(dir, "files/multi_line.ini")); 32 | ASSERT_EQ(0, dump_ini(dir, "files/normal.ini")); 33 | ASSERT_EQ(0, dump_ini(dir, "files/user_error.ini")); 34 | } 35 | -------------------------------------------------------------------------------- /test/files/bad_comment.ini: -------------------------------------------------------------------------------- 1 | This is an error 2 | -------------------------------------------------------------------------------- /test/files/bad_multi.ini: -------------------------------------------------------------------------------- 1 | indented 2 | -------------------------------------------------------------------------------- /test/files/bad_section.ini: -------------------------------------------------------------------------------- 1 | [section1] 2 | name1=value1 3 | [section2 4 | [section3 ; comment ] 5 | name2=value2 6 | -------------------------------------------------------------------------------- /test/files/multi_line.ini: -------------------------------------------------------------------------------- 1 | [section1] 2 | single1 = abc 3 | multi = this is a 4 | multi-line value 5 | single2 = xyz 6 | [section2] 7 | multi = a 8 | b 9 | c 10 | [section3] 11 | single: ghi 12 | multi: the quick 13 | brown fox 14 | name = bob smith ; comment line 1 15 | ; comment line 2 16 | -------------------------------------------------------------------------------- /test/files/normal.ini: -------------------------------------------------------------------------------- 1 | ; This is an INI file 2 | [section1] ; section comment 3 | one=This is a test ; name=value comment 4 | two = 1234 5 | ; x=y 6 | 7 | [ section 2 ] 8 | happy = 4 9 | sad = 10 | 11 | [empty] 12 | ; do nothing 13 | 14 | [comment_test] 15 | test1 = 1;2;3 ; only this will be a comment 16 | test2 = 2;3;4;this won't be a comment, needs whitespace before ';' 17 | test;3 = 345 ; key should be "test;3" 18 | test4 = 4#5#6 ; '#' only starts a comment at start of line 19 | #test5 = 567 ; entire line commented 20 | # test6 = 678 ; entire line commented, except in MULTILINE mode 21 | 22 | [colon_tests] 23 | Content-Type: text/html 24 | foo:bar 25 | adams : 42 26 | -------------------------------------------------------------------------------- /test/files/user_error.ini: -------------------------------------------------------------------------------- 1 | [section] 2 | a = b 3 | user = parse_error 4 | c = d 5 | -------------------------------------------------------------------------------- /test/handy.ut.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "test_harness.h" 5 | 6 | using namespace std; 7 | using namespace handy; 8 | 9 | TEST(test::TestBase, Ip4Addr) { 10 | // ASSERT_EQ("127.0.0.1:80", Ip4Addr("localhost", 80).toString()); 11 | ASSERT_EQ(true, Ip4Addr("www.baidu.com", 80).isIpValid()); 12 | ASSERT_EQ(Ip4Addr("127...", 80).isIpValid(), false); 13 | ASSERT_EQ(true, Ip4Addr("127.0.0.1", 80).isIpValid()); 14 | } 15 | 16 | TEST(test::TestBase, EventBase) { 17 | EventBase base; 18 | base.safeCall([] { info("task by base.addTask"); }); 19 | thread th([&] { 20 | usleep(50000); 21 | info("base exit"); 22 | base.exit(); 23 | }); 24 | base.loop(); 25 | th.join(); 26 | } 27 | 28 | TEST(test::TestBase, Timer) { 29 | EventBase base; 30 | long now = util::timeMilli(); 31 | info("adding timers "); 32 | TimerId tid1 = base.runAt(now + 100, [] { info("timer at 100"); }); 33 | TimerId tid2 = base.runAfter(50, [] { info("timer after 50"); }); 34 | TimerId tid3 = base.runAfter(20, [] { info("timer interval 10"); }, 10); 35 | base.runAfter(120, [&] { 36 | info("after 120 then cancel above"); 37 | base.cancel(tid1); 38 | base.cancel(tid2); 39 | base.cancel(tid3); 40 | base.exit(); 41 | }); 42 | base.loop(); 43 | } 44 | 45 | TEST(test::TestBase, TcpServer1) { 46 | EventBase base; 47 | ThreadPool th(2); 48 | TcpServer delayEcho(&base); 49 | int r = delayEcho.bind("", 2099); 50 | ASSERT_EQ(r, 0); 51 | delayEcho.onConnRead([&th, &base](const TcpConnPtr &con) { 52 | th.addTask([&base, con] { 53 | usleep(200 * 1000); 54 | info("in pool"); 55 | base.safeCall([con, &base] { 56 | con->send(con->getInput()); 57 | base.exit(); 58 | }); 59 | }); 60 | con->close(); 61 | }); 62 | TcpConnPtr con = TcpConn::createConnection(&base, "localhost", 2099); 63 | con->onState([](const TcpConnPtr &con) { 64 | if (con->getState() == TcpConn::Connected) 65 | con->send("hello"); 66 | }); 67 | base.loop(); 68 | th.exit(); 69 | th.join(); 70 | } 71 | 72 | TEST(test::TestBase, kevent) { 73 | EventBase base; 74 | TcpServer echo(&base); 75 | int r = echo.bind("", 2099); 76 | ASSERT_EQ(r, 0); 77 | echo.onConnRead([](const TcpConnPtr &con) { con->send(con->getInput()); }); 78 | TcpConnPtr con = TcpConn::createConnection(&base, "localhost", 2099); 79 | con->onState([](const TcpConnPtr &con) { 80 | if (con->getState() == TcpConn::Connected) 81 | con->send("hello"); 82 | }); 83 | base.runAfter(5, [con, &base] { 84 | con->closeNow(); 85 | base.exit(); 86 | }); 87 | base.loop(); 88 | } -------------------------------------------------------------------------------- /test/tcpcli.ut.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test_harness.h" 4 | 5 | using namespace std; 6 | using namespace handy; 7 | 8 | TcpConnPtr connectto(EventBase *base, const char *host, unsigned short port) { 9 | TcpConnPtr con1 = TcpConn::createConnection(base, host, port); 10 | con1->onState([=](const TcpConnPtr con) { 11 | if (con->getState() == TcpConn::Connected) { 12 | con->send("GET / HTTP/1.1\r\n\r\n"); 13 | } else if (con->getState() == TcpConn::Closed) { 14 | info("connection to %s %d closed", host, port); 15 | } else if (con->getState() == TcpConn::Failed) { 16 | info("connect to %s %d failed", host, port); 17 | } 18 | }); 19 | con1->onRead([=](const TcpConnPtr con) { 20 | printf("%s %d response length is %lu bytes\n", host, port, con->getInput().size()); 21 | con->getInput().clear(); 22 | }); 23 | return con1; 24 | } 25 | 26 | TEST(test::TestBase, tcpcli) { 27 | EventBase base; 28 | TcpConnPtr baidu = connectto(&base, "www.baidu.com", 80); 29 | TcpConnPtr c = connectto(&base, "www.baidu.com", 81); 30 | TcpConnPtr local = connectto(&base, "localhost", 10000); 31 | for (int i = 0; i < 5; i++) { 32 | base.loop_once(50); 33 | } 34 | // ASSERT_EQ(TcpConn::Connected, baidu->getState()); 35 | ASSERT_EQ(TcpConn::Handshaking, c->getState()); 36 | ASSERT_EQ(TcpConn::Failed, local->getState()); 37 | } 38 | -------------------------------------------------------------------------------- /test/test_harness.cc: -------------------------------------------------------------------------------- 1 | #include "test_harness.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace handy { 11 | namespace test { 12 | 13 | namespace { 14 | struct Test { 15 | const char *base; 16 | const char *name; 17 | void (*func)(); 18 | }; 19 | std::vector *tests; 20 | } // namespace 21 | 22 | bool RegisterTest(const char *base, const char *name, void (*func)()) { 23 | if (tests == NULL) { 24 | tests = new std::vector; 25 | } 26 | Test t; 27 | t.base = base; 28 | t.name = name; 29 | t.func = func; 30 | tests->push_back(t); 31 | return true; 32 | } 33 | 34 | int RunAllTests(const char *matcher) { 35 | int num = 0; 36 | if (tests != NULL) { 37 | for (size_t i = 0; i < tests->size(); i++) { 38 | const Test &t = (*tests)[i]; 39 | if (matcher != NULL) { 40 | std::string name = t.base; 41 | name.push_back('.'); 42 | name.append(t.name); 43 | if (strstr(name.c_str(), matcher) == NULL) { 44 | continue; 45 | } 46 | } 47 | fprintf(stderr, "==== Test %s.%s\n", t.base, t.name); 48 | (*t.func)(); 49 | ++num; 50 | } 51 | } 52 | fprintf(stderr, "==== PASSED %d tests\n", num); 53 | return 0; 54 | } 55 | 56 | std::string TmpDir() { 57 | return "/tmp"; 58 | } 59 | 60 | int RandomSeed() { 61 | return 301; 62 | } 63 | 64 | } // namespace test 65 | } // namespace handy 66 | -------------------------------------------------------------------------------- /test/test_harness.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace handy { 6 | namespace test { 7 | 8 | // Run some of the tests registered by the TEST() macro. If the 9 | // environment variable "LEVELDB_TESTS" is not set, runs all tests. 10 | // Otherwise, runs only the tests whose name contains the value of 11 | // "LEVELDB_TESTS" as a substring. E.g., suppose the tests are: 12 | // TEST(Foo, Hello) { ... } 13 | // TEST(Foo, World) { ... } 14 | // LEVELDB_TESTS=Hello will run the first test 15 | // LEVELDB_TESTS=o will run both tests 16 | // LEVELDB_TESTS=Junk will run no tests 17 | // 18 | // Returns 0 if all tests pass. 19 | // Dies or returns a non-zero value if some test fails. 20 | extern int RunAllTests(const char *matcher); 21 | 22 | // Return the directory to use for temporary storage. 23 | extern std::string TmpDir(); 24 | 25 | // Return a randomization seed for this run. Typically returns the 26 | // same number on repeated invocations of this binary, but automated 27 | // runs may be able to vary the seed. 28 | extern int RandomSeed(); 29 | 30 | // An instance of Tester is allocated to hold temporary state during 31 | // the execution of an assertion. 32 | class Tester { 33 | private: 34 | bool ok_; 35 | const char *fname_; 36 | int line_; 37 | std::stringstream ss_; 38 | 39 | public: 40 | Tester(const char *f, int l) : ok_(true), fname_(f), line_(l) {} 41 | 42 | ~Tester() { 43 | if (!ok_) { 44 | fprintf(stderr, "%s:%d:%s\n", fname_, line_, ss_.str().c_str()); 45 | exit(1); 46 | } 47 | } 48 | 49 | Tester &Is(bool b, const char *msg) { 50 | if (!b) { 51 | ss_ << " Assertion failure " << msg; 52 | ok_ = false; 53 | } 54 | return *this; 55 | } 56 | 57 | #define BINARY_OP(name, op) \ 58 | template \ 59 | Tester &name(const X &x, const Y &y) { \ 60 | if (!(x op y)) { \ 61 | ss_ << " failed: " << x << (" " #op " ") << y; \ 62 | ok_ = false; \ 63 | } \ 64 | return *this; \ 65 | } 66 | 67 | BINARY_OP(IsEq, ==) 68 | BINARY_OP(IsNe, !=) 69 | BINARY_OP(IsGe, >=) 70 | BINARY_OP(IsGt, >) 71 | BINARY_OP(IsLe, <=) 72 | BINARY_OP(IsLt, <) 73 | #undef BINARY_OP 74 | 75 | // Attach the specified value to the error message if an error has occurred 76 | template 77 | Tester &operator<<(const V &value) { 78 | if (!ok_) { 79 | ss_ << " " << value; 80 | } 81 | return *this; 82 | } 83 | }; 84 | 85 | #define ASSERT_TRUE(c) ::handy::test::Tester(__FILE__, __LINE__).Is((c), #c) 86 | #define ASSERT_FALSE(c) ::handy::test::Tester(__FILE__, __LINE__).Is(!(c), #c) 87 | #define ASSERT_EQ(a, b) ::handy::test::Tester(__FILE__, __LINE__).IsEq((a), (b)) 88 | #define ASSERT_NE(a, b) ::handy::test::Tester(__FILE__, __LINE__).IsNe((a), (b)) 89 | #define ASSERT_GE(a, b) ::handy::test::Tester(__FILE__, __LINE__).IsGe((a), (b)) 90 | #define ASSERT_GT(a, b) ::handy::test::Tester(__FILE__, __LINE__).IsGt((a), (b)) 91 | #define ASSERT_LE(a, b) ::handy::test::Tester(__FILE__, __LINE__).IsLe((a), (b)) 92 | #define ASSERT_LT(a, b) ::handy::test::Tester(__FILE__, __LINE__).IsLt((a), (b)) 93 | 94 | #define TCONCAT(a, b) TCONCAT1(a, b) 95 | #define TCONCAT1(a, b) a##b 96 | 97 | #define TEST(base, name) \ 98 | class TCONCAT(_Test_, name) : public base { \ 99 | public: \ 100 | void _Run(); \ 101 | static void _RunIt() { \ 102 | TCONCAT(_Test_, name) t; \ 103 | t._Run(); \ 104 | } \ 105 | }; \ 106 | bool TCONCAT(_Test_ignored_, name) = ::handy::test::RegisterTest(#base, #name, &TCONCAT(_Test_, name)::_RunIt); \ 107 | void TCONCAT(_Test_, name)::_Run() 108 | 109 | // Register the specified test. Typically not used directly, but 110 | // invoked via the macro expansion of TEST. 111 | extern bool RegisterTest(const char *base, const char *name, void (*func)()); 112 | 113 | class TestBase {}; 114 | 115 | } // namespace test 116 | } // namespace handy 117 | -------------------------------------------------------------------------------- /test/threads.ut.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test_harness.h" 4 | 5 | using namespace std; 6 | using namespace handy; 7 | 8 | TEST(test::TestBase, ThreadPool) { 9 | ThreadPool pool(2, 5, false); 10 | int processed = 0; 11 | int *p = &processed; 12 | int added = 0; 13 | for (int i = 0; i < 10; i++) { 14 | added += pool.addTask([=] { 15 | printf("task %d processed\n", i); 16 | ++*p; 17 | }); 18 | } 19 | pool.start(); 20 | usleep(100 * 1000); 21 | pool.exit(); 22 | pool.join(); 23 | ASSERT_EQ(added, processed); 24 | printf("pool tested\n"); 25 | ThreadPool pool2(2); 26 | usleep(100 * 1000); 27 | processed = 0; 28 | added = 0; 29 | for (int i = 0; i < 10; i++) { 30 | added += pool2.addTask([=] { 31 | printf("task %d processed\n", i); 32 | ++*p; 33 | }); 34 | } 35 | usleep(100 * 1000); 36 | pool2.exit(); 37 | pool2.join(); 38 | ASSERT_EQ(added, processed); 39 | } 40 | 41 | TEST(test::TestBase, SafeQueue) { 42 | SafeQueue q; 43 | atomic exit(false); 44 | q.push(-1); 45 | thread t([&] { 46 | while (!exit.load(memory_order_relaxed)) { 47 | int v = q.pop_wait(50); 48 | if (v) { 49 | printf("%d recved in consumer\n", v); 50 | } 51 | bool r = q.pop_wait(&v, 50); 52 | if (r) { 53 | printf("%d recved in consumer use pointer\n", v); 54 | } 55 | } 56 | }); 57 | usleep(300 * 1000); 58 | printf("push other values\n"); 59 | for (int i = 0; i < 5; i++) { 60 | q.push(i + 1); 61 | } 62 | usleep(300 * 1000); 63 | exit.store(true, memory_order_relaxed); 64 | t.join(); 65 | ASSERT_EQ(q.size(), 0); 66 | } 67 | -------------------------------------------------------------------------------- /test/ut.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "test_harness.h" 3 | 4 | int main(int argc, char **argv) { 5 | char junk = 0; 6 | for (int i = 1; i < argc; i++) { 7 | if (sscanf(argv[i], "-%c", &junk) == 1) { 8 | if (junk == 'h') { 9 | printf("%s [options] [matcher]\n", argv[0]); 10 | printf("options:\n\t-v verbose mode\n\t-h help\n"); 11 | printf("matcher:\n\tonly run test contain 'matcher'\n"); 12 | return 0; 13 | } else if (junk == 'v') { 14 | handy::Logger::getLogger().setLogLevel("TRACE"); 15 | } else { 16 | printf("unknown option"); 17 | return 1; 18 | } 19 | } else { 20 | handy::test::RunAllTests(argv[i]); 21 | return 0; 22 | } 23 | } 24 | handy::test::RunAllTests(NULL); 25 | return 0; 26 | } -------------------------------------------------------------------------------- /test/util.ut.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "test_harness.h" 3 | 4 | using namespace std; 5 | using namespace handy; 6 | 7 | TEST(test::TestBase, static_func) { 8 | ASSERT_EQ("a", util::format("a")); 9 | string s1 = "hello"; 10 | for (int i = 0; i < 999; i++) { 11 | s1 += "hello"; 12 | } 13 | string s2 = util::format("%s", s1.c_str()); 14 | ASSERT_EQ(1000 * 5, s2.length()); 15 | } 16 | 17 | TEST(test::TestBase, ExitCaller) { 18 | ExitCaller caller1([] { printf("exit function called\n"); }); 19 | printf("after caller\n"); 20 | } 21 | --------------------------------------------------------------------------------