The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .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 <handy/handy.h>
  2 | #include <sys/wait.h>
  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 <host> <begin port> <end port> <conn count> <create seconds> <subprocesses> <hearbeat interval> <send size> <management port>\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<TcpConnPtr> 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<int, Report> 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 <handy/handy.h>
 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 <begin port> <end port> <subprocesses> <management port>\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<TcpServerPtr> 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<int, Report> 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 
lt; -o $@
56 | 
57 | .c.o:
58 | 		$(CC) $(CFLAGS) -c 
lt; -o $@
59 | 
60 | .cc:
61 | 	$(CXX) -o $@ 
lt; $(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 <handy/handy.h>
 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 <handy/handy.h>
 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,能够找到<openssl/ssl.h>,项目会自动下载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 <<EOF
136 | int main(){
137 |   unsigned short a = 1;
138 |   return *(unsigned char*)&a == 1;
139 | }
140 | EOF
141 | $TMPDIR/handy_build_config.out
142 | PLATFORM_IS_LITTLE_ENDIAN=$?
143 | 
144 | #$CXX -x c++ - -o $TMPDIR/handy_build_config.out >/dev/null 2>&1 <<EOF
145 | ##include <sys/epoll.h>
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 <<EOF
151 | #include <openssl/ssl.h>
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 | <h2 id="sample">example--echo</h2>
 13 | 
 14 | ```c
 15 | #include <handy/handy.h>
 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 | <h2 id="event-base">EventBase: events dispatcher</h2>
 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 | <h2 id="tcp-conn">TcpConn tcp connection</h2>
100 | use shared_ptr to manage connection, no need to release manually
101 | 
102 | ### reference count
103 | 
104 | ```c
105 | typedef std::shared_ptr<TcpConn> 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<class T> T& context();
166 | 
167 | con->context<std::string>() = "user defined data";
168 | ```
169 | 
170 | <h2 id="tcp-server">TcpServer</h2>
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<TcpConnPtr()>& 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<int>() = 1;
193 |         }
194 |     }
195 |     return con;
196 | });
197 | ```
198 | 
199 | <h2 id="http-server">HttpServer</h2>
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 | <h2 id="hsha">half sync half async server</h2>
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 | <h2 id="sample">使用示例--echo</h2>
 13 | 
 14 | ```c
 15 | 
 16 | #include <handy/handy.h>
 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 | <h2 id="event-base">EventBase事件分发器</h2>
 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 | <h2 id="tcp-conn">TcpConn tcp连接</h2>
 84 | 连接采用引用计数的方式进行管理,因此用户无需手动释放连接
 85 | ### 引用计数
 86 | 
 87 | ```c
 88 | typedef std::shared_ptr<TcpConn> 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<class T> T& context();
154 | 
155 | con->context<std::string>() = "user defined data";
156 | ```
157 | 
158 | <h2 id="tcp-server">TcpServer tcp服务器</h2>
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<TcpConnPtr()>& 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<int>() = 1;
188 |         }
189 |     }
190 |     return con;
191 | });
192 | ```
193 | 
194 | [例子程序](examples/codec-svr.cc)
195 | 
196 | <h2 id="http-server">HttpServer http服务器</h2>
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 | <h2 id="hsha">半同步半异步服务器</h2>
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 <handy/handy.h>
 2 | #include <map>
 3 | 
 4 | using namespace std;
 5 | using namespace handy;
 6 | 
 7 | int main(int argc, const char *argv[]) {
 8 |     setloglevel("TRACE");
 9 |     map<intptr_t, TcpConnPtr> 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<int>() = userid;
21 |                 const char *welcome = "<id> <msg>: send msg to <id>\n<msg>: 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<int>());
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<int>();
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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <start|stop|restart>\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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 2 | #include <handy/stat-svr.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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 <handy/handy.h>
 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<int>(i + 1);
17 |             } else {
18 |                 msg = Slice(data.data(), i);
19 |                 return static_cast<int>(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 <cstdlib>
  3 | #include <algorithm>
  4 | #include <memory>
  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<string> 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<string>() : 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<FILE, decltype(fclose) *> release2(file, fclose);
126 |     static const int MAX_LINE = 16 * 1024;
127 |     char *ln = new char[MAX_LINE];
128 |     unique_ptr<char[]> 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 <list>
 4 | #include <map>
 5 | #include <string>
 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<std::string> getStrings(const std::string& section, const std::string& name);
34 | 
35 |     std::map<std::string, std::list<std::string>> 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<TcpConn>, 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 <class C = TcpConn>
 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 <class C = TcpConn>
 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 <class T>
 36 |     T &context() {
 37 |         return ctx_.context<T>();
 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<IdleId> 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<CodecBase> 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<TcpConnPtr()> &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<TcpConnPtr()> createcb_;
144 |     std::unique_ptr<CodecBase> codec_;
145 |     void handleAccept();
146 | };
147 | 
148 | typedef std::function<std::string(const TcpConnPtr &, const std::string &msg)> RetMsgCallBack;
149 | //半同步半异步服务器
150 | struct HSHA;
151 | typedef std::shared_ptr<HSHA> 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 <cerrno>
  3 | #include <fcntl.h>
  4 | #include <csignal>
  5 | #include <cstdio>
  6 | #include <cstdlib>
  7 | #include <cstring>
  8 | #include <strings.h>
  9 | #include <unistd.h>
 10 | #include <functional>
 11 | #include <map>
 12 | #include <string>
 13 | #include <utility>
 14 | 
 15 | using namespace std;
 16 | 
 17 | namespace handy {
 18 | 
 19 | namespace {
 20 | 
 21 | struct ExitCaller {
 22 |     ~ExitCaller() { functor_(); }
 23 |     ExitCaller(std::function<void()> &&functor) : functor_(std::move(functor)) {}
 24 | 
 25 |    private:
 26 |     std::function<void()> 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<int, function<void()>> handlers;
193 | void signal_handler(int sig) {
194 |     handlers[sig]();
195 | }
196 | }  // namespace
197 | 
198 | void Signal::signal(int sig, const function<void()> &handler) {
199 |     handlers[sig] = handler;
200 |     ::signal(sig, signal_handler);
201 | }
202 | 
203 | }  // namespace handy


--------------------------------------------------------------------------------
/handy/daemon.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include <csignal>
 3 | #include <functional>
 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<void()> &handler);
26 | };
27 | }  // namespace handy
28 | 


--------------------------------------------------------------------------------
/handy/event_base.cc:
--------------------------------------------------------------------------------
  1 | #include "event_base.h"
  2 | #include <fcntl.h>
  3 | #include <cstring>
  4 | #include <map>
  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<IdleNode>::iterator Iter;
 33 |     IdleIdImp(list<IdleNode> *lst, Iter iter) : lst_(lst), iter_(iter) {}
 34 |     list<IdleNode> *lst_;
 35 |     Iter iter_;
 36 | };
 37 | 
 38 | struct EventsImp {
 39 |     EventBase *base_;
 40 |     PollerBase *poller_;
 41 |     std::atomic<bool> exit_;
 42 |     int wakeupFds_[2];
 43 |     int nextTimeout_;
 44 |     SafeQueue<Task> tasks_;
 45 | 
 46 |     std::map<TimerId, TimerRepeatable> timerReps_;
 47 |     std::map<TimerId, Task> timers_;
 48 |     std::atomic<int64_t> timerSeq_;
 49 |     // 记录每个idle时间(单位秒)下所有的连接。链表中的所有连接,最新的插入到链表末尾。连接若有活动,会把连接从链表中移到链表尾部,做法参考memcache
 50 |     std::map<int, std::list<IdleNode>> idleConns_;
 51 |     std::set<TcpConnPtr> 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<thread> 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<int64_t> 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<TcpConn> TcpConnPtr;
  8 | typedef std::shared_ptr<TcpServer> TcpServerPtr;
  9 | typedef std::function<void(const TcpConnPtr &)> TcpCallBack;
 10 | typedef std::function<void(const TcpConnPtr &, Slice msg)> 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<EventsImp> 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<int> id_;
 68 |     std::vector<EventBase> 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<void()> readcb_, writecb_, errorcb_;
108 | };
109 | 
110 | }  // namespace handy
111 | 


