瀏覽代碼

show on_write

ithewei 3 年之前
父節點
當前提交
63510bc845
共有 2 個文件被更改,包括 50 次插入18 次删除
  1. 9 1
      README-CN.md
  2. 41 17
      examples/tinyhttpd.c

+ 9 - 1
README-CN.md

@@ -178,8 +178,16 @@ int main() {
 以上示例只是简单的`echo`服务,TCP是流式协议,实际应用中请务必添加边界进行拆包。<br>
 文本协议建议加上`\0`或者`\r\n`分隔符,可参考 [examples/jsonrpc](examples/jsonrpc);<br>
 二进制协议建议加上自定义协议头,通过头部长度字段表明负载长度,可参考 [examples/protorpc](examples/protorpc);<br>
-然后通过`hio_set_unpack`、`TcpServer::setUnpack`设置拆包规则。<br>
+通过`setUnpack`(c接口即`hio_set_unpack`)设置拆包规则,支持固定包长、分隔符、头部长度字段三种常见的拆包方式,<br>
+内部根据拆包规则处理粘包与分包,保证`onMessage`回调上来的是完整的一包数据,大大节省了上层处理粘包与分包的成本。<br>
 不想自定义协议和拆包组包的可直接使用现成的`HTTP/WebSocket`协议。<br>
+<br>
+`channel->write`(c接口即`hio_write`)是非阻塞的(事件循环异步编程里所有的一切都要求是非阻塞的),且多线程安全的。<br>
+发送大数据时应该做流控,通过`onWriteComplete`监听写完成事件,在可写时再发送下一帧数据。<br>
+具体示例代码可参考 [examples/tinyhttpd.c](examples/tinyhttpd.c) 中的 `http_serve_file`。<br>
+<br>
+`channel->close`(c接口即`hio_close`) 也是多线程安全的,这可以让网络IO事件循环线程里接收数据、拆包组包、反序列化后放入队列,<br>
+消费者线程/线程池从队列里取出数据、处理后发送响应和关闭连接,变得更加简单。<br>
 
 #### TCP客户端
 **c版本**: [examples/tcp_client_test.c](examples/tcp_client_test.c)

+ 41 - 17
examples/tinyhttpd.c

@@ -93,6 +93,9 @@ typedef struct {
     http_state_e    state;
     http_msg_t      request;
     http_msg_t      response;
+    // for http_serve_file
+    FILE*           fp;
+    hbuf_t          filebuf;
 } http_conn_t;
 
 static char s_date[32] = {0};
@@ -152,6 +155,33 @@ static int http_reply(http_conn_t* conn,
     return nwrite < 0 ? nwrite : msglen;
 }
 
+static void http_send_file(http_conn_t* conn) {
+    if (!conn || !conn->fp) return;
+    // alloc filebuf
+    if (!conn->filebuf.base) {
+        conn->filebuf.len = 4096;
+        HV_ALLOC(conn->filebuf, conn->filebuf.len);
+    }
+    char* filebuf = conn->filebuf.base;
+    size_t filebuflen = conn->filebuf.len;
+    // read file
+    int nread = fread(filebuf, 1, filebuflen, conn->fp);
+    if (nread <= 0) {
+        // eof or error
+        hio_close(conn->io);
+        return;
+    }
+    // send file
+    hio_write(conn->io, filebuf, nread);
+}
+
+static void on_write(hio_t* io, const void* buf, int writebytes) {
+    if (!io) return;
+    if (!hio_write_is_complete(io)) return;
+    http_conn_t* conn = (http_conn_t*)hevent_userdata(io);
+    http_send_file(conn);
+}
+
 static int http_serve_file(http_conn_t* conn) {
     http_msg_t* req = &conn->request;
     http_msg_t* resp = &conn->response;
@@ -161,12 +191,12 @@ static int http_serve_file(http_conn_t* conn) {
     if (*filepath == '\0') {
         filepath = "index.html";
     }
-    FILE* fp = fopen(filepath, "rb");
-    if (!fp) {
+    // open file
+    conn->fp = fopen(filepath, "rb");
+    if (!conn->fp) {
         http_reply(conn, 404, NOT_FOUND, TEXT_HTML, HTML_TAG_BEGIN NOT_FOUND HTML_TAG_END, 0);
         return 404;
     }
-    char buf[4096] = {0};
     // send head
     size_t filesize = hv_filesize(filepath);
     resp->content_length = filesize;
@@ -177,22 +207,9 @@ static int http_serve_file(http_conn_t* conn) {
     } else {
         // TODO: set content_type by suffix
     }
+    hio_setcb_write(conn->io, on_write);
     int nwrite = http_reply(conn, 200, "OK", content_type, NULL, 0);
     if (nwrite < 0) return nwrite; // disconnected
-    // send file
-    int nread = 0;
-    while (1) {
-        nread = fread(buf, 1, sizeof(buf), fp);
-        if (nread <= 0) break;
-        nwrite = hio_write(conn->io, buf, nread);
-        if (nwrite < 0) return nwrite; // disconnected
-        if (nwrite == 0) {
-            // send too fast or peer recv too slow
-            // WARN: too large file should control sending rate, just delay a while in the demo!
-            hv_delay(10);
-        }
-    }
-    fclose(fp);
     return 200;
 }
 
@@ -263,6 +280,13 @@ static void on_close(hio_t* io) {
     // printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io));
     http_conn_t* conn = (http_conn_t*)hevent_userdata(io);
     if (conn) {
+        if (conn->fp) {
+            // close file
+            fclose(conn->fp);
+            conn->fp = NULL;
+        }
+        // free filebuf
+        HV_FREE(conn->filebuf.base);
         HV_FREE(conn);
         hevent_set_userdata(io, NULL);
     }