ngxin系列--(二)--stream模块的加载、accept、read/write

本文主要介绍了stream流程的:模块启动、accept、read/write

20241021 22:59 遗留问题:在哪里写回响应,是不是写回响应也是一个handler,而不是单独的函数?今天下班了。已解决,就是在handler中处理

1:stream处理流程

upstream和downstream:downstream代表客户端,upstream代表后端服务。客户端发来一个请求到nginx,最先到到downstream流程代码,然后downstream流程把请求转发给backendserver,当backendserver处理完后会发消息给nginx,此时与nginx通信的就是upstream,nginx收到后通过upstrema流程发给client

1-1:模块启动和listenfd注册到epoll流程

总结下来流程就是:1:解析配置文件,边读边解析,递归处理嵌套配置块,不管是最顶层的还是嵌套的配置块,一个配置块都对应一个命令,每个命令都包含一个set函数,每读取到一个配置块就调用命令的set函数来解析该配置块

nginx.main
  ngx_module.ngx_preinit_modules                    #给模块编号,ngxin_modules是一个全局数组,保存了所有模块,每个元素是一个ngx_module_t结构体
                                                    #每个ngx_module_t结构体内含有一个ngx_command_t数组和ngx_core_module_t变量,
                                                    #这两个变量都是是全局变量,ngx_modules标识一个模块,ngx_command_t标识一个模块支持哪些模块命令
                                                    #ngx_core_module_t变量是模块的上下文变量,包含了一系列函数指针
                                                    #ngx_module_names是一个module名字数组
                                                    #!!!ngx_modules保存的是模块对应的信息,ngx_command_t是一个数组,可以包含多个command,
                                                    #!!!每个command对应一个配置块,command的名字就是配置块的名字,
                                                    #!!!解析一个配置块就是调用对应command的set函数
                                                    #!!!一个配置块与他内部嵌套的配置块对应的配置命令可能会放在不同的模块内。
                                                    #就是说一个http模块,对应配置为http{ server{},servername=xx,},
                                                    #http配置块对应的命令放在http_modules模块中
                                                    #但是server配置块的信息却放在http_core_modules块中,最顶层的块一般只有一个命令,
                                                    #主要是用来标识模块,但是块内的嵌套块却可以有多个,因为一个模块内可以有多个配置块
                                                    #每个配置块对应一个模块,如上所示,http是最顶层的块,标识检测到了一个http模块
                                                    #而server和servername则是http模块内的两个嵌套块(server嵌套块还可以有嵌套块)
                                                    #一旦解析到http块就会调用http模块的http命令的set函数(http模块只有一个http命令)
                                                    #一旦解析到server块或者servername块,就会调用ngx_http_core_module中对应命令的set函数
    for (i = 0; ngx_modules[i]; i++) {  
        ngx_modules[i]->index = i;
        ngx_modules[i]->name = ngx_module_names[i];
    }
  ngx_cycle.ngx_init_cycle
    ngx_conf_file.ngx_conf_parse           #解析conf文件,根据conf文件里的配置块来调用对应模块命令的set
      for ; ;                              #for conf文件的每一个配置块,调用对应模块的对应命令的set函数
        ngx_conf_file.ngx_conf_read_token  #读取一个token
        ngx_conf_file.ngx_conf_handler     #就是处理块比如workProcessor块、http/stream模块下面举例
          if name== modules[i]->commands->name:      #name就是配置块的名字,modules[i]->commands就是模块对应的命令,
                                                     #commands->name就是该命令对应的名字
                                                     #也就是说nginx根据conf文件中块的名字来匹配对应的模块以及调用对应的模块命令的set函数
                                                     #备注:一个模块可以有多个命令
            cmd=modules[i]->commands
          cmd->set                                   #cmd->set就是各个模块对应的set函数,不同的模块对应不同的函数
                                                     #比如stream模块就调用ngx_stream_block,http就用ngx_http_block
                                                     #!!!也就是说nginx用函数指针来实现了一种多态的效果
                                                     #!!!也就是说可以用同一套代码流程来加载http、stream模块
          1: worker_processes 块
            nginx.ngx_set_worker_processes   #对应配置文件里的 worker_processes 配置块
          2:event块
            ngx_event.ngx_events_block  
          3: http块,这是最顶层的http 块   
            ngx_http.ngx_http_block                         #http模块set函数,包括很多操作,比如初始化好几张表结构(如loaction)等
                                                            #block是块的意思,不是阻塞的意思
              ngx_conf_file.ngx_conf_parse                  #递归处理http嵌套块,ngxin是递归嵌套解析的,比如http里的server块
                ngx_conf_file.ngx_conf_handler  
                  ngx_http_core_module.ngx_http_core_server #这里以server块为例
                    ...略...

              ngx_http.ngx_http_init_phases              #回到这里表明嵌套块都处理完了(递归特点决定的),可以进行一些操作了
                                                         #http的处理阶段分为11个阶段,这里就列举其phases的初始化和server对象的创建,其他过程略  
                                                         #这里给每个阶段的handler数组分配空间 ,就是说有很多个阶段,并且每个阶段的handler可以不止一个

              ngx_http.ngx_http_init_phase_handlers      #!!!给每个阶段都设置对应的checker和handler, 并平铺到一个数组phase_handler中
                                                         #举例:pi,ci,hj分别表示:
                                                         #第i个阶段,第i个阶段对应的checker,第i个阶段对应的第j个handler
                                                         #最终数组为:phase_handler={c0-h0,c0-h1,c1-h0,c1-h1,c1-h2,c2-h0,.....}
                                                         #这样处理请求的时候就只要一个for循环就可以调用所有阶段的所有handler
                                                         #checker与handler:处理请求时,外部代码是调用checker,然后checker内部会调用handler,
                                                         #checker主要用于控制 HTTP 请求在不同阶段(phase)的处理逻辑 
                                                         #通常是对 handler 的返回值进行检查或进一步处理。
                                                         #笔记:处理的时候默认是从handler数组的第0个元素开始调用handler,
                                                         #但是对于内部请求等一些场景,并不需要从0开始,可以直接从某个pos直接开始,比如rewrite_index
                                                         #一共有11个阶段,默认是一共有14个handler,即数组长度为14,可以百度,这里就不记录了
                                                         #可以直接搜索NGX_HTTP_POST_READ_PHASE这个枚举值就可以找到整个枚举,一共11个阶段
                                                         #笔记:elts表示数组基地址,nlts表示数组元素

              ngx_http.ngx_http_optimize_servers         #主要初始化listen socket相关结构
                ngx_http.ngx_http_init_listening         #!!!注意:只是初始化相关listen socket结构,并不会bind,
                                                         #因为此时还是初始化阶段,不能打开端口,所以bind操作不在这里
                                                         #等到conf处理完以及其他初始化工作都处理后才开启
                  ngx_http.ngx_http_add_listening
                    ls=ngx_create_listening  
                    ls.handler=ngx_http_init_connection   #!!!设置connection handler。
                                                          #!!!就是accept连接的时候accept_handler会初始化一个connection结构体
                                                          #!!!然后填充部分参数,然后调用connection_handler来处理新建立的连接
                                                          #!!!这里listenfd对新连接的处理函数设置为ngx_http_init_connection
                                                          #!!!stream模块中则设置为ngx_stream_init_connection
                                                          #!!!再次强调,ngx通过函数指针来模拟了多态,
                                                          #!!!也就是同一套代码可以同时处理httpfd和streamfd

          4:stream块。流程和http模块一模一样,只是函数名字不同,比如ls.handler的名字分别为ngx_stream_init_connection和ngx_http_init_connection
            ngx_stream.ngx_stream_block                  #cmd->set函数,即配置块对应的解析函数,这里处理conf中的stream块即stream 模块
              ngx_conf_file.ngx_conf_parse               #递归处理stream的嵌套块
                ngx_conf_file.ngx_conf_handler  
                    ...略...
              ngx_stream.ngx_stream_init_phases          #同http模块,不过stream只有7个阶段
              ngx_stream.ngx_stream_init_phase_handlers  #同http模块  
              ngx_stream.ngx_stream_optimize_servers     #同http模块
                ......
                  ngx_http.ngx_stream_add_listening
                    ls=ngx_create_listening  
                    ls.handler=ngx_stream_init_connection #!!!设置connection handler。
                                                          #!!!就是accept连接的时候accept_handler会初始化一个connection结构体
                                                          #!!!然后填充部分参数,然后调用connection_handler来处理新建立的连接

    #!!!下面的代码不管是http还是stream,都是同一个处理流程:注册到epollfd
    #!!!因为nginx中http和strema模块的不同在于处理函数的不同,而nginx把不同的handler封装在event_t结构体中
    #!!!而event_t结构体又包含在connection结构体中,clientfd存在epollfd中的就是connection结构,
    #!!!也就是说不管是http还是stream,用的都是connection结构,而他们的区别只有字段值的不同
    #!!!正因为如此,所以可以用同一个epollfd来同时处理httpserver-listenfd、streamserver-listenfd
    #!!!、httpclientfd、streamclientfd、streamupstreamfd上的读写事件

    ngx_connection.ngx_open_listening_sockets            #ngx_init_cycle函数中处理完配置文件后就打开所有的listenfd
      listenfd=ngx_socket                                #创建sokcet。前面只是初始化listenfd所需的各种结构,但是listenfd是没有初始化的
                                                         #这里创建fd后,直接复制给前面初始化的结构:xx.fd=listenfd
      socket.bind                                        #绑定端口
      socket.listen                                      #开启监听,也就是这时候可以接受连接。注意:是可以接受连接
                                                         #但是listenfd还没有注册到epoll中所以nginx不会accept连接,连接都会放到back队列
    ngx_connection.ngx_configure_listening_sockets       #对listenfd进行一些设置

    ngx_model.ngx_init_modules                           #!!!前面只是根据配置文件中的信息来调用对应模块的set函数,set函数不是init函数
                                                         #因为是根据conf文件对模块进行设置,所以该函数指针才叫做set,这里就是init_modules模块
                                                         #ngx规定了每个许多模块都必须实现的函数,init_modules是其中一个
      for modules->init_modules

  ngx_process_cycle.ngx_master_process_cycle             #main函数里调用这个,开始进入多进程
                                                         #!!!master主要开启子进程并监控子进程,master不负责接受连接和处理连接
    ngx_process
