一个简单的Socket实现的HTTP响应服务器(GET/POST/UPLOAD)

本文介绍了一个简单的Socket实现的HTTP响应服务器,用于演示HTTP协议的基本交互过程。服务器监听8000端口,支持GET和POST请求,并能显示浏览器发送的数据。

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

  1. package test.io;   
  2.   
  3. import java.net.*;   
  4. import java.io.*;   
  5.   
  6. /**  
  7.  * 一个简单的Socket实现的HTTP响应服务器。<br>  
  8.  * 只用于熟悉HTTP协议的目的,可以看到浏览器发过来的数据格式。  
  9.  *   
  10.  * @author 赵学庆 www.java2000.net  
  11.  */  
  12. public class MyWebServer {   
  13.   public static void main(String[] args) {   
  14.     Socket socket = null;   
  15.     try {   
  16.       // 创建一个监听8000端口的服务器Socket   
  17.       ServerSocket s = new ServerSocket(80003);   
  18.       System.out.println("MyWebServer等待来自浏览器的连接\n");   
  19.       while (true) {   
  20.         socket = s.accept();   
  21.         System.out.println("连接已建立。端口号:" + socket.getPort());   
  22.         new MyWebServerThread(socket).start();   
  23.       }   
  24.     } catch (IOException e) {   
  25.       e.printStackTrace();   
  26.     }   
  27.   }   
  28. }   
  29.   
  30. class MyWebServerThread extends Thread {   
  31.   private Socket socket;   
  32.   
  33.   MyWebServerThread(Socket socket) {   
  34.     this.socket = socket;   
  35.   }   
  36.   
  37.   @Override  
  38.   public void run() {   
  39.     try {   
  40.       InputStreamReader is = new InputStreamReader(socket.getInputStream());   
  41.       char[] bs = new char[2048];   
  42.       PrintStream out;   
  43.       out = new PrintStream(socket.getOutputStream());   
  44.       StringBuilder msg = new StringBuilder();   
  45.       // 如果10毫秒还没有数据,则视同没有新的数据了。   
  46.       // 因为有Keep-Alive的缘故,浏览器可能不主动断开连接的。   
  47.       // 实际应用,会根据协议第一行是GET还是 POST确定。   
  48.       socket.setSoTimeout(10);   
  49.       //   
  50.       // 此处读入请求数据并做相应的处理   
  51.       //   
  52.       int len = -1;   
  53.       try {   
  54.         while ((len = is.read(bs)) != -1) {   
  55.           msg.append(bs, 0, len);   
  56.           msg.append("\n");   
  57.         }   
  58.       } catch (Exception ex) {   
  59.         // ex.printStackTrace();   
  60.       }   
  61.       // 下面是由服务器直接生成的主页内容   
  62.       // 1、首先向浏览器输出响应头信息   
  63.       out.println("HTTP/1.1 200 OK");   
  64.       out.println("Content-Type:text/html;charset:GBK");   
  65.       out.println();   
  66.       // 2、输出主页信息   
  67.       out   
  68.           .println("<HTML><BODY>"  
  69.               + "<center>"  
  70.               + "<H1>HTTP协议测试服务器,当前时间:"  
  71.               + new java.util.Date()   
  72.               + "</h1>"  
  73.               + "<form method='get'>username:<input type='text' name='username'/>password:<input type='text' name='password'/><input type='submit' value='GET测试'/></form><br/>"  
  74.               + "<form method='post'>username:<input type='text' name='username'/>password:<input type='text' name='password'/><input type='submit' value='POST测试'/></form><br/>"  
  75.               + "<form method='post'  enctype='multipart/form-data'>phototitle:<input type='text' name='phototitle'/>photo:<input type='file' name='photo'/><input type='submit' value='Upload测试'/></form>"  
  76.               + "</center>您提交的数据如下:<pre>" + msg.toString() + "</pre></BODY></HTML>");   
  77.       out.flush();   
  78.       out.close();   
  79.       is.close();   
  80.       System.out.println("close");   
  81.       // 关闭连接   
  82.       socket.close();   
  83.     } catch (IOException e) {   
  84.       e.printStackTrace();   
  85.     }   
  86.   }   
  87. }  
package test.io;

import java.net.*;
import java.io.*;

