• mongoose 应用程序开发流程


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

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

    1. int main(int argc, char *argv[])
    2. {
    3. if(argc < 2)
    4. {
    5. errorf("Usage: %s port\n", argv[0]);
    6. return 1;
    7. }
    8. int port = atoi(argv[1]);
    9. // webStart();
    10. struct userData uData{100};
    11. struct mg_mgr mgr;
    12. //mgr里的user_data指针将会指向第二个参数,当有连接过来时
    13. //mg_connection中的mgr中的user_data指向的就是第二个参数
    14. mg_mgr_init(&mgr, &uData);
    15. char buf[32] = {0};
    16. snprintf(buf, sizeof(buf), "%d", port);
    17. struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);
    18. if(con == NULL) {
    19. errorf("mg_bind fail\n");
    20. return -1;
    21. }
    22. mg_set_protocol_http_websocket(con);
    23. uData.index = 200;
    24. infof("listen ip[%s], port[%d]....\n", inet_ntoa(con->sa.sin.sin_addr), port);
    25. //uri是/fileUpload 时调用函数fileUpload
    26. // mg_register_http_endpoint(con, "/fileUpload", fileUpload);
    27. while (1)
    28. {
    29. mg_mgr_poll(&mgr, 100);
    30. sleep(1);
    31. }
    32. mg_mgr_free(&mgr);
    33. return 0;
    34. }

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

    1,mg_mgr_init()

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

    1. struct mg_mgr {
    2. struct mg_connection *active_connections;
    3. #if MG_ENABLE_HEXDUMP
    4. const char *hexdump_file; /* Debug hexdump file path */
    5. #endif
    6. #if MG_ENABLE_BROADCAST
    7. sock_t ctl[2]; /* Socketpair for mg_broadcast() */
    8. #endif
    9. void *user_data; /* User data */
    10. int num_ifaces;
    11. int num_calls;
    12. struct mg_iface **ifaces; /* network interfaces */
    13. const char *nameserver; /* DNS server to use */
    14. };

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

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

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

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

    1. /* clang-format off */
    2. #define MG_SOCKET_IFACE_VTABLE \
    3. { \
    4. mg_socket_if_init, \
    5. mg_socket_if_free, \
    6. mg_socket_if_add_conn, \
    7. mg_socket_if_remove_conn, \
    8. mg_socket_if_poll, \
    9. mg_socket_if_listen_tcp, \
    10. mg_socket_if_listen_udp, \
    11. mg_socket_if_connect_tcp, \
    12. mg_socket_if_connect_udp, \
    13. mg_socket_if_tcp_send, \
    14. mg_socket_if_udp_send, \
    15. mg_socket_if_tcp_recv, \
    16. mg_socket_if_udp_recv, \
    17. mg_socket_if_create_conn, \
    18. mg_socket_if_destroy_conn, \
    19. mg_socket_if_sock_set, \
    20. mg_socket_if_get_conn_addr, \
    21. }
    22. /* clang-format on */

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

    1. struct mg_iface_vtable {
    2. void (*init)(struct mg_iface *iface);
    3. void (*free)(struct mg_iface *iface);
    4. void (*add_conn)(struct mg_connection *nc);
    5. void (*remove_conn)(struct mg_connection *nc);
    6. time_t (*poll)(struct mg_iface *iface, int timeout_ms);
    7. /* Set up a listening TCP socket on a given address. rv = 0 -> ok. */
    8. int (*listen_tcp)(struct mg_connection *nc, union socket_address *sa);
    9. /* Request that a "listening" UDP socket be created. */
    10. int (*listen_udp)(struct mg_connection *nc, union socket_address *sa);
    11. /* Request that a TCP connection is made to the specified address. */
    12. void (*connect_tcp)(struct mg_connection *nc, const union socket_address *sa);
    13. /* Open a UDP socket. Doesn't actually connect anything. */
    14. void (*connect_udp)(struct mg_connection *nc);
    15. /* Send functions for TCP and UDP. Sent data is copied before return. */
    16. int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
    17. int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
    18. int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);
    19. int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,
    20. union socket_address *sa, size_t *sa_len);
    21. /* Perform interface-related connection initialization. Return 1 on ok. */
    22. int (*create_conn)(struct mg_connection *nc);
    23. /* Perform interface-related cleanup on connection before destruction. */
    24. void (*destroy_conn)(struct mg_connection *nc);
    25. /* Associate a socket to a connection. */
    26. void (*sock_set)(struct mg_connection *nc, sock_t sock);
    27. /* Put connection's address into *sa, local (remote = 0) or remote. */
    28. void (*get_conn_addr)(struct mg_connection *nc, int remote,
    29. union socket_address *sa);
    30. };

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

    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地址和端口,也可以只写端口号,如:

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

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

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

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

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

    3,mg_set_protocol_http_websocket()

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

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

     4,mg_mgr_poll()

    1. int mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
    2. int i, num_calls_before = m->num_calls;
    3. infof("num_ifaces = %d\n", m->num_ifaces);
    4. for (i = 0; i < m->num_ifaces; i++) {
    5. m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
    6. }
    7. return (m->num_calls - num_calls_before);
    8. }

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

    1. while (1)
    2. {
    3. mg_mgr_poll(&mgr, 100);
    4. sleep(1);
    5. }

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

    5,mg_mgr_free()

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

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

  • 相关阅读:
    数据库(mysql)之事务和存储引擎
    一种解决多旅行商问题的改进的遗传算法
    【Python中is和==的区别】
    【软考经验分享】软考-中级-嵌入式备考
    【电商】电商后台系统整体介绍
    点成分享 | 微流控技术集成系统的应用
    【测试开发】Mq消息重复如何测试?
    ElasticSearch(九):ELK 架构
    关于RabbitMQ的小总结
    荧光标记转铁蛋白-(FITC, cy3, cy5, cy7, 香豆素, 罗丹明)
  • 原文地址:https://blog.csdn.net/tianyexing2008/article/details/126563370