#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <errno.h> // 添加errno头文件
#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.2.0\r\n"
// 函数声明
void* accept_request(void *);
void bad_request(int);
void cat(int, FILE *);
void error_die(const char *);
int get_line(int, char *, int);
void headers(int, const char *, const char *);
void not_found(int);
void serve_file(int, const char *);
void serve_file_post(int, const char *);
int startup(unsigned short *);
void unimplemented(int);
void forbidden(int);
void serve_directory(int, const char *);
void url_decode(char *, const char *);
const char *get_mime_type(const char *);
void log_request(const char *, const char *, int);
void set_socket_timeout(int, int);
/**********************************************************************/
/* 处理客户端请求 */
/**********************************************************************/
void* accept_request(void *pclient)
{
int client = *(int*)pclient;
free(pclient);
set_socket_timeout(client, 2);
char buf[65536];
int numchars;
char method[255];
char url[255];
char path[1024]; // 增加路径长度
char decoded_url[1024]; // 存储解码后的URL
size_t i, j;
struct stat st;
// 获取请求的第一行
numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0';
// 只支持GET和POST方法
if (strcasecmp(method, "GET") && strcasecmp(method, "POST") && strcasecmp(method, "OPTIONS"))
{
unimplemented(client);
return NULL;
}
// 读取URL
i = 0;
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
// URL解码
url_decode(decoded_url, url);
// 记录请求日志
log_request(method, decoded_url, client);
// 构建文件路径(使用当前目录)
sprintf(path, "web%s", decoded_url);
// 防止路径遍历攻击
if (strstr(path, "..")) {
forbidden(client);
close(client);
return NULL;
}
// 处理目录请求
if (path[strlen(path) - 1] == '/')
strcat(path, "Index.html");
// 检查文件/目录是否存在
if (stat(path, &st) == -1) {
// 检查是否存在.html扩展名文件
char alt_path[1024];
sprintf(alt_path, "%s.html", path);
if (stat(alt_path, &st) == 0) {
strcpy(path, alt_path);
} else {
/* 跳过剩余头部 */
while ((numchars > 0) && strcmp("\n", buf))
numchars = get_line(client, buf, sizeof(buf));
not_found(client);
close(client);
return NULL;
}
}
// 如果是目录
if ((st.st_mode & S_IFMT) == S_IFDIR) {
// 检查目录中是否有index.html
char index_path[1024];
sprintf(index_path, "%s/Index.html", path);
if (stat(index_path, &st) == 0) {
strcpy(path, index_path);
} else {
// 显示目录列表
serve_directory(client, path);
close(client);
return NULL;
}
}
if (strcasecmp(method, "GET") == 0)
{
serve_file(client, path);
}
else if (strcasecmp(method, "POST") == 0)
{
serve_file_post(client, path);
}
close(client);
return NULL;
}
/**********************************************************************/
/* URL解码 */
/**********************************************************************/
void url_decode(char *dest, const char *src) {
char *p = dest;
while (*src) {
if (*src == '%') {
if (src[1] && src[2]) {
char hex[3] = {src[1], src[2], '\0'};
*p++ = (char)strtol(hex, NULL, 16);
src += 3;
} else {
*p++ = *src++;
}
} else if (*src == '+') {
*p++ = ' ';
src++;
} else {
*p++ = *src++;
}
}
*p = '\0';
}
/**********************************************************************/
/* 获取MIME类型 */
/**********************************************************************/
const char *get_mime_type(const char *filename) {
const char *dot = strrchr(filename, '.');
if (!dot) return "text/plain";
if (strcasecmp(dot, ".html") == 0 || strcasecmp(dot, ".htm") == 0)
return "text/html";
if (strcasecmp(dot, ".css") == 0)
return "text/css";
if (strcasecmp(dot, ".js") == 0)
return "application/javascript";
if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcasecmp(dot, ".png") == 0)
return "image/png";
if (strcasecmp(dot, ".gif") == 0)
return "image/gif";
if (strcasecmp(dot, ".json") == 0)
return "application/json";
if (strcasecmp(dot, ".ico") == 0)
return "image/x-icon";
return "text/plain";
}
/**********************************************************************/
/* 记录请求日志 */
/**********************************************************************/
void log_request(const char *method, const char *url, int client) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
getpeername(client, (struct sockaddr*)&addr, &addr_len);
char *ip = inet_ntoa(addr.sin_addr);
printf("[%s] %s %s %s\n", timestamp, ip, method, url);
}
/**********************************************************************/
/* 处理目录列表 */
/**********************************************************************/
void serve_directory(int client, const char *path) {
char buf[4096];
// 发送HTTP头
sprintf(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
// 发送HTML头部
sprintf(buf, "<html><head><title>Index of %s</title></head>", path);
send(client, buf, strlen(buf), 0);
sprintf(buf, "<body><h1>Index of %s</h1><ul>", path);
send(client, buf, strlen(buf), 0);
// 打开目录
DIR *dir = opendir(path);
if (dir) {
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
// 跳过隐藏文件
if (ent->d_name[0] == '.') continue;
char full_path[1024];
sprintf(full_path, "%s/%s", path, ent->d_name);
struct stat st;
stat(full_path, &st);
char size_buf[32];
if (S_ISDIR(st.st_mode)) {
strcpy(size_buf, "[DIR]");
} else {
if (st.st_size < 1024) {
sprintf(size_buf, "%ld B", st.st_size);
} else if (st.st_size < 1024 * 1024) {
sprintf(size_buf, "%.1f KB", st.st_size / 1024.0);
} else {
sprintf(size_buf, "%.1f MB", st.st_size / (1024.0 * 1024));
}
}
sprintf(buf, "<li><a href=\"%s\">%s</a> - %s</li>",
ent->d_name, ent->d_name, size_buf);
send(client, buf, strlen(buf), 0);
}
closedir(dir);
}
// 发送HTML尾部
sprintf(buf, "</ul></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 400 Bad Request */
/**********************************************************************/
void bad_request(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>400 Bad Request</h1></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 403 Forbidden */
/**********************************************************************/
void forbidden(int client) {
char buf[1024];
sprintf(buf, "HTTP/1.0 403 Forbidden\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>403 Forbidden</h1><p>Access to this resource is denied.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 发送文件内容 */
/**********************************************************************/
void cat(int client, FILE *resource)
{
char buf[65536];
fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), 0);
fgets(buf, sizeof(buf), resource);
}
}
/**********************************************************************/
/* 错误处理 */
/**********************************************************************/
void error_die(const char *sc)
{
perror(sc);
exit(1);
}
/**********************************************************************/
/* 读取一行 */
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = recv(sock, &c, 1, 0);
if (n > 0)
{
if (c == '\r')
{
n = recv(sock, &c, 1, MSG_PEEK);
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return(i);
}
/**********************************************************************/
/* 发送HTTP头 */
/**********************************************************************/
void headers(int client, const char *filename, const char *content_type)
{
char buf[1024];
(void)filename; // 未使用
strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: %s\r\n", content_type);
send(client, buf, strlen(buf), 0);
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 404 Not Found */
/**********************************************************************/
void not_found(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>404 Not Found</h1><p>The requested URL was not found on this server.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 服务静态文件(修复二进制文件处理) */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
size_t bytes_read;
long file_size;
// 丢弃请求头
buf[0] = 'A'; buf[1] = '\0';
while ((numchars > 0) && strcmp("\n", buf))
numchars = get_line(client, buf, sizeof(buf));
// 使用二进制模式打开文件
resource = fopen(filename, "rb");
if (resource == NULL) {
not_found(client);
return;
}
// 获取文件大小
fseek(resource, 0, SEEK_END);
file_size = ftell(resource);
fseek(resource, 0, SEEK_SET);
// 发送HTTP头
const char *content_type = get_mime_type(filename);
// 创建并发送头部
char header_buf[2048];
sprintf(header_buf, "HTTP/1.0 200 OK\r\n");
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, SERVER_STRING);
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, "Content-Type: %s\r\n", content_type);
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, "Content-Length: %ld\r\n", file_size);
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, "\r\n");
send(client, header_buf, strlen(header_buf), 0);
// 使用二进制模式发送文件内容
while ((bytes_read = fread(buf, 1, sizeof(buf), resource)) > 0) {
ssize_t sent = send(client, buf, bytes_read, MSG_NOSIGNAL);
if (sent < 0) {
// 处理发送错误(如连接关闭)
break;
}
}
fclose(resource);
}
/**********************************************************************/
/* 服务静态文件(专为POST请求设计,包含请求体处理) */
/**********************************************************************/
void serve_file_post(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
size_t bytes_read;
long file_size;
size_t content_length = 0; // 用于存储请求体长度
// 1. 读取并处理请求头
while ((numchars > 0) && strcmp("\n", buf) != 0)
{
numchars = get_line(client, buf, sizeof(buf));
// 解析Content-Length头部
if (strncasecmp(buf, "Content-Length:", 15) == 0) {
char *endptr;
unsigned long temp = strtoul(buf + 15, &endptr, 10);
if (endptr != buf + 15 && temp <= SIZE_MAX) {
content_length = (size_t)temp;
}
}
}
// 2. 读取并丢弃请求体
if (content_length > 0) {
size_t total_read = 0;
struct timeval tv;
tv.tv_sec = 5; // 设置5秒超时
tv.tv_usec = 0;
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
while (total_read < content_length) {
size_t to_read = sizeof(buf);
size_t remaining = content_length - total_read;
if (to_read > remaining) {
to_read = remaining;
}
bytes_read = recv(client, buf, to_read, 0);
if (bytes_read <= 0) {
if (bytes_read == 0) {
break; // 连接关闭
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
fprintf(stderr, "Request body read timeout\n");
break; // 超时
} else {
perror("recv failed");
break; // 错误
}
}
total_read += bytes_read;
}
// 恢复默认超时设置
tv.tv_sec = 0;
tv.tv_usec = 0;
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
// 3. 打开并发送文件(与serve_file相同)
resource = fopen(filename, "rb");
if (resource == NULL) {
not_found(client);
return;
}
// 获取文件大小
fseek(resource, 0, SEEK_END);
file_size = ftell(resource);
fseek(resource, 0, SEEK_SET);
// 发送HTTP头
const char *content_type = get_mime_type(filename);
char header_buf[2048];
sprintf(header_buf, "HTTP/1.0 200 OK\r\n");
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, SERVER_STRING);
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, "Content-Type: %s\r\n", content_type);
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, "Content-Length: %ld\r\n", file_size);
send(client, header_buf, strlen(header_buf), 0);
sprintf(header_buf, "\r\n");
send(client, header_buf, strlen(header_buf), 0);
// 发送文件内容
while ((bytes_read = fread(buf, 1, sizeof(buf), resource)) > 0) {
ssize_t sent = send(client, buf, bytes_read, MSG_NOSIGNAL);
if (sent < 0) {
// 处理发送错误
break;
}
}
fclose(resource);
}
/**********************************************************************/
/* 启动服务器 */
/**********************************************************************/
int startup(unsigned short *port)
{
int httpd = 0;
struct sockaddr_in name;
/* 创建socket */
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
/* 绑定地址 */
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
/* 若预设端口为0,随机取用可用端口*/
if (*port == 0)
{
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
/* 监听连接 */
if (listen(httpd, 5) < 0)
error_die("listen");
return(httpd);
}
/**********************************************************************/
/* 501 Not Implemented */
/**********************************************************************/
void unimplemented(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>501 Not Implemented</h1><p>The requested method is not implemented.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
// 设置套接字发送超时(在主循环accept后调用)
void set_socket_timeout(int sockfd, int timeout_sec) {
struct timeval tv;
tv.tv_sec = timeout_sec; // 超时秒数
tv.tv_usec = 0;
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
perror("setsockopt SO_SNDTIMEO failed");
}
}
/**********************************************************************/
/* 主函数 */
/**********************************************************************/
int main(void)
{
int server_sock = -1;
unsigned short port = 8080;
int client_sock = -1;
struct sockaddr_in client_name;
socklen_t client_name_len = sizeof(client_name);
pthread_t newthread;
server_sock = startup(&port);
printf("HTTP server running on port %d\n", port);
signal(SIGPIPE, SIG_IGN);
while (1)
{
/* 接受连接 */
client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);
if (client_sock == -1) {
perror("accept");
continue; // 继续接受新连接,而不是退出
}
// 动态分配内存传递socket描述符
int *pclient = malloc(sizeof(int));
if (!pclient) {
perror("malloc failed");
close(client_sock);
continue;
}
*pclient = client_sock;
// 创建线程处理请求(不再在主线程设置超时)
if (pthread_create(&newthread, NULL, accept_request, pclient) != 0) {
perror("pthread_create");
free(pclient);
close(client_sock);
} else {
// 分离线程,使其结束后自动释放资源
pthread_detach(newthread);
}
}
close(server_sock);
return(0);
}
以上是我通过 多线程方式 实现的webserver.
现在我想通过I/O多路复用的方式,实现同样的功能
请给出完整的实现
最新发布