文章目录
小型http服务器main.cc
#include "HttpServer.hpp"
#include "Log.hpp"
static void Usage(std::string _porc)
{
std::cout << "Usage: \n\t";
std::cout << "method one: "<<_porc << "port\n\t";
std::cout << "method two: "<<_porc << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 1 && argc != 2){
Usage(argv[0]);
return 1;
}
HttpServer *svr = nullptr;
if(argc == 1){
svr = HttpServer::GetInstance(8081);
}
else{
svr = HttpServer::GetInstance(atoi(argv[1]));
}
daemon(1, 1);
svr->InitServer();
svr->Start();
return 0;
}
HttpServer.hpp
#pragma once
#include <pthread.h>
#include "Sock.hpp"
#include "Protocol.hpp"
#include "ThreadPool.hpp"
#define PORT 8081
class HttpServer{
private:
int port;
int lsock;
ThreadPool *tp;
static HttpServer *http_svr;
static pthread_mutex_t lock;
//HttpServer()
//{}
public:
HttpServer(int _p = PORT)
:port(_p),lsock(-1),tp(nullptr)
{}
static HttpServer *GetInstance(int sk)
{
if(nullptr == http_svr){
pthread_mutex_lock(&lock);
if(nullptr == http_svr){
http_svr = new HttpServer(sk);
}
pthread_mutex_unlock(&lock);
}
return http_svr;
}
void InitServer()
{
signal(SIGPIPE, SIG_IGN);
lsock = Sock::Socket();创建
Sock::SetSockOpt(lsock);可复用
Sock::Bind(lsock, port);
Sock::Listen(lsock);
tp = new ThreadPool();
tp->InitThreadPool();
}
void Start()
{
for(;;){
int sock = Sock::Accept(lsock);
if(sock < 0){
continue;
}
LOG(Notice, "get a new link ...");
Task *tk = new Task(sock);
tp->PushTask(tk);
//demo 线程
//pthread_t tid;
//int *sockp = new int(sock);把整形初始化为sock
//pthread_create(&tid, nullptr, Entry::HandlerHttp, sockp);
//pthread_detach(tid);
}
}
pthread_create创建的线程如果永远不退出,会阻塞
HandlerHttp:获取请求、分析、响应
~HttpServer()
{
if(lsock >= 0){
close(lsock);
}
}
};
HttpServer *HttpServer::http_svr = nullptr;
pthread_mutex_t HttpServer::lock = PTHREAD_MUTEX_INITIALIZER;
创建线程 处理请求HandlerRequest(获取请求 分析请求) 分离线程 制作响应 发送响应
Sock.hpp
#pragma once
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/sendfile.h>
#include<unistd.h>
#include<stdlib.h>
#include<cstring>
#include<vector>
#include<unordered_map>
#include<sstream>
#include"Log.hpp"
#define BACKLOG 5
class Sock
{
public:
static int Socket()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
//原先我们都是直接把错误打印出来,但现在改为日志的形式
if(sock < 0){
LOG(Fatal,"socket create error");
exit(SocketErr);
}
return sock;
}
static void Bind(int sock,int port)
{
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
LOG(Fatal,"socket bind error");
exit(BindErr);
}
}
static void Listen(int sock)
{
if(listen(sock,BACKLOG) < 0){
LOG(Fatal,"socket listen error");
exit(ListenErr);
}
}
static void SetSockOpt(int sock)
{
int opt = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
}
static int Accept(int sock)
{获取新连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int s = accept(sock,(struct sockaddr*)&peer,&len);
//peer连接我
if(s < 0){
LOG(Warning,"accept error");
//再次申明一次,这里就相当于你去引流,如果没有引到,你会接着去引,所以说即使错误了也不要紧,可不敢直接exit
}
return s;
}
//从sock读取一行内容
static void GetLine(int sock,std::string& line)
{引用
char c = 'X';//按字符读取,初始化
while(c != '\n'){
ssize_t s = recv(sock,&c,1,0);从sock读,放到c
if(s > 0){
if(c == '\r'){
ssize_t ss = recv(sock,&c,1,MSG_PEEK);
if(ss > 0 && c == '\n'){
//换行符是\r\n
recv(sock,&c,1,0);
}
else{
// 把\r改为\n
c = '\n';
}
}
//走到这里时:情况1.读取到的内容是常规字符;况2.所有换行符改为\n
if(c != '\n'){
line.push_back(c);
}
}
}
}
};
recv函数的扩展MSG_PEEK(数据的窥探功能):换行符是没有硬性规定的,有些平台下有可能是\r \n \r\n 这3种都有可能是换行符标志,这里以\n为换行符;知道是\r时,判断一下是否是\n,如果是正常字符,非\n,正常字符放回内核缓冲区是很难做到的,用MSG_PEEK检测下一个字符是否是正常字符 ,但是并不从内核缓冲区拷贝到用户缓冲区
Log.hpp
[日志级别][message][时间戳][filename文件名]line无符号整形、行号]
#pragma once
#include<iostream>
#include<string>
#include<sys/time.h>
#define Notice 1
#define Warning 2
#define Error 3
#define Fatal 4
//尽可能不要在项目中出现数字
enum ERR
{
SocketErr = 1,
BindErr,
ListenErr,
ArgErr//命令行参数错误
};
#define LOG(level,message) \
Log(#level,message,__FILE__,__LINE__)
void Log(std::string level,std::string message,std::string filename, size_t line)
{
struct timeval curr;
gettimeofday(&curr,nullptr);
std::cout << "[" << level << "]"<< "[" << message << "]" << "[" << curr.tv_sec << "]" << "["<< filename <<"]"<<"["<< line <<"]" <<std::endl;
}
Util.hpp
工具类代码
#pragma once
#include"Sock.hpp"
#include"Log.hpp"
//打过来的请求是一行一行,so也要按行:读取
class Util
{
public:
//字符串拆分,也就是1变多
static void StringParse(std::string& request_line,std::string& method,std::string& uri,std::string& version)
{
//这是最简单的方法来进行字符串解析
std::stringstream ss(request_line);
ss >> method >> uri >> version;
}
//容器内的东西是坚决不能修改的,所以最好传参的时候不要传引用
static void MakeStringToKV(std::string line,std::string& k,std::string& v)
{
std::size_t pos = line.find(": ");//pos指向冒号的位置
if(pos != std::string::npos){
//找到了
k = line.substr(0,pos);从0下标开始到pos
v = line.substr(pos+2);从pos+2到结尾
}
}
static ssize_t StringToInt(const std::string& v)
{
//方法1: return stoi(v);
//stringstream 也可以转换成整数
方法2:
std::stringstream ss(v);
ssize_t i = 0;
ss >> i;流追加到i,自动识别类型
return i;
}
};
ThreadPool.hpp
生产者消费者模型,对外提供:任务队列、push pop接口,有事就处理,没事就等待,
#pragma once
#include<iostream>
#include<pthread.h>
#include<queue>
#include"Protocol.hpp"
typedef void (*handler_t)(int);
class Task
{
private:
int sock;
handler_t handler;
public:
Task(int sk):sock(sk),handler(Entry::HandlerHttp)
{}
void Run()
{
handler(sock);
}
~Task()
{}
};
class ThreadPool
{
private:
std::queue<Task*> q;
int num;
pthread_mutex_t lock;
pthread_cond_t cond;条件变量
public:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
bool IsEmpty()
{
return q.size() == 0;
}
void ThreadWait()
{
pthread_cond_wait(&cond,&lock);在cond条件下等,释放曾经拥有的锁
}
void ThreadWakeup()
{唤醒特定条件变量下的信号
pthread_cond_signal(&cond);
}
public:
ThreadPool(int n = 8):num(n)默认线程数=8
{}
static void *Routine(void *args)
{
//static是静态成员函数,不能够去访问非静态的成员函数,但是我现在要使用你ThreadPool里面queue,对弈可以通过对象的方式来
ThreadPool *this_p = (ThreadPool*)args;
this_p->LockQueue();锁住队列,任务队列:临界资源,任务队列里都是指针
while(this_p->IsEmpty()){
this_p->ThreadWait();线程休眠
}
Task *tk = this_p->PopTask();取任务
this_p->UnlockQueue();
tk->Run();处理任务
delete tk;
}
void InitThreadPool()
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
pthread_t tid[num];
for(int i = 0; i < num;i++){1次创建1批线程
pthread_create(tid+i,nullptr,Routine,this);
pthread_detach(tid[i]);
}
}
void PushTask(Task *tk)
{往线程池里push任务
LockQueue();
q.push(tk);//队列里插入任务
UnlockQueue();
ThreadWakeup();唤醒线程,让处理任务
}
Task* PopTask()把任务拿出来
{
Task *tk = q.front();
q.pop();
return tk;
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
Makefile
BIN=http_server
SRC=main.cc
CC=g++
FLAGS=-std=c++11 -lpthread #-DEBUG
CURR_PATH=$(shell pwd)
.PHONY:all
all:CGI $(BIN)
.PHONY:CGI
CGI:
cd $(CURR_PATH)/cgi;\
make;\
cd $(CURR_PATH)
$(BIN):$(SRC)
$(CC) -o $@ $^ $(FLAGS)
.PHONY:clean
clean:
rm -f $(BIN);\
cd $(CURR_PATH)/cgi;\
make clean;\
cd $(CURR_PATH);\
rm -rf output
.PHONY:output
output:
mkdir -p output/wwwroot/cgi;cp $(BIN) output;cp -rf wwwroot/* output/wwwroot;cp cgi/test_cgi output/wwwroot/cgi
#-DEBUG:-D命令行式的宏定义
Protocol.hpp 协议
#pragma once
#include"Sock.hpp"
#include"Log.hpp"
#include"Util.hpp"
//服务器从webroot(web根目录)中拿资源
#define WEBROOT "webroot"
#define HOMEPAGE "index.html"//首页
#define VERSION "HTTP/1.0"
//不想要被外面人访问到,函数只和本文件强相关
static std::string CodeToDesc(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 SuffixToDesc(const std::string& suffix)
{
if(suffix == ".html" || suffix == ".htm"){
return "text/html";
}
else if(suffix == ".js"){
return "application/x-javascript";
}
else if(suffix == ".css"){
return "text/css";
}
else if(suffix ==".jpg"){
return "image/jpeg";
}
else{
return "text/html";
}
}
//服务器处理请求Request(接收)
//请求行、请求报头、空行、正文
class HttpRequest
{
private:
std::string request_line;
std::vector<std::string> request_header;
std::string blank;
std::string request_body;
private:
std::string method;方法
std::string uri;
std::string version;
std::string path;url的路径
std::string query_string;请求参数
std::unordered_map<std::string,std::string> header_kv;
ssize_t content_length; //为了区分开给三个默认为-1表示不存在 0表示没有 >0表示有正文
bool cgi;
ssize_t file_size; //供后续使用的,不然会显得代码很冗余再调用一次stat
std::string suffix;//提取文件后缀
public:
类对象有默认构造函数
HttpRequest():blank("\n"),content_length(-1),path(WEBROOT),cgi(false),suffix("text/html")
{}
void SetRequest(std::string& line)
{获取的1行1行内容放入请求
request_line = line;
}
void RequestLineParse()
{//解析请求行:1个request_line分为3个字符串:method uri version
Util::StringParse(request_line,method,uri,version);
//LOG(Notice,request_line);
//LOG(Notice,method);
//LOG(Notice,uri);
//LOG(Notice,version);
}
void InsertHeaderLine(const std::string& line)
{报头行插入vector(request_header)
request_header.push_back(line);
LOG(Notice,"line");
}
//字符串在vector,遍历vector,每个字符串切分为KV,插入map,拆分1行报头
void RequestHeaderParse()
{ for(auto& e: request_header){
std::string k,v;
Util::MakeStringToKV(e,k,v);
LOG(Notice,k);
LOG(Notice,v);
if(k == "Content-Length"){
content_length = Util::StringToInt(v);
}
header_kv.insert({k,v});
}
}
//1.请求方法必须得是post
//2.Content-length必须不是0
bool IsNeedRecvBody()是否需要读正文
{
//Post PoSt POST 统一大小写
if(strcasecmp(method.c_str(),"POST") == 0 && content_length > 0){
cgi = true;
return true;
}
return false;
}
ssize_t GetContentLength()
{
return content_length;
}
void SetRequestBody(const std::string& body)
{
request_body = body;
}
bool IsMethodLegal()
{判断是否合法
if(strcasecmp(method.c_str(),"POST") == 0 || strcasecmp(method.c_str(),"GET") == 0){
return true;
}
return false;
}
bool IsGet()
{
return strcasecmp(method.c_str(),"GET") == 0? true : false;
}
bool IsPost()
{
return strcasecmp(method.c_str(),"POST") == 0? true : false;
}
void UriParse()
{
std::size_t pos = uri.find('?');找字符?
if(pos == std::string::npos){
path += uri;
}
else{//找到了'?',有参数query_string,Pos位置指向'?'
path += uri.substr(0,pos);前闭后开区间
query_string = uri.substr(pos+1);
cgi = true;
}
}
void SetUriEqPath()
{
path += uri;
}
std::string GetPath()
{
return path;
}
void IsAddHomePage()
{字符串已/结尾时:添加首页HomePage,字符串支持[]操作
if(path[path.size()-1] == '/'){
path += HOMEPAGE;
}
}
std::string SetPath(std::string _path)
{
return path = _path;
}
void SetCgi()
{
cgi = true;
}
bool IsCgi()
{
return cgi;
}
void SetFileSize(ssize_t s)
{
file_size = s;
}
ssize_t GetFileSize()
{
return file_size;
}
std::string GetQueryString()
{
return query_string;
}
std::string GetBody()
{
return request_body;
}
std::string GetMethod()
{
return method;
}
std::string MakeSuffix()制作后缀,后缀在path里
{
std::string suffix;
size_t pos = path.rfind(".");
if(std::string::npos != pos){
suffix = path.substr(pos);
}
return suffix;
}
~HttpRequest()
{}
};
//状态行、响应报头、空行、响应正文(发出去)
//定义一套标准
class HttpResponse
{
private:
std::string status_line;
std::vector<std::string> response_header;
std::string blank;
std::string response_body;
public:
HttpResponse():blank("\r\n")
{}
//版本号 状态码 状态码描述
void SetStatusLine(const std::string& line)
{
status_line =line;
}
std::string GetStatusLine()
{
return status_line;
}
const std::vector<std::string>& GetRspHeader()
{
response_header.push_back("\n");
return response_header;
}
void AddHeader(const std::string& ct)
{
response_header.push_back(ct);
}
~HttpResponse()
{}
};
class EndPoint
{
private:
int sock;
HttpRequest req;
HttpResponse rsp;
private:
void GetRequestLine()//获取请求行
{
std::string line;
Sock::GetLine(sock,line);
//读上来的这一行我们要拿去给HttpRequest进行分析
req.SetRequest(line);
//设置进去了之后,最主要的是能够分析出来他所使用请求方法、URL、版本号
req.RequestLineParse();
}
void SetResponseStatusLine(int code)
{
std::string status_line;
status_line = VERSION;
status_line += " ";
status_line += std::to_string(code);转为字符串
status_line += " ";
status_line += CodeToDesc(code);//状态码转为所对应的描述
status_line += "\r\n";//对于我们自己写的我们就设置为以\r\n为结束
rsp.SetStatusLine(status_line);
}
void GetRequestHeader()
{//不断的循环读,一直读取到"\n"停止
std::string line;
do{
line = "";读之前,清空
Sock::GetLine(sock,line);
req.InsertHeaderLine(line);
}while(!line.empty());
req.RequestHeaderParse(); //解析报头
}
void SetResponseHeaderLine()
{根据后缀制作Content-type
//图片、网页类型不同表示Content-type不同
std::string suffix = req.MakeSuffix();
std::string content_type = "Content-Type: ";
content_type += SuffixToDesc(suffix);
content_type += "\r\n";
rsp.AddHeader(content_type);
}
void GetRequestBody()
{
int len = req.GetContentLength();
char c;
std::string body;临时变量
while(len != 0){
ssize_t s = recv(sock,&c,1,0);
body.push_back(c);把c放入body
len--;
}
req.SetRequestBody(body);
}
public:
EndPoint(int _sock):sock(_sock)
{}
void RecvRequest()
{ //获取完整http请求
//分析http请求
//读取并分析完了
GetRequestLine();
//读取报头
GetRequestHeader();
if(req.IsNeedRecvBody())
{
GetRequestBody();//读正文+设置进协议里
}
//已经读完了所有的请求
}
void MakeResponse()
{
int code = 200;
std::string path;
if(!req.IsMethodLegal()){
//不是POST或者GET方法
LOG(Warning,"method is not legal");
code = 404;
goto end;
}
if(req.IsGet()){//get方法
req.UriParse();
}
else{
//POST方法
req.SetUriEqPath();
}
req.IsAddHomePage();
path = req.GetPath();
LOG(Notice,path);
struct stat st;
if(stat(path.c_str(),&st) < 0){
LOG(Warning,"html is not exist! 404");文件不存在
code = 404;
goto end;
}
else{ //文件路径合法
if(S_ISDIR(st.st_mode)){文件是目录
//拼接:/index.html
path += "/";
req.SetPath(path);
req.IsAddHomePage();
}
else{ //可执行程序有可执行权限属性
if((st.st_mode & S_IXUSR) ||\
(st.st_mode & S_IXGRP) ||\
(st.st_mode & S_IXOTH)){
req.SetCgi();
}
else{
//网页请求
}
}
if(!req.IsCgi()){
req.SetFileSize(st.st_size);//设置文件大小
}
}
end:
//制作response
//设置状态行
SetResponseStatusLine(code);
SetResponseHeaderLine();
//设置响应报头
//SetResponseHeader();
}
//打开文件(文件在path里),文件里面的内容通过sock发回去
void ExecNonCgi(const std::string& path)
{
ssize_t s = req.GetFileSize();把fd传给sock
int fd = open(path.c_str(),O_RDONLY);
if(fd < 0){
LOG(Error,"path is not exists bug!!!");
return;
}
sendfile(sock,fd,nullptr,s);
close(fd);
}
void ExecCgi()
{
//std::string arg;
//if(req.IsGet()){get方法,参数在GetQueryString
//arg = req.GetQueryString();
//}
//else{post方法,参数在body
//arg = req.GetBody();
//}
std::string content_length_env;
std::string path = req.GetPath();
std::string method = req.GetMethod();
std::string method_env = "METHOD=";
method_env += method;
std::string query_string;
std::string query_string_env;
std::string body;
int pipe_in[2] ={0};
int pipe_out[2] = {0};
pipe(pipe_in);
pipe(pipe_out);
putenv((char*)method_env.c_str());导入环境变量
pid_t id = fork();
if(id == 0){ //子进程
//但是此时数据是父进程拿着呢,但是要交给子进程去处理,所以这里还涉及到一个进程间通信的问题
close(pipe_in[1]); //父进程来写数据进管道,子进程来读
close(pipe_out[0]);//父进程来读结果,子进程来写
dup2(pipe_in[0],0);子进程用pipe_in[0]读重定向到0
dup2(pipe_out[1],1);显示到标准输出里的重定向到管道
//第1种
if(req.IsGet()){
query_string = req.GetQueryString();
query_string_env = "QUERY_STRING=";
query_string_env += query_string;
putenv((char*)query_string_env.c_str());
}
else if(req.IsPost()){
content_length_env = "CONTENT-LENGTH=";
content_length_env += std::to_string(req.GetContentLength());
putenv((char*)content_length_env.c_str());
}
已经知道方法、参数,开始替换(执行程序)
execl(path.c_str(),path.c_str(),nullptr);
exit(0);如果替换失败,直接返回
}
//father
close(pipe_in[0]);
close(pipe_out[1]);
char c = 'X';
//第2种
if(req.IsPost()){
body = req.GetBody();
int size = body.size();
int i = 0;
for(;i< size;i++){
write(pipe_in[1],&body[i],1);
}
}
//替换程序结束后,父进程从管道里读返回结果
ssize_t s = 0;
do{
s = read(pipe_out[0],&c,1);
if(s > 0){
send(sock,&c,1,0);
}
}while(s > 0);子进程处理完数据,退出,管道关闭,s=0. do while循环结束
waitpid(id,nullptr,0);
}
void SendResponse()
{ //发送状态行,发送的本质:拷贝到发送缓冲区
std::string line = rsp.GetStatusLine()获得状态行
send(sock,line.c_str(),line.size(),0);发送状态行
//发送响应报头
for(auto& e : rsp.GetRspHeader()){
send(sock,e.c_str(),e.size(),0);
}
//发送空行,
//判断执行模式,有可能你只是想要一个普通的html,还有可能是给我传过参数
//1. GET方法uri里面有参数
//2.POST方法 有正文
//3 分析出来的路径最终访问的是一个可执行程序都要使用cgi技术
if(req.IsCgi()){
LOG(Notice,"use cgi model!");
ExecCgi();
}
else{
LOG(Notice,"use no cgi model!");
//普通html
std::string path = req.GetPath();
ExecNonCgi(path);
}
}
sock:正文
//短链接,完成一次请求和响应后,链接关闭:
~EndPoint()
{
if(sock >= 0){
close(sock);
}
}
};
class Entry
{
public:
static void HandlerHttp(int sock)处理http协议
{
//int sock = *(int*)arg;//堆空间上的arg拷贝到私有栈
//delete (int*)arg;//释放堆空间保存的sock
#ifdef DEBUG//条件编译,方便调试时看到底哪里出错了
char request[10240];
recv(sock,request,sizeof(request),0);从sock读,放到request
close(sock);
#else
EndPoint *ep = new EndPoint(sock);
ep->RecvRequest();
ep->MakeResponse(); //2种方法:是GET和POST 对于其他的方法如果来了,直接构建response响应返回回去
ep->SendResponse();
delete ep;
#endif
}
};
c++面向对象:方法由对象提供。strcasecmp():忽略字母大小写问题。shift+#:函数跳转。上网目标:1.把网页资源从服务器上拿下来2.把自己的数据提交给服务器。守护进程:不会随着终端的关闭而释放,特殊的孤儿进程,
get方法时:分2种:1无参数,如:https: / / www.baidu.com/index.html\访问首页,静态文件,把网页资源从服务器上拿下来,访问的资源在path,
2有参数,如:https://www. baidu.com/s? ie=utf8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=nba&fenlei=256,类比url=Path ?参数,把自己的数据提交给服务器, path + query_string
网址里的某些信息自动拼接到域名后面
post方法 时:路径在uri中,参数在body中,访问的资源在path,把自己的数据提交给服务器
goto 和 end之间最好不要定义变量,因为跳转的时候有可能会导致一些变量的定义丢失
访问:1目录,文件路径合法:结尾没有带’/'但是最后一个是一个目录,如./ wwwroot/a/b/dir,每个目录下面必须有index.html,是文本(静态资源)则直接返回;2普通的html;3可执行程序(二进制)(动态资源),CGI:服务器把程序跑完,返回运行结果
stat(),成功返回0,失败返回-1,stat=检测是否存path文件=看文件属性,系统是c语言写的,stat系统调用时,path.c_str()转为c语言,c++永远摆脱不了c语言,
通过sock来获取过来的Request和Response,然后在交给对应的两个类里面的具体方法进行拆解分析解析。 EndPoint类:只负责读取协议
switch(code):代码的维护性高,如果来了新的错误,直接加一个case即可
ssize_t sendfile(int out_fd,int in_fd, off_t *offset,size_t count);in_fd :为了读; out_fd :为了写; offset 偏移量 ,系统调用接口
在命令行输入错字母,但删除不了,则ctrl+删除键
创建两个管道1个是父进程把参数给子进程(打开写端),即子进程用pipe_in读取, 一个是子进程给父进程返回结果, 站在被调用进程的角度说In(读)、out,pipe[0]是读端 pipe[1]是写端,给客户端响应正文:cgi模式:服务器创建一个子进程,子进程去处理参数,父进程读取结果pipe_out[0],父进程在返回, 对于url有参数的就是要环境变量传参; 环境变量是全局变量,子进程可以获得到的;第1种是通过环境变量传参给子进程 : GET方法的参数在query_string 、第2种 : POST方法在 body ,可能body十分的长,管道来传参数;
execl(执行谁(路径),以什么方式执行,nullptr)程序替换只替换数据、代码,不换文件描述符,
waitpid():只有1个线程在等,其他线程在干其他事情,等子进程运行完,把结果给父进程,回收已退出的子进程资源
dup2:写入管道的内容重定向到cgi 的 0 读取和1写入,
wwwroot
所有资源保存到web根目录
index.html
w3school:学习写网页,静态网页:不需要交互;
<from action="cgi/test_cgi">
First date:<>
<input type="text" name="a" value="0">
<br>
Last date:<br>
<input type="text" name="b" value="0">
<br><br>
<input type="submit" value="Submit">
</form>
cgi
服务器调用cgi
访问可执行程序A,A得到传过来的参数,运行A,结果给服务器,web服务器再把结果给浏览器
#include<iostream>
#include<stdlib.h>
#include<cstring>
#include<string>
#include<unistd.h>
void CalDate(std::string& qs)
{
//a=100&b=200
std::string part1;
std::string part2;
int x = 0;
int y = 0;
std::size_t pos = qs.find("&");
if(pos != std::string::npos){
part1 = qs.substr(0,pos);
part2 = qs.substr(pos+1);
}
pos = part1.find("=");
if(pos != std::string::npos){
x = std::atoi(part1.substr(pos+1).c_str());
}
pos = part2.find("=");
if(pos != std::string::npos){
y = std::atoi(part2.substr(pos+1).c_str());
}
std::cout <<"<html>" << std::endl;
std::cout << "<h1>"<<x << " + " << y <<" = " << x + y <<"</h1>"<< std::endl;
std::cout << x << " - " << y <<" = " << x - y << std::endl;
std::cout << x << " * " << y <<" = " << x * y << std::endl;
std::cout << x << " / " << y <<" = " << x / y << std::endl;
std::cout << "</html>" << std::endl;
}
//119.29.193.229:8081/cgi/test_cgi/?a=100&b=200
int main()
{
//对于getenv这个成功就返回地址,失败就返回nullptr
std::string method;
std::string query_string;
if(getenv("METHOD")){
method = getenv("METHOD");
}
else{
return 1;
}
if(strcasecmp(method.c_str(),"GET") == 0)
{
//可以通过环境变量直接的获取参数
query_string = getenv("QUERY_STRING");
}
else if(strcasecmp(method.c_str(),"POST") == 0){
//cgi -> 0 读取
//cgi -> 1 写入
int cl = std::atoi(getenv("CONTENT-LENGTH"));
char c = 0;
while(cl){
read(0,&c,1);
query_string.push_back(c);
cl--;
}
}
//处理数据
CalDate(query_string);
return 0;
}
test_cgi:test_cgi.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f test_cgi
路径+s+……:把某些东西交给s程序,S程序做搜索,