├── .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[](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[](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 | 
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 |
--------------------------------------------------------------------------------