1 Star 0 Fork 270

乐观的我们 / libhv

forked from libhv / libhv 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README-CN.md 17.26 KB
一键复制 编辑 原始数据 按行查看 历史
ithewei 提交于 2022-01-27 21:19 . New feature: WITH_MQTT

English | 中文

libhv

platform CI benchmark
release stars forks issues PRs license
gitee awesome-c awesome-cpp

libhv是一个类似于libevent、libev、libuv的跨平台网络库,提供了更易用的接口和更丰富的协议。

✨ 特征

  • 跨平台(Linux, Windows, MacOS, Solaris, Android, iOS)
  • 高性能事件循环(网络IO事件、定时器事件、空闲事件、自定义事件)
  • TCP/UDP服务端/客户端/代理
  • TCP支持心跳、转发、拆包、多线程安全write和close等特性
  • 可靠UDP支持: WITH_KCP
  • SSL/TLS加密通信(可选WITH_OPENSSL、WITH_GNUTLS、WITH_MBEDTLS)
  • HTTP服务端/客户端(支持https http1/x http2 grpc)
  • HTTP支持静态文件服务、目录服务、同步/异步API处理函数
  • HTTP支持RESTful风格、URI路由、keep-alive长连接、chunked分块等特性
  • WebSocket服务端/客户端
  • MQTT客户端

⌛️ 构建

BUILD.md

libhv提供了以下构建方式:

1、通过Makefile:

./configure
make
sudo make install

2、通过cmake:

mkdir build
cd build
cmake ..
cmake --build .

3、通过vcpkg:

vcpkg install libhv

4、通过xmake:

xrepo install libhv

⚡️ 快速入门

体验

运行脚本./getting_started.sh:

# 下载编译
git clone https://github.com/ithewei/libhv.git
cd libhv
make

# 运行httpd服务
bin/httpd -h
bin/httpd -d
#bin/httpd -c etc/httpd.conf -s restart -d
ps aux | grep httpd

# 文件服务
bin/curl -v localhost:8080

# 目录服务
bin/curl -v localhost:8080/downloads/

# API服务
bin/curl -v localhost:8080/ping
bin/curl -v localhost:8080/echo -d "hello,world!"
bin/curl -v localhost:8080/query?page_no=1\&page_size=10
bin/curl -v localhost:8080/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
bin/curl -v localhost:8080/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
bin/curl -v localhost:8080/form -F 'user=admin' -F 'pswd=123456'
bin/curl -v localhost:8080/upload -d "@LICENSE"
bin/curl -v localhost:8080/upload -F "file=@LICENSE"

bin/curl -v localhost:8080/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
bin/curl -v localhost:8080/test -F 'bool=1' -F 'int=123' -F 'float=3.14' -F 'string=hello'
# RESTful API: /group/:group_name/user/:user_id
bin/curl -v -X DELETE localhost:8080/group/test/user/123

# 压力测试
bin/wrk -c 1000 -d 10 -t 4 http://127.0.0.1:8080/

TCP

TCP服务端

c版本: examples/tcp_echo_server.c

#include "hloop.h"

static void on_close(hio_t* io) {
    printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io));
}

static void on_recv(hio_t* io, void* buf, int readbytes) {
    // echo
    hio_write(io, buf, readbytes);
}

static void on_accept(hio_t* io) {
    hio_setcb_close(io, on_close);
    hio_setcb_read(io, on_recv);
    hio_read(io);
}

int main() {
    int port = 1234;
    hloop_t* loop = hloop_new(0);
    hio_t* listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
    if (listenio == NULL) {
        return -1;
    }
    hloop_run(loop);
    hloop_free(&loop);
    return 0;
}

c++版本: evpp/TcpServer_test.cpp

#include "TcpServer.h"
using namespace hv;

int main() {
    int port = 1234;
    TcpServer srv;
    int listenfd = srv.createsocket(port);
    if (listenfd < 0) {
        return -1;
    }
    printf("server listen on port %d, listenfd=%d ...\n", port, listenfd);
    srv.onConnection = [](const SocketChannelPtr& channel) {
        std::string peeraddr = channel->peeraddr();
        if (channel->isConnected()) {
            printf("%s connected! connfd=%d\n", peeraddr.c_str(), channel->fd());
        } else {
            printf("%s disconnected! connfd=%d\n", peeraddr.c_str(), channel->fd());
        }
    };
    srv.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
        // echo
        channel->write(buf);
    };
    srv.setThreadNum(4);
    srv.start();

    // press Enter to stop
    while (getchar() != '\n');
    return 0;
}

注意:

以上示例只是简单的echo服务,TCP是流式协议,实际应用中请务必添加边界进行拆包。
文本协议建议加上\0或者\r\n分隔符,可参考 examples/jsonrpc;
二进制协议建议加上自定义协议头,通过头部长度字段表明负载长度,可参考 examples/protorpc;
然后通过hio_set_unpackTcpServer::setUnpack设置拆包规则。
不想自定义协议和拆包组包的可直接使用现成的HTTP/WebSocket协议。

TCP客户端

c版本: examples/nc.c

#include "hloop.h"

static void on_close(hio_t* io) {
    printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io));
}

static void on_recv(hio_t* io, void* buf, int readbytes) {
    printf("< %.*s\n", readbytes, (char*)buf);
}

static void on_connect(hio_t* io) {
    hio_setcb_read(io, on_recv);
    hio_read(io);

    hio_write(io, "hello", 5);
}

int main() {
    const char host[] = "127.0.0.1";
    int port = 1234;
    hloop_t* loop = hloop_new(0);
    hio_t* io = hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE);
    if (io == NULL) {
        perror("socket");
        exit(1);
    }
    hio_setcb_connect(io, on_connect);
    hio_setcb_close(io, on_close);
    hio_connect(io);
    hloop_run(loop);
    hloop_free(&loop);
    return 0;
}