<think>好的,用户现在问的是用C语言写网络应用。我需要先回顾之前的对话,用户之前问过UNIX作为软路由的问题,现在转向网络应用的开发,可能他对网络编程有实际需求,或者在学习相关技术。 首先,用户可能是开发者,想了解C语言在网络应用中的应用场景和基本方法。需要明确C语言的优势,比如高性能、底层控制能力强,适合需要高并发和高效率的场景,比如服务器、网络设备等。 接下来,用户可能需要知道基本步骤,比如套接字编程,这是C语言网络应用的基础。需要分步骤讲解,从创建套接字、绑定端口、监听连接到处理数据,这些流程都需要涵盖。同时,要考虑错误处理,因为网络编程中错误处理很重要,比如检查每个系统调用的返回值。 然后,用户可能对多线程或多进程处理并发连接感兴趣。需要解释fork()和pthread的使用,以及各自的优缺点。比如多进程资源消耗大,但稳定性好;多线程轻量,但需要注意线程安全。此外,还可以提到IO多路复用技术,如select、poll、epoll,这些对于高性能服务器很关键。 示例部分,给出一个简单的TCP服务器代码,这样用户可以直观看到结构。同时要提醒编译命令,比如gcc加上必要的参数,以及运行方式。还要注意代码中的关键点,比如错误检查、关闭套接字等,避免常见问题。 高级部分可能需要介绍第三方库,比如libevent、libuv,这些库简化了异步编程,帮助处理高并发。此外,安全方面的问题,比如缓冲区溢出、DoS攻击防护,这也是用户可能关心的点,特别是如果他们要开发公开的服务的话。 应用场景部分,需要列举C语言网络应用的典型用途,比如Web服务器、游戏服务器、物联网等,让用户了解实际应用价值。同时,对比其他语言如Python、Go,说明C语言在性能和资源控制上的优势,以及开发复杂度上的劣势,帮助用户权衡选择。 注意事项里,要强调内存管理和指针安全,因为C语言容易在这方面出错,尤其是在网络数据处理时。另外,提到调试工具如GDB、Valgrind,帮助用户提高代码质量。最后,测试的重要性,特别是压力测试,确保服务器稳定。 总结部分,要肯定C语言在网络应用中的地位,同时建议根据项目需求选择工具,如果追求性能和控制力,C是好的选择,否则可以考虑更高级的语言。此外,可能用户有后续问题,比如具体例子或深入某个技术点,需要保持回答的开放性,鼓励进一步提问。</think>--- ### 一、C 语言开发网络应用的核心优势 1. **底层控制能力** - 直接操作系统级 API(如 POSIX Socket),无中间层性能损耗。 - 精确管理内存、线程和网络资源(例如:手动优化数据包缓冲区)。 2. **高性能与低延迟** - 适用于高频交易系统、游戏服务器等场景(例如:Ethereum 早期节点用 C/C++ 实现)。 - 可结合 `epoll`(Linux)或 `kqueue`(BSD)实现高并发 IO 多路复用。 3. **跨平台兼容性** - 标准套接字接口在 UNIX/Linux/Windows 上行为一致(需注意 `WSAStartup` 等 Windows 特有初始化)。 --- ### 、网络应用开发基础:套接字(Socket) #### 1. 关键步骤 - **创建套接字** ```c int sockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP 流式套接字 if (sockfd < 0) { perror("socket"); exit(1); } ``` - **绑定地址与端口** ```c struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(8080); // 端口号 addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡 if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); exit(1); } ``` - **监听与接受连接** ```c listen(sockfd, 5); // 队列最多容纳 5 个待处理连接 struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); ``` - **数据收发** ```c char buffer[1024]; ssize_t n = recv(client_fd, buffer, sizeof(buffer), 0); // 接收数据 send(client_fd, "Hello from C server!", 20, 0); // 发送响应 ``` #### 2. 并发处理模型 - **多进程**:通过 `fork()` 为每个连接创建子进程 ```c if (fork() == 0) { // 子进程 close(sockfd); // 关闭监听套接字 handle_client(client_fd); exit(0); } close(client_fd); // 父进程关闭客户端套接字 ``` - **多线程**:使用 `pthread` 库 ```c pthread_t tid; pthread_create(&tid, NULL, thread_handler, (void*)client_fd); ``` - **IO 多路复用**(单线程高并发) ```c // epoll 示例(Linux) struct epoll_event ev, events[MAX_EVENTS]; int epoll_fd = epoll_create1(0); ev.events = EPOLLIN; ev.data.fd = sockfd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev); while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < nfds; i++) { if (events[i].data.fd == sockfd) { // 处理新连接 } else { // 处理数据 } } } ``` --- ### 三、典型网络应用代码示例(TCP Echo 服务器) ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 #define BUFFER_SIZE 1024 void handle_client(int client_fd) { char buffer[BUFFER_SIZE]; while (1) { ssize_t n = recv(client_fd, buffer, BUFFER_SIZE, 0); if (n <= 0) break; send(client_fd, buffer, n, 0); // 回传数据 } close(client_fd); } int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(PORT), .sin_addr.s_addr = INADDR_ANY }; bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); listen(sockfd, 5); while (1) { int client_fd = accept(sockfd, NULL, NULL); if (client_fd < 0) continue; handle_client(client_fd); } return 0; } ``` **编译与运行**: ```bash gcc -o server server.c # 编译 ./server # 运行(需 root 权限绑定低端口) ``` --- ### 四、高级网络编程技术 1. **协议封装** - 自定义进制协议(如类似 Redis 的 RESP 协议): ```c typedef struct { uint32_t length; // 数据长度(网络字节序) char data[]; // 实际数据 } CustomPacket; ``` 2. **第三方库支持** - **libevent**:事件驱动库简化异步编程 ```c struct event_base *base = event_base_new(); struct event *ev = event_new(base, sockfd, EV_READ|EV_PERSIST, callback, NULL); event_add(ev, NULL); event_base_dispatch(base); ``` - **OpenSSL**:实现 TLS 加密通信 ```c SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); SSL *ssl = SSL_new(ctx); SSL_set_fd(ssl, client_fd); SSL_accept(ssl); SSL_write(ssl, "Secure Hello", 12); ``` 3. **性能优化** - 零拷贝技术(如 `sendfile()` 传输文件) - 内存池预分配减少动态内存开销 --- ### 五、安全注意事项 1. **缓冲区溢出防护** - 使用 `strncpy` 替代 `strcpy`,限制输入长度 - 启用编译保护(如 `-fstack-protector`) 2. **拒绝服务(DoS)防御** - 设置连接数限制 - 非阻塞 IO + 超时机制防止资源耗尽 3. **输入验证** - 严格校验客户端发送的数据格式 --- ### 六、应用场景与替代方案对比 | 场景 | C 语言优势 | 替代方案(如 Python/Go) | |---------------------|-----------------------------------|---------------------------------| | 高频交易系统 | 纳秒级延迟控制 | 难以满足极端性能需求 | | 嵌入式网络设备 | 直接操作硬件,内存占用极低 | 运行时环境资源消耗较大 | | 大规模分布式系统节点 | 长期运行稳定性 | 依赖垃圾回收可能引发延迟抖动 | --- ### 七、学习资源推荐 1. **书籍** - 《UNIX 网络编程 卷1:套接字 API》(W. Richard Stevens) - 《C 专家编程》(Peter van der Linden) 2. **开源项目参考** - Nginx(C 语言实现的高性能 Web 服务器) - Redis(C 语言编写的高并发内存数据库) --- ### 总结 用 C 语言开发网络应用需要直面底层细节(如手动管理连接、内存和并发),但能获得**极致性能与资源控制权**。适合以下场景: - 需要处理 10,000+ 并发连接的服务器 - 对延迟敏感(如金融交易系统) - 嵌入式或资源受限环境 **如果项目不追求极限性能,可优先考虑 Go(goroutine 简化并发)或 Rust(内存安全 + 高性能)**。但对于深入理解网络原理和系统编程,C 语言仍是不可替代的学习工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值