描述 采用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(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);
}
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;
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();
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"
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);
}
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
/*
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:把数据从一个文件描述符拷贝给另一给文件描述符,只在内核进行
return NOT_FOUND;
}
int ProcessCGI()
{
LOG(INFO,"process cgi");
int code = OK;
auto &method = http_request.method;
auto &query_string = http_request.query_string;//GET
auto &body_text = http_request.request_body;//POST
int content_length = http_request.content_length;
auto &response_body = http_response.response_body;
//新线程,但是从头到尾只有一个进程:httpserver
//如何用一个进程去执行另一个进程-》程序替换:exec*
//不能直接替换(替换的是httpserver)-》可以子线程实现
auto &bin = http_request.path;//要让子进程执行的目标程序
//站在父进程角度
int input[2];//创建管道:父子间进程通信
int output[2];
std::string query_string_env;//环境变量
std::string method_env;//方法的环境变量
std::string content_length_env;
if(pipe(input) < 0)
{
//error
LOG(ERROR,"input errpr");
code = SERVER_ERROR;
return code;
}
if(pipe(output) < 0)
{
//error
LOG(ERROR,"output error");
code = SERVER_ERROR;
return code;
}
pid_t pid = fork();
if(pid == 0)//child
{
//exec*
close(input[0]);
close(output[1]);
method_env = "METHOD=";
method_env += method;
std::cout<<"cgi: "<<method_env<<std::endl;
putenv((char*)method_env.c_str());
if(method == "GET")
{
//父进程 -》数据-》 子进程
//通过: 管道 环境变量
//GET需要导环境变量
//环境变量是具有全局属性的(可以被子进程继承下去,不受exec*的影响)
query_string_env = "QUERY_STRING=";
query_string_env += query_string;
putenv((char*)query_string_env.c_str());
//子进程如何区分是从标准输入中读取还是从环境变量里面拿到数据?通过知道请求方法
LOG(INFO,"Get Method,Add Query_string Env");
}
else if(method == "POST")
{
content_length_env = "CONTENT_LENGTH=";
content_length_env += std::to_string(content_length);
putenv((char*)content_length_env.c_str());
LOG(INFO,"Post Method,Add Content-Length Env");
}
else
{
//do nothing
}
/*
//替换成功之后,目标子进程如何得知对应的读写文件描述符是多少呢?
//在层序替换之后,数据没有了单数曾经打开文件pipe还在(程序替换只替换代码和数据,并不替换内核进程相关的数据结构包括文件描述符表)
//-》让目标被替换之后的进程,读取管道等价于读取标准输入,写入管道等于写到标准输出
//->重定向技术,可以在exec*系列函数被执行前进行重定向
*/
//站在子进程角度:
//inout[1]:write output[0]:read
std::cout<<"bin "<<bin<<std::endl;
dup2(output[0],0);
dup2(input[1],1);
execl(bin.c_str(),bin.c_str(),nullptr);
exit(1);
}
else if(pid < 0)
{
//创建子进程失败
LOG(ERROR,"fork error");
return 404;
}
else
{
//father
close(input[1]);//0:read 1:write
close(output[0]);
if(method == "POST")
{
const char* start = body_text.c_str();
int total = 0;
int size = 0;
while((total< content_length) &&(size = write(output[1],total+start,body_text.size()-total)) > 0)
{
total += size;
}
}
//让父进程获得子进程的输出结果
char c;
while(read(input[0],&c,1) > 0)
{
//CGI执行完之后的结果,并不可以直接返回给浏览器,因为这部分内容只是响应的正文
response_body.push_back(c);
}
int status = 0;
pid_t ret = waitpid(pid,&status,0);//等待子进程
if(ret == pid)
{
//wait success
if(WIFEXITED(status))//检验进程退出是否正常
{
if(WEXITSTATUS(status) == 0)//获得退出码
{
code = OK;
}
else
{
code = BAD_REQUEST;
}
}
else
{
code = SERVER_ERROR;
}
}
close(input[1]);//0:read 1:write
close(output[0]);
}
return code;
}
void HandlerERROR(std::string page)
{
http_request.cgi = false;
http_response.fd = open(page.c_str(),O_RDONLY);
if(http_response.fd > 0)
{
struct stat st;
stat(page.c_str(),&st);
http_request.size = st.st_size;
std::string line = "Content-Type: text/html";
line += LINE_END;
http_response.response_header.push_back(line);
line = "Content-Length: ";
line += std::to_string(st.st_size);
line += LINE_END;
http_response.response_header.push_back(line);
}
}
void BuildOKResponse()
{
std::string line = "Content-Type: ";
line +=Suffix2Desc(http_request.suffix);
line += LINE_END;
http_response.response_header.push_back(line);
line += "Content-Length: ";
if(http_request.cgi)
{
line += std::to_string(http_response.response_body.size());
}
else
{
line += std::to_string(http_request.size);//GET
}
line += LINE_END;
http_response.response_header.push_back(line);
}
void BuildHttpresponseHelper()
{
auto &code = http_response.status_code;
//构建状态行
auto &line = http_response.status_line;
line += HTTP_VERSION;
line += " ";
line += std::to_string(code);
line += " ";
line += Code2Desc(code);
line += LINE_END;
//构建响应正文
std::string path = WEB_ROOT;
path +="/";
switch(code)
{
case OK:
BuildOKResponse();break;
case NOT_FOUND:
path += PAGE_404;
HandlerERROR(path);break;
case BAD_REQUEST:
break;
case SERVER_ERROR:
break;
default:
break;
}
}
public:
Endpoit(int _sock):sock(_sock)
{}
void RecvHttprequest()
{
//读取请求
//读取的基本单位按照行来读取:需要兼容各种行分隔符
//XXXX\r\n XXXX\r XXXX\n
RecvHttpRequestLine();
RecvHttpRequestHeader();
}
void ParseHttprequest()
{
//分析请求
ParseHttprequestLine();
ParseHttprequestHeader();
//是否有正文需要读取 method:GET:没有正文 POST:需要读取正文
//确认正文中有多少个字节读取:Content-Length决定
RecvHttpRequestBody();
}
void BuildHttpresponse()
{
//构建响应
std::string _path;
struct stat st;
//知道文件的大小
//int size = 0;
std::size_t found= 0;
auto &code = http_response.status_code;
if(http_request.method != "GET" && http_request.method != "POST")
{
//非法请求
LOG(WARNING,"method is not right");
code = BAD_REQUEST;
goto END;
}
/*
//状态码决定报文的处理方式
//目前所有的上网行为宏观上只有两种:
//1、浏览器想从服务器拿下来某种资源(打开网页,下载等)GET
//2、浏览器想将自己是数据上传至服务器(上传文件,登录注册)POST通过正文传参,GET:URL进行传参
//客户端上传数据是为了让http或相关程序进行数据处理
//所以情况2:first:拿到数据 second:数据处理
*/
//所以,此时需判断是否带参
if(http_request.method == "GET")
{
size_t pos = http_request.uri.find("?");
if(pos != std::string::npos)
{
//带参
Util::CutString(http_request.uri,http_request.path,http_request.query_string,"?");
http_request.cgi = true;
}
else
{
//不带参
http_request.path = http_request.uri;
}
}
else if(http_request.method == "POST")
{
//POST
http_request.cgi = true;
http_request.path = http_request.uri;
}
else
{
//do nothing
}
//path路径表明了服务器上的某种资源,是从根目录开始吗?不一定需要指明web根目录,根目录下一定有一个默认的首页
//我们需要把path路径转化成我们的web根目录/...
//相当于给path添加前缀
_path = http_request.path;
http_request.path = WEB_ROOT;
http_request.path += _path;
if(http_request.path[http_request.path.size()-1] == '/')
{
//需要添加首页信息
http_request.path += HOME_PAGE;
}
//path路径对应的资源判断是否存在
//stat:指明路径下获取一个文件的属性,如果成功获得属性,说明路径在
if(stat(http_request.path.c_str(),&st) == 0)
{
//资源存在,但是存在不一定可以访问资源,有可能是一个目录
if(S_ISDIR(st.st_mode))
{
//所求的资源是一个目录,不被允许,需要做相关处理:添加目录首页
//虽然是目录,但不会以 /结尾
http_request.path += "/";
http_request.path += HOME_PAGE;
stat(http_request.path.c_str(),&st);
}
if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP)|| (st.st_mode & S_IXOTH))
{
//请求的是一个可执行程序
//什么时候需要CGI进行数据处理?只要用户有数据上传上来
http_request.cgi = true;
}
http_request.size = st.st_size;//拿到对应的文件大小
}
else
{
//资源不存在
LOG(WARNING,http_request.path +" Not Found");
code = NOT_FOUND;
goto END;
}
//把后缀提取出来
//请求资源是什么类型取决于资源的后缀
found = http_request.path.rfind(".");
if(found == std::string::npos)
{
http_request.suffix = ".html";
}
else
{
http_request.suffix = http_request.path.substr(found);
}
if(http_request.cgi == true)
{
code = ProcessCGI();//执行目标程序,拿到结果:http_response.response_body
}
else
{
//目标网页一定存在
//返回并不是返回网页,而是构建http响应
code = ProcessNonCGI();//简单的网页返回,返回静态网页,只需要打开即可
}
//std::cout<<"debug:url "<<http_request.path<<"?"<<http_request.query_string<<std::endl;
END:
//响应
BuildHttpresponseHelper();//状态行,响应报头,空行,正文都有了
//return;
}
void SendHttpresponse()
{
send(sock,http_response.status_line.c_str(),http_response.status_line.size(),0);
for(auto iter : http_response.response_header)
{
send(sock,iter.c_str(),iter.size(),0);
}
send(sock,http_response.blank.c_str(),http_response.blank.size(),0);
if(http_request.cgi)
{
//发body
auto &response_body = http_response.response_body;
size_t size = 0;
size_t total = 0;
const char *start = response_body.c_str();
while((total < response_body.size()) && (size = send(sock,start+total,response_body.size()-total,0)) > 0)
{
total += size;
}
}
else
{
sendfile(sock,http_response.fd,nullptr,http_request.size);
close(http_response.fd);
}
}
~Endpoit()
{
close(sock);
}
};
class Entrance
{
public:
static void *HandlerRequest(void *_sock)//处理线程
{
LOG(INFO,"Handler Request Begin");
int sock = *(int *)_sock;
delete (int*)_sock;
//std::cout<<"get a new link..."<<sock<<std::endl;
//for test
char buffer[4096];
recv(sock,buffer,sizeof(buffer),0);
std::cout<<"-----------begin-----------------"<<std::endl;
std::cout<<buffer << std::endl;
std::cout<<"-----------end-----------------"<<std::endl;
Endpoit *ep = new Endpoit(sock);
ep->RecvHttprequest();
ep->ParseHttprequest();
ep->BuildHttpresponse();
ep->SendHttpresponse();
delete ep;
/*
std::string line;
Util::Readline(sock,line);
std::cout<<line<<std::endl;
close(sock);
return nullptr;
*/
LOG(INFO,"Handler Request End");
return nullptr;
}
};
HttpServer.hpp
#pragma once
#include "Log.hpp"
#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()
{
LOG(INFO,"Loop Begin!");
int listen_sock = tcp_server->Sock();
while(!stop)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
continue;
}
LOG(INFO,"Get a new link!");
int *_sock = new int(sock);
pthread_t tid;
pthread_create(&tid,nullptr,Entrance::HandlerRequest,_sock);
pthread_detach(tid);
}
}
~HttpServer()
{}
};
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>
#include "Log.hpp"
#define Backlog 5
class TcpServer
{
private:
int port;
int listen_sock;
static TcpServer *svr;
private:
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();
LOG(INFO,"tcp_server init success!");
}
void Socket()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
LOG(FATAL,"socket error");
exit(1);
}
int opt = 1;
setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
LOG(INFO,"socket success!");
}
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;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
{
LOG(FATAL,"bind error");
exit(2);
}
LOG(INFO,"bind success!");
}
void Listen()
{
if(listen(listen_sock,Backlog) < 0)
{
LOG(FATAL,"listen error");
exit(3);
}
LOG(INFO,"listen success!");
}
int Sock()
{
return listen_sock;
}
~TcpServer()
{
if(listen_sock >= 0)
close(listen_sock);
}
};
TcpServer* TcpServer::svr = nullptr;
Log.hpp
//日志
void Log(std::string level,std::string message,std::string file_name,int line)
{
//[日志级别][时间戳][日志信息][错误文件名称][行数]
//级别:INFO:正常输出WARNING:警告ERROR:错误FATAL:致命信息,程序应立即终止
//时间戳:ctime time(nullptr)
//信息:错误,风险,提示等
//错误文件名: __FILE__
//行数:__LINE__
std::cout<<"["<<level<<"]"<<"["<<time(nullptr)<<"]"<<"["<<message<<"]"<<"["<<file_name<<"]"<<"["<<line<<"]"<<std::endl;
}
Util.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
class Util
{
public:
static int Readline(int sock,std::string &out)
{
char ch = 'X';
while(ch != '\n')
{
ssize_t s = recv(sock,&ch,1,0);
if(s > 0)
{
if(ch == '\r')
{
recv(sock,&ch,1,MSG_PEEK);
if(ch == '\n')
{
recv(sock,&ch,1,0);
}
else
{
ch = '\n';
}
}
out.push_back(ch);
}
else if(s == 0)
{
std::cout<<"client close"<<std::endl;
return 0;
}
else
{
std::cout<<"error"<<std::endl;
return -1;
}
}
return out.size();
}
static bool CutString(std::string &target,std::string &sub1_out,std::string &sub2_out,std::string sep)
{
size_t pos = target.find(sep);
if(pos != std::string::npos)
{
sub1_out = target.substr(0,pos);
sub2_out = target.substr(pos+sep.size());
return true;
}
return false;
}
};
main.cc
#include <iostream>
#include <string>
#include <memory>
#include "HttpServer.hpp"
#include "Log.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);
}
int port =atoi(argv[1]);
std::shared_ptr<HttpServer> http_server(new HttpServer(port));
http_server->InitServer();
http_server->Loop();
return 0;
}
test_cgi.cc
#include <iostream>
#include <cstdlib>
#include <unistd.h>
using namespace std;
bool GetQueryString(std::string &query_string)
{
bool result = false;
std::string method = getenv("METHOD");
if(method == "GET")
{
query_string = getenv("QUERY_STRING");
result = true;
}
else if(method == "POST")
{
int content_length = atoi(getenv("CONTENT_LENGTH"));
char c = 0;
while(content_length)
{
read(0,&c,1);
query_string.push_back(c);
content_length--;
}
result = true;
}
else
{
result = false;
}
return result;
}
void CutString(std::string &in,const std::string &sep,std::string &out1,std::string &out2)
{
auto pos = in.find(sep);
if(pos != std::string::npos)
{
out1 = in.substr(0,pos);
out2 = in.substr(pos+sep.size());
}
}
int main()
{
std::string query_string;
GetQueryString(query_string);
std::string str1;
std::string str2;
std::string name1;
std::string value1;
CutString(query_string,"&",str1,str2);
CutString(str1,"=",name1,value1);
std::string name2;
std::string value2;
CutString(str2,"=",name2,value2);
std::cout<<name1 <<": "<<value1<<std::endl;
std::cout<<name2 <<": "<<value2<<std::endl;
std::cerr<<name1 <<": "<<value1<<std::endl;
std::cerr<<name2 <<": "<<value2<<std::endl;
return 0;
}
Makefile
bin=httpserver
cgi=test_cgi
cc=g++
LD_FLAGS=-std=c++11 -lpthread
curr=$(shell pwd)
src=main.cc
ALL:$(bin) $(cgi)
.PHONY:ALL
$(bin):$(src)
$(cc) -o $@ $^ $(LD_FLAGS)
$(cgi):cgi/test_cgi.cc
$(cc) -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f $(bin) $(cgi)
rm -rf output
.PHONY:output
output:
mkdir -p output
cp $(bin) output
cp -rf wwwroot output
cp $(cgi) output/wwwroot
build.sh
#!/bin/bash
make clean
make
make output
线程池介入
解决问题:
- 大量链接过来导致服务器内部进程或者线程暴增,进而导致服务器效率严重降低或者挂掉
- 节省链接请求到来时,创建线程的时间成本
- 让服务器的效率在一个恒定的稳定区间内(线程个数不增多,CPU调度成本不变)
ThreadPool.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include "Log.hpp"
#define NUM 3
class ThreadPool
{
private:
int num;
bool stop;
std::queue<Task> task_queue;
pthread_mutex_t lock;
pthread_cond_t cond;
ThreadPool(int _num = NUM):num(_num),stop(false)
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
}
ThreadPool(const ThreadPool &){}
static ThreadPool *single_instance;
public:
static ThreadPool* getinstance()
{
static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
if(single_instance == nullptr)
{
pthread_mutex_lock(&_mutex);
if(single_instance == nullptr)
{
single_instance = new ThreadPool();
single_instance->InitThreadPool();
}
pthread_mutex_unlock(&_mutex);
}
return single_instance;
}
bool IsStop()
{
return stop;
}
void Lock()
{
pthread_mutex_lock(&lock);
}
void Unlock()
{
pthread_mutex_unlock(&lock);
}
bool TaskQueueIsEmpty()
{
return task_queue.size()==0 ? true : false;
}
void ThreadWait()
{
pthread_cond_wait(&cond,&lock);
}
void ThreadWakeUp()
{
pthread_cond_signal(&cond);
}
static void *ThreadRoutine(void *args)
{
ThreadPool *tp = (ThreadPool*)args;
while(true)
{
Task t;
tp->Lock();
while(tp->TaskQueueIsEmpty())
{
tp->ThreadWait();
}
tp->PopTask(t);
tp->Unlock();
t.ProcessOn();
}
}
bool InitThreadPool()
{
for(int i = 0;i < num;i++)
{
pthread_t tid;
if(pthread_create(&tid,nullptr,ThreadRoutine,this) != 0)
{
LOG(FATAL,"cretae threadpool error");
return false;
}
}
LOG(INFO,"create threadpool success");
return true;
}
void PushTask(const Task &task)
{
Lock();
task_queue.push(task);
Unlock();
ThreadWakeUp();
}
void PopTask(Task &task)
{
task = task_queue.front();
task_queue.pop();
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
ThreadPool* ThreadPool::single_instance = nullptr;
Task.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"
class Task
{
private:
int sock;
CallBack handler;
public:
Task(){}
Task(int _sock):sock(_sock)
{}
void ProcessOn()
{
handler(sock);
}
~Task()
{}
};
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>
#include "Log.hpp"
#define Backlog 5
class TcpServer
{
private:
int port;
int listen_sock;
static TcpServer *svr;
private:
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();
LOG(INFO,"tcp_server init success!");
}
void Socket()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
LOG(FATAL,"socket error");
exit(1);
}
int opt = 1;
setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
LOG(INFO,"socket success!");
}
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;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
{
LOG(FATAL,"bind error");
exit(2);
}
LOG(INFO,"bind success!");
}
void Listen()
{
if(listen(listen_sock,Backlog) < 0)
{
LOG(FATAL,"listen error");
exit(3);
}
LOG(INFO,"listen success!");
}
int Sock()
{
return listen_sock;
}
~TcpServer()
{
if(listen_sock >= 0)
close(listen_sock);
}
};
TcpServer* TcpServer::svr = nullptr;
HttpSever.hpp
#pragma once
#include "Log.hpp"
#include "TcpServer.hpp"
#include <iostream>
#include <pthread.h>
#include <signal.h>
#include "Task.hpp"
#include "ThreadPool.hpp"
#define PORT 8081
class HttpServer
{
private:
int port;
bool stop;
public:
HttpServer(int _port = PORT):port(_port),stop(false)
{}
void InitServer()
{
signal(SIGPIPE,SIG_IGN);
}
void Loop()
{
TcpServer *tsvr = TcpServer::getinstance(port);
LOG(INFO,"Loop Begin!");
while(!stop)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(tsvr->Sock(),(struct sockaddr*)&peer,&len);
if(sock < 0)
{
continue;
}
LOG(INFO,"Get a new link!");
Task task(sock);
ThreadPool::getinstance()->PushTask(task);
}
}
~HttpServer()
{}
};
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
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;
std::string uri;
std::string version;
std::unordered_map<std::string,std::string> header_kv;
int content_length;
std::string path;
std::string query_string;
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(){}
};
class Endpoit
{
private:
int sock;
HttpRequest http_request;
HttpResponse http_response;
bool stop;
private:
bool RecvHttpRequestLine()
{
auto &line = http_request.request_line;
if(Util::Readline(sock,line) > 0)
{
line.resize(line.size()-1);
LOG(INFO,http_request.request_line);
}
else
{
stop = true;
}
return stop;
}
bool RecvHttpRequestHeader()
{
std::string line;
while(true)
{
line.clear();
if(Util::Readline(sock,line) <= 0)
{
stop = true;
break;
}
if(line == "\n")
{
http_request.blank = line;
break;
}
line.resize(line.size()-1);
http_request.request_header.push_back(line);
LOG(INFO,line);
}
return stop;
}
void ParseHttprequestLine()
{
auto &line = http_request.request_line;
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;
}
bool 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
{
stop = true;
break;
}
}
}
return stop;
}
int ProcessNonCGI()
{
http_response.fd = open(http_request.path.c_str(),O_RDONLY);
if(http_response.fd >= 0)
{
LOG(INFO,http_request.path+"open success!");
return OK;
}
return NOT_FOUND;
}
int ProcessCGI()
{
LOG(INFO,"process cgi");
int code = OK;
auto &method = http_request.method;
auto &query_string = http_request.query_string;
auto &body_text = http_request.request_body;
int content_length = http_request.content_length;
auto &response_body = http_response.response_body;
auto &bin = http_request.path;
int input[2];
int output[2];
std::string query_string_env;
std::string method_env;
std::string content_length_env;
if(pipe(input) < 0)
{
LOG(ERROR,"input errpr");
code = SERVER_ERROR;
return code;
}
if(pipe(output) < 0)
{
LOG(ERROR,"output error");
code = SERVER_ERROR;
return code;
}
pid_t pid = fork();
if(pid == 0)
{
close(input[0]);
close(output[1]);
method_env = "METHOD=";
method_env += method;
std::cout<<"cgi: "<<method_env<<std::endl;
putenv((char*)method_env.c_str());
if(method == "GET")
{
query_string_env = "QUERY_STRING=";
query_string_env += query_string;
putenv((char*)query_string_env.c_str());
LOG(INFO,"Get Method,Add Query_string Env");
}
else if(method == "POST")
{
content_length_env = "CONTENT_LENGTH=";
content_length_env += std::to_string(content_length);
putenv((char*)content_length_env.c_str());
LOG(INFO,"Post Method,Add Content-Length Env");
}
else
{
}
std::cout<<"bin "<<bin<<std::endl;
dup2(output[0],0);
dup2(input[1],1);
execl(bin.c_str(),bin.c_str(),nullptr);
exit(1);
}
else if(pid < 0)
{
LOG(ERROR,"fork error");
return 404;
}
else
{
close(input[1]);
close(output[0]);
if(method == "POST")
{
const char* start = body_text.c_str();
int total = 0;
int size = 0;
while((total< content_length) &&(size = write(output[1],total+start,body_text.size()-total)) > 0)
{
total += size;
}
}
char c;
while(read(input[0],&c,1) > 0)
{
response_body.push_back(c);
}
int status = 0;
pid_t ret = waitpid(pid,&status,0);
if(ret == pid)
{
if(WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0)
{
code = OK;
}
else
{
code = BAD_REQUEST;
}
}
else
{
code = SERVER_ERROR;
}
}
close(input[1]);
close(output[0]);
}
return code;
}
void HandlerERROR(std::string page)
{
http_request.cgi = false;
http_response.fd = open(page.c_str(),O_RDONLY);
if(http_response.fd > 0)
{
struct stat st;
stat(page.c_str(),&st);
http_request.size = st.st_size;
std::string line = "Content-Type: text/html";
line += LINE_END;
http_response.response_header.push_back(line);
line = "Content-Length: ";
line += std::to_string(st.st_size);
line += LINE_END;
http_response.response_header.push_back(line);
}
}
void BuildOKResponse()
{
std::string line = "Content-Type: ";
line +=Suffix2Desc(http_request.suffix);
line += LINE_END;
http_response.response_header.push_back(line);
line += "Content-Length: ";
if(http_request.cgi)
{
line += std::to_string(http_response.response_body.size());
}
else
{
line += std::to_string(http_request.size);
}
line += LINE_END;
http_response.response_header.push_back(line);
}
void BuildHttpresponseHelper()
{
auto &code = http_response.status_code;
auto &line = http_response.status_line;
line += HTTP_VERSION;
line += " ";
line += std::to_string(code);
line += " ";
line += Code2Desc(code);
line += LINE_END;
std::string path = WEB_ROOT;
path +="/";
switch(code)
{
case OK:
BuildOKResponse();break;
case NOT_FOUND:
path += PAGE_404;
HandlerERROR(path);break;
case BAD_REQUEST:
break;
case SERVER_ERROR:
break;
default:
break;
}
}
public:
Endpoit(int _sock):sock(_sock),stop(false)
{}
bool IsStop()
{
return stop;
}
void RecvHttprequest()
{
if((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()))
{
ParseHttprequestLine();
ParseHttprequestHeader();
RecvHttpRequestBody();
}
}
void BuildHttpresponse()
{
std::string _path;
struct stat st;
std::size_t found= 0;
auto &code = http_response.status_code;
if(http_request.method != "GET" && http_request.method != "POST")
{
LOG(WARNING,"method is not right");
code = BAD_REQUEST;
goto END;
}
if(http_request.method == "GET")
{
size_t pos = http_request.uri.find("?");
if(pos != std::string::npos)
{
Util::CutString(http_request.uri,http_request.path,http_request.query_string,"?");
http_request.cgi = true;
}
else
{
http_request.path = http_request.uri;
}
}
else if(http_request.method == "POST")
{
http_request.cgi = true;
http_request.path = http_request.uri;
}
else
{
}
_path = http_request.path;
http_request.path = WEB_ROOT;
http_request.path += _path;
if(http_request.path[http_request.path.size()-1] == '/')
{
http_request.path += HOME_PAGE;
}
if(stat(http_request.path.c_str(),&st) == 0)
{
if(S_ISDIR(st.st_mode))
{
http_request.path += "/";
http_request.path += HOME_PAGE;
stat(http_request.path.c_str(),&st);
}
if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP)|| (st.st_mode & S_IXOTH))
{
http_request.cgi = true;
}
http_request.size = st.st_size;
}
else
{
LOG(WARNING,http_request.path +" Not Found");
code = NOT_FOUND;
goto END;
}
found = http_request.path.rfind(".");
if(found == std::string::npos)
{
http_request.suffix = ".html";
}
else
{
http_request.suffix = http_request.path.substr(found);
}
if(http_request.cgi == true)
{
code = ProcessCGI();
}
else
{
code = ProcessNonCGI();
}
END:
BuildHttpresponseHelper();
}
void SendHttpresponse()
{
send(sock,http_response.status_line.c_str(),http_response.status_line.size(),0);
for(auto iter : http_response.response_header)
{
send(sock,iter.c_str(),iter.size(),0);
}
send(sock,http_response.blank.c_str(),http_response.blank.size(),0);
if(http_request.cgi)
{
auto &response_body = http_response.response_body;
size_t size = 0;
size_t total = 0;
const char *start = response_body.c_str();
while((total < response_body.size()) && (size = send(sock,start+total,response_body.size()-total,0)) > 0)
{
total += size;
}
}
else
{
sendfile(sock,http_response.fd,nullptr,http_request.size);
close(http_response.fd);
}
}
~Endpoit()
{
close(sock);
}
};
class CallBack
{
public:
CallBack()
{}
void operator()(int sock)
{
HandlerRequest(sock);
}
void *HandlerRequest(int sock)
{
LOG(INFO,"Handler Request Begin");
#ifdef 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;
#else
Endpoit *ep = new Endpoit(sock);
ep->RecvHttprequest();
if(!ep->IsStop())
{
LOG(INFO,"Recv NO ERROR, Build and Send");
ep->BuildHttpresponse();
ep->SendHttpresponse();
}
else{
LOG(WARNING,"Recv Error,Stop Build and Send");
}
delete ep;
#endif
LOG(INFO,"Handler Request End");
return nullptr;
}
~CallBack()
{}
};
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<form action="/test_cgi" method="GET">
x:<input type="text" name="data_x" value="0">
<br>
y:<input type="text" name="data_y" value="0">
<br>
<input type="submit" value="Submit">
</form>
<p>点击提交,会将数据提交给test_cgi进行后台数据处理</p>
</body>
</html>
(其余代码不变) test_cgi.cc
#include <iostream>
#include <cstdlib>
#include <unistd.h>
using namespace std;
bool GetQueryString(std::string &query_string)
{
bool result = false;
std::string method = getenv("METHOD");
if(method == "GET")
{
query_string = getenv("QUERY_STRING");
result = true;
}
else if(method == "POST")
{
int content_length = atoi(getenv("CONTENT_LENGTH"));
char c = 0;
while(content_length)
{
read(0,&c,1);
query_string.push_back(c);
content_length--;
}
result = true;
}
else
{
result = false;
}
return result;
}
void CutString(std::string &in,const std::string &sep,std::string &out1,std::string &out2)
{
auto pos = in.find(sep);
if(pos != std::string::npos)
{
out1 = in.substr(0,pos);
out2 = in.substr(pos+sep.size());
}
}
int main()
{
std::string query_string;
GetQueryString(query_string);
std::string str1;
std::string str2;
std::string name1;
std::string value1;
CutString(query_string,"&",str1,str2);
CutString(str1,"=",name1,value1);
std::string name2;
std::string value2;
CutString(str2,"=",name2,value2);
std::cout<<name1 <<": "<<value1<<std::endl;
std::cout<<name2 <<": "<<value2<<std::endl;
std::cerr<<name1 <<": "<<value1<<std::endl;
std::cerr<<name2 <<": "<<value2<<std::endl;
int x = atoi(value1.c_str());
int y = atoi(value2.c_str());
std::cout<< "<html>";
std::cout<< "<head><meta charset=\"UTF-8\"></head>";
std::cout<< "<body>";
std::cout<< "<h3>"<< value1<< "+ "<< value2 << "=" << x + y<<"</h3>";
std::cout<< "</body>";
std::cout<< "</html>";
return 0;
}
Get通过uri传参,from提交的时候会自动拼接url POST通过正文传参 Get通过uri传参,大小一般有限制(通过环境变量传给子进程),提交的数据比较公开
通过CGI访问数据库
连接数据库
rz -e 数据库压缩包
tar xzf 数据库压缩包
其中 include 包含所有的方法声明, lib 包含所有的方法实现(打包成库)
gcc -o mysql_conn mysql_conn.cc -I ./include -L ./lib -lmysqlclient
export LD_LIBRARY_PATH=./lib #动态库查找路径
---------------------------
gcc -o mysql_conn mysql_conn.cc -std=c++11 -I ./include -L ./lib -lmysqlclient -plthread -ldl -static
ldd mysql_conn//找库
通过 mysql_get_client_info() 函数
mysql_get_client_info() //查看
------------------------
mysql client Version: 6.1.6
创建新用户
create user '用户名'@'localhost' identified by '密码'
mysql接口介绍
初始化mysql_init()
要使用库,必须先进行初始化! MYSQL *mysql_init(MYSQL *mysql); 如:MYSQL *mfp = mysql_init(NULL)
链接数据库mysql_real_connect
初始化完毕之后,必须先链接数据库,在进行后续操作。(mysql网络部分是基于TCP/IP的)
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long clientflag);
- mysql:需要链接的数据库
- host :127.0.0.1
- user: 用户名
- password 密码
- db:数据库
- Port:端口号
- 套接字指针:通常设置为nullptr
- clientflag:连接标志通常设置为0
下发mysql命令mysql_query
int mysql_query(MYSQL *mysql, const char *q);
//第二个参数为要执行的sql语句,如“select * from table”。
获取执行结果mysql_store_result
MYSQL_RES *mysql_store_result(MYSQL *mysql);
**关闭mysql链接mysql_close **
void mysql_close(MYSQL *sock);
设置编码格式
mysql_set_character_set();
|