libhv教程06--创建一个简单的TCP服务端
下文以TCP echo server为例,使用libhv创建TCP服务端。- `setThreadNum`:设置IO线程数- `setMaxConnectionNum`:设置最大连接数- `setLoadBalance`: 设置负载均衡策略(轮询、随机、最少连接数)- `setUnpack`:设置拆包规则(固定包长、分界符、头部长度字段)- `withTLS`:SSL/TLS加密通信
下文以TCP echo server
为例,使用libhv创建TCP服务端。
c版本
#include "hv/hloop.h"
void on_close(hio_t* io) {
}
void on_recv(hio_t* io, void* buf, int readbytes) {
// 回显数据
hio_write(io, buf, readbytes);
}
void on_accept(hio_t* io) {
// 设置close回调
hio_setcb_close(io, on_close);
// 设置read回调
hio_setcb_read(io, on_recv);
// 开始读
hio_read(io);
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: cmd port\n");
return -10;
}
int port = atoi(argv[1]);
// 创建事件循环
hloop_t* loop = hloop_new(0);
// 创建TCP服务
hio_t* listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
if (listenio == NULL) {
return -20;
}
// 运行事件循环
hloop_run(loop);
// 释放事件循环
hloop_free(&loop);
return 0;
}
编译运行:
$ cc examples/tcp_echo_server.c -o bin/tcp_echo_server -I/usr/local/include/hv -lhv
$ bin/tcp_echo_server 1234
类unix系统可使用nc作为客户端测试:
$ nc 127.0.0.1 1234
< hello
> hello
windows端可使用telnet作为客户端测试:
$ telent 127.0.0.1 1234
更多TCP服务端示例参考:
hio_t
更多实用接口:
hio_enable_ssl
:启用SSL/TLS加密通信hio_set_connect_timeout
:设置连接超时(仅用作TCP客户端)hio_set_read_timeout
:设置读超时,一段时间没有数据接收,自动断开连接hio_set_write_timeout
:设置写超时,一段时间没有数据发送,自动断开连接hio_set_keepalive_timeout
:设置keepalive
超时,一段时间没有数据读写,自动断开连接hio_set_heartbeat
: 设置心跳hio_set_unpack
:设置拆包规则,支持固定包长、分隔符、头部长度字段
三种常见的拆包方式,内部根据拆包规则处理粘包与分包,保证回调上来的是完整的一包数据,大大节省了上层处理粘包与分包的成本hio_read_until_length
:读取数据直到指定长度才回调上来hio_read_until_delim
:读取数据直到遇到分割符才回调上来hio_setup_upstream
:设置转发
c++版本
代码示例参考evpp/TcpServer_test.cpp
#include "hv/TcpServer.h"
using namespace hv;
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: %s port\n", argv[0]);
return -10;
}
int port = atoi(argv[1]);
TcpServer srv;
int listenfd = srv.createsocket(port);
if (listenfd < 0) {
return -20;
}
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
printf("< %.*s\n", (int)buf->size(), (char*)buf->data());
channel->write(buf);
};
srv.onWriteComplete = [](const SocketChannelPtr& channel, Buffer* buf) {
printf("> %.*s\n", (int)buf->size(), (char*)buf->data());
};
srv.setThreadNum(4);
srv.start();
// press Enter to stop
while (getchar() != '\n');
return 0;
}
编译运行:
$ c++ -std=c++11 evpp/TcpServer_test.cpp -o bin/TcpServer_test -I/usr/local/include/hv -lhv -lpthread
$ bin/TcpServer_test 5678
TcpServer
更多实用接口
setThreadNum
:设置IO线程数setMaxConnectionNum
:设置最大连接数setLoadBalance
: 设置负载均衡策略(轮询、随机、最少连接数)setUnpack
:设置拆包规则(固定包长、分界符、头部长度字段)withTLS
:SSL/TLS加密通信broadcast
: 广播
Tips
:
以上示例只是简单的echo服务,TCP是流式协议
,实际应用中请务必添加边界进行拆包。
文本协议建议加上\0
或者\r\n
分隔符,可参考 examples/jsonrpc;
二进制协议建议加上自定义协议头,通过头部长度字段表明负载长度,可参考 examples/protorpc;
然后通过hio_set_unpack
、TcpClient::setUnpack
、TcpServer::setUnpack
设置拆包规则。
不想自定义协议和拆包组包的可直接使用现成的HTTP/WebSocket
协议;
Tips:
hio_write
是非阻塞的(事件循环异步编程里所有的一切都要求是非阻塞的),且多线程安全的,关于hio_write
的实现这里简单介绍下,每个hio_t
的内部都会维护一个写队列write_queue
,调用hio_write
时会上锁检查写队列是否为空,为空会先尝试发送,一般小于系统发送缓冲区的数据在这一步就直接写入了,如果没写完的就会放入hio_t
的写队列(深拷贝,不依赖上层buffer),监听可写事件,在可写时再从写队列顺序取出数据发送,即使调用hio_write
后马上hio_close
(短连接发送完响应后马上close
是常规操作,而实际期望是写数据完成再断开连接),如果写队列非空,也不会马上关闭连接,而是起了一个close_timer
定时器,等待写完成后或者定时器超时(默认是一分钟,可以通过hio_set_close_timeout设置该值)才执行真正的close
操作。hio_write
的返回值也和阻塞里的write/send
返回值有所不同,有效范围是[0, len]
,<0
代表出错,=0
表示该次一个字节也没有写入,并不是断链了,=len
表示写入了全部数据到系统发送缓冲区,<len
也是正常现象(一般发送几M以上的数据时就会发生),表示一部分写入了系统发送缓冲区,另外一部分写入了hio_t的写队列;所以hio_write
的返回值并不需要关心和判断,如果是<0
也会统一触发close回调
的,不需要再次手动调用hio_close
,而对于小数据量的发送(几十M以下),我们也根本就不需关心写完成回调,只有在发送大数据量时我们才需要做数据分片
和发送速率控制
,避免全部积压到写队列导致内存爆炸;hio_close
也是多线程安全的,这可以让网络IO事件循环线程里接收数据、拆包组包、反序列化后放入队列
,消费者线程从队列里取出数据、处理后发送响应和关闭连接
,变得更加简单;- 所以
libhv
根本用不着libevent
的bufferevent
,也比libuv
的uv_write
方便的多,我认为这正是libhv
比libevent
和libuv
使用简单最重要的一点(并不是libhv
还提供了c++封装
和HTTP/WebSocket实现
,虽然这也是很重要的基础设施,libevent、libuv
因为局限于c语言并没有提供官方c++封装,evhttp
因为是c语言的接口也是相当难用,libwebsockets
更是难用)。 - 有人说没必要使用网络库,直接使用
epoll
不行吗?也就socket->bind->listen->accept
、epoll_create、epoll_ctl、epoll_wait
几个系统API,当然可以,但当你真正工程实践时,且不论跨平台
,定时心跳
、定时推送
需要用到定时器
、write、close的线程安全问题
、非阻塞写队列
的维护、读缓冲readbuf的自动扩缩容
、粘包分包
的处理、负载均衡
策略,哪一项不是有挑战性的难题,久而久之自然就形成了一层封装,形成所谓的网络库,而且网络库需要时间的沉淀和考验。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)