实现一个HTTP服务器就是实现一个程序可以接受客户端发送给服务器进程的请求消息,通过解析这些请求消息,做出相应的响应。下面我们先来梳理一下整体的思路:
-
进行服务器的初始化:
int init_server(char* ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return -1;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(ip);
server.sin_port = htons(port);
if(bind(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
perror("bind");
close(sock);
return -1;
}
if(listen(sock, 5) < 0)
{
perror("listen");
close(sock);
return -1;
}
return sock;
}
- 下面,进入事件循环,收到客户端消息,解析并作出响应。
可是,如果在循环中直接读取请求,处理请求,作出响应,这样的话,在本次循环未结束前(也就是本次请求未处理完成前),因为只调用了一次accept,无法处理其他客户端发来的请求(能建立连接,因为这是由内核负责的)。这时候,我们应该循环调用accept接收客户端连接,在之后将处理该客户端发送的请求这件事交给另外一个线程去做,实现多线程的服务器程序。
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("usage : ./http_server [IP] [port]\n");
return -1;
}
//进行服务器的初始化
int sock = init_server(argv[1], atoi(argv[2]));
printf("sock = %d\n", sock);
if(sock < 0)
{
printf("服务器初始化失败\n");
return -1;
}
printf("server init ok\n");
//进入事件循环
while(1)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(sock, (struct sockaddr*)&client, &len);
printf("new_sock:%d\n", new_sock);
if(new_sock < 0)
{
perror("accept");
continue;
}
pthread_t pid;
pthread_create(&pid, NULL, ThreadEntry, (void*)new_sock); //这里采用传值传递的方式进行,因为这是两个执行流,如果传址可能n
//若采用将该变量声明为静态变量,可能会导致线程不安全
pthread_detach(pid);
}
return 0;
}
void* ThreadEntry(void* arg)
{
int64_t sock = (int64_t)arg;
//从sock中读取请求并处理
HandlerRequest(sock);
return NULL;
}
- 下面,解析请求,并做出响应
想要正确的解析请求,就要了解HTTP请求的格式:
HTTP请求格式:
首 行:方法 url HTTP协议版本
header部分:请求的属性值,以冒号分隔的键值对的形式出现
空 行:遇到空行说明header部分结束了
body 部 分:如果存在,header部分一定存在Content-Length属性说明body部分的长度
于是,我们可以整理出这一部分的整体逻辑:
静态页面是指不需要进行动态生成的,直接将HTTP服务器工作目录下现有的页面发送给客户端的页面叫做静态页面;动态页面是指页面的内容时变化的,需要服务器进行“计算”,将结果写回客户端。
void HandlerRequest(int sock)
{
//读取首行并解析
// 读取首行并解析
Req req;
memset(&req, 0, sizeof(req));
if(ReadLine(sock, req.first_line) < 0)
{
printf("ReadLine error! read first_line: %s\n", req.first_line);
err_code = 404;
//构造404的响应代码
goto END;
}
printf("ReadLined\n");
printf("first_line:%s\n", req.first_line);
if(ParseFirstLine(req.first_line, &req.method, &req.url) < 0)
{
printf("ParseFirstLine error! method: %s url: %s\n", req.method, req.url);
err_code = 404;
//构造404的响应代码
goto END;
}
printf("ParseFirstLine ok\n");
printf("method=%s\n", req.method);
// 解析URL
if(Parseurl(req.url, &req.url_path, &req.query_string) < 0)
{
printf("Parseurl error! query_string : %s\n", req.query_string);
err_code = 404;
//构造404的响应代码
goto END;
}
printf("Parseurl ok\n");
printf("query_string=%s\n", req.query_string);
// 解析header(此处为了简单,只保留content_length)
if(HandlerHeader(sock, &req.content_length) < 0)
{
printf("HandlerHeader error!");
err_code = 404;
//构造404的响应代码
goto END;
}
printf("HandlerHeader ok\n");
//根据情况决定执行静态还是动态页面
// 若方法是GET且没有query_string,生成静态页面
if(strcmp(req.method, "GET") == 0 && req.query_string == NULL)
err_code = HandlerStaticFile(sock, &req);
// 若方法是GET且有query_string,生成动态页面
else if(strcmp(req.method,"GET") == 0 && req.query_string != NULL)
err_code = HanndlerCGI(sock, &req);
// 若方法为POST(登录页面)
else if(strcmp(req.method, "POST") == 0)
err_code = HanndlerCGI(sock, &req);
else
{
printf("method not found! method : %s\n", req.method);
err_code = 404;
//构造404的响应代码
goto END;
}
END:
//这次请求处理结束的收尾工作
if(err_code != 200)
{
Handler404(sock);
}
close(sock);
}
这里将具体的请求解析和计算,以及响应任务交给了不同页面逻辑代码。下一篇文章就会进行具体页面处理逻辑的实现了。