因为最近想对HTTP协议里面的操作细节有更深层更详细的的理解,就自己模拟实现了一个基于HTTP协议线程池版本的Web服务器的小项目。
开发环境是CentOS Linux release 7.4.
- 设计思路:
1.首先http协议是基于TCP通信的,所以我们得先实现TCP通信(socket编程通信部分代码)。
class Server{//封装成一个类
private:
int port;
int sock;
public:
Server(int _port=8080):port(_port),sock(-1)
{}
void Init()
{
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
cerr<<"socket err!"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=htonl(INADDR_ANY);
local.sin_port=htons(port);
if(bind(sock,(struct sockaddr *)&local,sizeof(local))<0)//绑定
{
cerr<<"bind err!"<<endl;
exit(3);
}
if(listen(sock,8)<0)//监听
{
cerr<<"listen err!"<<endl;
exit(4);
}
}
void Run()
{
struct sockaddr_in peer;
socklen_t len=0;
while(1)
{
int nsock=accept(sock,(struct sockaddr *)&peer,&len);//接收新连接
if(nsock<0)
{
cerr<<"accept err!"<<endl;
exit(5);
}
cout<<"Get a new link..."<<endl;
pthread_t pd;//让线程去处理,后面改成线程池版本。
int *p=&nsock;
pthread_create(&pd,NULL,Entry::HandleRequest,(void *)p);//在HandleRequest函数里面进行请求处理,它在另一个类中。
}
}
~Server()
{
if(sock>=0)
{
close(sock);
}
}
};
2.当通信建立好了之后,客户端可以向服务端发起请求,客户端可以使用GET,POST方法请求资源。因为将来可能同时有很多的用户同时访问服务器,所以在服务器端可以维护一个线程池,实现多用户高效率,而当服务器收到一个请求后,就从线程池里面唤醒一个线程,让这个线程为用户请求提供服务。接下来服务端需要对请求进行相应的分析。比如分析得到请求的方法,请求的资源路径url,判断资源是否合法并产生出相应的错误码,以及判断是否携带参数,如果有参数,参数在哪个部分等等。(可在源码中查看具体代码实现)
3.如果这个请求携带参数或者请求的资源是一个可执行程序等情况,则要调用CGI程序。
4.之后服务器将客户端请求的资源以html页面的形式响应给客户端,并可以差错处理。比如,客户端请求的资源不存在时,返回一个404页面。大致思路就是这个样子,其中实现起来也有很多细节需要注意。
- 项目特点:
1、支持客户端/服务器模式,客户端能够使用 GET、POST 方法请求资 源,并利用线程池实现多用户,高效率。
2、客户向服务器请求服务时,只需传送请求方法和路径。
3、允许传输多种类型的数据对象,正在传输的类型由 Content-Type 加以 标记处理。
当然啦在项目中也遇见了一些问题。
第一个问题,我原本的思路的是,收到不带参的请求时,服务器构造响应的html页面就是服务器的首页,在这个html文件中,标题和正文部分有中文,但是开始的时候我没有注意编码格式的问题,就导致后面测试的结果页面出现乱码,那这个问题如何解决呢。如果你的网页里面出现了中文,在头部没有加 meta charset=“UTF-8”,将会导致中文乱码,这是编码格式,相当于是告诉给浏览器用什么方式来读这页代码。meta charset="UTF-8"是设置网页文件展示时使用的字符集(编码),那其实除了网页文件展示时有编码以外,网页文件本身还有编码。必须两者统一时才不会乱码。
第二个问题,不能显示图片(这个问题是没有将所有发送的情况考虑完全,只考虑到目录、可执行程序,但没有考虑到如果请求的是一个路径明确的普通文件) 后来的解决办法是,测试请求一个路径明确的test.html文件,加入调试信息 ,将问题定位在:如果请求的资源存在,应该如何处理。对于普通文件,找到后并回显给浏览器;如果是目录,应答的是默认页面;如果是可执行程序,执行后返回结果 就可以了
- 测试功能:
可以在本地浏览器上面做测试,浏览器为客户端。
1.在测试之前需要先关闭Linux的防火墙,在命令行输入命令systemctl status firewalld可查看防火墙情况,输入命令 systemctl stop firewalld 关闭防火墙,如果弹出下面窗口,你输入当前用户的密码完成认证就可以了。
2.在Linux命令行上可用命令 ifconfig 查看当前Linux下的网络情况,找到它的ip.
3.启动服务器端(用到了端口号),然后用本地浏览器连接这个ip,加端口号,此时浏览器(客户端)发起请求,服务器端收到请求构造响应发送响应。因为是以html的形式响应的,故在浏览器(客户端)可直接看到响应的页面。
这个是请求的是首页:
如果请求的资源不存在等情况,会返回一个404页面,如下图:
完整的项目源码:模拟Web服务器
若有问题 欢迎交流~