/*
*\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;
}
帮我看看问题吧