/**
 * 一个简单的Socket实现的HTTP响应服务器。<br>
 * 只用于熟悉HTTP协议的目的,可以看到浏览器发过来的数据格式。
 * 
 * @author  */
public class MyWebServer {
  public static void main(String[] args) {
    Socket socket = null;
    try {
      // 创建一个监听8000端口的服务器Socket
      ServerSocket s = new ServerSocket(8000, 3);
      System.out.println("MyWebServer等待来自浏览器的连接\n");
      while (true) {
        socket = s.accept();
        System.out.println("连接已建立。端口号:" + socket.getPort());
        new MyWebServerThread(socket).start();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

class MyWebServerThread extends Thread {
  private Socket socket;

  MyWebServerThread(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    try {
      InputStreamReader is = new InputStreamReader(socket.getInputStream());
      char[] bs = new char[2048];
      PrintStream out;
      out = new PrintStream(socket.getOutputStream());
      StringBuilder msg = new StringBuilder();
      // 如果10毫秒还没有数据,则视同没有新的数据了。
      // 因为有Keep-Alive的缘故,浏览器可能不主动断开连接的。
      // 实际应用,会根据协议第一行是GET还是 POST确定。
      socket.setSoTimeout(10);
      //
      // 此处读入请求数据并做相应的处理
      //
      int len = -1;
      try {
        while ((len = is.read(bs)) != -1) {
          msg.append(bs, 0, len);
          msg.append("\n");
        }
      } catch (Exception ex) {
        // ex.printStackTrace();
      }
      // 下面是由服务器直接生成的主页内容
      // 1、首先向浏览器输出响应头信息
      out.println("HTTP/1.1 200 OK");
      out.println("Content-Type:text/html;charset:GBK");
      out.println();
      // 2、输出主页信息
      out
          .println("<HTML><BODY>"
              + "<center>"
              + "<H1>HTTP协议测试服务器,当前时间:"
              + new java.util.Date()
              + "</h1>"
              + "<form method='get'>username:<input type='text' name='username'/>password:<input type='text' name='password'/><input type='submit' value='GET测试'/></form><br/>"
              + "<form method='post'>username:<input type='text' name='username'/>password:<input type='text' name='password'/><input type='submit' value='POST测试'/></form><br/>"
              + "<form method='post'  enctype='multipart/form-data'>phototitle:<input type='text' name='phototitle'/>photo:<input type='file' name='photo'/><input type='submit' value='Upload测试'/></form>"
              + "</center>您提交的数据如下:<pre>" + msg.toString() + "</pre></BODY></HTML>");
      out.flush();
      out.close();
      is.close();
      System.out.println("close");
      // 关闭连接
      socket.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}



运行结果
GET方法

引用:
GET /?username=111&password=222 HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://127.0.0.1:8000/?username=1&password=2



POST方法

引用:
POST /?username=111&password=222 HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://127.0.0.1:8000/?username=111&password=222
Content-Type: application/x-www-form-urlencoded
Content-Length: 25

username=111&password=222



UPLOAD

引用:
POST /?username=111&password=222 HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://127.0.0.1:8000/?username=111&password=222
Content-Type: multipart/form-data; boundary=---------------------------23757983230932
Content-Length: 1704

-----------------------------23757983230932
Content-Disposition: form-data; name="phototitle"

12
-----------------------------23757983230932
Content-Disposition: form-data; name="photo"; filename="技术考核题.txt"
Content-Type: text/plain

技术考核
本次是个编程题,题目选Java/语言进行回答。

 
问题描述:
假设你的爱好是集邮。目前总共有N种不同的邮票,编号为从0到N-1.每种邮票的价钱被定义在数组int[] prices中(序号从0开始,数组的第i个元素表示第i种邮票的价格)。
你的目标是收集尽可能多种的邮票。你当前拥有的邮票存储在int[] have这个数组中。初始时,你没有钱,但是你可以卖掉已有邮票来买不同的邮票。返回你能收集到的不同种邮票的最大数量。
 
定义:
class: PostmarksCollection
method: numberOfDistinctPostmarks
Parameters: int[], int[]
Returns: int
 
Method signature: public int numberOfDistinctPostmarks(int[] prices, int[] have)
 //为了进行测试,必须保证是public
 
约束:
1) N从1到50
2)prices中的元素数量刚好是N个元素
3)prices中的元素的值,从1到1,000,000
4)have中的元素数量是0个到N个
 
5)have中的每个元素是不同的
6)have中的每个元素的值是从0到N-1.
 
