Skip to content
13 changes: 13 additions & 0 deletions docs/cn/hloop.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ hwrite_cb hio_getcb_write(hio_t* io);
// 获取关闭回调
hclose_cb hio_getcb_close(hio_t* io);

// 回调语义见下文“IO回调语义与缓冲区生命周期”。

// 开启SSL/TLS加密通信
int hio_enable_ssl(hio_t* io);
// 是否SSL/TLS加密通信
Expand Down Expand Up @@ -311,6 +313,7 @@ int hio_accept (hio_t* io);

// 连接
// connect => hio_add(io, HV_WRITE) => hconnect_cb
// connect_cb表示应用层连接可用;配置代理或TLS时会在相应握手全部成功后才调用。
int hio_connect(hio_t* io);

// 读
Expand Down Expand Up @@ -347,10 +350,12 @@ int hio_read_until_delim (hio_t* io, unsigned char delim);

// 写
// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb
// write_cb报告实际写出的字节数;使用hio_write_is_complete判断写队列是否排空。
int hio_write (hio_t* io, const void* buf, size_t len);

// 关闭
// hio_del(io, HV_RDWR) => close => hclose_cb
// close_cb在一次hio生命周期中最多调用一次,适合释放连接关联的上下文。
int hio_close (hio_t* io);

// 异步关闭 (投递一个close事件)
Expand Down Expand Up @@ -644,6 +649,14 @@ int hio_set_kcp(hio_t* io, kcp_setting_t* setting DEFAULT(NULL));

