nginx系列--(一)--调试环境搭建

辅助脚本:

复制代码
#!/bin/bash
mkdir -p $(pwd)/nginxhome
# 生成 Makefile,--prefix need a absolute path --with-stream表示要包括stream模块
auto/configure --prefix=$(pwd)/nginxhome --with-stream
#	lsof -i tcp:10086  && fuser -k 10086/tcp ||true
# 定义 Makefile 的路径
MAKEFILE="Makefile"
OBJS_MAKEFILE="objs/Makefile"

# 检查 Makefile 是否存在
if [ ! -f "$MAKEFILE" ]; then
    echo "Makefile not found!"
    exit 1
fi

# 检查 objs/Makefile 是否存在
if [ ! -f "$OBJS_MAKEFILE" ]; then
    echo "objs/Makefile not found!"
    exit 1
fi

# 创建一个临时文件来存储修改后的内容
TEMP_FILE=$(mktemp)

# 替换 objs/Makefile 中的 CFLAGS
while IFS= read -r line; do
    if [[ "$line" == CFLAGS* ]]; then
        echo "CFLAGS = -pipe -O0 -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g" >> "$TEMP_FILE"
    else
        echo "$line" >> "$TEMP_FILE"
    fi
done < "$OBJS_MAKEFILE"

# 替换原 objs/Makefile
mv "$TEMP_FILE" "$OBJS_MAKEFILE"

# 创建一个临时文件来存储修改后的内容
TEMP_FILE=$(mktemp)