例如:
1)
{13,10,14,20}
{3,0,2,1}
Returns: 4
你已经拥有所有种类的邮票。
 
2)
{7,5,9,7}
{}
Returns: 0
你开始没有任何邮票,所以你也不能组任何事情。
 
3)
{4,13,9,1,5}
{1,3,2}
Returns: 4
卖掉邮票2,买入邮票0和4,(邮票2的价钱是9,邮票0的价钱是4,邮票4的价钱是5,卖掉2刚好买入0和4)
 
4)
 
{16,32,13,2,17,10,8,8,20,17}
{7,0,4,1,6,8}
Returns: 8

--------------------------------------------------------------------------------



  使用Java请在此处答题(请使用JDK5.0编译,编译不能通过者不计算分数)

-----------------------------23757983230932--
<think>我们正在实现一个支持GETPOST请求的多线程Web服务器,使用C语言编写,并添加详细注释。我们将使用线程池和epoll来实现高并发处理。以下是一个整合的代码,包含详细注释: 设计要点: 1. 使用线程池处理连接,避免频繁创建销毁线程。 2. 使用epoll进行I/O多路复用,监听多个套接字。 3. 解析HTTP请求,支持GETPOST方法。 4. 实现简单的路由,处理不同的请求路径。 5. 发送HTTP响应,包括状态码、头部和内容。 注意:为了简化,我们不考虑持久连接(Keep-Alive),每次请求后关闭连接。 我们将代码写在一个文件中,并添加详细的注释。</think>以下是一个详细注释的C语言多线程Web服务器实现,支持GET/POST请求处理和高并发性能。代码整合在单个C文件中: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <pthread.h> #include <sys/epoll.h> #include <fcntl.h> #include <sys/sendfile.h> #include <sys/stat.h> #define PORT 8080 // 服务器监听端口 #define THREAD_POOL_SIZE 16 // 线程池大小 #define MAX_EVENTS 1024 // epoll最大事件数 #define BUFFER_SIZE 8192 // 读写缓冲区大小 #define BACKLOG 128 // 最大等待连接数 #define MAX_PATH_LEN 256 // 最大路径长度 // HTTP请求结构体 typedef struct { char method[16]; // 请求方法(GET/POST) char path[MAX_PATH_LEN]; // 请求路径 char protocol[16]; // 协议版本 char headers[BUFFER_SIZE]; // 请求头 char *body; // 请求体(POST使用) int content_length; // 内容长度 } HttpRequest; // HTTP响应结构体 typedef struct { int status_code; // 状态码 char *content; // 响应内容 size_t content_len; // 内容长度 char headers[BUFFER_SIZE]; // 响应头 } HttpResponse; // 任务队列节点 typedef struct TaskNode { int client_fd; // 客户端文件描述符 struct TaskNode *next; // 下一个任务指针 } TaskNode; // 线程安全任务队列 typedef struct { TaskNode *front; // 队列头部 TaskNode *rear; // 队列尾部 pthread_mutex_t lock; // 互斥锁 pthread_cond_t cond; // 条件变量 int shutdown; // 关闭标志 } TaskQueue; // 线程池结构 typedef struct { pthread_t threads[THREAD_POOL_SIZE]; // 工作线程数组 TaskQueue *queue; // 任务队列 int shutdown; // 关闭标志 } ThreadPool; // 初始化任务队列 TaskQueue* init_task_queue() { TaskQueue *q = malloc(sizeof(TaskQueue)); if (!q) { perror("Failed to allocate task queue"); exit(EXIT_FAILURE); } q->front = q->rear = NULL; q->shutdown = 0; // 初始化互斥锁和条件变量 if (pthread_mutex_init(&q->lock, NULL) != 0) { perror("Mutex init failed"); exit(EXIT_FAILURE); } if (pthread_cond_init(&q->cond, NULL) != 0) { perror("Condition variable init failed"); exit(EXIT_FAILURE); } return q; } // 添加任务到队列 void enqueue(TaskQueue *q, int client_fd) { // 创建新任务节点 TaskNode *node = malloc(sizeof(TaskNode)); if (!node) { perror("Failed to allocate task node"); return; } node->client_fd = client_fd; node->next = NULL; // 加锁操作队列 pthread_mutex_lock(&q->lock); // 队列为空时直接添加 if (q->rear == NULL) { q->front = q->rear = node; } // 队列非空时添加到尾部 else { q->rear->next = node; q->rear = node; } // 唤醒等待线程并解锁 pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->lock); } // 从队列获取任务 int dequeue(TaskQueue *q) { pthread_mutex_lock(&q->lock); // 队列为空时等待条件变量 while (q->front == NULL && !q->shutdown) { pthread_cond_wait(&q->cond, &q->lock); } // 服务器关闭时返回-1 if (q->shutdown) { pthread_mutex_unlock(&q->lock); return -1; } // 获取队列头部任务 TaskNode *node = q->front; int client_fd = node->client_fd; // 更新队列头部 if (q->front == q->rear) { q->front = q->rear = NULL; } else { q->front = q->front->next; } // 释放节点内存并解锁 free(node); pthread_mutex_unlock(&q->lock); return client_fd; } // 工作线程函数 void* worker_thread(void *arg) { ThreadPool *pool = (ThreadPool*)arg; // 持续处理任务直到关闭 while (!pool->shutdown) { // 从队列获取客户端连接 int client_fd = dequeue(pool->queue); if (client_fd < 0) continue; // 处理客户端请求 handle_client(client_fd); // 关闭客户端连接 close(client_fd); } return NULL; } // 初始化线程池 ThreadPool* init_thread_pool(TaskQueue *queue) { ThreadPool *pool = malloc(sizeof(ThreadPool)); if (!pool) { perror("Failed to allocate thread pool"); exit(EXIT_FAILURE); } pool->queue = queue; pool->shutdown = 0; // 创建工作线程 for (int i = 0; i < THREAD_POOL_SIZE; i++) { if (pthread_create(&pool->threads[i], NULL, worker_thread, pool) != 0) { perror("Failed to create thread"); exit(EXIT_FAILURE); } } return pool; } // 创建服务器套接字 int create_server_socket(int port) { int server_fd; struct sockaddr_in addr; // 创建TCP套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } // 设置SO_REUSEADDR选项避免TIME_WAIT状态 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) { perror("Setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } // 配置服务器地址 addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port); // 绑定地址到套接字 if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("Bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听连接 if (listen(server_fd, BACKLOG) < 0) { perror("Listen failed"); close(server_fd); exit(EXIT_FAILURE); } return server_fd; } // 解析HTTP请求 HttpRequest* parse_request(char *buffer) { HttpRequest *req = malloc(sizeof(HttpRequest)); if (!req) { perror("Failed to allocate request"); return NULL; } memset(req, 0, sizeof(HttpRequest)); // 解析请求行: METHOD PATH PROTOCOL if (sscanf(buffer, "%15s %255s %15s", req->method, req->path, req->protocol) != 3) { free(req); return NULL; } // 查找头部结束位置(空行) char *header_end = strstr(buffer, "\r\n\r\n"); if (header_end) { // 计算头部长度并复制 size_t header_len = header_end - buffer; strncpy(req->headers, buffer, header_len); req->headers[header_len] = '\0'; // 处理POST请求体 if (strcmp(req->method, "POST") == 0) { // 查找Content-Length头部 char *cl_header = strstr(req->headers, "Content-Length:"); if (cl_header) { // 获取内容长度 if (sscanf(cl_header, "Content-Length: %d", &req->content_length) != 1) { req->content_length = 0; } // 分配内存并复制请求体 if (req->content_length > 0) { req->body = malloc(req->content_length + 1); if (!req->body) { perror("Failed to allocate request body"); free(req); return NULL; } memcpy(req->body, header_end + 4, req->content_length); req->body[req->content_length] = '\0'; } } } } return req; } // 发送HTTP响应 void send_response(int client_fd, HttpResponse *res) { char status_line[128]; // 根据状态码生成状态行 switch (res->status_code) { case 200: sprintf(status_line, "HTTP/1.1 200 OK\r\n"); break; case 404: sprintf(status_line, "HTTP/1.1 404 Not Found\r\n"); break; case 500: sprintf(status_line, "HTTP/1.1 500 Internal Server Error\r\n"); break; default: sprintf(status_line, "HTTP/1.1 %d Status\r\n", res->status_code); } // 构建完整响应 char full_response[BUFFER_SIZE * 2]; int header_len = snprintf(full_response, sizeof(full_response), "%s%sContent-Length: %zu\r\n\r\n", status_line, res->headers, res->content_len); // 复制响应内容 if (res->content && res->content_len > 0) { memcpy(full_response + header_len, res->content, res->content_len); } // 计算总长度并发送 size_t total_len = header_len + res->content_len; send(client_fd, full_response, total_len, 0); } // 安全路径检查(防止路径遍历攻击) int is_safe_path(const char *path) { // 检查是否包含父目录引用 if (strstr(path, "..") != NULL) { return 0; } // 检查是否尝试访问根目录之外 if (strstr(path, "/etc/passwd") != NULL) { return 0; } return 1; } // 处理GET请求 void handle_get(int client_fd, HttpRequest *req, HttpResponse *res) { // 默认响应 res->status_code = 200; res->content = "Hello, World!"; res->content_len = strlen(res->content); // 简单路由处理 if (strcmp(req->path, "/") == 0) { // 首页响应 res->content = "<html><body><h1>Welcome to C Web Server</h1></body></html>"; res->content_len = strlen(res->content); snprintf(res->headers, sizeof(res->headers), "Content-Type: text/html\r\n"); } // 处理文件请求 else { char file_path[MAX_PATH_LEN]; snprintf(file_path, sizeof(file_path), ".%s", req->path); // 检查路径安全性 if (!is_safe_path(file_path)) { res->status_code = 403; res->content = "Forbidden"; res->content_len = strlen(res->content); return; } // 检查文件是否存在 struct stat st; if (stat(file_path, &st) == 0) { // 打开文件 int fd = open(file_path, O_RDONLY); if (fd >= 0) { // 设置响应头 const char *ext = strrchr(file_path, '.'); if (ext) { if (strcmp(ext, ".html") == 0) { snprintf(res->headers, sizeof(res->headers), "Content-Type: text/html\r\n"); } else if (strcmp(ext, ".css") == 0) { snprintf(res->headers, sizeof(res->headers), "Content-Type: text/css\r\n"); } else if (strcmp(ext, ".js") == 0) { snprintf(res->headers, sizeof(res->headers), "Content-Type: application/javascript\r\n"); } else { snprintf(res->headers, sizeof(res->headers), "Content-Type: application/octet-stream\r\n"); } } // 使用sendfile零拷贝传输文件 sendfile(client_fd, fd, NULL, st.st_size); close(fd); return; } } // 文件不存在 res->status_code = 404; res->content = "404 Not Found"; res->content_len = strlen(res->content); } } // 处理POST请求 void handle_post(HttpRequest *req, HttpResponse *res) { // 默认响应 res->status_code = 200; res->content = "Form submitted successfully"; res->content_len = strlen(res->content); // 示例:处理表单提交 if (strstr(req->path, "/submit")) { printf("Received POST data: %.*s\n", req->content_length, req->body); snprintf(res->headers, sizeof(res->headers), "Content-Type: text/plain\r\n"); } // 处理文件上传 else if (strstr(req->path, "/upload")) { // 查找boundary char *boundary = strstr(req->headers, "boundary="); if (boundary) { boundary += 9; // 移动到boundary值 // 截断boundary字符串 char *end = strchr(boundary, '\r'); if (end) *end = '\0'; // 解析multipart数据 char *body_ptr = req->body; while ((body_ptr = strstr(body_ptr, boundary))) { // 跳过头部信息 body_ptr = strstr(body_ptr, "\r\n\r\n"); if (!body_ptr) break; body_ptr += 4; // 跳过空行 // 查找下一个boundary char *next_boundary = strstr(body_ptr, boundary); if (!next_boundary) break; // 计算文件内容长度 size_t file_size = next_boundary - body_ptr - 4; // 减去前置的\r\n // 写入文件 FILE *file = fopen("uploaded_file", "wb"); if (file) { fwrite(body_ptr, 1, file_size, file); fclose(file); res->content = "File uploaded successfully"; res->content_len = strlen(res->content); return; } } } // 文件上传失败 res->status_code = 500; res->content = "File upload failed"; res->content_len = strlen(res->content); } } // 处理客户端请求 void handle_client(int client_fd) { char buffer[BUFFER_SIZE]; // 读取客户端请求 ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0); // 错误或连接关闭 if (bytes_read <= 0) { close(client_fd); return; } // 确保字符串以NULL结尾 buffer[bytes_read] = '\0'; // 解析HTTP请求 HttpRequest *req = parse_request(buffer); if (!req) { close(client_fd); return; } // 初始化响应结构 HttpResponse res = {0}; // 根据请求方法路由处理 if (strcmp(req->method, "GET") == 0) { handle_get(client_fd, req, &res); } else if (strcmp(req->method, "POST") == 0) { handle_post(req, &res); } else { // 不支持的HTTP方法 res.content = "Method Not Allowed"; res.content_len = strlen(res.content); res.status_code = 405; } // 发送响应(文件请求已直接发送,跳过) if (res.content) { send_response(client_fd, &res); } // 清理资源 if (req->body) free(req->body); free(req); } int main() { // 创建服务器套接字 int server_fd = create_server_socket(PORT); printf("Server listening on port %d\n", PORT); // 创建任务队列 TaskQueue *queue = init_task_queue(); // 创建线程池 ThreadPool *pool = init_thread_pool(queue); // 创建epoll实例 int epoll_fd = epoll_create1(0); if (epoll_fd < 0) { perror("epoll_create1 failed"); exit(EXIT_FAILURE); } // 添加服务器套接字到epoll struct epoll_event ev; ev.events = EPOLLIN; // 监听可读事件 ev.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) < 0) { perror("epoll_ctl failed"); exit(EXIT_FAILURE); } // 主事件循环 struct epoll_event events[MAX_EVENTS]; while (1) { // 等待事件(无限等待) int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds < 0) { perror("epoll_wait failed"); continue; } // 处理所有就绪事件 for (int i = 0; i < nfds; i++) { // 新连接请求 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); // 接受新连接 int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len); if (client_fd < 0) { perror("accept failed"); continue; } // 设置非阻塞模式 int flags = fcntl(client_fd, F_GETFL, 0); fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); // 添加新连接到epoll(边缘触发模式) ev.events = EPOLLIN | EPOLLET; ev.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) { perror("epoll_ctl add client failed"); close(client_fd); } } // 现有连接有数据可读 else { // 添加到任务队列 enqueue(queue, events[i].data.fd); // 从epoll中移除,避免重复处理 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); } } } // 清理资源(实际不会执行到这里) close(server_fd); close(epoll_fd); return 0; } ``` ### 关键设计说明 1. **线程池模型** - 预创建16个工作线程处理请求 - 使用互斥锁(`pthread_mutex_t`)和条件变量(`pthread_cond_t`)实现线程安全队列 - 避免线程频繁创建销毁的开销 2. **高并发实现** ```c // 使用epoll实现I/O多路复用 int epoll_fd = epoll_create1(0); epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev); // 边缘触发模式(EPOLLET)提高效率 ev.events = EPOLLIN | EPOLLET; ``` 3. **零拷贝文件传输** ```c // 使用sendfile系统调用高效传输文件 sendfile(client_fd, fd, NULL, st.st_size); ``` 4. **安全防护** ```c // 防止路径遍历攻击 int is_safe_path(const char *path) { if (strstr(path, "..") != NULL) return 0; if (strstr(path, "/etc/passwd") != NULL) return 0; return 1; } ``` 5. **HTTP解析** - 使用`sscanf`解析请求行 - 自动处理POST请求的内容长度 - 支持multipart/form-data文件上传 ### 编译与测试 ```bash # 编译 gcc -o webserver server.c -lpthread # 运行 ./webserver # 测试GET请求 curl http://localhost:8080/ curl http://localhost:8080/index.html # 测试POST请求 curl -X POST -d "name=value" http://localhost:8080/submit # 测试文件上传 curl -F "file=@test.txt" http://localhost:8080/upload ``` ### 性能优化建议 1. **连接复用**:实现HTTP Keep-Alive减少连接开销 2. **内存池**:预分配请求/响应内存减少`malloc`调用 3. **日志系统**:添加异步日志记录功能 4. **配置文件**:支持端口、线程数等参数配置 5. **SSL支持**:添加OpenSSL实现HTTPS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值