前言:
我们在之前的TinyHttpd的精读(可以在首页去查看)中已经是基本的了解了显示一个网页的基本过程,那么我们学习后可以通过手写一个精简版的进行巩固下。
0.新工程的建立
我们也可以顺带复习下如何通过cmake在ubuntu下新建一个工程(记得提前下载cmake)。
1.新建文件夹My_Tinyhttpd。
2.新建文件:CMakeLists.txt,service_socket.cpp。
3.CMakeList.txt的内容:
cmake_minimum_required(VERSION 3.0.0)
project("Main")
add_executable(service service_socket.cpp)
4.service_socket.cpp:
#include <iostream>
int main(){
std::cout<<"hello world\n";
return 1;
}
5.执行cmake ./ 和 cmake。
如果没有报错则成果物会被正常的生成出来为service,然后我们执行 ./service 就可以看到控制台输出hello world了。我们的项目就基本构建完成了。接下来就是具体的内容。
1.main函数:
在这个main函数中我们的目的是新建一个socket链接,然后创建一个线程去接受信息并进行处理。最后当我们处理完信息后我们就关闭socket链接。具体代码如下:
void accept_request(int client_id){
printf("Get Message...\n");
}
int main(){
char buffer1[MAXNUM] = {0};
int service_id,new_socket;
struct sockaddr_in address;
socklen_t addlen = sizeof(address);
//socklen_t addlen;
// service_id = start(address);
// int service_id,new_socket;
service_id = socket(AF_INET,SOCK_STREAM,0);
//创建socket
if(service_id == 0){
printf("socket failed\n");
return service_id;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8008);
printf("Bind...\n");
//绑定
if(bind(service_id, (struct sockaddr *)&address, sizeof(address)) < 0){
printf("bind failed\n");
printf("Error Message is %s\n",strerror(errno));
return -1;
}
//监听
printf("Listen...\n");
if(listen(service_id,3) < 0){
printf("listen error\n");
return -1;
}
while(1){
//接受链接:
int new_socket = accept(service_id,(struct sockaddr*)&address,(socklen_t*)&addlen);
if(new_socket <0){
printf("accept client error\n");
}
std::thread m_thread(accept_request,new_socket);
m_thread.detach();
}
printf("Close Socket...\n");
close(service_id);
return 1;
}
我们按照这个写完代码后,我们执行make然后运行下service(./service).然后打开浏览器输入
http://localhost:8008/.然后看log是否会打印Get Message...。如果可以的话那么基本上我们的这段代码就基本OK了。然后我们把sokcet通信部分的代码封装在start函数中。
void accept_request(int client_id){
printf("Get Message...\n");
}
int start(struct sockaddr_in& address){
int service_id,new_socket;
service_id = socket(AF_INET,SOCK_STREAM,0);
//创建socket
if(service_id == 0){
printf("socket failed\n");
return service_id;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8008);
printf("Bind...\n");
//绑定
if(bind(service_id, (struct sockaddr *)&address, sizeof(address)) < 0){
printf("bind failed\n");
printf("Error Message is %s\n",strerror(errno));
return -1;
}
//监听
printf("Listen...\n");
if(listen(service_id,3) < 0){
printf("listen error\n");
return -1;
}
return service_id;
}
int main(){
char buffer1[MAXNUM] = {0};
int service_id,new_socket;
struct sockaddr_in address;
socklen_t addlen = sizeof(address);
//socklen_t addlen;
service_id = start(address);
while(1){
//接受链接:
int new_socket = accept(service_id,(struct sockaddr*)&address,(socklen_t*)&addlen);
if(new_socket <0){
printf("accept client error\n");
}
std::thread m_thread(accept_request,new_socket);
m_thread.detach();
}
printf("Close Socket...\n");
close(service_id);
return 1;
}
然后我们同样可以去验证下看看是否会有Get Message...信息。
2.accept_reques函数
基本的sokcet通讯已经完成了我们接下来进行数据分析/处理函数的部分。这部分函数会涉及到别的函数,我们暂时先聚焦于这个函数的主体思路。
主体思路:从我们的socket中提出method方法,来判断我们是显示静态网页和动态网页。
基于以上思路,我们首先来看下我们从socket反馈中能获取到啥信息,这样才方便我们后面写代码的思路。
void print_buffer(char buffer[]){
for(int i = 0;i < MAXNUM;i++){
if(buffer[i] != '\0')
std::cout<<buffer[i];
}
}
void accept_request(int client_id){
char buffer_line[MAXNUM] = {0};
char buffer[MAXNUM] = {0};
char method[MAXNUM] = {0};
char path[MAXNUM] = {0};
int i = 0,j = 0;
printf("Get Message...\n");
auto getmessage = read(client_id,buffer,sizeof(buffer));
if(getmessage <= 0){
printf("wait client message\n");
} else {
std::cout<<"client say :";
print_buffer(buffer);
}
}
结果如下:
我们发现我们要获取的信息(GET OR POST)是在这个信息的第一行,所以我们第一步得先获取第一行,然后再这行信息中获取GET或者POST方法。
2.1get_line函数:
void get_line(char buffer1[],char buffer2[]){
int i = 0;
//buffer1 = {0};
while(buffer2[i] != '\n'){
buffer1[i] = buffer2[i];
i++;
}
buffer1[i] = '\0';
}
思路:以换行符为行数的确定标记,来将第一行复制出来。
2.2获取method
当我们获取到第一行后我们就需要考虑当前我们是收到的GET还是POST方法呢?从我们的获取到的buffer信息中可以看到当前网页给的第一次是GET方法。那么从一行字符串中获取到一个子串的方法就不在详细赘述了,直接看代码即可。
void accept_request(int client_id){
char buffer_line[MAXNUM] = {0};
char buffer[MAXNUM] = {0};
char method[MAXNUM] = {0};
char path[MAXNUM] = {0};
int i = 0,j = 0;
printf("Get Message...\n");
auto getmessage = read(client_id,buffer,sizeof(buffer));
if(getmessage <= 0){
printf("wait client message\n");
} else {
std::cout<<"client say :";
print_buffer(buffer);
}
get_line(buffer_line,buffer);
printf("accept_request:");
print_buffer(buffer_line);
// //get method
while(buffer_line[i] != ' '){
method[i] = buffer_line[i];
i++;
}
print_buffer(method);
}
2.3判断当前的method方法
这个就是直接判断是GET还是POST方法,如果是GET方法,我们就显示静态网页,如果是POST我们就显示动态网页。如果两者都不是我们就直接报错。
void accept_request(int client_id){
char buffer_line[MAXNUM] = {0};
char buffer[MAXNUM] = {0};
char method[MAXNUM] = {0};
char path[MAXNUM] = {0};
int i = 0,j = 0;
printf("Get Message...\n");
auto getmessage = read(client_id,buffer,sizeof(buffer));
if(getmessage <= 0){
printf("wait client message\n");
} else {
std::cout<<"client say :";
print_buffer(buffer);
}
get_line(buffer_line,buffer);
printf("accept_request:");
print_buffer(buffer_line);
//get method
while(buffer_line[i] != ' '){
method[i] = buffer_line[i];
i++;
}
print_buffer(method);
if(strcasecmp(method,"GET") && strcasecmp(method,"POST")){
printf("error Method");
}
if(strcasecmp(method,"GET") == 0){
headers(client_id);
saver_file(client_id);
}
}
至此,一个基本完整的accept_request函数就基本完成了。那么下一章我们就来处理网页的显示。