qfedu-network-advanced-level/day4/webserver.c

220 lines
6.3 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// http网页服务器简单实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <pthread.h>
#include <signal.h>
char root_dir[] = "/home/flykhan/html";
char html_hi[] = "HTTP/1.1 200 OK\r\n"
"Server: MyWebServer1.0\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
"\r\n\r\n"
"<h3 style=\"color:blue\">hi</h3>\r\n"
"\r\n";
char html_404[] = "HTTP/1.1 404 Not Found\r\n"
"Server: MyWebServer1.0\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
"\r\n\r\n"
"<h3 style=\"color:red\">Page Not Found 404</h3>\r\n"
"\r\n";
// 正常网页的 HEAD 头
char html_ok[] = "HTTP/1.1 200 OK\r\n"
"Server: MyWebServer1.0\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
"\r\n\r\n";
// 正常网页的 图片 头
char image_ok[] = "HTTP/1.1 200 OK\r\n"
"Server: MyWebServer1.0\r\n"
"Content-Type: image/png;\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: keep-alive\r\n"
"Content-Length: %d\r\n"
"\r\n\r\n";
int sock_fd; // 全局定义 服务器 socket 套接字
// 自定义信号处理函数
void signalHandler(int signal)
{
printf("接收到信号 %d, 套接字为: %d\n", signal, sock_fd);
close(sock_fd);
// printf("套接字现在为 %d\n", sock_fd);
}
// 定义结构体:用于描述连接的客户端信息
typedef struct client_s
{
unsigned char ip[INET_ADDRSTRLEN]; // 16
int fd;
} Client;
void *client_task(void *arg)
{
Client *client = (Client *)arg;
// 读取客户端的请求
char buf[1024] = ""; // 存请求报文
int len = recv(client->fd, buf, 1024, 0); // 接收数据
printf("request data: %s\n", buf);
// 解析请求第一行和头属性以及BODY
char *line1 = strtok(buf, "\r\n");
printf("%s\n", line1);
char method_path_[3][100];
int i = 0;
char *token = strtok(line1, " ");
while (token != NULL)
{
strcpy(method_path_[i], token);
i++;
token = strtok(NULL, " ");
}
printf("path: %s\n", method_path_[1]); // 解析出地址路径
// 查找 html 目录中是否存在此文件
if (strcmp(method_path_[1], "/") == 0)
{
strcpy(method_path_[1], "/index.html");
}
// 拼接服务器文件的完整访问路径
char path[200] = "";
strcat(path, root_dir); // 拼接根目录路径
strcat(path, method_path_[1]); // 拼接请求路径
printf("filepath: %s\n", path);
int file_fd = open(path, O_RDONLY);
if (file_fd < 0)
{
// 文件不存在
// 相应 404
perror("open");
send(client->fd, html_404, sizeof(html_404), 0);
}
else
{
// 读取文件的数据(验证读取文件的类型:文本文件、图片文件)
// 向客户端发送 html_ok 头
if (strstr(method_path_[1], ".png") != NULL ||
strstr(method_path_[1], ".jpg") != NULL)
{
// 发图片头
struct stat st;
stat(path, &st);
char _ok[sizeof(image_ok) + 20] = "";
sprintf(_ok, image_ok, st.st_size); // 图片头拼接新获取的图片的大小
send(client->fd, _ok, strlen(_ok), 0);
}
else
{
// 发文本头
send(client->fd, html_ok, sizeof(html_ok), 0);
}
// 向客户端发送文件的数据(页面文件)
while (1)
{
char file_buf[512] = "";
ssize_t read_len = read(file_fd, file_buf, 512);
write(client->fd, file_buf, read_len);
// send(client->fd, file_buf, read_len, 0);
if (read_len < 512)
{
break;
}
}
close(file_fd);
}
// 相应一个HELLO主页数据HTML
// send(client->fd, html_hi, sizeof(html_hi), 0);
close(client->fd);
printf("%s 关闭连接\n", client->ip);
free(client); // 回收空间
}
int main(int argc, char const *argv[])
{
// 1. 创建 socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
{
perror("socket");
return 1;
}
// 2. bind
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1]));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int flag = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (flag != 0)
{
perror("bind");
close(sock_fd);
return 1;
}
// 注册信号处理函数
signal(SIGINT, signalHandler); // ctrl + c 发送
// 3. 创建监听队列
listen(sock_fd, 100);
printf("-----WEB 服务器已开启端口号为:%s-----\n", argv[1]);
// 4. 开始接收客户端的连接(并发接收多个客户端)
while (1)
{
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
int client_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &client_addr_len);
char clinet_ip[INET_ADDRSTRLEN] = "";
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, clinet_ip, INET_ADDRSTRLEN);
printf("%s 连接成功\n", clinet_ip);
// 创建线程实现并发通信
Client *client = malloc(sizeof(Client)); // 创建堆空间
strcpy(client->ip, clinet_ip);
client->fd = client_fd;
pthread_t tid;
pthread_create(&tid, NULL, client_task, client);
pthread_detach(tid); // 分离线程
// pthread_join(tid,NULL);
}
// 主进程的范畴
close(sock_fd);
return 0;
}