使用c++编写,基于epoll+线程池实现的小型web服务器。
具体实现:
- 启动服务器,在浏览器中发送地址
- 记录过程:一进来先注册socket事件完成三次握手,建立线程池
- 线程池内部开始运转,在构造函数中开始创建线程并调用类的工作函数使队列没有任务状态下的线程等待),主线程进行设置表单事件,进入轮播循环等待客户端连接,若是新连接,在表单中注册事件,若是已有连接,查看任务(当为有数据写事件入时,new一个对象建立任务Task(将该事件中的客户端fd和多路复用只中的socket文件描述符写入改Task进行初始化),并将这个任务加入线程池中(线程池中接收到任务将任务加入任务队列,并唤醒一个工作线程,工作线程将任务从队列中取出进行执行task->doit任务
-
- task内部运转recv接受客户端发来的消息到缓冲区,将接收来的内容打印出来,利用sscanf函数将开头的三个字段取出 分别是方法,网址,协议
如打印出来其中之一是:recv: method=GET uri=/1.mp3 version=HTTP/1.1-
如果是GET方法,将请求的uri和长度start放入get_post函数去继续执行:
将uri进行引用成为string类型,并初始化同类型filename与从第一个以后的字符开始(目的是如果访问资源为了将/去掉)
如接收到的GET /picture/hehe.jpg HTTP/1.1 recv: method=GET uri=/picture/hehe.jpg version=HTTP/1.1 filename:picture/hehe.jpg5close
如果是第一次访问默认uri为/或者访问index.html页面,调用send_file将要发送的网页以及类型标识进行封装统一发送。
send_file( “index.html”, “text/html”, start );
如果调用uri.find(".后缀")查找其他资源标识并找到的情况下,调用send_file封装发送
send_file( filename, “image/jpg”, start ); -
如果是POST方法,将请求的uri和buf继续放到post_get函数执行:
还是同样像GET方法中开始的那样将uri文件的/去掉成string类型。
如果在uri中找到调用CGI程序的标识,继续从buf中获取请求长度,获取参数,用tmp存储参数发送到CGI服务器,fork产生子进程,执行CGI服务器计算;
if( fork() == 0 ) { execl( filename.c_str(), tmp, NULL ); }
否则sendfile404
其他,构造发送的头部,向客户端发送没有501错误
调用send_file函数发送报文及打开服务器相关资源
int Task::send_file( const string & filename, const char *type,int start, const int num, const char *info )
-
- send_file内部实现:获取文件名,通过文件名获取文件信息,若打开失败或没有该文件(获取失败),发送给客户端404提示报文,并打开404文件。
若获取成功,发送成功报文,并打开该文件。
- task内部运转recv接受客户端发来的消息到缓冲区,将接收来的内容打印出来,利用sscanf函数将开头的三个字段取出 分别是方法,网址,协议
,执行结束delete task,该线程继续沉睡,直到下一次唤醒(signal或brodecast)
附一个GET报文和一个POST报文以作参考
GET的buf接收的是这种
GET /1.png HTTP/1.1 Host: localhost:8888 Connection: keep-alive User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Accept: image/webp,image/apng,image/,/*;q=0.8 Referer: http://localhost:8888/submit.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: _ga=GA1.1.1296320880.1547386986
POST的buf接收的是这样
POST /CGI/adder HTTP/1.1 Host: localhost:8888 Connection: keep-alive Content-Length: 7 Cache-Control: max-age=0 Origin: http://localhost:8888 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Referer: http://localhost:8888/submit.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: _ga=GA1.1.1296320880.1547386986 a=1&b=3
后记:原来服务器的作用就好比你要访问一个网页,如果这个网页的源码就在你本地,那么你自然能够打开,那你写了一个网页想让别人也看看内容呢,
方法一:把你的源码发给ta, 让他保存源码后打开网页。 但是不可能你让每个想看你写的页面的人都去有一套编译器且要保存你的源码吧。 且要查看访问图片,音频,还得在你本地有保存的资源(笑哭,一点都没有互联网精神)
方法二:把资源(视频,音频,图片,计算等等等等)放到一台服务器主机上,通过http,tcp,ip连接(三次握手等)协议将我现在的这台主机(客户端)与远程服务器连接起来,我就可以通过特定请求去访问这个网页,以及网页上的资源。 这样我只要有台计算机且有网,就能够连接服务器(服务器能不能连接上是另外一回事,涉及到网关等网络层面的知识),
比如你想访问一个网页:(前提服务器开着)当我这个浏览器客户端输入网址时,httptcpip协议们等开始与服务器进行连接,连接成功,服务器会返回给客户端你想访问的这个页面,及找服务器相关文件夹的文件来显示页面上的相关资源。
详细代码请见:我的github