--------------------------------------------------------------------------------
/handy/file.cc:
--------------------------------------------------------------------------------
  1 | #include "file.h"
  2 | #include <dirent.h>
  3 | #include <fcntl.h>
  4 | #include <sys/stat.h>
  5 | #include <unistd.h>
  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<std::string> *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 <string>
 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<std::string> *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 <unistd.h>
 3 | #include <memory>
 4 | #include <set>
 5 | #include <utility>
 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<IdleIdImp> IdleId;
20 | typedef std::pair<int64_t, int64_t> TimerId;
21 | 
22 | struct AutoContext : noncopyable {
23 |     void *ctx;
24 |     Task ctxDel;
25 |     AutoContext() : ctx(0) {}
26 |     template <class T>
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<string, string> &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 <cr> 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 <map>
  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<std::string, std::string> 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<std::string, std::string> &m, const std::string &n);
 43 | };
 44 | 
 45 | struct HttpRequest : public HttpMsg {
 46 |     HttpRequest() { clear(); }
 47 |     std::map<std::string, std::string> 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<void(const HttpConnPtr &)> HttpCallBack;
 92 | 
 93 |     HttpRequest &getRequest() const { return tcp->internalCtx_.context<HttpContext>().req; }
 94 |     HttpResponse &getResponse() const { return tcp->internalCtx_.context<HttpContext>().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 <class Conn = TcpConn>
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<TcpConnPtr()> conncb_;
141 |     std::map<std::string, std::map<std::string, HttpCallBack>> cbs_;
142 | };
143 | 
144 | }  // namespace handy
145 | 


