mongoose 应用程序开发流程

        mongoose 是一个支持多协议的开源代码,这里主要结合 mongoose 源码梳理一下应用程序开发的基本步骤。

        一般的 main 函数或是启动 mongoose 监听功能的代码,如下:

int main(int argc, char *argv[])
{   
    if(argc < 2)
    {
        errorf("Usage: %s port\n", argv[0]);
        return 1;
    }

    int port = atoi(argv[1]);

    // webStart();

    struct userData uData{100};
    struct mg_mgr mgr;

    //mgr里的user_data指针将会指向第二个参数,当有连接过来时
    //mg_connection中的mgr中的user_data指向的就是第二个参数
    mg_mgr_init(&mgr, &uData);

    char buf[32] = {0};
    snprintf(buf, sizeof(buf), "%d", port);
    struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);

    if(con == NULL) {
        errorf("mg_bind fail\n");
        return -1;
    }

    mg_set_protocol_http_websocket(con);

    uData.index = 200;
    infof("listen ip[%s], port[%d]....\n", inet_ntoa(con->sa.sin.sin_addr), port);

    //uri是/fileUpload 时调用函数fileUpload
    // mg_register_http_endpoint(con, "/fileUpload", fileUpload);
    
    while (1)
    {
        mg_mgr_poll(&mgr, 100);
        sleep(1);
    }
    
    mg_mgr_free(&mgr);
    return 0;
}

其中涉及到 mongoose 接口的就有 5 个,下面我们一起看一下这 5 个接口主要做了哪些工作。

1,mg_mgr_init()

函数原型是:void mg_mgr_init(struct mg_mgr *mgr, void *user_data); 这个接口就是对 mgr 参数进行赋值操作,其实第二个参数也会赋值到 mgr 中,struct mg_mgr 结构体中就一个指针参数

struct mg_mgr {
  struct mg_connection *active_connections;
#if MG_ENABLE_HEXDUMP
  const char *hexdump_file; /* Debug hexdump file path */
#endif
#if MG_ENABLE_BROADCAST
  sock_t ctl[2]; /* Socketpair for mg_broadcast() */
#endif
  void *user_data; /* User data */
  int num_ifaces;
  int num_calls;
  struct mg_iface **ifaces; /* network interfaces */
  const char *nameserver;   /* DNS server to use */
};

中的 void *user_data; 第二个参数就是赋值给 user_data。

那这个 user_data 指针有什么用呢?实际使用时这个指针会保存某个类(单例)的地址,这样在事件触发时,回调函数里的参数 struct mg_connection *nc 就带着这个指针,这样的话就可以把这个指针转换成你实际的类,然后进行类操作,比如事件回调函数是全局的,但实际处理事件的是一个单例类,就可以这样:

static void onHttpEvents(mg_connection *nc, int event, void *eventData) {
    WebSvrImpl* server = (WebSvrImpl*)(nc->mgr->user_data);
    server->onEvent(nc,event,eventData,true);
}

这个初始化函数最主要的工作是指定了与 socket 相关的函数接口,如 listen、poll、connect、send、recv 等函数,这些函数才是真正操作 socket 的函数,而我们代码里调用的一些发送接口,如:mg_send() 接口只是把数据发送到 mg_connection 的 send_mbuf 缓冲区,下面的 mg_socket_if_tcp_send 接口才是真正发送数据的。这些默认接口为:

/* clang-format off */
#define MG_SOCKET_IFACE_VTABLE                                          \
  {                                                                     \
    mg_socket_if_init,                                                  \
    mg_socket_if_free,                                                  \
    mg_socket_if_add_conn,                                              \
    mg_socket_if_remove_conn,                                           \
    mg_socket_if_poll,                                                  \
    mg_socket_if_listen_tcp,                                            \
    mg_socket_if_listen_udp,                                            \
    mg_socket_if_connect_tcp,                                           \
    mg_socket_if_connect_udp,                                           \
    mg_socket_if_tcp_send,                                              \
    mg_socket_if_udp_send,                                              \
    mg_socket_if_tcp_recv,                                              \
    mg_socket_if_udp_recv,                                              \
    mg_socket_if_create_conn,                                           \
    mg_socket_if_destroy_conn,                                          \
    mg_socket_if_sock_set,                                              \
    mg_socket_if_get_conn_addr,                                         \
  }
/* clang-format on */

