server_name 获取出错的问题

本文介绍了Nginx中server_name指令的配置方式及其对请求的影响。通过实例展示了一个域名指向多个子域的情况,并解释了如何在这种配置下获取正确的服务器名称。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

nginx配置:
server_name a.domain.com b.domain.com

访问:b.domain.com/xxx
服务端服务
获取 $_SERVER[‘SERVER_NAME’] 的值为 a.domain.com

<think>我们注意到之前的代码使用了select模型,但后来改为了epoll模型。现在提供的代码片段是select模型的实现。从问题描述看,浏览器加载不出来,而终端打印显示文件已经发送完成并关闭了连接。问题可能在于: 1. 连接关闭过早:在发送完文件后立即关闭连接,但可能浏览器还没有完全接收完数据。 2. 非阻塞问题:使用select模型,但套接字是阻塞模式,可能导致长时间操作阻塞其他连接。 3. 请求处理不完整:可能只处理了一个请求就关闭了连接,而现代浏览器会复用连接发送多个请求(如对同一个连接请求多个图片)。 解决方案: 1. 使用非阻塞I/O配合select,避免一个请求阻塞整个服务。 2. 实现持久连接(HTTP Keep-Alive),在一个连接上处理多个请求。 3. 确保在请求完全处理完毕后再关闭连接。 我们将重构代码,使用非阻塞I/O,并支持HTTP Keep-Alive。同时,我们将使用状态机来跟踪每个连接的处理状态。 步骤: 1. 将套接字设置为非阻塞模式。 2. 为每个连接创建一个状态结构体,记录当前状态(解析请求头、发送文件等)。 3. 在select循环中,根据套接字的可读/可写事件进行相应处理。 4. 实现HTTP Keep-Alive:解析请求头中的Connection字段,如果是keep-alive,则在处理完一个请求后不关闭连接,等待下一个请求。 由于代码较长,我将分步骤给出关键部分: 第一步:定义连接状态结构体 ```c typedef enum { REQUEST_READ, // 等待读取请求 FILE_SENDING, // 正在发送文件 CGI_PROCESSING // 正在处理CGI } ConnectionState; typedef struct { int fd; ConnectionState state; char buffer[4096]; // 请求头缓冲区 int buffer_len; // 缓冲区中数据长度 int buffer_offset; // 已解析的偏移量 FILE *file; // 打开的文件指针 off_t file_offset; // 文件发送偏移量 off_t content_length; // 文件长度 char method[16]; // HTTP方法 char path[512]; // 请求路径 int keep_alive; // 是否保持连接 } Connection; ``` 第二步:修改accept_request函数,使其非阻塞并支持状态机 由于原accept_request是阻塞的,我们需要将其拆分为多个步骤,在每次可读事件时读取一部分数据,直到解析完请求头。 第三步:修改主循环,使用非阻塞I/O和状态机 我们将为每个连接维护一个状态结构体,存储在全局数组或链表中。 第四步:实现文件发送的非阻塞版本,分块发送。 由于代码量较大,这里给出主循环和状态处理的框架: ```c // 全局连接数组,用于存储每个连接的状态 #define MAX_CONNECTIONS 1000 Connection *connections[MAX_CONNECTIONS] = {0}; int main(void) { // ... 初始化server_sock ... // 设置服务器套接字为非阻塞 set_nonblocking(server_sock); while (1) { FD_ZERO(&read_set); FD_ZERO(&write_set); FD_SET(server_sock, &read_set); int max_fd = server_sock; // 将现有连接加入select集合 for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i] == NULL) continue; Connection *conn = connections[i]; if (conn->state == REQUEST_READ) { FD_SET(conn->fd, &read_set); } else if (conn->state == FILE_SENDING) { FD_SET(conn->fd, &write_set); // 当需要发送数据时,监听可写事件 } if (conn->fd > max_fd) max_fd = conn->fd; } activity = select(max_fd + 1, &read_set, &write_set, NULL, NULL); // 处理新连接 if (FD_ISSET(server_sock, &read_set)) { client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); if (client_sock < 0) { perror("accept"); continue; } set_nonblocking(client_sock); // 创建新的连接状态 Connection *conn = calloc(1, sizeof(Connection)); conn->fd = client_sock; conn->state = REQUEST_READ; // 添加到连接数组 for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i] == NULL) { connections[i] = conn; break; } } } // 处理现有连接 for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i] == NULL) continue; Connection *conn = connections[i]; int fd = conn->fd; // 可读事件:接收请求 if (FD_ISSET(fd, &read_set)) { if (conn->state == REQUEST_READ) { // 非阻塞读取 int n = recv(fd, conn->buffer + conn->buffer_len, sizeof(conn->buffer) - conn->buffer_len, 0); if (n > 0) { conn->buffer_len += n; // 检查是否收到完整的请求头(以\r\n\r\n结束) if (memstr(conn->buffer, conn->buffer_len, "\r\n\r\n", 4) != NULL) { // 解析请求头 parse_request(conn); // 根据请求内容决定状态:如果是文件请求,则准备发送文件;如果是CGI则进入CGI处理 if (should_serve_file(conn)) { // 打开文件,获取文件长度等 if (open_file(conn) == 0) { conn->state = FILE_SENDING; } else { // 文件打开失败,发送404 send_404(fd); close_connection(conn, i); } } else { // 其他处理(如CGI) // 这里省略 } } } else if (n == 0 || (n < 0 && errno != EAGAIN)) { // 连接关闭或出错 close_connection(conn, i); } } } // 可写事件:发送文件 if (FD_ISSET(fd, &write_set)) { if (conn->state == FILE_SENDING) { if (send_file_chunk(conn) < 0) { // 发送出错 close_connection(conn, i); } else { // 检查是否发送完成 if (conn->file_offset >= conn->content_length) { // 文件发送完成 if (conn->keep_alive) { // 重置连接状态,等待下一个请求 reset_connection(conn); } else { close_connection(conn, i); } } } } } } } // ... 清理 ... } ``` 第五步:实现辅助函数(如memstr用于在二进制数据中查找字符串,parse_request解析请求头等) 由于代码量限制,这里只给出关键函数的大致框架: ```c // 在buf中查找子串,返回找到的位置 void *memstr(const void *buf, size_t buf_len, const void *pattern, size_t pattern_len) { if (pattern_len == 0) return (void*)buf; if (buf_len < pattern_len) return NULL; for (size_t i = 0; i <= buf_len - pattern_len; i++) { if (memcmp((char*)buf+i, pattern, pattern_len) == 0) { return (char*)buf + i; } } return NULL; } // 解析请求头 void parse_request(Connection *conn) { char *request_end = memstr(conn->buffer, conn->buffer_len, "\r\n\r\n", 4); if (request_end == NULL) return; // 解析第一行 char *line = conn->buffer; char *end = strchr(line, '\r'); if (end) *end = '\0'; // 解析方法、路径 sscanf(line, "%s %s", conn->method, conn->path); // 解析Connection头 char *conn_header = strstr(line, "Connection:"); if (conn_header) { conn_header += 11; // 跳过"Connection:" while (*conn_header == ' ') conn_header++; if (strncasecmp(conn_header, "keep-alive", 10) == 0) { conn->keep_alive = 1; } } } // 发送文件块 int send_file_chunk(Connection *conn) { char buf[4096]; size_t to_send = (conn->content_length - conn->file_offset) > sizeof(buf) ? sizeof(buf) : conn->content_length - conn->file_offset; if (to_send == 0) return 0; size_t nread = fread(buf, 1, to_send, conn->file); if (nread <= 0) return -1; ssize_t nwritten = send(conn->fd, buf, nread, 0); if (nwritten <= 0) { if (errno == EAGAIN) { return 0; // 下次再试 } return -1; // 出错 } conn->file_offset += nwritten; return 0; } ``` 注意:上述代码是一个简化的框架,实际实现需要处理更多细节,如错误处理、内存管理、HTTP头部的正确解析等。 此外,我们还需要实现reset_connection函数来重置连接状态以处理下一个请求: ```c void reset_connection(Connection *conn) { // 重置缓冲区 conn->buffer_len = 0; conn->buffer_offset = 0; // 关闭文件(如果有) if (conn->file) { fclose(conn->file); conn->file = NULL; } conn->file_offset = 0; conn->content_length = 0; // 重置状态 conn->state = REQUEST_READ; // 注意:不清除keep_alive标志,因为它是请求头中设置的 } ``` 最后,在连接处理完成后,需要正确关闭连接并释放资源: ```c void close_connection(Connection *conn, int index) { if (conn->file) fclose(conn->file); close(conn->fd); free(conn); connections[index] = NULL; } ``` 由于这个重构比较复杂,可能需要逐步调试。但这样设计后,服务器将能够处理并发请求,并支持持久连接。 注意:如果使用持久连接,需要设置超时机制,避免空闲连接长时间占用资源。可以在Connection结构体中添加一个时间戳,记录最后一次活动时间,在select循环中检查超时。 以上是一个基于select的非阻塞HTTP服务器框架,用于解决图片加载问题(通过支持持久连接和并发处理)。实际部署时还需要进行压力测试和细节调整。 §§相关问题§§: 1. 在非阻塞I/O模型中,如何处理部分发送(即send只发送了部分数据)? 2. 如何为HTTP服务器添加超时机制以关闭空闲连接? 3. 使用epoll比select有哪些优势?如何将上述代码改为epoll实现? 4. 如何测试HTTP服务器的并发性能?</think># 基于select的HTTP服务器优化与问题解决 您提供的代码使用了select模型来实现HTTP服务器,但遇到了浏览器无法加载图片的问题。我将分析代码中的问题并提供完整的优化方案。 ## 问题分析 ### 1. 连接管理问题 ```c FD_SET(client_sock, &master_set); // ... for (int i = 0; i <= max_sd; i++) { if (i == server_sock) continue; if (FD_ISSET(i, &working_set)) { accept_request(i); FD_CLR(i, &master_set); // 处理完请求后立即移除 // ... } } ``` 这里的主要问题是:**处理完请求后立即关闭并移除连接**。现代浏览器会为单个页面发送多个并行请求(HTML、CSS、JS、图片等),但您的代码处理完一个请求就关闭连接,导致后续请求失败。 ### 2. 请求处理阻塞 `accept_request()`函数是阻塞的,在处理大文件或慢客户端时会阻塞整个select循环。 ### 3. 缺少持久连接支持 没有实现HTTP Keep-Alive机制,浏览器无法复用连接。 ### 4. 状态管理缺失 没有维护连接状态,导致无法处理部分接收的请求或分块发送大文件。 ## 完整解决方案 ### 1. 连接状态结构体 ```c typedef enum { STATE_READ_REQUEST, STATE_READ_HEADERS, STATE_SEND_HEADERS, STATE_SEND_FILE, STATE_CLOSING } ConnectionState; typedef struct { int fd; ConnectionState state; char method[16]; char path[512]; FILE* file; off_t file_size; off_t bytes_sent; char buffer[4096]; size_t buf_len; time_t last_activity; uint8_t keep_alive : 1; } Connection; ``` ### 2. 主循环优化 ```c int main(void) { // ... 初始化代码 ... Connection* connections[MAX_CONNECTIONS] = {0}; struct timeval timeout = {.tv_sec = 30, .tv_usec = 0}; // 30秒超时 while (1) { FD_ZERO(&read_set); FD_ZERO(&write_set); FD_SET(server_sock, &read_set); max_sd = server_sock; // 添加现有连接 for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i]) { Connection* conn = connections[i]; FD_SET(conn->fd, &read_set); // 需要发送数据时监听写事件 if (conn->state == STATE_SEND_FILE || conn->state == STATE_SEND_HEADERS) { FD_SET(conn->fd, &write_set); } if (conn->fd > max_sd) max_sd = conn->fd; } } activity = select(max_sd + 1, &read_set, &write_set, NULL, &timeout); // 处理新连接 if (FD_ISSET(server_sock, &read_set)) { client_sock = accept(server_sock, (struct sockaddr*)&client_name, &client_name_len); if (client_sock < 0) { perror("accept"); continue; } // 设置非阻塞 set_nonblocking(client_sock); // 创建新连接 Connection* conn = calloc(1, sizeof(Connection)); conn->fd = client_sock; conn->state = STATE_READ_REQUEST; conn->last_activity = time(NULL); // 添加到连接数组 for (int i = 0; i < MAX_CONNECTIONS; i++) { if (!connections[i]) { connections[i] = conn; FD_SET(conn->fd, &master_set); break; } } } // 处理现有连接 for (int i = 0; i < MAX_CONNECTIONS; i++) { if (!connections[i]) continue; Connection* conn = connections[i]; int fd = conn->fd; // 检查超时 if (time(NULL) - conn->last_activity > 30) { close_connection(conn, connections, i); continue; } // 可读事件 if (FD_ISSET(fd, &read_set)) { conn->last_activity = time(NULL); handle_readable(conn); } // 可写事件 if (FD_ISSET(fd, &write_set)) { conn->last_activity = time(NULL); handle_writable(conn); } // 检查连接是否需要关闭 if (conn->state == STATE_CLOSING) { close_connection(conn, connections, i); } } } close(server_sock); return 0; } ``` ### 3. 请求处理函数 ```c void handle_readable(Connection* conn) { char buf[4096]; ssize_t nread = recv(conn->fd, buf, sizeof(buf), 0); if (nread <= 0) { if (nread == 0 || (nread < 0 && errno != EAGAIN)) { conn->state = STATE_CLOSING; } return; } // 添加到缓冲区 if (conn->buf_len + nread < sizeof(conn->buffer)) { memcpy(conn->buffer + conn->buf_len, buf, nread); conn->buf_len += nread; } else { // 缓冲区溢出 send_error(conn->fd, 500, "Internal Server Error"); conn->state = STATE_CLOSING; return; } // 处理请求状态 switch (conn->state) { case STATE_READ_REQUEST: if (parse_request_line(conn)) { conn->state = STATE_READ_HEADERS; } break; case STATE_READ_HEADERS: if (parse_headers(conn)) { if (strcasecmp(conn->method, "GET") == 0) { prepare_file_response(conn); conn->state = STATE_SEND_HEADERS; } else { // 处理其他方法 send_error(conn->fd, 501, "Not Implemented"); conn->state = STATE_CLOSING; } } break; default: // 忽略额外数据 break; } } void handle_writable(Connection* conn) { switch (conn->state) { case STATE_SEND_HEADERS: if (send_headers(conn)) { conn->state = STATE_SEND_FILE; } break; case STATE_SEND_FILE: if (send_file_data(conn)) { // 文件发送完成 if (conn->keep_alive) { reset_connection(conn); } else { conn->state = STATE_CLOSING; } } break; default: break; } } ``` ### 4. 文件发送函数 ```c int send_file_data(Connection* conn) { if (!conn->file) return 1; // 从文件中读取数据 char buf[4096]; size_t to_read = sizeof(buf); if (conn->bytes_sent + to_read > conn->file_size) { to_read = conn->file_size - conn->bytes_sent; } size_t nread = fread(buf, 1, to_read, conn->file); if (nread <= 0) { return 1; // 完成或错误 } // 发送数据 ssize_t nwritten = send(conn->fd, buf, nread, 0); if (nwritten < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; // 稍后重试 } return 1; // 错误 } conn->bytes_sent += nwritten; // 检查是否完成 if (conn->bytes_sent >= conn->file_size) { fclose(conn->file); conn->file = NULL; return 1; // 完成 } return 0; // 需要继续发送 } ``` ### 5. 持久连接支持 ```c int parse_headers(Connection* conn) { char* buf = conn->buffer; char* end = strstr(buf, "\r\n\r\n"); if (!end) return 0; // 头部未接收完整 // 解析Connection头 char* conn_header = strstr(buf, "Connection:"); if (conn_header) { conn_header += 11; // 跳过"Connection:" while (*conn_header == ' ') conn_header++; if (strncasecmp(conn_header, "keep-alive", 10) == 0) { conn->keep_alive = 1; } } return 1; } void reset_connection(Connection* conn) { // 重置状态 memset(conn->buffer, 0, sizeof(conn->buffer)); conn->buf_len = 0; if (conn->file) { fclose(conn->file); conn->file = NULL; } conn->bytes_sent = 0; conn->file_size = 0; memset(conn->method, 0, sizeof(conn->method)); memset(conn->path, 0, sizeof(conn->path)); conn->state = STATE_READ_REQUEST; conn->last_activity = time(NULL); } ``` ## 性能优化技巧 ### 1. 使用分散/聚集I/O ```c struct iovec iov[2]; char header_buf[512]; char file_buf[4096]; // 构建响应头 int header_len = snprintf(header_buf, sizeof(header_buf), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "Connection: %s\r\n\r\n", content_type, file_size, keep_alive ? "keep-alive" : "close"); iov[0].iov_base = header_buf; iov[0].iov_len = header_len; iov[1].iov_base = file_buf; iov[1].iov_len = to_send; // 一次性发送 ssize_t nwritten = writev(conn->fd, iov, 2); ``` ### 2. 内存池管理 ```c #define POOL_SIZE 100 typedef struct { Connection connections[POOL_SIZE]; int free_list[POOL_SIZE]; int free_count; } ConnectionPool; Connection* acquire_connection(ConnectionPool* pool) { if (pool->free_count == 0) return NULL; return &pool->connections[pool->free_list[--pool->free_count]]; } void release_connection(ConnectionPool* pool, Connection* conn) { int index = conn - pool->connections; pool->free_list[pool->free_count++] = index; } ``` ### 3. 定时器轮检测超时 ```c // 每5秒检查一次超时 void check_timeouts(Connection** connections, int max_conn) { time_t now = time(NULL); for (int i = 0; i < max_conn; i++) { if (connections[i] && (now - connections[i]->last_activity) > 30) { close_connection(connections[i], connections, i); } } } ``` ## 完整测试方案 ### 1. 压力测试工具 ```bash # 安装siege sudo apt-get install siege # 测试100个并发用户 siege -c 100 -t 1M http://localhost:80/test.jpg ``` ### 2. 连接状态监控 ```c // 添加监控函数 void print_connections(Connection** connections, int max_conn) { printf("\nActive connections:\n"); for (int i = 0; i < max_conn; i++) { if (connections[i]) { Connection* c = connections[i]; printf("FD %d: State %d, Sent %ld/%ld, Keep-Alive %d\n", c->fd, c->state, c->bytes_sent, c->file_size, c->keep_alive); } } printf("-----------------------------\n"); } // 在main循环中定期调用 if (time(NULL) - last_print > 5) { print_connections(connections, MAX_CONNECTIONS); last_print = time(NULL); } ``` ### 3. 性能指标监控 | 指标 | 计算方法 | 优化目标 | |------|----------|----------| | 连接吞吐量 | 成功处理连接数/秒 | >1000/秒 | | 请求延迟 | 请求开始到结束时间 | <50ms | | 内存使用 | 连接结构体大小 * 活动连接数 | <10MB | | CPU使用率 | 系统CPU使用百分比 | <70% |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值