# 读取 Makefile,找到并删除原有的 clean 命令
while IFS= read -r line; do
    if [[ "$line" == "clean:" ]]; then
        # 跳过原来的 clean 规则
        read -r
        continue
    fi
    # 替换 $(MAKE)
    line=${line/\$(MAKE)/\$(MAKE) CFLAGS=\"\$(CFLAGS)\"}
    # 保留其他行
    echo "$line" >> "$TEMP_FILE"
done < "$MAKEFILE"

# 插入新的 clean 规则和 all 目标
{
    echo ""
    echo "CFLAGS = -O0 -g"
    echo ""
    echo "clean:"
    echo -e "\tfind objs -type f \( -name '*.o' -o -name '*.d' -o -name '*.so' -o -name '*.a' \) -exec rm -f {} +"
    echo -e "\trm -rf nginxhome/*"
    echo ""
    echo -e "all: build install\n\ndefault:\tall"
    echo -e "nginx:\tbuild install"
} >> "$TEMP_FILE"

# 替换原 Makefile
mv "$TEMP_FILE" "$MAKEFILE"

sync
echo "Makefile updated successfully."
make all CFLAGS="-O0 -g"

sudo fuser -k 10086/tcp #杀掉占用10086端口的进程

最终makefile:

复制代码
default:	build


.PHONY:	default clean

build:
	lsof -i tcp:10086  && fuser -k 10086/tcp ||true   #这是自己加的 
	$(MAKE) CFLAGS="$(CFLAGS)" -f objs/Makefile       #这是自己修改的的 

install:
	$(MAKE) CFLAGS="$(CFLAGS)" -f objs/Makefile install

modules:
	$(MAKE) CFLAGS="$(CFLAGS)" -f objs/Makefile modules

upgrade:
	/home/cser/work/code/nginx/nginxhome/sbin/nginx -t

	kill -USR2 `cat /home/cser/work/code/nginx/nginxhome/logs/nginx.pid`
	sleep 1
	test -f /home/cser/work/code/nginx/nginxhome/logs/nginx.pid.oldbin

	kill -QUIT `cat /home/cser/work/code/nginx/nginxhome/logs/nginx.pid.oldbin`

.PHONY:	build install modules upgrade

CFLAGS = -O0 -g

clean:  
	find objs -type f \( -name '*.o' -o -name '*.d' -o -name '*.so' -o -name '*.a' \) -exec rm -f {} +
	rm -rf nginxhome/*

all: build install #这是自己加的 

default:	all #这是自己加的 
nginx:	build install #这是自己加的 

1:配置

复制代码
 #!!!一定要用绝对路径,这里设置安装路径,makefile中会用到
auto/configure --prefix=/home/cser/work/code/nginx/nginxhome

2:修改makefile:1-修改clean;2:添加all;3:修改default;4:修改$(MAKE),因为默认的build/install目标是没有用到CFLAGS,所以命令行传递给makefile脚本的CFLAGS会不起作用

笔记:all需要包括build 和install,clion要求makefile含有all,并且会调用clean删除文件,而默认的则是删除所有文件,不好,所以需要我们手动修改clean目标

复制代码
clean:
	#find objs -type f \( -name '*.o' -o -name '*.a' -o -name '*.so' -o -name '*.lo' -o -name '*.la' -o -name '*.out' \) -exec rm -rf {} +
	find objs -type f -name '*.o' -exec rm -f {} +
.PHONY: default clean
.PHONY:	build install modules upgrade
all: build install
default: all

#修改$(MAKE)
旧:
build:
    $(MAKE) -f objs/Makefile
新:
build:
    $(MAKE) CFLAGS="$(CFLAGS)" -f objs/Makefile  #!!!CFLAGS="xx",记得加双引号

3:make并关闭优化

复制代码
make CFLAGS="-O0" CXXFLAGS="-O0"

4:然后重新load makefile project !!!这一步非常重要。可以点击图标或者tool->makefile->relaod makefile project

5:修改配置。

笔记:通过下面三项设置之后我们修改代码,clion可以自动重新编译;

笔记:启动以后可以 curl localhost:10086 检测nginx是否启动成功

5-1:要修改target为makefile中的target,这样每次构建的时候就会make 这个target,我们需要all,即我们需要build和install。

5-2:构建完之后我们还需要指定一个可运行程序,这个程序是我们--prefix指定的目录下的nginx程序,不能是objs目录下的nginx

5-3:修改端口,默认是80端口,因为1024以下的端口需要特权,root模式启动clion需要重新激活很麻烦,所以改成10086

(笔记:如果printf没有立即输出,可以用fflush(stdout);强制刷新)

复制代码
vim /path/to/config 
#这个path可以随便,因为我们会通过命令行参数-c指定配置文件
#listen       80; 改成 listen       10086;

完整配置如下:

复制代码
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {  #!!!!nginx是根据这个模块名来调用不同的模块来解析这个配置,如果是http_abc,nginx就会调用http_abc模块来解析这个块
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       10086;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
        location /hello {
            alias html;
            index  index.html index.htm;
        }
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}
stream {
    # 转发到后端服务
    server {
        listen 10087;  # 监听 10087 端口
        proxy_pass 127.0.0.1:10088;  # 转发到 10088 端口,我们写了一个程序监听10088端口
    }
}

5-4:指定命令行参数,包括两个:1:关闭后台运行,不关则nginx会以守

护进程方式运行,无法用cliondebug;2:设置配置文件路径。总的启动参数如下:

复制代码
 -c /home/cser/work/code/nginx/conf/nginx.conf -g "daemon off;" 

6:clion调试多进程。如果不设置,那么就无法用clion debug子进程而对请求的处理都在子进程中

1.在程序的开始处设置一个断点(注意是在父进程中)

2.转到clion的调试器控制台(带有gdb的选项卡)

在fork出子进程前,依次输入set follow-fork-mode child、set detach-on-fork off

echo "hello" | nc localhost 10088 、 ./tcp_server

备注:最好不要在gdb里用方向键上翻下翻,因为此时虚拟机特别容易莫名其妙卡死,可能是vmware的问题也可能是我电脑的问题

accept=7 client=3 upstream=11


tcp_server程序:作为stream的backend

收到什么就返回:msg is :x

1:发送stream请求命令

复制代码
sudo apt install netcat
lsof -i tcp:10088  && fuser -k 10088/tcp  
echo "hello" | nc localhost 10088
gcc tcp_server.c -o tcp_server
./tcp_server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define DEFAULT_PORT 10088
#define BUFFER_SIZE 1024

int main(int argc, char *argv[]) {
    int port = DEFAULT_PORT;
    if (argc > 1) {
        port = atoi(argv[1]);
    }

    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

    // 创建 socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置地址和端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);

    // 绑定 socket
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 开始监听
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", port);

    // 接受客户端连接
    while (1) {
        client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_fd < 0) {
            perror("Accept failed");
            continue;
        }
        printf("accept a connection\n");
        fflush(stdout);

        // 读取数据
        ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';  // 确保字符串结束

            // 构造响应
            char response[BUFFER_SIZE];
            snprintf(response, sizeof(response), "msg is : %s", buffer);
            printf("msg is : %s\n", buffer);
            fflush(stdout);

            // 等待用户输入
            printf("Press Enter to continue...\n");
            getchar();  // 等待用户输入

            // 发送响应
            write(client_fd, response, strlen(response));
            printf("send ok\n------------------------------------\n");
        }

        close(client_fd);
    }

    close(server_fd);
    return 0;
}

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

nginx.conf

复制代码
worker_processes  2;                        #开启多个子进程,单worker和多worker有些流程不一样
events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    #keepalive_timeout  0;
    keepalive_timeout  65;


    server {
        listen       10086;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;           #curl localhost:10086可以debug http流程

            limit_except GET POST {                #主要是用来debug ngx_http_request_handler
                deny all;
            }
        }
        location /hello {
            alias html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}
stream {                                        #echo "hello world" | nc localhost 10087可以dubug stream流程
    # 转发到后端服务
    server {
        listen 10087;  # 监听 10087 端口
        proxy_pass 127.0.0.1:10088;  # 转发到 10088 端口
    }
}

tcp_client_post.c:

用来debug:把请求行、请求头、请求体分开发送,从而可以更有效地debug http请求流程 ngxin在读取完header之后就调用ngx_http_process_request,然后当请求体数据到来时,ngxin是如何处理的。

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 10086

void wait_for_input(int expected) {
    int input;
    while (1) {
        printf("请输入 %d 继续:", expected);
        scanf("%d", &input);
        if (input == expected) break;
    }
}

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    const char *request_line = "POST / HTTP/1.1\r\n";
    const char *request_headers = "Host: localhost\r\nContent-Length: 10\r\n\r\n";
    const char *request_body = "helloworld";

    while (1) {
        // 等待输入 0 开始新的连接
        wait_for_input(0);

        // 创建 socket
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) {
            perror("Socket 创建失败");
            exit(EXIT_FAILURE);
        }

        // 配置服务器地址
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERVER_PORT);
        inet_pton(AF_INET, SERVER_ADDR, &server_addr.sin_addr);

        // 连接服务器
        if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
            perror("连接服务器失败");
            close(sockfd);
            continue;
        }

        printf("连接已建立。\n");

        // 等待输入 1 并发送请求行
        wait_for_input(1);
        send(sockfd, request_line, strlen(request_line), 0);
        printf("已发送请求行:\n%s", request_line);

        // 等待输入 2 并发送请求头
        wait_for_input(2);
        send(sockfd, request_headers, strlen(request_headers), 0);
        printf("已发送请求头:\n%s", request_headers);

        // 等待输入 3 并发送请求体
        wait_for_input(3);
        send(sockfd, request_body, strlen(request_body), 0);
        printf("已发送请求体:\n%s", request_body);

        // 接收并打印服务器响应
        printf("等待服务器响应...\n");
        char buffer[1024];
        int received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (received > 0) {
            buffer[received] = '\0';
            printf("服务器响应:\n%s\n", buffer);
        } else {
            printf("未收到服务器响应或连接已关闭。\n");
        }

        // 关闭连接
        close(sockfd);
        printf("------------------\n");
    }

    return 0;
}
相关推荐
..过云雨7 分钟前
04.【Linux系统编程】基础开发工具2(makefile、进度条程序实现、版本控制器Git、调试器gdb/cgdb的使用)
linux·笔记·学习
zzzsde18 分钟前
【Linux】初识Linux
linux·运维·服务器
渡我白衣27 分钟前
Linux网络:应用层协议http
linux·网络·http
爱琴孩27 分钟前
企业级VIP+Nginx的网络访问方案
nginx·keepalive·vip
pofenx40 分钟前
使用nps创建隧道,进行内网穿透
linux·网络·内网穿透·nps
Ronin30540 分钟前
【Linux系统】单例式线程池
linux·服务器·单例模式·线程池·线程安全·死锁
wanhengidc1 小时前
手机云服务是什么意思?
运维·网络·安全·游戏·智能手机
desssq1 小时前
ubuntu 18.04 泰山派编译报错
linux·运维·ubuntu
Lzc7741 小时前
Linux的多线程
linux·linux的多线程
清风笑烟语1 小时前
Ubuntu 24.04 搭建k8s 1.33.4
linux·ubuntu·kubernetes