这些默认接口会被初始化到一个vtable变量里:m->ifaces[i]->vtable->init(m->ifaces[i]); 而这个init函数指针就是指向了上面的 MG_SOCKET_IFACE_VTABLE

struct mg_iface_vtable {
  void (*init)(struct mg_iface *iface);
  void (*free)(struct mg_iface *iface);
  void (*add_conn)(struct mg_connection *nc);
  void (*remove_conn)(struct mg_connection *nc);
  time_t (*poll)(struct mg_iface *iface, int timeout_ms);

  /* Set up a listening TCP socket on a given address. rv = 0 -> ok. */
  int (*listen_tcp)(struct mg_connection *nc, union socket_address *sa);
  /* Request that a "listening" UDP socket be created. */
  int (*listen_udp)(struct mg_connection *nc, union socket_address *sa);

  /* Request that a TCP connection is made to the specified address. */
  void (*connect_tcp)(struct mg_connection *nc, const union socket_address *sa);
  /* Open a UDP socket. Doesn't actually connect anything. */
  void (*connect_udp)(struct mg_connection *nc);

  /* Send functions for TCP and UDP. Sent data is copied before return. */
  int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
  int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);

  int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);
  int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,
                  union socket_address *sa, size_t *sa_len);

  /* Perform interface-related connection initialization. Return 1 on ok. */
  int (*create_conn)(struct mg_connection *nc);
  /* Perform interface-related cleanup on connection before destruction. */
  void (*destroy_conn)(struct mg_connection *nc);

  /* Associate a socket to a connection. */
  void (*sock_set)(struct mg_connection *nc, sock_t sock);

  /* Put connection's address into *sa, local (remote = 0) or remote. */
  void (*get_conn_addr)(struct mg_connection *nc, int remote,
                        union socket_address *sa);
};

这些函数指针的调用就是调用上面定义的那些函数了。 

2,mg_bind()

struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);

mg_bind 实际调用的是函数

return mg_bind_opt(srv, address, MG_CB(event_handler, user_data), opts);

mg_bind_opt()函数主要做地址解析,就是第二个参数 address,可以指定 tcp 或 udp,指定监听的 IP地址和端口,也可以只写端口号,如:

int port = atoi(argv[1]);    
char buf[32] = {0};
snprintf(buf, sizeof(buf), "%d", port);
struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);

这里从命令行参数里获取要绑定的端口号,传递到 mg_bind() 的第二个参数里。

上面说到的 user_data 就是在 mg_create_connection() 里赋值给创建的  struct mg_connection 变量进行返回,即 nc。往下就是进行监听和链接添加入管理

  if (nc->flags & MG_F_UDP) {
    rc = nc->iface->vtable->listen_udp(nc, &nc->sa);
  } else {
    rc = nc->iface->vtable->listen_tcp(nc, &nc->sa);
  }
  if (rc != 0) {
    fprintf(stderr, "Failed to open listener: %d", rc);
    MG_SET_PTRPTR(opts.error_string, "failed to open listener");
    mg_destroy_conn(nc, 1 /* destroy_if */);
    return NULL;
  }
  mg_add_conn(nc->mgr, nc);

执行完 mg_bind() 监听socket 已经创建。

3,mg_set_protocol_http_websocket()

void mg_set_protocol_http_websocket(struct mg_connection *nc) {
  nc->proto_handler = mg_http_handler;
}

就指定了一个回调函数。函数主要进行消息解析,然后进行事件的回调,即用户注册进来的回调函数。

 4,mg_mgr_poll()

int mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
  int i, num_calls_before = m->num_calls;
  infof("num_ifaces = %d\n", m->num_ifaces);
  for (i = 0; i < m->num_ifaces; i++) {
    m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
  }

  return (m->num_calls - num_calls_before);
}

这个函数是进行 poll 操作,但实际用的是 select() 系统调用,而不是 poll 和 epoll 函数。poll 函数指针指向的是这个函数 mg_socket_if_poll(),函数里调用 select() 系统调用。所以应用程序里 mg_mgr_poll() 一般是循环调用,即不停地判断是否有可读、可写或出错的 fd。

    while (1)
    {
        mg_mgr_poll(&mgr, 100);
        sleep(1);
    }

一秒执行一次 select () 的打印信息:

5,mg_mgr_free()

释放操作函数,也许程序要一直运行,但还是要在异常情况下或手动停止情况下进行资源的释放操作。

通过这几个函数基本框架就可以搭建起来了,最后剩下的就是业务处理模块了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值