ext.1.1系列--序

ext做界面的确不错,不过学习它的资料实在是太少了,最近它出了ext2.0 alpha,这个版本里面加上不少新功能,不过alpha属于内部测试版本,里面有不少bug,这里我们先分析ext.1,目前最新的稳定版本 ,等2.0正式版本出来之后我们再分析2.0。

有兴趣的同志,我们一起出发吧。

/* *\file server.c *\brief This code is a function for socket tcp server. *\author Chen Xinyi <chenxinyi1@tp-link.com.hk> *\version 1.0.0 *\date 6/8/2025 *\history \arg 1.0.0 6/8/2025, chenxinyi, Create file. */ /**************************************************************************************************/ /*****************************************INCLUDE_FILES********************************************/ /**************************************************************************************************/ #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <malloc.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <stdarg.h> #include <fcntl.h> #include <fcntl.h> /* 涉及到多进程操作时加入如下语句: */ #include <sys/wait.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> /* 涉及到多线程操作时加入如下语句: */ #include <pthread.h> #include <sys/poll.h> /**************************************************************************************************/ /********************************************DEFINES***********************************************/ /**************************************************************************************************/ #define PORT 80 #define BUFFER_SIZE 1024 /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ /* void send_error(int client_fd, int status_num, const char* status_msg, const char* edition) { char response[1024]; // snprintf 将格式化的数据写入指定的字符串缓冲区,若字符串长度超了,输出被截断,并在最后添加空字符 int length = snprintf(response, sizeof(response), "%s %s %d \r\n" "server: %d\r\n" "Content-Type: image/svg+xml\r\n" "<html>\r\n" "<head>\r\n" "<title>error page</title>\n" "</head>\n" "<body>\r\n" "<h1>%d</h1>\r\n" "<%s>\r\n" "</body>\r\n" "</html>\r\n", edition, status_msg, status_num, client_fd, status_num, status_msg); send(client_fd, response, length, 0); } */ /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ // 文件类型映射 const char* get_mime_type(const char *path) { const char *ext = strrchr(path, '.'); if (!ext) return "text/plain"; if (strcmp(ext, ".html") == 0) return "text/html"; if (strcmp(ext, ".css") == 0) return "text/css"; if (strcmp(ext, ".js") == 0) return "application/javascript"; if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg"; if (strcmp(ext, ".png") == 0) return "image/png"; return "application/octet-stream"; } /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ /* 用来从server获取数据,因此需要解析GET请求的具体资源类型 */ void handle_get(int client_fd, char* path) { char uri[BUFFER_SIZE], http_version[BUFFER_SIZE]; sscanf(path, "%s %s", uri, http_version); char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: Text/html\r\n" "\r\n" "<html><body><h1>open</h1></body></html>"; /* 当请求是GET,需要读取文件并且返回,调用open等与文件系统交互,同时处理404错误和目录遍历问题 */ /* 1.打开文件 */ FILE* file = fopen(uri, "rb"); if (!file) { printf("无法打开文件: %s (%s)\n", uri, strerror(errno)); char *response_er = "HTTP/1.1 500 Internal Server Error\r\n" "\r\n"; send(client_fd, response_er, strlen(response_er), 0); return; } // 构建文件路径 char full_path[512]; if (strcmp(path, "/") == 0) { snprintf(full_path, sizeof(full_path), "%s/Index.html", "./web"); } else { snprintf(full_path, sizeof(full_path), "%s%s", "./web", path); } // 获取文件大小 fseek(file, 0, SEEK_END); long size = ftell(file); fseek(file, 0, SEEK_SET); // 发送HTTP头 char header[BUFFER_SIZE]; snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %ld\r\nConnection: close\r\n\r\n", get_mime_type(path), size); send(client_fd, header, strlen(header), 0); // 发送文件内容 char buffer[BUFFER_SIZE]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { send(client_fd, buffer, bytes_read, 0); } fclose(file); } /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ char *get_header_value(char *header, char *key) { char *key_pos = strstr(header, key); if (key_pos == NULL) { return NULL; } /* strchr搜索在另字符串中第一次出现,默认false,如果为true则返回search参数第一次出现之前的字符串部分 */ char *value_pos = strchr(key_pos, ':'); if(value_pos == NULL) { return NULL; } value_pos++; while (*value_pos == ' ') { value_pos++; } char *end_pos = strstr(value_pos, "\r\n"); if(end_pos == NULL) { return NULL; } *end_pos = '\0'; return value_pos; } /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ /* 修改server数据,数据放在request里: * post产生两个TCP包,浏览器先发送header,100后,浏览器再发送打压,返回200 */ void handle_post(int client_fd, char* path) { char *header = {0}; char *body = {0}; /* 寻找有无Length头 */ header = strstr(path, "Content-Length"); if (header == NULL) { return; } /* atoi把参数str所指向的字符串转换为一个整数 */ int content_length = atoi(get_header_value(header, "Content-Length")); body = strstr(path, "\r\n\r\n")+4; if (body == NULL) { return; } printf("POST request body: %.*s\n", content_length, body); char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: Text/plain\r\n" "\r\n" "success!"; send(client_fd, response, strlen(response), 0); } /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ /* 解读http报文 */ void handle_http_request(int client_fd) { char request[BUFFER_SIZE] = {0}; /* 1.读取浏览器请求*/ //read(client_fd, request, BUFFER_SIZE-1); ssize_t bytes_read = recv(client_fd, request, BUFFER_SIZE - 1, 0); if (bytes_read <= 0) { if (bytes_read == 0) { printf("客户端关闭了连接\n"); } else { perror("接收请求失败"); } return; } request[bytes_read] = '\0'; // 确保字符串结束 printf("------------------ 收到请求 ------------------%s\n",request); /* 2.提取请求信息(方法、路径) */ /* strtok将字符串分割,并修改传入的字符串将分隔符替为\0, 后续调用时,传NULL,表示继续分割同一个字符串 */ char* method = strtok(request, " "); char* path = strtok(NULL, ""); if (strcmp(method, "GET") == 0) { handle_get(client_fd, path); } else if (strcmp(method, "POST") == 0) { handle_post(client_fd, path); } else { char *response = "HTTP/1.1 405 Method Not Allowed\r\n" "Content-Length: 0\r\n" "\r\n"; send(client_fd, response, strlen(response), 0); } close(client_fd); return; } /**************************************************************************************************/ /********************************************FUNCTION**********************************************/ /**************************************************************************************************/ int main() { /* 1.创建socket文件描述符:创建套接字成功时返回一个非负整数 */ int server_fd; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } /* 声明serv_addr变量, sockaddr_in定义在头文件<arpa/inet.h>中,专用于互联网环境下的套接字地址格式 */ /* struct sockaddr_in { short int sin_family; 地址族 unsigned short int sin_port;端口号 struct in_addr sin_addr; Internet地址 unsigned char sin_zero[8]; 与struct sockaddr一样的长度 }; */ /* 2.配置服务器地址 */ struct sockaddr_in serv_addr = {0}; memset(&serv_addr, 0, sizeof(serv_addr)); /* 使用IPV4地址 */ serv_addr.sin_family = AF_INET; /* 设置IP,监听本地所有网口,仅服务端可用INADDR_ANY */ serv_addr.sin_addr.s_addr = INADDR_ANY; /* 端口!需要转化为网络字节再赋给socket */ serv_addr.sin_port = htons(PORT); /* 3.绑定server_fd到端口,文件描述符绑定到指定地址和端口 */ /* bind(socket描述字,要绑定给描述字的协议地址,对应地址长度)函数把一个地址组中特定地址赋给socket */ /* 绑定成功之后,如果客户端试图连接指定地址和端口建立TCP连接,系统就会转给server_fd这个文件处理 */ if (bind(server_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } /* 4.进入监听状态 如果客户端用connct()发出请求,服务器就会监听到 */ /* int listen(int sockfd, int backlog) 监听的socket描述字,相应socket可以排队的最大连接个数 */ /* 监听server_fd,就是鉴定这个地址和端口 */ if (listen(server_fd, 3) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } printf("TCP服务器已启动, 监听端口: %d\n", PORT); /* server_fd就是用于监听的,client_fd采用来进行实际的数据传输 */ int client_fd; struct sockaddr_in client_addr = {0}; socklen_t clnt_addr_size = sizeof(client_addr); /* 循环处理每个连接 */ while (1) { /* 接受客户端连接,返回新连接的套接字描述符 */ /* 如果客户端连接成功,client_fd这个套接字描述符代表的就是这个客户端连接,通过对这个套接字的读写来接收和发送数据 */ if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &clnt_addr_size)) < 0) { perror("accept failed"); continue; } printf("Connection established with new client\n"); handle_http_request(client_fd); /* 不断读取客户端发送的数据,直至连接关闭 */ /* 使用pthread_create创建新线程,设计线程创建、同步机制如互斥锁、条件变量等,使用epoll等高效IO模型 */ close(client_fd); } /* 关闭套接字 */ close(client_fd); close(server_fd); return 0; } 帮我看看问题吧
08-09
在 `server_thread.c` 文件的 `handle_get` 函数中,存在多个编译错误和潜在逻辑问题。以下是对这些问题的分析和修复建议。 ### 编译错误:implicit declaration of function 'get_mime_type' C 语言要求所有函数在使用前必须显式声明或定义。如果 `get_mime_type` 函数未在当前文件中声明或包含相应的头文件,编译器会报出隐式声明错误。为了解决这个问题,可以在 `server_thread.c` 文件顶部添加函数声明: ```c const char *get_mime_type(const char *filename); ``` 或者将该声明放入一个头文件中,并在 `server_thread.c` 中包含该头文件: ```c #include "mime_type.h" ``` 如果 `get_mime_type` 是在其他源文件中定义的函数,确保它在头文件中被正确声明[^1]。 ### 编译错误:format string type mismatch 格式字符串与参数类型不匹配通常出现在使用 `printf` 系列函数时。例如,如果格式字符串使用 `%d` 但实际参数是 `size_t` 类型(如 `fwrite` 的返回值),就会触发该错误。应确保格式字符串与参数类型一致。例如: ```c size_t file_size = get_file_size(full_path); printf("File size: %zu\n", file_size); // 使用 %zu 来匹配 size_t 类型 ``` 如果使用 `snprintf` 构建 HTTP 响应头,也应确保数值类型与格式字符串匹配: ```c snprintf(buffer, sizeof(buffer), "Content-Length: %zu\r\n", file_size); ``` ### 编译错误:unused variable 'response' 如果变量 `response` 在 `handle_get` 函数中被声明但从未使用,可以将其删除以消除警告: ```c // 删除未使用的变量声明 // char *response; // 未使用 ``` 如果 `response` 是用于构建 HTTP 响应但尚未实现相关逻辑,应补充相应的代码: ```c char response[1024]; snprintf(response, sizeof(response), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "\r\n", mime_type, file_size); send(client_fd, response, strlen(response), 0); ``` ### GET 请求处理逻辑分析 在 `handle_get` 函数中,处理逻辑如下: 1. 构建完整的文件路径。 2. 获取文件大小并打开文件。 3. 获取 MIME 类型并构建 HTTP 响应头。 4. 发送 HTTP 响应头。 5. 将文件内容发送给客户端。 整体逻辑是合理的,但需要注意以下几点: - **路径拼接安全**:确保路径拼接不会导致目录穿越攻击。例如,如果 `path` 包含 `../`,可能导致访问非预期的文件。应使用 `realpath` 或其他方式验证路径是否在 `WEB_ROOT` 目录下。 - **错误处理**:如果文件打开失败,应发送 404 错误响应。 - **二进制文件读取**:使用 `"rb"` 模式打开文件以确保正确读取二进制内容。 - **响应头格式**:确保响应头以 `\r\n` 分隔,并以 `\r\n\r\n` 结尾。 - **资源释放**:确保在函数结束前关闭文件流。 ### 示例修复后的代码片段 ```c #include "mime_type.h" void handle_get(int client_fd, const char *path) { char full_path[512]; const char *mime_type; if (strcmp(path, "/") == 0) { snprintf(full_path, sizeof(full_path), "%s/Index.html", WEB_ROOT); } else { snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path); } FILE *file = fopen(full_path, "rb"); if (!file) { const char *not_found = "HTTP/1.1 404 Not Found\r\n\r\n"; send(client_fd, not_found, strlen(not_found), 0); return; } fseek(file, 0, SEEK_END); size_t file_size = ftell(file); rewind(file); mime_type = get_mime_type(full_path); char response[1024]; snprintf(response, sizeof(response), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "\r\n", mime_type, file_size); send(client_fd, response, strlen(response), 0); char buffer[4096]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { send(client_fd, buffer, bytes_read, 0); } fclose(file); } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值