一.实现一个顺序的代理服务器
1.main函数
首先根据实验指导我可以确定要传入两个参数,第二个就是端口号,所以main函数要带参数
创建监听套接字open listenfd和等待连接的accept函数在csapp.c中已经写好,我们直接用
大致过程就是根据传入端口号创建相应监听套接字,然后循环调用accept等待请求,处理客户端请求
int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <listen_port>\n", argv[0]); exit(EXIT_FAILURE); } int port = atoi(argv[1]); if (port < 1024 || port > 65535) { fprintf(stderr, "Port must be 1024~65535\n"); exit(EXIT_FAILURE); } int listenfd = open_listenfd(argv[1]); if (listenfd < 0) { fprintf(stderr, "Open listen socket failed\n"); exit(EXIT_FAILURE); } printf("Proxy server running on port %d...\n", port); struct sockaddr_in clientaddr; socklen_t clientlen = sizeof(clientaddr); while (1) { int connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); if (connfd < 0) { perror("accept error"); continue; } printf("Accepted connection from %s:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); handle_client(connfd); } close(listenfd); return 0; }2.构造请求结构体
host是请求的域名,path是相对路径,port是端口号,默认80,headers存的是所有请求头
3.完成其他函数
函数handle_client的大致流程是用创建和客户端的连接后的连接套接字读取请求头和请求行,解析客户端发来的请求,创建和服务器端的连接套接字,用包装好的请求行和请求头去发送给服务器端,最后把服务器的响应发给客户端。
void handle_client(int client_fd) { char buffer[BUFFER_SIZE] = {0}; RequestInfo info; ssize_t n = recv(client_fd, buffer, BUFFER_SIZE-1, 0); if (n <= 0) { close(client_fd); return; } if (parse_request(buffer, &info) < 0) { const char *err = "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\nBad Request"; send(client_fd, err, strlen(err), 0); close(client_fd); return; } // int端口转字符串,适配open_clientfd参数 char port_str[MAX_PORT_STR_LEN] = {0}; snprintf(port_str, sizeof(port_str)-1, "%d", info.port); int server_fd = open_clientfd(info.host, port_str); if (server_fd < 0) { const char *err = "HTTP/1.0 502 Bad Gateway\r\nConnection: close\r\n\r\nBad Gateway"; send(client_fd, err, strlen(err), 0); close(client_fd); return; } if (forward_request(server_fd, &info) < 0) { const char *err = "HTTP/1.0 500 Internal Error\r\nConnection: close\r\n\r\nInternal Error"; send(client_fd, err, strlen(err), 0); close(server_fd); close(client_fd); return; } send_response_to_client(client_fd, server_fd); close(server_fd); close(client_fd); }解析请求和发送请求参考课件PPT和gitee实验指导要求
int parse_request(char *buffer, RequestInfo *info) { memset(info, 0, sizeof(RequestInfo)); char method[16], url[1024], version[16]; if (sscanf(buffer, "%s %s %s", method, url, version) != 3) { fprintf(stderr, "Invalid HTTP request line\n"); return -1; } if (strcasecmp(method, "GET") != 0) { fprintf(stderr, "Only GET method is supported\n"); return -1; } char *url_ptr = url; if (strstr(url, "http://") == url) url_ptr += 7; char *port_ptr = strchr(url_ptr, ':'); char *path_ptr = strchr(url_ptr, '/'); if (port_ptr && (!path_ptr || port_ptr < path_ptr)) { int host_len = port_ptr - url_ptr; strncpy(info->host, url_ptr, host_len); info->host[host_len] = '\0'; url_ptr = port_ptr + 1; int port_end = path_ptr ? (path_ptr - url_ptr) : strlen(url_ptr); char port_str[MAX_PORT_STR_LEN] = {0}; strncpy(port_str, url_ptr, port_end); info->port = atoi(port_str); strcpy(info->path, path_ptr ? path_ptr : "/"); } else { int host_len = path_ptr ? (path_ptr - url_ptr) : strlen(url_ptr); strncpy(info->host, url_ptr, host_len); info->host[host_len] = '\0'; info->port = DEFAULT_HTTP_PORT; strcpy(info->path, path_ptr ? path_ptr : "/"); } char *header_start = strstr(buffer, "\r\n") + 2; char *header_end = strstr(header_start, "\r\n\r\n"); if (header_end) { int header_len = header_end - header_start; strncpy(info->headers, header_start, header_len); info->headers[header_len] = '\0'; } return 0; } /** * 构造并转发请求到目标服务器(无标红,头信息完整) */ int forward_request(int server_fd, RequestInfo *info) { char request_buffer[BUFFER_SIZE] = {0}; snprintf(request_buffer, sizeof(request_buffer)-1, "GET %s HTTP/1.0\r\n", info->path); // 补充Host头(客户端无则手动添加,实验强制要求) if (!strstr(info->headers, "Host:")) { strncat(request_buffer, "Host: ", BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, info->host, BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); } // 追加固定头信息,无标红 strncat(request_buffer, user_agent_hdr, BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "Connection: close\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "Proxy-Connection: close\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); // 转发客户端原始请求头 if (strlen(info->headers) > 0) { strncat(request_buffer, info->headers, BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); } // HTTP请求结束空行 strncat(request_buffer, "\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); if (send(server_fd, request_buffer, strlen(request_buffer), 0) < 0) { perror("send to server failed"); return -1; } return 0; } /** * 将服务器响应转发给客户端(无标红,循环读写) */ int send_response_to_client(int client_fd, int server_fd) { char buffer[BUFFER_SIZE]; ssize_t n; while ((n = recv(server_fd, buffer, BUFFER_SIZE, 0)) > 0) { if (send(client_fd, buffer, n, 0) < 0) { perror("send to client failed"); return -1; } } return (n < 0) ? -1 : 0; }第一阶段拿到基础的40分就算完成了
二.处理多个并发请求
这部分比较简单,只需要把原来main函数的对于客户端的顺序执行改为创建线程并并行执行的过程,注意实验指导要求是分离的线程,并且这个过程不用考虑线程安全的问题
第二阶段拿到15分证明成功了
三.缓存Web对象
第三部分运用LRU策略缓存响应,具体完整代码如下
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <errno.h> #include <sys/types.h> #include <pthread.h> #include <time.h> // 新增:时间戳(实现极简LRU) #include "csapp.h" /* 实验要求的大小限制 */ #define MAX_CACHE_SIZE 1049000 // 1 MiB #define MAX_OBJECT_SIZE 102400 // 100 KiB #define DEFAULT_HTTP_PORT 80 #define BUFFER_SIZE 4096 #define MAX_PORT_STR_LEN 8 #define MAX_URL_LEN 1024 #define CACHE_ENTRY_NUM 10 // 缓存项数量(极简LRU用) /* 固定User-Agent头 */ static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n"; // ====================== 极简缓存模块(核心!)====================== /** * 极简缓存项(数组存储,用时间戳实现LRU) */ typedef struct { char key[MAX_URL_LEN]; // 缓存key:完整URL char value[MAX_OBJECT_SIZE];// 缓存value:Web对象 size_t size; // 对象大小 time_t last_used; // 最后使用时间戳(LRU核心) int valid; // 是否有效(1=有效,0=无效) } CacheEntry; // 全局缓存数组(极简实现,实验够用) CacheEntry cache[CACHE_ENTRY_NUM]; pthread_rwlock_t cache_rwlock; // 读写锁(满足多读单写) /** * 初始化缓存(仅初始化锁+清空缓存) */ void cache_init() { pthread_rwlock_init(&cache_rwlock, NULL); // 清空所有缓存项 for (int i = 0; i < CACHE_ENTRY_NUM; i++) { cache[i].valid = 0; cache[i].size = 0; cache[i].last_used = 0; } } /** * 查找缓存(读操作,加读锁) * @return 命中返回缓存项指针,未命中返回NULL */ CacheEntry* cache_find(const char *key) { pthread_rwlock_rdlock(&cache_rwlock); // 读锁:多线程可同时读 for (int i = 0; i < CACHE_ENTRY_NUM; i++) { if (cache[i].valid && strcmp(cache[i].key, key) == 0) { cache[i].last_used = time(NULL); // 更新使用时间(LRU) pthread_rwlock_unlock(&cache_rwlock); return &cache[i]; } } pthread_rwlock_unlock(&cache_rwlock); return NULL; } /** * 新增缓存(写操作,加写锁) */ void cache_add(const char *key, const char *value, size_t size) { // 1. 校验大小(实验强制要求) if (size > MAX_OBJECT_SIZE || size == 0) return; pthread_rwlock_wrlock(&cache_rwlock); // 写锁:仅单线程可写 // 2. 找替换位置(LRU:选最久未使用/无效项) int replace_idx = 0; time_t oldest = time(NULL); for (int i = 0; i < CACHE_ENTRY_NUM; i++) { if (!cache[i].valid) { // 优先替换无效项 replace_idx = i; break; } if (cache[i].last_used < oldest) { // 找最久未使用的 oldest = cache[i].last_used; replace_idx = i; } } // 3. 写入缓存 CacheEntry *entry = &cache[replace_idx]; strncpy(entry->key, key, MAX_URL_LEN-1); strncpy(entry->value, value, size); entry->size = size; entry->last_used = time(NULL); entry->valid = 1; pthread_rwlock_unlock(&cache_rwlock); } // ====================== 缓存模块结束 ====================== // 请求解析结构体(不变) typedef struct { char host[256]; char path[1024]; int port; char headers[4096]; } RequestInfo; // 线程入口函数(不变) void *thread_handle_client(void *arg) { int client_fd = (int)(long)arg; pthread_detach(pthread_self()); handle_client(client_fd); return NULL; } // 解析请求函数(完全不变) int parse_request(char *buffer, RequestInfo *info) { memset(info, 0, sizeof(RequestInfo)); char method[16], url[1024], version[16]; if (sscanf(buffer, "%s %s %s", method, url, version) != 3) { fprintf(stderr, "Invalid HTTP request line\n"); return -1; } if (strcasecmp(method, "GET") != 0) { fprintf(stderr, "Only GET method is supported\n"); return -1; } char *url_ptr = url; if (strstr(url, "http://") == url) url_ptr += 7; char *port_ptr = strchr(url_ptr, ':'); char *path_ptr = strchr(url_ptr, '/'); if (port_ptr && (!path_ptr || port_ptr < path_ptr)) { int host_len = port_ptr - url_ptr; strncpy(info->host, url_ptr, host_len); info->host[host_len] = '\0'; url_ptr = port_ptr + 1; int port_end = path_ptr ? (path_ptr - url_ptr) : strlen(url_ptr); char port_str[MAX_PORT_STR_LEN] = {0}; strncpy(port_str, url_ptr, port_end); info->port = atoi(port_str); strcpy(info->path, path_ptr ? path_ptr : "/"); } else { int host_len = path_ptr ? (path_ptr - url_ptr) : strlen(url_ptr); strncpy(info->host, url_ptr, host_len); info->host[host_len] = '\0'; info->port = DEFAULT_HTTP_PORT; strcpy(info->path, path_ptr ? path_ptr : "/"); } char *header_start = strstr(buffer, "\r\n") + 2; char *header_end = strstr(header_start, "\r\n\r\n"); if (header_end) { int header_len = header_end - header_start; strncpy(info->headers, header_start, header_len); info->headers[header_len] = '\0'; } return 0; } // 转发请求函数(完全不变) int forward_request(int server_fd, RequestInfo *info) { char request_buffer[BUFFER_SIZE] = {0}; snprintf(request_buffer, sizeof(request_buffer)-1, "GET %s HTTP/1.0\r\n", info->path); if (!strstr(info->headers, "Host:")) { strncat(request_buffer, "Host: ", BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, info->host, BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); } strncat(request_buffer, user_agent_hdr, BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "Connection: close\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "Proxy-Connection: close\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); if (strlen(info->headers) > 0) { strncat(request_buffer, info->headers, BUFFER_SIZE - strlen(request_buffer) - 1); strncat(request_buffer, "\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); } strncat(request_buffer, "\r\n", BUFFER_SIZE - strlen(request_buffer) - 1); if (send(server_fd, request_buffer, strlen(request_buffer), 0) < 0) { perror("send to server failed"); return -1; } return 0; } // 处理客户端函数(仅集成极简缓存) void handle_client(int client_fd) { char buffer[BUFFER_SIZE] = {0}; RequestInfo info; ssize_t n = recv(client_fd, buffer, BUFFER_SIZE-1, 0); char full_url[MAX_URL_LEN] = {0}; if (n <= 0) { close(client_fd); return; } // 1. 解析请求 if (parse_request(buffer, &info) < 0) { const char *err = "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\nBad Request"; send(client_fd, err, strlen(err), 0); close(client_fd); return; } // 2. 拼接缓存key(完整URL) snprintf(full_url, MAX_URL_LEN-1, "http://%s:%d%s", info.host, info.port, info.path); // 3. 查找缓存(命中直接返回,核心简化!) CacheEntry *cache_entry = cache_find(full_url); if (cache_entry) { send(client_fd, cache_entry->value, cache_entry->size, 0); close(client_fd); return; } // 4. 缓存未命中:连接目标服务器(原有逻辑不变) char port_str[MAX_PORT_STR_LEN] = {0}; snprintf(port_str, sizeof(port_str)-1, "%d", info.port); int server_fd = open_clientfd(info.host, port_str); if (server_fd < 0) { const char *err = "HTTP/1.0 502 Bad Gateway\r\nConnection: close\r\n\r\nBad Gateway"; send(client_fd, err, strlen(err), 0); close(client_fd); return; } if (forward_request(server_fd, &info) < 0) { const char *err = "HTTP/1.0 500 Internal Error\r\nConnection: close\r\n\r\nInternal Error"; send(client_fd, err, strlen(err), 0); close(server_fd); close(client_fd); return; } // 5. 接收响应并缓存(核心简化!) char resp_buffer[MAX_OBJECT_SIZE] = {0}; ssize_t resp_len = 0, read_len; while ((read_len = recv(server_fd, resp_buffer + resp_len, MAX_OBJECT_SIZE - resp_len - 1, 0)) > 0) { resp_len += read_len; if (resp_len > MAX_OBJECT_SIZE) { // 超过大小限制,丢弃 resp_len = 0; break; } send(client_fd, resp_buffer + resp_len - read_len, read_len, 0); // 实时转发 } // 6. 写入缓存(仅核心逻辑) if (resp_len > 0 && resp_len <= MAX_OBJECT_SIZE) { cache_add(full_url, resp_buffer, resp_len); } close(server_fd); close(client_fd); } // 主函数(仅新增缓存初始化) int main(int argc, char *argv[]) { cache_init(); // 初始化缓存(一行搞定!) if (argc != 2) { fprintf(stderr, "Usage: %s <listen_port>\n", argv[0]); exit(EXIT_FAILURE); } int port = atoi(argv[1]); if (port < 1024 || port > 65535) { fprintf(stderr, "Port must be 1024~65535\n"); exit(EXIT_FAILURE); } int listenfd = open_listenfd(argv[1]); if (listenfd < 0) { fprintf(stderr, "Open listen socket failed\n"); exit(EXIT_FAILURE); } printf("Proxy server running on port %d...\n", port); struct sockaddr_in clientaddr; socklen_t clientlen = sizeof(clientaddr); while (1) { int connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); if (connfd < 0) { perror("accept error"); continue; } printf("Accepted connection from %s:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); // 并发处理 pthread_t tid; if (pthread_create(&tid, NULL, thread_handle_client, (void*)(long)connfd) < 0) { perror("pthread_create error"); close(connfd); continue; } } close(listenfd); return 0; }