--------------------------------------------------------------------------------
/handy/logging.cc:
--------------------------------------------------------------------------------
  1 | #include "logging.h"
  2 | #include <cassert>
  3 | #include <cerrno>
  4 | #include <fcntl.h>
  5 | #include <cstdarg>
  6 | #include <cstring>
  7 | #include <sys/stat.h>
  8 | #include <ctime>
  9 | #include <syslog.h>
 10 | #include <unistd.h>
 11 | #include <thread>
 12 | #include <sys/time.h>
 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<int>(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 <cstdio>
 3 | #include <atomic>
 4 | #include <string>
 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<int64_t> realRotate_;
81 |     long rotateInterval_;
82 |     std::string filename_;
83 | };
84 | 
85 | }  // namespace handy
86 | 


--------------------------------------------------------------------------------
/handy/net.cc:
--------------------------------------------------------------------------------
  1 | #include "net.h"
  2 | #include <cerrno>
  3 | #include <fcntl.h>
  4 | #include <netinet/tcp.h>
  5 | #include <sys/socket.h>
  6 | #include <string>
  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 <netinet/in.h>
  3 | #include <cstring>
  4 | #include <algorithm>
  5 | #include <string>
  6 | #include "port_posix.h"
  7 | #include "slice.h"
  8 | 
  9 | namespace handy {
 10 | 
 11 | struct net {
 12 |     template <class T>
 13 |     static T hton(T v) {
 14 |         return port::htobe(v);
 15 |     }
 16 |     template <class T>
 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 <class T>
 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 <fcntl.h>
  2 | #include "event_base.h"
  3 | #include "logging.h"
  4 | #include "util.h"
  5 | #include "poller.h"
  6 | 
  7 | #ifdef OS_LINUX
  8 | #include <sys/epoll.h>
  9 | #elif defined(OS_MACOSX)
 10 | #include <sys/event.h>
 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<Channel *> 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<Channel *> 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 <cassert>
 3 | #include <poll.h>
 4 | #include <cstring>
 5 | #include <sys/time.h>
 6 | #include <sys/types.h>
 7 | #include <atomic>
 8 | #include <map>
 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<int64_t> 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 <netdb.h>
 3 | #include <cstring>
 4 | #include <sys/syscall.h>
 5 | #include <unistd.h>
 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<struct in_addr *>(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<struct in_addr *>(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 <netinet/in.h>
 3 | #include <string>
 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 <cstring>
  3 | #include <string>
  4 | #include <vector>
  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<Slice> 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> Slice::split(char ch) const {
115 |     std::vector<Slice> 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("<a href=\"/?stat=%s\">%s</a>", path.c_str(), path.c_str());
 12 | }
 13 | 
 14 | static string page_link(const string &path) {
 15 |     return util::format("<a href=\"/%s\">%s</a>", 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("<a href=\"/\">refresh</a><br/>\n");
 37 |             buf.append("<table>\n");
 38 |             buf.append("<tr><td>Stat</td><td>Desc</td><td>Value</td></tr>\n");
 39 |             for (auto &stat : statcbs_) {
 40 |                 HttpResponse r;
 41 |                 req.uri = stat.first;
 42 |                 stat.second.second(req, r);
 43 |                 buf.append("<tr><td>")
 44 |                     .append(page_link(stat.first))
 45 |                     .append("</td><td>")
 46 |                     .append(stat.second.first)
 47 |                     .append("</td><td>")
 48 |                     .append(r.body)
 49 |                     .append("</td></tr>\n");
 50 |             }
 51 |             buf.append("</table>\n<br/>\n<table>\n").append("<tr><td>Page</td><td>Desc</td>\n");
 52 |             for (auto &stat : pagecbs_) {
 53 |                 buf.append("<tr><td>").append(page_link(stat.first)).append("</td><td>").append(stat.second.first).append("</td></tr>\n");
 54 |             }
 55 |             buf.append("</table>\n<br/>\n<table>\n").append("<tr><td>Cmd</td><td>Desc</td>\n");
 56 |             for (auto &stat : cmdcbs_) {
 57 |                 buf.append("<tr><td>").append(query_link(stat.first)).append("</td><td>").append(stat.second.first).append("</td></tr>\n");
 58 |             }
 59 |             buf.append("</table>\n");
 60 |             if (resp.body.size()) {
 61 |                 buf.append(util::format("<br/>SubQuery %s:<br/> %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 <functional>
 4 | #include <map>
 5 | #include "event_base.h"
 6 | #include "http.h"
 7 | #include "slice.h"
 8 | 
 9 | namespace handy {
10 | 
11 | typedef std::function<void(const HttpRequest &, HttpResponse &)> StatCallBack;
12 | typedef std::function<std::string()> InfoCallBack;
13 | typedef std::function<int64_t()> 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<std::string, StatCallBack> DescState;
43 |     std::map<std::string, DescState> statcbs_, pagecbs_, cmdcbs_;
44 |     std::map<std::string, StatCallBack> allcbs_;
45 | };
46 | 
47 | }  // namespace handy
48 | 


--------------------------------------------------------------------------------
/handy/status.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include <cerrno>
 3 | #include <cstdarg>
 4 | #include <cstring>
 5 | #include <cstdint>
 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 <cassert>
 3 | #include <utility>
 4 | 
 5 | using namespace std;
 6 | 
 7 | namespace handy {
 8 | 
 9 | template class SafeQueue<Task>;
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 <atomic>
  3 | #include <condition_variable>
  4 | #include <functional>
  5 | #include <limits>
  6 | #include <list>
  7 | #include <mutex>
  8 | #include <thread>
  9 | #include <vector>
 10 | #include "util.h"
 11 | 
 12 | namespace handy {
 13 | 
 14 | template <typename T>
 15 | struct SafeQueue : private std::mutex, private noncopyable {
 16 |     static const int wait_infinite = std::numeric_limits<int>::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<T> items_;
 32 |     std::condition_variable ready_;
 33 |     size_t capacity_;
 34 |     std::atomic<bool> exit_;
 35 |     void wait_ready(std::unique_lock<std::mutex> &lk, int waitMs);
 36 | };
 37 | 
 38 | typedef std::function<void()> Task;
 39 | extern template class SafeQueue<Task>;
 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<Task> tasks_;
 59 |     std::vector<std::thread> threads_;
 60 | };
 61 | 
 62 | //以下为实现代码,不必关心
 63 | template <typename T>
 64 | size_t SafeQueue<T>::size() {
 65 |     std::lock_guard<std::mutex> lk(*this);
 66 |     return items_.size();
 67 | }
 68 | 
 69 | template <typename T>
 70 | void SafeQueue<T>::exit() {
 71 |     exit_ = true;
 72 |     std::lock_guard<std::mutex> lk(*this);
 73 |     ready_.notify_all();
 74 | }
 75 | 
 76 | template <typename T>
 77 | bool SafeQueue<T>::push(T &&v) {
 78 |     std::lock_guard<std::mutex> 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 <typename T>
 87 | void SafeQueue<T>::wait_ready(std::unique_lock<std::mutex> &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 <typename T>
101 | bool SafeQueue<T>::pop_wait(T *v, int waitMs) {
102 |     std::unique_lock<std::mutex> 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 <typename T>
113 | T SafeQueue<T>::pop_wait(int waitMs) {
114 |     std::unique_lock<std::mutex> 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 <fcntl.h>
  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<UdpConn> UdpConnPtr;
10 | typedef std::shared_ptr<UdpServer> UdpServerPtr;
11 | typedef std::function<void(const UdpConnPtr &, Buffer)> UdpCallBack;
12 | typedef std::function<void(const UdpServerPtr &, Buffer, Ip4Addr)> UdpSvrCallBack;
13 | const int kUdpPacketSize = 4096;
14 | // Udp服务器
15 | struct UdpServer : public std::enable_shared_from_this<UdpServer>, 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<UdpConn>, 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 <class T>
50 |     T &context() {
51 |         return ctx_.context<T>();
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<std::string(const UdpServerPtr &, const std::string &, Ip4Addr)> RetMsgUdpCallBack;
82 | //半同步半异步服务器
83 | struct HSHAU;
84 | typedef std::shared_ptr<HSHAU> 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 <fcntl.h>
 3 | #include <cstdarg>
 4 | #include <algorithm>
 5 | #include <chrono>
 6 | #include <memory>
 7 | 
 8 | using namespace std;
 9 | 
10 | namespace handy {
11 | 
12 | string util::format(const char *fmt, ...) {
13 |     char buffer[500];
14 |     unique_ptr<char[]> 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<chrono::system_clock> p = chrono::system_clock::now();
50 |     return chrono::duration_cast<chrono::microseconds>(p.time_since_epoch()).count();
51 | }
52 | int64_t util::steadyMicro() {
53 |     chrono::time_point<chrono::steady_clock> p = chrono::steady_clock::now();
54 |     return chrono::duration_cast<chrono::microseconds>(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 <cstdlib>
 3 | #include <cstring>
 4 | #include <functional>
 5 | #include <string>
 6 | #include <utility>
 7 | #include <vector>
 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<void()> &&functor) : functor_(std::move(functor)) {}
40 | 
41 |    private:
42 |     std::function<void()> 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=. 
lt;
30 | 	@touch $@
31 | 
32 | clean:
33 | 	-rm -f $(PROGRAMS) middle
34 | 	-rm -f *.o *.pb.*
35 | 
36 | .cc.o:
37 | 	$(CXX) $(CXXFLAGS) -c 
lt; -o $@ `pkg-config --cflags protobuf`
38 | 
39 | .c.o:
40 | 	$(CC) $(CFLAGS) -c 
lt; -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 <google/protobuf/descriptor.h>
 3 | #include <string>
 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 <google/protobuf/message.h>
 3 | #include <handy/conn.h>
 4 | #include <map>
 5 | 
 6 | namespace handy {
 7 | 
 8 | typedef ::google::protobuf::Message Message;
 9 | typedef ::google::protobuf::Descriptor Descriptor;
10 | typedef std::function<void(TcpConnPtr con, Message *msg)> 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 <typename M>
21 |     void onMsg(std::function<void(TcpConnPtr con, M *msg)> cb) {
22 |         protocbs_[M::descriptor()] = [cb](TcpConnPtr con, Message *msg) { cb(con, dynamic_cast<M *>(msg)); };
23 |     }
24 | 
25 |    private:
26 |     std::map<const Descriptor *, ProtoCallBack> 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<Query *>(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<Query>(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\>  [ program can be \"$(PROGRAMS)\" ]
 7 | 
 8 | clean:
 9 | 	-rm -f $(PROGRAMS)
10 | 	-rm -f *.o
11 | 
12 | .cc.o:
13 | 	$(CXX) $(CXXFLAGS) -c 
lt; -o $@
14 | 
15 | .c.o:
16 | 	$(CC) $(CFLAGS) -c 
lt; -o $@
17 | 
18 | .cc:
19 | 	$(CXX) -o $@ 
lt; $(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 <arpa/inet.h>
  8 | #include <cerrno>
  9 | #include <fcntl.h>
 10 | #include <netinet/in.h>
 11 | #include <csignal>
 12 | #include <cstdio>
 13 | #include <cstdlib>
 14 | #include <cstring>
 15 | #include <sys/epoll.h>
 16 | #include <sys/socket.h>
 17 | #include <unistd.h>
 18 | #include <map>
 19 | #include <string>
 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<int, Con> 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 <arpa/inet.h>
  7 | #include <cerrno>
  8 | #include <fcntl.h>
  9 | #include <netinet/in.h>
 10 | #include <csignal>
 11 | #include <cstdio>
 12 | #include <cstdlib>
 13 | #include <cstring>
 14 | #include <sys/epoll.h>
 15 | #include <sys/socket.h>
 16 | #include <unistd.h>
 17 | #include <map>
 18 | #include <string>
 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<int, Con> 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 <arpa/inet.h>
  9 | #include <cerrno>
 10 | #include <fcntl.h>
 11 | #include <netinet/in.h>
 12 | #include <cstdio>
 13 | #include <cstdlib>
 14 | #include <cstring>
 15 | #include <sys/event.h>
 16 | #include <sys/socket.h>
 17 | #include <unistd.h>
 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 <handy/conf.h>
 2 | #include <cstring>
 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 <handy/conn.h>
 2 | #include <handy/logging.h>
 3 | #include <thread>
 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 <handy/conn.h>
 2 | #include <handy/logging.h>
 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 <cstdlib>
 4 | #include <cstring>
 5 | #include <sys/stat.h>
 6 | #include <sys/types.h>
 7 | #include <string>
 8 | #include <vector>
 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<Test> *tests;
20 | }  // namespace
21 | 
22 | bool RegisterTest(const char *base, const char *name, void (*func)()) {
23 |     if (tests == NULL) {
24 |         tests = new std::vector<Test>;
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 <cstdio>
  2 | #include <cstdlib>
  3 | #include <sstream>
  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 <class X, class Y>                            \
 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 <class V>
 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 <handy/threads.h>
 2 | #include <unistd.h>
 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<int> q;
43 |     atomic<bool> 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 <handy/logging.h>
 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 <handy/util.h>
 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 | 


--------------------------------------------------------------------------------