```

### IO回调语义与缓冲区生命周期

- `connect_cb` 表示应用层连接已可用:若设置了代理或TLS,代理握手和TLS握手均已成功;它并不只是TCP三次握手完成。
- `write_cb` 的 `writebytes` 表示本次实际写出的字节数,不表示一条业务消息已经全部发送;需要通过 `hio_write_is_complete(io)` 判断写队列是否排空。
- `read_cb` 和 `write_cb` 都可能同步触发:例如 `hio_read` 处理已有读缓存,或 `hio_write` 立即写入成功时。因此回调代码必须能处理可重入调用。
- `close_cb` 在一次 `hio_t` 生命周期内最多触发一次,是释放连接关联上下文的合适位置。
- `read_cb` / `write_cb` 收到的 `buf` 由libhv借用提供,只保证在本次回调返回前有效;需要保存或跨异步边界使用时必须自行复制。

示例代码:

- 事件循环: [examples/hloop_test.c](../../examples/hloop_test.c)
Expand Down
1 change: 1 addition & 0 deletions event/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
├── unpack.h 拆包
├── rudp.h 可靠UDP
├── proxy.c 代理
├── tls.c TLS握手
├── socks5.c SOCKS5代理
├── iowatcher.h IO多路复用统一抽象接口
├── select.c EVENT_SELECT实现
Expand Down
3 changes: 3 additions & 0 deletions event/hevent.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ static void hio_socket_init(hio_t* io) {
}

void hio_init(hio_t* io) {
io->phase = HIO_PHASE_NONE;
// alloc localaddr,peeraddr when hio_socket_init
/*
if (io->localaddr == NULL) {
Expand Down Expand Up @@ -94,6 +95,7 @@ void hio_ready(hio_t* io) {
// public:
io->id = hio_next_id();
io->io_type = HIO_TYPE_UNKNOWN;
io->phase = HIO_PHASE_READY;
io->error = 0;
io->events = io->revents = 0;
io->last_read_hrtime = io->last_write_hrtime = io->loop->cur_hrtime;
Expand Down Expand Up @@ -426,6 +428,7 @@ void hio_write_cb(hio_t* io, const void* buf, int len) {
void hio_close_cb(hio_t* io) {
io->connected = 0;
io->closed = 1;
io->phase = HIO_PHASE_CLOSED;
hclose_cb close_cb = io->close_cb;
if (close_cb) {
// printd("close_cb------\n");
Expand Down
19 changes: 18 additions & 1 deletion event/hevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@
#define HIO_READ_UNTIL_LENGTH 0x2
#define HIO_READ_UNTIL_DELIM 0x4

// Internal NIO transport lifecycle. hio_handle_events is the single readiness
// dispatcher; phase selects its current transport or handshake action.
typedef enum {
HIO_PHASE_NONE = 0,
HIO_PHASE_READY,
HIO_PHASE_ACCEPTING,
HIO_PHASE_CONNECTING,
HIO_PHASE_PROXY_HANDSHAKING,
HIO_PHASE_PROXY_ESTABLISHED,
HIO_PHASE_TLS_SERVER_HANDSHAKING,
HIO_PHASE_TLS_CLIENT_HANDSHAKING,
HIO_PHASE_TLS_ESTABLISHED,
HIO_PHASE_ESTABLISHED,
HIO_PHASE_CLOSED,
} hio_phase_e;

ARRAY_DECL(hio_t*, io_array);
ARRAY_DECL(hsignal_t*, signal_array);
QUEUE_DECL(hevent_t, event_queue);
Expand Down Expand Up @@ -115,7 +131,7 @@ struct hperiod_s {
};

QUEUE_DECL(offset_buf_t, write_queue);
// sizeof(struct hio_s)=416 on linux-x64
// sizeof(struct hio_s)=424 on linux-x64
struct hio_s {
HEVENT_FIELDS
// flags
Expand All @@ -134,6 +150,7 @@ struct hio_s {
unsigned alloced_ssl_ctx :1; // for hio_new_ssl_ctx
// public:
hio_type_e io_type;
hio_phase_e phase;
uint32_t id; // fd cannot be used as unique identifier, so we provide an id
int fd;
int error;
Expand Down
5 changes: 5 additions & 0 deletions event/hloop.c
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,11 @@ int hio_add(hio_t* io, hio_cb cb, int events) {
io->events |= add_events;

if (!io->active) {
// A NULL callback only changes the watched mask. Keep the transport
// dispatcher installed by the initial hio_add call.
if (cb == NULL) {
cb = (hio_cb)io->cb;
}
EVENT_ADD(loop, io, cb);
loop->nios++;
}
Expand Down
17 changes: 15 additions & 2 deletions event/hloop.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ typedef void (*hread_cb) (hio_t* io, void* buf, int readbytes);
typedef void (*hwrite_cb) (hio_t* io, const void* buf, int writebytes);
typedef void (*hclose_cb) (hio_t* io);

/*
* Callback buffer lifetime: buf passed to hread_cb/hwrite_cb is borrowed from
* libhv and is valid only until the callback returns. Copy it before retaining
* it or using it asynchronously. hio_read/hio_write can deliver a callback
* synchronously (for buffered data or an immediately writable socket), so
* callback code must tolerate re-entry.
*/

typedef enum {
HLOOP_STATUS_STOP,
HLOOP_STATUS_RUNNING,
Expand Down Expand Up @@ -423,6 +431,8 @@ HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_
HV_EXPORT int hio_accept (hio_t* io);

// connect => hio_add(io, HV_WRITE) => hconnect_cb
// hconnect_cb means the application connection is ready: any configured proxy
// and TLS handshakes have completed. It is not merely TCP connect completion.
HV_EXPORT int hio_connect(hio_t* io);

// hio_add(io, HV_READ) => read => hread_cb
Expand All @@ -444,12 +454,15 @@ HV_EXPORT int hio_read_remain(hio_t* io);
#define hio_read_until(io, len) hio_read_until_length(io, len)

// NOTE: hio_write is thread-safe, locked by recursive_mutex, allow to be called by other threads.
// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb
// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb. hwrite_cb
// reports bytes written, not completion of a logical application message; use
// hio_write_is_complete(io) to determine whether the write queue is empty.
HV_EXPORT int hio_write (hio_t* io, const void* buf, size_t len);
HV_EXPORT int hio_sendto (hio_t* io, const void* buf, size_t len, struct sockaddr* addr);

// NOTE: hio_close is thread-safe, hio_close_async will be called actually in other thread.
// hio_del(io, HV_RDWR) => close => hclose_cb
// hio_del(io, HV_RDWR) => close => hclose_cb. hclose_cb is invoked at most once
// per hio lifecycle and is the place to release connection-associated state.
HV_EXPORT int hio_close (hio_t* io);
// NOTE: hloop_post_event(hio_close_event)
HV_EXPORT int hio_close_async(hio_t* io);
Expand Down
Loading
Loading