自主web服务器
Posted qnbk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自主web服务器相关的知识,希望对你有一定的参考价值。
自主web服务器
描述
采用C/S模型,编写支持中小型应用的http,并结合mysql,理解常见互联网应用行为,做完该项目,你可以从技术上完全理解从上网开始,到关闭浏览器的所有操作中的技术细节。
www
WWW是环球信息网的缩写,(亦作“Web”、“WWW”、“‘W3’”,英文全称为“World Wide Web”),中文名字为“万维网”,"环球网"等,常简称为Web。
分为Web客户端和Web服务器程序。 WWW可以让Web客户端(常用浏览器)访问浏览Web服务器上的页面。 是一个由许多互相链接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。
万维网联盟(英语:World Wide Web Consortium,简称W3C),又称W3C理事会。1994年10月在麻省理工学院(MIT)计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。
http分层概念
特点
客户/服务器模式(B/S,C/S)
- 简单快速,HTTP服务器的程序规模小,因而通信速度很快。
- 灵活,HTTP允许传输任意类型的数据对象,正在传输的类型由Content-Type加以标记。
- 无连接,每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。(http/1.0具有的功能,http/1.1兼容)
- 无状态
http协议每当有新的请求产生,就会有对应的新响应产生。协议本身并不会保留你之前的一切请求或者响应,这是为了更快的处理大量的事务,确保协议的可伸缩性。
可是,随着web的发展,因为无状态而导致业务处理变的棘手起来。比如保持用户的登陆状态。http/1.1虽然也是无状态的协议,但是为了保持状态的功能,引入了cookie技术。
URI & URL & URN
- URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
- URL,是uniform resource locator,统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
- URN,uniform resource name,统一资源命名,是通过名字来标识资源
URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。
URL是 URI 的子集。任何东西,只要能够唯一地标识出来,都可以说这个标识是 URI 。如果这个标识是一个可获取到上述对象的路径,那么同时它也可以是一个 URL ;但如果这个标识不提供获取到对象的路径,那么它就必然不是URL 。
URI: /hello/index.html
URL: www.xxx.com:/hello/index.html
浏览器URL格式
- HTTP(超文本传输协议)是基于TCP的连接方式进行网络连
- HTTP/1.1版本中给出一种持续连接的机制(长链接)
- 绝大多数的Web开发,都是构建在HTTP协议之上的Web应用
HTTP URL (URL是一种特殊类型的URI,包含了如何获取指定资源)的格式如下:
http://host[":"port][abs_path]
- http表示要通过HTTP协议来定位网络资源
- host表示合法的Internet主机域名或者IP地址,本主机IP:127.0.0.1
- port指定一个端口号,为空则使用缺省端口80
- abs_path指定请求资源的URI
- 如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常浏览器自动完成。
如果用户的url没有指明要访问的某种资源(路径),虽然浏览器会自动添加/ ,但是浏览器还是不知道需要访问什么资源,此时,浏览器默认返回对应服务的首页
HTTP请求与响应
利用套接字获取请求报文
Makefile:
bin=httpserver
cc=g++
LD_FLAGS=-std=c++11 -lpthread
src=main.cc
$(bin):$(src)
$(cc) -o $@ $^ $(LD_FLAGS)
.PHONY:clean
clean:
rm -f $(bin)
TcpServer.hpp
#pragma once
#include <iostream>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#define Backlog 5
class TcpServer
private:
int port;
int listen_sock;
static TcpServer *svr;
private://把tcpserver设置为单例模式
TcpServer(int _port):port(_port),listen_sock(-1)
TcpServer(const TcpServer &s)
public:
static TcpServer *getinstance(int port)//获取单例
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
if(svr == nullptr)
pthread_mutex_lock(&lock);
if(svr == nullptr)
svr = new TcpServer(port);
svr->InitServer();
pthread_mutex_unlock(&lock);
return svr;
void InitServer()
Socket();
Bind();
Listen();
void Socket()
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
exit(1);
//socket 地址复用:服务器崩了,连接还在,此时服务器重启不了,因为监听套接字对应的端口号
//默认是绑定的,端口没有被完全释放,导致服务器无法立即重启,所以需要地址复用
int opt = 1;
setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
void Bind()
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;//云服务器不能直接绑定公网ip
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
std::cout<<"bind error"<<std::endl;
exit(2);
void Listen()
if(listen(listen_sock,Backlog) < 0)
exit(3);
int Sock()
return listen_sock;
~TcpServer()
if(listen_sock >= 0)
close(listen_sock);
;
HttpServer.hpp
#pragma once
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <pthread.h>
#define PORT 8081
class HttpServer
private:
int port;
TcpServer *tcp_server;
bool stop;
public:
HttpServer(int _port = PORT):port(_port),tcp_server(nullptr),stop(false)
void InitServer()
tcp_server = TcpServer::getinstance(port);
void Loop()
int listen_sock = tcp_server->Sock();
//std::cout<<"HttpServer::Loop sock "<<listen_sock<<std::endl;
while(!stop)
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock < 0)
continue;
int *_sock = new int(sock);
pthread_t tid;
pthread_create(&tid,nullptr,Entrance::HandlerRequest,_sock);
pthread_detach(tid);
~HttpServer()
;
Protocol.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
class Entrance
public:
static void *HandlerRequest(void *_sock)//处理线程
int sock = *(int *)_sock;
delete (int*)_sock;
std::cout<<"get a new link..."<<sock<<std::endl;
#ifndef DEGUB
#define DEBUG
char buffer[4096];
recv(sock,buffer,sizeof(buffer),0);
std::cout<<"-----------begin-----------------"<<std::endl;
std::cout<<buffer << std::endl;
std::cout<<"-----------end-----------------"<<std::endl;
#endif
close(sock);
return nullptr;
;
main.cc
#include <iostream>
#include <string>
#include <memory>
#include "HttpServer.hpp"
//#include "TcpServer.hpp"
static void Usage(std::string proc)
std::cout<<"Usage: "<<proc << " port "<<std::endl;
int main(int argc,char *argv[])
if(argc != 2)
Usage(argv[0]);
exit(4);
//std::cout<<"hello http"<<std::endl;
int port = atoi(argv[1]);
std::shared_ptr<HttpServer> http_server(new HttpServer(port));
http_server->InitServer();
http_server->Loop();
for(;;)
return 0;
CGI
CGI(Common Gateway Interface) 是WWW技术中最重要的技术之一,有着不可替代的重要地位。CGI是外部应用程序(CGI程序)与WEB服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的过程。
首先需要理解GET方法和POST方法的区别
- GET方法从浏览器传参数给http服务器时,是需要将参数跟到URI后面的
- POST方法从浏览器传参数给http服务器时,是需要将参数放的请求正文的。
- GET方法,如果没有传参,http按照一般的方式进行,返回资源即可
- GET方法,如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果(期望资源)返回给浏览器
- POST方法,一般都需要使用CGI方式来进行处理
如何看待子CGI程序?
子CGI的标准输出是浏览器
子CGI的标准输入是浏览器
具体实现
Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <sstream>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>
#include "Util.hpp"
#include "Log.hpp"
#include <algorithm>
#include <sys/wait.h>
#include <sys/sendfile.h>
#define SEP ": "
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\\r\\n"
#define PAGE_404 "404.html"
#define SERVER_ERROR 500
/*
class Code2Desc
private:
std::unordered_map<int,std::string> Code;
public:
Code2Desc()
void InitCode2Desc()
Code.insert(200,"OK");
~Code2Desc()
;
*/
static std::string Code2Desc(int code)
std::string desc;
switch(code)
case 200:
desc="OK";break;
case 404:
desc = "Not Found";break;
default:
break;
return desc;
static std::string Suffix2Desc(const std::string &suffix)
static std::unordered_map<std::string,std::string> Suffix2desc =
".html","text/html",
".css","text/css",
".js","appplication/javascript",
".jpg","application/x-jpg",
;
auto iter = Suffix2desc.find(suffix);
if(iter != Suffix2desc.end())
return iter->second;
return ".text/html";
class HttpRequest
public:
std::string request_line;//请求行
std::vector<std::string> request_header;//请求报头
std::string blank;//空行
std::string request_body;//正文
//解析完毕之后的结果
std::string method;//请求方法 GET:有正文 POST:需要读取正文
std::string uri;//请求资源 path?args
std::string version;//请求版本
std::unordered_map<std::string,std::string> header_kv;//HOST: XXX.XXX Connection: XXX
int content_length;//正文长度
std::string path;//想访问的资源
std::string query_string;//path后面的参数
std::string suffix;//请求资源的后缀
bool cgi;
int size;
public:
HttpRequest():content_length(0),cgi(false)
~HttpRequest()
;
class HttpResponse
public:
std::string status_line;//状态行
std::vector<std::string> response_header;//响应报头
std::string blank;//空行
std::string response_body;//正文
int status_code;//状态码
int fd;
int size;//想访问的资源的大小
public:
HttpResponse():blank(LINE_END),status_code(OK),fd(-1)
~HttpResponse()
;
//读取请求,分析请求,构建响应
//IO通信
class Endpoit
//对端:完成业务逻辑
private:
int sock;
HttpRequest http_request;
HttpResponse http_response;
private:
void RecvHttpRequestLine()
auto &line = http_request.request_line;
Util::Readline(sock,line);
line.resize(line.size()-1);
LOG(INFO,http_request.request_line);
void RecvHttpRequestHeader()
//如何保证请求报头已经读取完毕
//1、空格结束 2、都是按照行进行陈列的
std::string line;
while(true)
line.clear();
Util::Readline(sock,line);
if(line == "\\n")
http_request.blank = line;
break;
line.resize(line.size()-1);//去掉\\n
http_request.request_header.push_back(line);
LOG(INFO,line);
void ParseHttprequestLine()
auto &line = http_request.request_line;
// 方法 URI HTTP/版本
//stringstream :默认按照空格把字符串打散切分
std::stringstream ss(line);
ss >> http_request.method >> http_request.uri >> http_request.version;
auto &method = http_request.method;
std::transform(method.begin(),method.end(),method.begin(),::toupper);
LOG(INFO,line);
void ParseHttprequestHeader()
std::string key;
std::string value;
for(auto &iter : http_request.request_header)
if(Util::CutString(iter,key,value,SEP))
http_request.header_kv.insert(key,value);
bool IsNeedRecvHttpRequestBody()
auto method = http_request.method;
if(method == "POST")
auto &header_kv = http_request.header_kv;
auto iter = header_kv.find("Content-Length");
if(iter != header_kv.end())
//找到了
LOG(INFO,"Post Method,Content-Length"+iter->second);
http_request.content_length = atoi(iter->second.c_str());
return true;
return false;
void RecvHttpRequestBody()
if(IsNeedRecvHttpRequestBody())
int content_length = http_request.content_length;
auto &body = http_request.request_body;
char ch = 0;
while(content_length)
ssize_t s = recv(sock,&ch,1,0);
if(s > 0)
body.push_back(ch);
content_length--;
else
break;
//LOG(INFO,body);
int ProcessNonCGI()
//eg: HTTP/1.0 200 OK
http_response.fd = open(http_request.path.c_str(),O_RDONLY);
if(http_response.fd >= 0)
LOG(INFO,http_request.path+"open success!");
/*
//获取信息成功
http_response.status_line = HTTP_VERSION;
http_response.status_line += " ";
http_response.status_line +=std::to_string(http_response.status_code);
http_response.status_line += " ";
http_response.status_line += Code2Desc(http_response.status_code);
http_response.status_line += LINE_END;
http_response.size = size;
std::string header_line = "Content-Type: ";
header_line += Suffix2Desc(http_request.suffix);
header_line += LINE_END;
http_response.response_header.push_back(header_line);
header_line = "Content-Length: ";
header_line += std::to_string(size);
header_line += LINE_END;
http_response.response_header.push_back(header_line);
*/
return OK;
//http_response.response_body是用户层的缓冲区 :磁盘-》内核层-》用户层
//sendfile:把数据从一个文件描述符拷贝给另一给文件描述符,只在内核进行
介绍自主小型Web服务器实现