c++版本: evpp/TcpClient_test.cpp

#include "TcpClient.h"
using namespace hv;

int main() {
    int port = 1234;
    TcpClient cli;
    int connfd = cli.createsocket(port);
    if (connfd < 0) {
        return -1;
    }
    cli.onConnection = [](const SocketChannelPtr& channel) {
        std::string peeraddr = channel->peeraddr();
        if (channel->isConnected()) {
            printf("connected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
            channel->write("hello");
        } else {
            printf("disconnected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
        }
    };
    cli.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
        printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
    };
    cli.start();

    // press Enter to stop
    while (getchar() != '\n');
    return 0;
}

HTTP

HTTP服务端

examples/http_server_test.cpp

golang gin 风格

#include "HttpServer.h"

int main() {
    HttpService router;
    router.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
        return resp->String("pong");
    });

    router.GET("/data", [](HttpRequest* req, HttpResponse* resp) {
        static char data[] = "0123456789";
        return resp->Data(data, 10);
    });

    router.GET("/paths", [&router](HttpRequest* req, HttpResponse* resp) {
        return resp->Json(router.Paths());
    });

    router.GET("/get", [](HttpRequest* req, HttpResponse* resp) {
        resp->json["origin"] = req->client_addr.ip;
        resp->json["url"] = req->url;
        resp->json["args"] = req->query_params;
        resp->json["headers"] = req->headers;
        return 200;
    });

    router.POST("/echo", [](const HttpContextPtr& ctx) {
        return ctx->send(ctx->body(), ctx->type());
    });

    http_server_t server;
    server.port = 8080;
    server.service = &router;
    http_server_run(&server);
    return 0;
}

HTTP客户端

examples/http_client_test.cpp

python requests 风格

#include "requests.h"

int main() {
    auto resp = requests::get("http://www.example.com");
    if (resp == NULL) {
        printf("request failed!\n");
    } else {
        printf("%s\n", resp->body.c_str());
    }

    resp = requests::post("127.0.0.1:8080/echo", "hello,world!");
    if (resp == NULL) {
        printf("request failed!\n");
    } else {
        printf("%s\n", resp->body.c_str());
    }

    return 0;
}

js axios 风格

#include "axios.h"

int main() {
    const char* strReq = R"({
        "method": "POST",
        "url": "http://127.0.0.1:8080/echo",
        "params": {
            "page_no": "1",
            "page_size": "10"
        },
        "headers": {
            "Content-Type": "application/json"
        },
        "body": {
            "app_id": "123456",
            "app_secret": "abcdefg"
        }
    })";

    // sync
    auto resp = axios::axios(strReq);
    if (resp == NULL) {
        printf("request failed!\n");
    } else {
        printf("%s\n", resp->body.c_str());
    }

    // async
    int finished = 0;
    axios::axios(strReq, [&finished](const HttpResponsePtr& resp) {
        if (resp == NULL) {
            printf("request failed!\n");
        } else {
            printf("%s\n", resp->body.c_str());
        }
        finished = 1;
    });

    // wait async finished
    while (!finished) hv_sleep(1);
    return 0;
}

🍭 更多示例

c版本

c++版本

模拟实现著名的命令行工具

🥇 性能测试

TCP压测

cd echo-servers
./build.sh
./benchmark.sh

吞吐量:

libevent running on port 2001
libev running on port 2002
libuv running on port 2003
libhv running on port 2004
asio running on port 2005
poco running on port 2006

==============2001=====================================
[127.0.0.1:2001] 4 threads 1000 connections run 10s
total readcount=1616761 readbytes=1655563264
throughput = 157 MB/s

==============2002=====================================
[127.0.0.1:2002] 4 threads 1000 connections run 10s
total readcount=2153171 readbytes=2204847104
throughput = 210 MB/s

==============2003=====================================
[127.0.0.1:2003] 4 threads 1000 connections run 10s
total readcount=1599727 readbytes=1638120448
throughput = 156 MB/s

==============2004=====================================
[127.0.0.1:2004] 4 threads 1000 connections run 10s
total readcount=2202271 readbytes=2255125504
throughput = 215 MB/s

==============2005=====================================
[127.0.0.1:2005] 4 threads 1000 connections run 10s
total readcount=1354230 readbytes=1386731520
throughput = 132 MB/s

==============2006=====================================
[127.0.0.1:2006] 4 threads 1000 connections run 10s
total readcount=1699652 readbytes=1740443648
throughput = 165 MB/s

HTTP压测

# sudo apt install wrk
wrk -c 100 -t 4 -d 10s http://127.0.0.1:8080/

# sudo apt install apache2-utils
ab -c 100 -n 100000 http://127.0.0.1:8080/

libhv(port:8080) vs nginx(port:80)

libhv-vs-nginx.png

以上测试结果可以在 Github Actions 中查看。

📚 中文资料

💎 用户案例

如果您在使用libhv,欢迎通过PR将信息提交至此列表,让更多的用户了解libhv的实际使用场景,以建立更好的网络生态。

用户 (公司名/项目名/个人联系方式) 案例 (项目简介/业务场景)
阅面科技 猎户AIoT平台设备管理、人脸检测HTTP服务、人脸搜索HTTP服务
socks5-libhv socks5代理
hvloop 类似uvloop的python异步IO事件循环
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C/C++
1
https://gitee.com/Gerryfan/libhv.git
git@gitee.com:Gerryfan/libhv.git
Gerryfan
libhv
libhv
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891