103 Star 833 Fork 262

GVPlibhv / libhv

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
BSD-3-Clause

English | 中文

libhv

Linux Windows macOS Android iOS benchmark
release stars forks issues PRs contributors license
gitee awesome-c awesome-cpp

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

📚 中文资料

✨ 特性

  • 跨平台(Linux, Windows, macOS, Android, iOS, BSD, Solaris)
  • 高性能事件循环(网络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风格、路由、中间件、keep-alive长连接、chunked分块、SSE等特性
  • WebSocket服务端/客户端
  • MQTT客户端

⌛️ 构建

BUILD.md

libhv提供了以下构建方式:

1、通过Makefile:

./configure
make
sudo make install

2、通过cmake:

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

3、通过bazel:

bazel build libhv

4、通过vcpkg:

vcpkg install libhv

5、通过xmake:

xrepo install libhv

⚡️ 快速入门

体验

运行脚本./getting_started.sh:

# 下载编译
git clone https://github.com/ithewei/libhv.git
cd libhv
./configure
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

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;
通过setUnpack(c接口即hio_set_unpack)设置拆包规则,支持固定包长、分隔符、头部长度字段三种常见的拆包方式,
内部根据拆包规则处理粘包与分包,保证onMessage回调上来的是完整的一包数据,大大节省了上层处理粘包与分包的成本。
不想自定义协议和拆包组包的可直接使用现成的HTTP/WebSocket协议。

channel->write(c接口即hio_write)是非阻塞的(事件循环异步编程里所有的一切都要求是非阻塞的),且多线程安全的。
发送大数据时应该做流控,通过onWriteComplete监听写完成事件,在可写时再发送下一帧数据。
具体示例代码可参考 examples/tinyhttpd.c 中的 http_serve_file

channel->close(c接口即hio_close) 也是多线程安全的,这可以让网络IO事件循环线程里接收数据、拆包组包、反序列化后放入队列,
消费者线程/线程池从队列里取出数据、处理后发送响应和关闭连接,变得更加简单。

TCP客户端

c版本: examples/tcp_client_test.c

c++版本: evpp/TcpClient_test.cpp

#include <iostream>
#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());
        } 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();

    std::string str;
    while (std::getline(std::cin, str)) {
        if (str == "close") {
            cli.closesocket();
        } else if (str == "start") {
            cli.start();
        } else if (str == "stop") {
            cli.stop();
            break;
        } else {
            if (!cli.isConnected()) break;
            cli.send(str);
        }
    }
    return 0;
}

HTTP

HTTP服务端

examples/http_server_test.cpp

golang gin 风格

#include "HttpServer.h"
using namespace hv;

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());
    });

    HttpServer server(&router);
    server.setPort(8080);
    server.setThreadNum(4);
    server.run();
    return 0;
}

注意:

上面示例直接运行在main主线程,server.run()会阻塞当前线程运行,所以routerserver对象不会被析构,
如使用server.start()内部会另起线程运行,不会阻塞当前线程,但需要注意routerserver的生命周期,
不要定义为局部变量被析构了,可定义为类成员变量或者全局变量,下面的WebSocket服务同理。

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;
}

附HTTP相关接口文档:

WebSocket

WebSocket服务端

examples/websocket_server_test.cpp

#include "WebSocketServer.h"
using namespace hv;

int main(int argc, char** argv) {
    WebSocketService ws;
    ws.onopen = [](const WebSocketChannelPtr& channel, const HttpRequestPtr& req) {
        printf("onopen: GET %s\n", req->Path().c_str());
    };
    ws.onmessage = [](const WebSocketChannelPtr& channel, const std::string& msg) {
        printf("onmessage: %.*s\n", (int)msg.size(), msg.data());
    };
    ws.onclose = [](const WebSocketChannelPtr& channel) {
        printf("onclose\n");
    };

    WebSocketServer server(&ws);
    server.setPort(9999);
    server.setThreadNum(4);
    server.run();
    return 0;
}

WebSocket客户端

examples/websocket_client_test.cpp

#include "WebSocketClient.h"
using namespace hv;

int main(int argc, char** argv) {
    WebSocketClient ws;
    ws.onopen = []() {
        printf("onopen\n");
    };
    ws.onmessage = [](const std::string& msg) {
        printf("onmessage: %.*s\n", (int)msg.size(), msg.data());
    };
    ws.onclose = []() {
        printf("onclose\n");
    };

    // reconnect: 1,2,4,8,10,10,10...
    reconn_setting_t reconn;
    reconn_setting_init(&reconn);
    reconn.min_delay = 1000;
    reconn.max_delay = 10000;
    reconn.delay_policy = 2;
    ws.setReconnect(&reconn);

    ws.open("ws://127.0.0.1:9999/test");

    std::string str;
    while (std::getline(std::cin, str)) {
        if (!ws.isConnected()) break;
        if (str == "quit") {
            ws.close();
            break;
        }
        ws.send(str);
    }

    return 0;
}

🍭 更多示例

c版本

c++版本

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

🥇 性能测试

TCP回显服务pingpong测试

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

TCP代理服务压测

# sudo apt install iperf
iperf -s -p 5001 > /dev/null &
bin/tcp_proxy_server 1212 127.0.0.1:5001 &
iperf -c 127.0.0.1 -p 5001 -l 8K
iperf -c 127.0.0.1 -p 1212 -l 8K

带宽:

------------------------------------------------------------
[  3] local 127.0.0.1 port 52560 connected with 127.0.0.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  20.8 GBytes  17.9 Gbits/sec

------------------------------------------------------------
[  3] local 127.0.0.1 port 48142 connected with 127.0.0.1 port 1212
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  11.9 GBytes  10.2 Gbits/sec

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事件循环
tsproxyd-android 一个基于libhv实现的android端web代理服务
玄舟智维 C100K设备连接网关服务
BSD 3-Clause License Copyright (c) 2020, ithewei All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

简介

🔥 比libevent/libuv/asio更易用的国产网络库,用来开发 TCP/UDP/SSL/HTTP/WebSocket/MQTT 客户端/服务端 展开 收起
BSD-3-Clause
取消

发行版 (9)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
C++
1
https://gitee.com/libhv/libhv.git
git@gitee.com:libhv/libhv.git
libhv
libhv
libhv
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891