Linux网络基础2

目录

  • 实现网络版本计算器
    • 自己定协议实现
    • 用json协议实现
  • 重谈OSI七层模型
  • HTTP协议
    • 域名介绍
    • url介绍
    • HTTP请求和响应
  • 实现一个简易的HTTP服务器
    • 实现简易Http服务器初级版
    • 实现简易Http服务器中级版
  • 实现一个简易的HTTP服务器最终版
    • 请求方法
    • HTTP状态码
    • HTTP常见的Header

实现网络版本计算器

tcp通信时是全双工的,意思就是我发送消息的时候也可以接收消息,这两个动作可以同时进行
如何做到的呢?因为tcp有两个缓冲区,一个是接收缓冲区和发送缓冲区,这两个缓冲区是独立的,资源是独立的,所以不会相互影响
在这里插入图片描述
因为tcp叫传输层控制协议,所以什么时候发?发多少?发送出错了怎么办?全部都由Tcp自己解决。如果对端一直不读取,tcp是面向数据流,对方接收缓冲区就会多个数据粘在一起,如果发送端发现对方接收缓冲区的空间不多了,则发送端可能会将完整的数据分段,只发送一段过去。所以在读端将一流数据读取上来后,如何处理呢?所以就需要有应用层协议的定制

现在我们实现网络版本计算器(+-*/%),我们需要客户端把要计算的表达式发过去,然后由服务器进行计算,最后再把结果返回给客户端

用自己的协议实现

我们定协议(协议就是约定):
约定一:客户端发送一个形如“1 + 1”的字符串,数字和运算符之间有一个空格
约定二:定义结构体来表示我们需要交互的信息。
发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符转回结构体----这个过程叫做“序列化”和“反序列化”
请求协议:
struct resquest
{
int x;
int y;
char op;//运算符
}
应答协议
struct response
{
int result;//结果
int code;//结果是否可靠
}
每一个字段都是双方约定好的

问题:为什么发送的时候不直接发送结构体?为什么要将结构体转为字符串进行发送?因为同一个结构体在不同的编译器下编译结果体大小是可能会不一样的。默认对齐数不同,这就会导致结构体的大小不一样

在qq群里发送信息时,不止有发送的内容,还有头像,昵称,时间,是一个一个发送还是打个包一起发送呢?如果是一个一个发送,那接收端都不知道哪些是来自同一个发送端的。所以实际上是把三个字符串转换成一个字符串,收到后再把一个字符串解析成三个字符串
在这里插入图片描述

网络版本计算器代码逻辑链
在这里插入图片描述

Socket.hpp文件

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include "Log.hpp"Log lg;
class Socket
{
public:Socket(){}~Socket(){}void CreateSocket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){lg(Fatal, "create socket failed:%s", strerror(errno));exit(1);}}void Bind(uint16_t serverPort){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(serverPort);server.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr*)&server, sizeof(server));if (n < 0){lg(Fatal, "bind socket failed:%s", strerror(errno));exit(2);}}void Listen(){int n = listen(_sockfd, 5);if (n < 0){lg(Fatal, "listen socket failed:%s", strerror(errno));exit(3);}}int Accept(string* clientIp, uint16_t* clinetPort){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newsocket = accept(_sockfd, (struct sockaddr*)&peer, &len);if (newsocket < 0){lg(Error, "accept error:%s", strerror(errno));return -1;}*clientIp = inet_ntoa(peer.sin_addr);*clinetPort = ntohs(peer.sin_port);return newsocket;}int Connect(const string& serverIp, uint16_t serverPort){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);int n = connect(_sockfd, (struct sockaddr*)&server, sizeof(server));if (n < 0){lg(Error, "connect error:%s", strerror(errno));return -1;}return 0;}void Close(){close(_sockfd);}int Fd(){return _sockfd;}
private:int _sockfd;
};

Protocol.hpp文件

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>using namespace std;const string blank_space_sep = " ";
const string protocol_sep = "\n";
//添加报头:"内容" 转为 "长度\n内容\n"
bool Encode(string *package, const string& content)
{package->clear();*package += to_string(content.size());*package += protocol_sep;*package += content;*package += protocol_sep;return true;
}
//去除报头:"长度\n内容\n" 转为 "内容"
bool Decode(string &package, string *content)
{content->clear();int pos = package.find(protocol_sep);if (pos == string::npos) return false;int len = stoi(package.substr(0, pos));int totalLen = pos + len + 2;if (package.size() < totalLen) return false;//如果报文不完整,说明*content = package.substr(pos + protocol_sep.size(), len);package.erase(0, totalLen);return true;
}//提取字符串
bool Extract(const string& in, int& x, int& y, char& oper)
{int pos = in.find(blank_space_sep);if (pos == string::npos) return false;x = stoi(in.substr(0, pos));oper = *in.substr(pos + blank_space_sep.size(), 1).c_str();int pos2 = in.rfind(blank_space_sep);if (pos2 == string::npos) return false;y = stoi(in.substr(pos2 + blank_space_sep.size()));return true;
}//请求协议
class Request
{
public:Request(int x = 0, int y = 0, char oper = '+'):_x(x), _y(y), _oper(oper){}//序列化 -- 将结构体转为"_x + _y"bool Serialize(string* out){out->clear();*out += to_string(_x);*out += blank_space_sep;*out += _oper;*out += blank_space_sep;*out += to_string(_y);return true;}//反序列化 -- 将"_x + _y"转为结构体bool Deserialize(const string& in){return Extract(in, _x, _y, _oper);}
public:int _x;int _y;char _oper;
};//响应协议
class Response
{const string blank_space_sep = " ";
public:Response(int result = 0, int code = 0):_result(), _code(code){}//序列化 -- 将结构体转为"_result _code"bool Serialize(string* out){*out = to_string(_result) + blank_space_sep + to_string(_code);return true;}//反序列化 -- 将"_result _code"转为结构体bool Deserialize(const string& in){int pos = in.find(blank_space_sep);if (pos == string::npos) return false;_result = stoi(in.substr(0, pos));_code = stoi(in.substr(pos + blank_space_sep.size()));return true;}
public:int _result;int _code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

ServerCal.hpp文件

#include "Protocol.hpp"//服务器的计算服务
class ServerCal
{
public:ServerCal(){}Response CalculatorHelper(const Request &req){int x = req._x;char oper = req._oper;int y = req._y;Response rsp(0, 0);switch (oper){case '+':{rsp._result = x + y;rsp._code = 0;break;}case '-':{rsp._result = x - y;rsp._code = 0;break;}case '*':{rsp._result = x * y;rsp._code = 0;break;}case '/':{if (y == 0){rsp._result = 0;rsp._code = -1;break;}rsp._result = x / y;rsp._code = 0;break;}case '%':{if (y == 0){rsp._result = 0;rsp._code = -1;break;}rsp._result = x % y;rsp._code = 0;break;}default:break;}return rsp;}string Calculator(string& s){string content;if(!Decode(s, &content))//将"长度/n内容/n" -> "内容"return "";Request rq;rq.Deserialize(content);//将"内容"反序列化Response rsp = CalculatorHelper(rq);//得到答案内容content.clear();rsp.Serialize(&content);//序列化答案内容string ret;Encode(&ret, content);//将"内容" -> "长度/n内容/n"return ret;}
};

TcpServer.hpp文件

#include <iostream>
#include <unistd.h>
#include <functional>
#include <signal.h>
#include "Socket.hpp"using namespace std;#define defaultIp "0.0.0.0"class TcpServer
{using func_t = function<string(string&)>;
public:TcpServer(const uint16_t serverPort, const string& serverIp = defaultIp):_serverIp(serverIp),_serverPort(serverPort){}void Init()//初始化{_listensock.CreateSocket();_listensock.Bind(_serverPort);_listensock.Listen();}void Start(func_t func){signal(SIGCHLD, SIG_IGN);//父进程不再关系子进程是否退出,即父进程不管子进程也不会资源泄漏_func = func;while (1){string clientIp;uint16_t clienPort;int socket = _listensock.Accept(&clientIp, &clienPort);if (fork() == 0){_listensock.Close();string streamBuffer;//Tcp发送完整的报文的时候可能只发送一部分,为了保证获得一个完整的报文,我们就可以这样处理while (1){char readBuffer[4096];int n = read(socket, readBuffer, sizeof(readBuffer) - 1);if (n > 0){readBuffer[n] = 0;streamBuffer += readBuffer;while (1){string ret = _func(streamBuffer);//服务器对客户端的字符串进行处理if (ret.empty())//说明没有要处理的报文break;write(socket, ret.c_str(), ret.size());}}else if (n == 0){lg(Info, "[clientIp:%s, clientPort:%d] closed", clientIp.c_str(), clienPort);break;}else{lg(Warning, "server read failed:%s", strerror(errno));break;}}exit(0);}close(socket);}}
private:Socket _listensock;  //监听套接字string _serverIp;    //服务器Ip地址,一般设置为全0uint16_t _serverPort;//服务器端口号func_t _func;        //回调函数
};

TcpServer.cc文件

#include "TcpServer.hpp"
#include "ServerCal.hpp"//使用手册
static void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << " port\n" << std::endl; 
}// ./TcpServer 8080
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::atoi(argv[1]);TcpServer *tsvp = new TcpServer(port);tsvp->Init();ServerCal cal;tsvp->Start(bind(&ServerCal::Calculator, &cal, std::placeholders::_1));//C++的bind函数return 0;
}

TcpClient.cc文件

#include "Protocol.hpp"
#include "Socket.hpp"static void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << "\tserverIp\tport\n" << std::endl; 
}
int main(int argc, char* argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);Socket skt;skt.CreateSocket();skt.Connect(serverIp, serverPort);string streamBuffer;while (1){cout << "Enter#";fflush(stdout);string content;getline(cin, content);//提取字符串,得到”请求反序列化“Request rq;Extract(content, rq._x, rq._y, rq._oper);string s;rq.Serialize(&s);string ret;Encode(&ret, s);write(skt.Fd(), ret.c_str(), ret.size());char readBuffer[4096];int n = read(skt.Fd(), readBuffer, sizeof(readBuffer) - 1);if (n > 0){readBuffer[n] = 0;streamBuffer += readBuffer;string ret;Decode(streamBuffer, &ret);Response rsp;rsp.Deserialize(ret);cout << "code: " << rsp._code << "    result: " << rsp._result << endl;}else if (n == 0){cerr << "Server closed..." << endl;break;}else {cerr << "read failed:" << strerror(errno) << endl;exit(3);}}return 0;
}

运行结果
请添加图片描述

用json实现

我们也可以看到,如果自己定协议的话,还要序列化和反序列化,写起来太麻烦,这是不合理的,所以我们用别人已经写好的协议,如json(使用起来很简单),protobuf(纯二进制流,使用起来比较麻烦).

我们使用json则要安装这个库
yum install -y jsoncpp-devel
在这里插入图片描述
有这些.h和lib软链接文件说明已经安装好了

json的基本使用

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>using namespace std;int main()
{Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["dect"] = "this is a + oper";Json::FastWriter w;string s = w.write(root);//将序列化root 反序列化cout << s << endl;
}

记得使用第三方库的时候编译时要加第三方库名
在这里插入图片描述
运行结果:
在这里插入图片描述

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>using namespace std;int main()
{Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["dect"] = "this is a + oper";Json::StyledWriter w;string s = w.write(root);//将root 反序列化cout << s << endl;
}

运行结果:
在这里插入图片描述
StyleWriter可读性更好,FasterWriter序列化的更快

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>using namespace std;int main()
{Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["dect"] = "this is a + oper";Json::StyledWriter w;string s = w.write(root);//将root反序列化cout << s << endl;Json::Value v;Json::Reader r;r.parse(s, v);//将s反序列化为v//将序列化的内容提取出来int x = v["x"].asInt();int y = v["y"].asInt();char op = v["op"].asInt();string dect = v["dect"].asString();cout << x << endl;cout << y << endl;cout << op << endl;cout << dect << endl;
}

运行结果
在这里插入图片描述

用json实现网络版本计算器

更新Protocol.hpp文件

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>using namespace std;const string blank_space_sep = " ";
const string protocol_sep = "\n";
//添加报头:"内容" 转为 "长度\n内容\n"
bool Encode(string *package, const string& content)
{package->clear();*package += to_string(content.size());*package += protocol_sep;*package += content;*package += protocol_sep;return true;
}
//去除报头:"长度\n内容\n" 转为 "内容"
bool Decode(const string &package, string *content)
{content->clear();int pos = package.find(protocol_sep);if (pos == string::npos) return false;int len = stoi(package.substr(0, pos));*content = package.substr(pos + protocol_sep.size(), len);return true;
}//提取字符串
bool Extract(const string& in, int& x, int& y, char& oper)
{int pos = in.find(blank_space_sep);if (pos == string::npos) return false;x = stoi(in.substr(0, pos));oper = *in.substr(pos + blank_space_sep.size(), 1).c_str();int pos2 = in.rfind(blank_space_sep);if (pos2 == string::npos) return false;y = stoi(in.substr(pos2 + blank_space_sep.size()));return true;
}//请求协议
class Request
{
public:Request(int x = 0, int y = 0, char oper = '+'):_x(x), _y(y), _oper(oper){}//序列化 -- 将结构体转为"_x + _y"bool Serialize(string* out){
#ifdef MySelfout->clear();*out += to_string(_x);*out += blank_space_sep;*out += _oper;*out += blank_space_sep;*out += to_string(_y);return true;
#elseJson::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::StyledWriter w;*out = w.write(root);return true;
#endif}//反序列化 -- 将"_x + _y"转为结构体bool Deserialize(const string& in){
#ifdef MySelfreturn Extract(in, _x, _y, _oper);
#elseJson::Reader r;Json::Value root;r.parse(in, root);_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;
#endif}
public:int _x;int _y;char _oper;
};//响应协议
class Response
{const string blank_space_sep = " ";
public:Response(int result = 0, int code = 0):_result(), _code(code){}//序列化 -- 将结构体转为"_result _code"bool Serialize(string* out){
#ifdef MySelf*out = to_string(_result) + blank_space_sep + to_string(_code);return true;
#else Json::Value root;root["code"] = _code;root["result"] = _result;Json::StyledWriter w;*out = w.write(root);return true;
#endif}//反序列化 -- 将"_result _code"转为结构体bool Deserialize(const string& in){
#ifdef MySelfint pos = in.find(blank_space_sep);if (pos == string::npos) return false;_result = stoi(in.substr(0, pos));_code = stoi(in.substr(pos + blank_space_sep.size()));return true;
#elseJson::Reader r;Json::Value root;r.parse(in, root);_result = root["result"].asInt();_code = root["code"].asInt();return true;
#endif}
public:int _result;int _code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

gcc/g++编译器的条件编译
-D[宏定义]
不用json
在这里插入图片描述
用json
在这里插入图片描述
有了条件编译就能使我们在两种版本中轻易的切换

重谈OSI七层模型

下面给出定义
应用层功能:正对特定应用协议
表示层功能:设备固有数据格式和网络标准数据格式的转换
会话层功能:通信管理。负责建立和断开通信连接(数据流动的逻辑通路)。管理传输层以下的
分层

会话层主要负责连接管理,获取客户端连接,创建socketfd,创建子进程,最后关闭socketfd,对socketfd管理的生命周期
在代码中的体现:
在这里插入图片描述
表示层主要负责将固有的格式字符串转化为文字、图片、视频。我们定的协议"len\ncontent\n",其实还可以定成这样type\nlen\n\content\ntype可表示后面是什么内容(文字、图片、视频),这样就会有不同的解析方式来解析这个字符串
代码中的体现:
在这里插入图片描述
应用层主要负责处理数据,对解析后的请求数据进行处理的逻辑
代码中的体现:
在这里插入图片描述

这样就知道了,为什么教材里面说为5 层,OSI为7层,因为这3层被压为了一层,不同的场景有不同的应用协议,如我们只发送文字信息和只发送图片信息,协议报头必然是不同的,所以我们用户编程的需求是不同的,不能够把应用协议统一起来,所以不能放在操作系统里面。

HTTP协议

域名介绍

我们平时在浏览器的url输入框中可以搜索www.baidu.com,会直接跳到百度的首页,www.baidu.com这被称为域名,访问服务器的时候,在技术上我们只需要知道IP地址和端口号就可以了,但在日常生活中,我们使用的是域名,而非IP地址,为什么呢?因为域名容易被用户记住,用IP地址会导致用户的体验变差,浏览器里面内置了这个功能,使域名被解析成IP地址

用ping指令,ping一下百度的域名
在这里插入图片描述
可以看到域名被解析成了110.242.68.3,我们也可以用这个IP来访问百度
在这里插入图片描述
为什么我们访问的时候不用指定端口号呢?我们写IP地址的时候会默认拼接上http/https,而http/https应用层协议默认绑定了知名端口号,http绑定的是80,https绑定的是443

url介绍

当我们访问网站资源的时候,可以看到https://www.helloworld.net/p/4790426995,这被叫做url(Uniform Resource Locator统一资源定位符)所有网络上的资源,都可以用唯一的一个“字符串”标识,比如你同学要你分享一个网站,你可以直接给个链接过去,为什么呢?因为这个链接在全网中具有唯一性

下面是完整的url介绍
在这里插入图片描述
网络的行为可以分为两种
1.把别人的的东西拿下来。把别人曾经写好的网页拿到自己的浏览器上
2.把自己的东西传上去。可能在url中传递自己的信息,如上面的?uid=1。?后面带key/value结构的参数

在百度搜索引擎上搜索
在这里插入图片描述
在这里插入图片描述
这个url本质就是访问百度服务器,url上面有我们提供给百度服务器的参数。&作为分隔符,支持多参数提交
在url中,我们也能看到有很多特殊符号,我们如果也输入特殊符号,会不会有问题呢?
在这里插入图片描述
在这里插入图片描述
在少量的情况下,提交或者获取数据本身可能包含和url中与特殊的字符冲突的字符,则要求BS双方进行编码(encode)和解码(decode)
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
例如:
在这里插入图片描述

HTTP请求和响应

http请求和响应的格式:以行的形式来陈列,由多行构成

http request
在这里插入图片描述
报头=请求行+请求报头
第一部分(第一行)叫做请求行,
请求方法Method,95%都用这两种方法GET/POST,空格作为分隔符:一般为一个空格
URL:代表你要请求的资源是谁,url一般不包括域名,一般只有/web根目录及后面的内容
HTTP Version,有1.0 1.1 2.0,最常见的是1.1
以\r\n为结尾,也可以只用\n结尾
第二部分叫做请求报头,
每一行都是请求属性,格式为key:value
每一行以\r\n结尾
你如何将报头和有效载荷分离呢?你怎么保证哪里是报头(请求行+请求报头)和正文呢?
这里和我们的之前网络版本计算器一样,我们之前用的是\n就能把报头和有效载荷(正文)分开,有一个空行\r\n。我们一直按行读取,如果我们读到了空行,则说明前半部分是报头,后面是有效载荷
最后一部分叫做请求正文,http的请求可以带参数,也可以不带参数。
如何看待请求报文呢?我们看待这个请求报文的时候可以直接看成一个大字符串

通过循环读\r\n,你读完了报头,你怎么保证将正文完整的读取上来呢?报头里面有一个属性,叫Content-Length。这个属性代表正文的长度为多少,根据这个属性就能完整的读取上来了

http response
在这里插入图片描述

在网页发送的就是request,响应回来的就是response
介绍一个工具telnet,用这个工具构建一个最简单的请求报文(必须要有请求行和空行,其它的可以没有)
在这里插入图片描述
为什么都要有HTTP Version呢?浏览器用http协议的哪个版本,服务器用哪个版本,双方需要交互一下,就好像微信的客户端,有的是v1.0,有的是v2.0,微信升级的时候,不可能直接将所有人全部升级,万一有bug呢?会造成很大的损失,一般都是先升级一部分,没bug过一段时间再升级一部分那损失注定每个人用的会有不同的版本,所以客户端和服务器需要知道对方的版本,这样才能知道该给你发哪个版本对应的功能

请求一个不存在的资源
在这里插入图片描述
我们平常访问资源的时候也经常看到404,就如我们写的网络版本计算器的结果code表示结果是否可行。200就表示可信,404表示你向服务器请求的资源不存在
我们平头老百姓不知道这个状态码是什么意思,就有了状态码描述,字符串版本的描述,会在浏览器里面显示

下面我用两个工具来抓包看一下,报文是不是和我们所说的是一样的
Fidder抓包,启动后会自动帮我们在本地抓包,点Raw就会显示响应报文和请求报文
在这里插入图片描述
Fidder的原理:浏览器并不是直接和服务器进行交互,中间有第三方:Fidder。发送和接收都会经过Fidder。这个过程也叫做代理
在这里插入图片描述

用postman抓包

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
postman原理本质是模拟浏览器的行为

下面我们来自己写一个HTTP服务器,则会对其理解的更加深刻

实现一个简易的HTTP服务器

Socket.hpp文件和Log.hpp文件在往期文章实现过,如果没写也不要紧,Log.hpp就用printf代替,Socket.hpp就是对原函数的封装
Socket.hpp文件

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include "Log.hpp"Log lg;
class Socket
{
public:Socket(){}~Socket(){}void CreateSocket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){lg(Fatal, "create socket failed:%s", strerror(errno));exit(1);}}void Bind(uint16_t serverPort){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(serverPort);server.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr*)&server, sizeof(server));if (n < 0){lg(Fatal, "bind socket failed:%s", strerror(errno));exit(2);}}void Listen(){int n = listen(_sockfd, 5);if (n < 0){lg(Fatal, "listen socket failed:%s", strerror(errno));exit(3);}}int Accept(string* clientIp, uint16_t* clinetPort){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newsocket = accept(_sockfd, (struct sockaddr*)&peer, &len);if (newsocket < 0){lg(Error, "accept error:%s", strerror(errno));return -1;}*clientIp = inet_ntoa(peer.sin_addr);*clinetPort = ntohs(peer.sin_port);return newsocket;}int Connect(const string& serverIp, uint16_t serverPort){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);int n = connect(_sockfd, (struct sockaddr*)&server, sizeof(server));if (n < 0){lg(Error, "connect error:%s", strerror(errno));return -1;}return 0;}void Close(){close(_sockfd);}int Fd(){return _sockfd;}
private:int _sockfd;
};

Log.hpp文件

#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>using namespace std;#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define fileName "log.txt"
//使用前需要创建log目录
class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}void printOneFile(string logname, const string& logtxt){logname = path + logname;int fd = open(logname.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);//open只会创建文件不会创建目录if (fd < 0){perror("open failed");return;}write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const string& logtxt){string filename = fileName;filename += ".";filename += leveltoString(level);printOneFile(filename, logtxt);}void printLog(int level, const string& logtxt){if (printMethod == Screen){cout << logtxt << endl;return;}else if (printMethod == Onefile){printOneFile(fileName, logtxt);return;}else if (printMethod == Classfile){printClassFile(level, logtxt);return;}}const char* leveltoString(int level){if (level == Info) return "Info";else if (level == Debug) return "Debug";else if (level == Error) return "Error";else if (level == Fatal) return "Fatal";else return "default";}void operator()(int level, const char* format, ...){time_t t = time(nullptr);struct tm* st = localtime(&t);char leftbuffer[4096];snprintf(leftbuffer, sizeof(leftbuffer), "year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d\n\[%s]:",st->tm_year + 1900, st->tm_mon + 1, st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec, leveltoString(level));char rightbuffer[4096];va_list start;va_start(start, format);vsnprintf(rightbuffer, sizeof(rightbuffer), format, start);va_end(start);char logtxt[4096 * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);printLog(level, logtxt);}
private:int printMethod;string path;//路径与文件名解耦,最后将路径和文件粘合起来,再用open打开即可
};

HttpServer.hpp文件

#include <pthread.h>
#include <cstring>
#include "Socket.hpp"
#include "Log.hpp"class HttpServer;
struct ThreadData
{ThreadData(int sockfd, HttpServer* hs = nullptr):_ts(hs),_sockfd(sockfd){}int _sockfd;HttpServer* _ts;
};class HttpServer
{
public:HttpServer(){}static void* ThreadRun(void* args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);char readBuffer[4096];int n = recv(td->_sockfd, readBuffer, sizeof(readBuffer) - 1, 0);if (n > 0){readBuffer[n] = 0;cout << readBuffer << endl;}else if (n == 0) {cout << "client closed..." << endl;}else{cout << "read Error:" << strerror(errno) << endl;}close(td->_sockfd);delete td;}bool start(uint16_t port){_listensock.CreateSocket();_listensock.Bind(port);lg(Info, "Bind socket success\n");_listensock.Listen();lg(Info, "listen socket success\n");while (1){string clientIp;uint16_t clientPort;int sockfd = _listensock.Accept(&clientIp, &clientPort);pthread_t tid;ThreadData* td = new ThreadData(sockfd, this);pthread_create(&tid, nullptr, ThreadRun, td);}}
private:Socket _listensock;
};

HttpServer.cc文件

#include <memory>
#include "HttpServer.hpp"void Usage(char* HttpServer)
{cout << "\n\t" <<  HttpServer << "\tServerPort\n";
}
int main(int argc, char* argv[])
{if (argc != 2){Usage(argv[0]);exit(1);}unique_ptr<HttpServer> up(new HttpServer);uint16_t serverPort = atoi(argv[1]);up->start(serverPort);return 0;
}

用浏览器进行访问
请添加图片描述
可以看到收到了来自浏览器客户端的请求报文
User-Agent:为客户端的某些信息,如自己是哪个操作系统,是用哪个浏览器来访问的
这也就是为什么用电脑去浏览器搜索和手机去浏览器搜索的结果是不一样的
在这里插入图片描述
在这里插入图片描述

让浏览器能收到响应

实现简易Http服务器初级版

更新HttpServer.hpp文件

class HttpServer
{const string sep = "\r\n";
public:HttpServer(){}static void* ThreadRun(void* args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->_ts->HandlerHttp(td->_sockfd);delete td;}void HandlerHttp(int sockfd){char readBuffer[4096];int n = recv(sockfd, readBuffer, sizeof(readBuffer) - 1, 0);if (n > 0){readBuffer[n] = 0;cout << readBuffer << endl;//构建响应的过程string response;string responseLine = "HTTP/1.1 200 ok";string responseHeader = "Content-Length: ";//响应报头设置的时候格式一定要正确   "Key:Value: 长度\r\n"    有空格!string blankLine = "\r\n";string text = "hello world";responseHeader += to_string(text.size());response += responseLine;response += sep;response += responseHeader + sep;response += blankLine;response += text;write(sockfd, response.c_str(), response.size());}else if (n == 0) {cout << "client closed..." << endl;}else{cout << "read Error:" << strerror(errno) << endl;}close(sockfd);}bool start(uint16_t port){_listensock.CreateSocket();_listensock.Bind(port);lg(Info, "Bind socket success\n");_listensock.Listen();lg(Info, "listen socket success\n");while (1){string clientIp;uint16_t clientPort;int sockfd = _listensock.Accept(&clientIp, &clientPort);pthread_t tid;ThreadData* td = new ThreadData(sockfd, this);pthread_create(&tid, nullptr, ThreadRun, td);}}
private:Socket _listensock;
};

用postman进行访问
在这里插入图片描述
可以看到只有我们曾经设置过的Content-Length

用浏览器进行访问
在这里插入图片描述
当浏览器收到响应报文的时候,会自动对该报文进行解析。

可以在url中带路径/参数,请求服务器服务器资源
在这里插入图片描述
在这里插入图片描述
可以看到url中的请求信息保存到了请求行中,我们可以对请求行进行分析并处理,返回给客户端相应的资源

实现简易Http服务器中级版

为了能够分析客户端request传来的信息,我们对该报文进行反序列化,并对这些信息进行相应处理

新增网页内容
在这里插入图片描述

wwwroot/a/b/hello.html文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>第一张网页</h1><a href="http://192.168.214.128:8080/">回到首页</a>
</body>
</html>

wwwroot/x/y/world.html文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>第二张网页</h1><a href="http://192.168.214.128:8080/">回到首页</a>
</body>
</html>

wwwroot/index.html文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>这是首页</h1><a href="http://192.168.214.128:8080/a/b/hello.html">点我跳转下一个网页hello</a><a href="http://192.168.214.128:8080/x/y/world.html">点我跳转下一个网页world</a>
</body>
</html>

更新HttpServer.hpp文件

#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <pthread.h>
#include <cstring>
#include "Socket.hpp"
#include "Log.hpp"//web根目录
const string wwwroot = "./wwwroot";
const string sep = "\r\n";
const string homepage = "index.html";class HttpServer;
struct ThreadData
{ThreadData(int sockfd, HttpServer* hs = nullptr):_ts(hs),_sockfd(sockfd){}int _sockfd;HttpServer* _ts;
};class HttpRequest
{
public:void Deserialize(string req){while (1){int pos = req.find(sep);if (pos == string::npos) break;string tmp = req.substr(0, pos);if (tmp.empty()) break;reqHeader.push_back(tmp);req.erase(0, pos + sep.size());}text = req;}void Parse(){//我们用stringstream 对字符串进行流式分割,长字符串,默认就是以空格作为分隔符stringstream ss(reqHeader[0]);ss >> method >> url >> http_version;filePath += wwwroot;//wwwrootif (url == "/" || url == "/index.html"){cout << "i love you" << endl;filePath += "/";filePath += homepage;}else{filePath += url;}}vector<string> reqHeader;string text;string method;string url;string http_version;string filePath;
};class HttpServer
{
public:HttpServer(){}static void* ThreadRun(void* args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->_ts->HandlerHttp(td->_sockfd);delete td;}static string ReadHtmlContent(const string& htmlpath){ifstream in(htmlpath, ios::in | ios::binary);if (!in.is_open()){return "404";}string ret;string s;while (getline(in, s)){ret += s;}in.close();return ret;}void HandlerHttp(int sockfd){char readBuffer[4096];int n = recv(sockfd, readBuffer, sizeof(readBuffer) - 1, 0);if (n > 0){readBuffer[n] = 0;cout << readBuffer << endl;HttpRequest htq;htq.Deserialize(readBuffer);htq.Parse();//构建响应的过程string response;string responseLine = "HTTP/1.1 200 ok";string responseHeader = "Content-Length: ";//响应报头设置的时候格式一定要正确   "Key:Value: 长度\r\n"    有空格!string blankLine = "\r\n";//将网页内容给读取下来,构建在response里面,然后发回给客户端cout << htq.filePath << endl;string text = ReadHtmlContent(htq.filePath);responseHeader += to_string(text.size());response += responseLine;response += sep;response += responseHeader + sep;response += blankLine;response += text;write(sockfd, response.c_str(), response.size());}else if (n == 0) {cout << "client closed..." << endl;}else{cout << "read Error:" << strerror(errno) << endl;}close(sockfd);}bool start(uint16_t port){_listensock.CreateSocket();_listensock.Bind(port);lg(Info, "Bind socket success\n");_listensock.Listen();lg(Info, "listen socket success\n");while (1){string clientIp;uint16_t clientPort;int sockfd = _listensock.Accept(&clientIp, &clientPort);pthread_t tid;ThreadData* td = new ThreadData(sockfd, this);pthread_create(&tid, nullptr, ThreadRun, td);}}
private:Socket _listensock;
};

运行结果
请添加图片描述

实现一个简易的HTTP服务器最终版

请求方法

请求方法说明支持的HTTP协议版本
GET获取资源1.0 /1.1
POST传输实体主体1.0/1.1
PUT传输文件1.0/1.1
HEAD获得报文首部1.0/1.1
DELETE删除文件1.0/1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINK断开连接关系1.0

GET方法:获取远端资源。
POST方法:也可以获取远端资源,但更多的是要上传自己的东西
我们只讲POST和GET方法,剩下的不讲,了解即可,因为95%的情况下都是用的这两种请求方法,
HEAD方法:你不用把正文给我,只用把报头给我。DELETE方法:url表示你要删除服务器上的哪个文件,很显然在大部分情况下,是不允许客户端删除服务器的内容的,所以一般服务器屏蔽了这个方法。OPTIONS方法:询问服务器支持那些方法。

日常生活中,我们是使用网站(http/https),是如何把我们的数据提交给服务器的呢?
数据都是通过表单来提交的,如在百度网页中我们通过的搜索框进行提交数据
在这里插入图片描述

更新index.html文件,用get方法

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>这是首页</h1><!-- action:把这些表单里的数据提交给服务器里的哪个程序 带路径即可--><!-- method:你想以什么方式提交这个表单 --><form action="/a/b/hello.html" method="get"><!-- type代表输入框的类型 name代表将来提交的时候,url后面的key=value  key的名字 value:缺省值-->name: <input type="text" name="name" value=""><br>password: <input type="password" name="passwd"><br><!-- submit为按钮类型 --><input type="submit" value="提交"></form>
</body>
</html>

在这里插入图片描述
美丑的问题不是我们后端工程师要考虑的,是前端工程师要考虑的事情
在这里插入图片描述
action的意义你要将参数提交给哪个可执行程序

你输入信息,提交后,浏览器把我们的参数拼接到了url的后面
在这里插入图片描述
请求报文
在这里插入图片描述
作为服务器来讲,通过?分隔符,把左右分开,如果左边的路径访是一个可执行程序,代码逻辑就可以fork程序替换执行这个可执行程序,通过管道/进程减通信的技术把我们的参数交给这个程序

结论:用GET方法URL进行提参,参数数量受限制,不私密

方法改为POST方法
更新index.html文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>这是首页</h1><!-- action:把这些表单里的数据提交给服务器里的哪个程序 带路径即可--><!-- method:你想以什么方式提交这个表单 --><form action="/a/b/hello.html" method="POST"><!-- type代表输入框的类型 name代表将来提交的时候,url后面的key=value  key的名字 value:缺省值-->name: <input type="text" name="name" value=""><br>password: <input type="password" name="passwd"><br><!-- submit为按钮类型 --><input type="submit" value="提交"></form>
</body>
</html>

在这里插入图片描述
用fidder进行抓包
在这里插入图片描述
POST方法也支持参数提交,采用请求的正文提交参数

GET和POST进行参数提交的时候就只有这一个区别。平时我们上网时大部分用GET方法,因为大部分你是请求网页,下载音频,很少提交参数。
在这里插入图片描述
可以看到百度搜索时,参数在url上面,所以我们可以断定它表单传参用的是GET方法
前端知识:如果表单没有写method方法,则默认是get方法,可以看到它提交给了路径为/s的程序

提交参数的时候可以用get和post,问题是我们什么时候用get什么时候用post呢?
get通过url进行提参,也就意味着它的参数的数量是受限制的。get会把参数会先到url框,有人在我电脑后面看,就能知道我的秘密了,而post不会,有的人就会说了get是不安全的,无论是get还是post都不安全,上面测试post方法的时候,用fidder抓包很轻松就看到了参数。安不安全是是否加密来解决的
GET方法提参有两个特点:1.参数长度受限2.不私密

HTTP状态码

状态码类别原因短语
1XXInformational(信息性状态码)接受的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirecion(重定向状态码)需要进行附加操作已完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

1XX很少见,你发起了一个请求,服务器处理的时间可能会很长,http服务器会及时给你的浏览器进行发送响应,告诉你,你的请求我已经收到了,你别急,我处理完了再发给你
2XX,比如200才是http通信最常用的,表示已成功处理
4XX,叫做客户端错误,最典型的就是404,通常是我们要找的资源不存在,403一般是,你没有访问权限或者你被禁止访问了
5XX,服务器错误,什么情况下算服务器错误呢?比如你请求服务器,服务器创建一个线程,但创建失败了,这就是服务器错误

3XX的有很多,http状态码,规定的不严格,主要还是浏览器对状态码的审核不严格,为什么会这样呢?
因为浏览器的历史原因。我们在写代码的时候也发现了,有的时候http响应是残缺的,浏览器也能显示出页面,比如http服务器初级版,响应的就是一个字符串不是网页也能显示。现在的浏览器很强大了,有网络功能,有内存管理,能够h5、css代码解析,设计浏览器内核的成本不必操作系统的成本低。一个软件变强大的重点是1.本身写的很好2.有很强的容错性。浏览器有360浏览器、猎豹浏览器、edge浏览器、火狐浏览器,为什么客户端这么多呢?十几年前,我国最大的互联网公司,百度,国外的互联网谷歌,为什么呢?因为他们的流量大,当时中国网名80%都要用百度,因为基本上一个人上网,大概率会访问浏览器,谁把浏览器拿到了,谁就拿到了整个互联网的流量入口,因为流量大,所以公司赚钱。所以有公司愿意花大价钱做浏览器,谁做得好,谁就能挣钱。你做我也做,所以在浏览器的定制标准上就很难达成一致。这也就是为什么前端开发的人,写了一个页面,他需要在各种各样常见的浏览器上面都要做测试,因为代码可能这个支持了另一个不支持。总结一下,浏览器的标准是有的,但是大家遵守的不是那么好

301为永久重定向,302为临时重定向
什么是重定向呢?
在这里插入图片描述
让服务器知道浏览器,让浏览器访问新的地址–这就叫做重定向

写代码,看一下重定向的现象
更新HttpServer.hpp的HandlerHttp函数

void HandlerHttp(int sockfd){char readBuffer[4096];int n = recv(sockfd, readBuffer, sizeof(readBuffer) - 1, 0);if (n > 0){readBuffer[n] = 0;cout << readBuffer << endl;HttpRequest htq;htq.Deserialize(readBuffer);htq.Parse();bool ok = true;//将网页内容给读取下来,构建在response里面,然后发回给客户端string text = ReadHtmlContent(htq.filePath);cout << text << endl;//构建响应的过程string response;string responseLine;responseLine = "HTTP/1.0 302 Redirect";string blankLine = "\r\n";string responseHeader = "Content-Length: ";responseHeader += to_string(text.size());responseHeader += sep;responseHeader += "Location: ";responseHeader += "https://www.qq.com/";responseHeader += sep;response += responseLine;response += sep;response += responseHeader;response += blankLine;response += text;write(sockfd, response.c_str(), response.size());}else if (n == 0) {cout << "client closed..." << endl;}else{cout << "read Error:" << strerror(errno) << endl;}close(sockfd);}

运行结果
请添加图片描述

什么情况下适用于永久重定向呢?上面展示的那种情况就很合适,老用户访问的还是我的老服务器(老域名),但是我的服务器(新域名)已经更新了,怕别人找不到我这个新服务,就会在老服务器上部署永久重定向
什么情况下适用于临时重定向呢?比如我们在某一网站登录的时候,登录成功了浏览器会自动跳转到首页,这就是临时重定向

HTTP常见的Header

常见的Header描述
Content-Type数据类型
Content-LengthBody的长度
Host客户端告知服务器,所请求的资源是在哪个主机的端口上
User-Agent:声明用户的操作系统和浏览器版本信息
referer当前页面是从哪个页面跳转过来的
location搭配3xx状态码使用,告诉客户端接下来要去哪里访问
Cookie用于在客户端存储少量信息.通常用于实现会话的功能

一个页面是包含很多元素的,而一个元素就是一个资源
在这里插入图片描述
早期的网络资源比较小,采用的就是短连接的方式,一个连接就只请求一个资源,如果一个页面有100张图片,则我们请求时就要发起101次请求,第一次请求网页本身,网页里的100张图片都要请求一次,全部请求到后,浏览器会将TM组合并渲染,就成为了我们看到的样子
请求100次就要建立100次连接–这就叫做短连接
无疑,这种方案是低效的。就有了长连接
在这里插入图片描述
这样就能建立一次连接,传递多个请求
HTTP/1.0就只支持短连接,HTTP/1.1支持短连接长连接
这也就是为什么报头里面最开始就要写上对方的http版本。双方若都支持长连接
请求报头加上Connection: keep-alive
如果请求和响应时都携带这个选项,就说明协商完成,都采用长连接的方式进行通信,否则我们就用短连接
因为长连接很复杂,我们服务器就不实现了
在这里插入图片描述

Content-Type:数据类型。响应一个资源给浏览器的时候,能告诉浏览器,这个资源是什么格式,什么类型的,这样浏览器才好给你显示对应的资源
前面代码为什么没有带Content-Type也能正确显示呢?因为浏览器性能比较强了,加上前面的特征点还是比较明显的,浏览器是能识别出你是个网页。但如果给性能低的浏览器就不一定能解释了。
对应数据类型的格式可以搜索content-type对照表
在这里插入图片描述
ctri + F可以在该网页进行搜索
在这里插入图片描述

添加图片到网页里
注意:图片一般是二进制文件,要用二进制的方式读取

在wwwroot目录下添加图片
在这里插入图片描述
添加404页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>你要访问的资源不存在!!!</h1>
</body>
</html>

更新index.html文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>这是首页</h1><!-- action:把这些表单里的数据提交给服务器里的哪个程序 带路径即可--><!-- method:你想以什么方式提交这个表单 --><form action="/a/b/hello.html" method="POST"><!-- type代表输入框的类型 name代表将来提交的时候,url后面的key=value  key的名字 value:缺省值-->name: <input type="text" name="name" value=""><br>password: <input type="password" name="passwd"><br><!-- submit为按钮类型 --><input type="submit" value="提交"></form><a href="http://60.205.245.92:8082/a/b/hello.html">点我跳转下一个网页hello</a><a href="http://60.205.245.92:8082/x/y/world.html">点我跳转下一个网页world</a><br><!-- 根据src向我们的服务器,浏览器会自动发起二次请求 --><img src="./image/2.jfif" alt="小猪"><br></body>
</html>

更新HttpServer.hpp文件的class HttpServer类

class HttpServer
{
public:HttpServer(){contentType[".jfif"] = "image/jpeg";contentType[".html"] = "text/html";contentType[".png"] = "image/png";contentType[".jpg"] = "image/jpeg";}static void* ThreadRun(void* args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->_ts->HandlerHttp(td->_sockfd);delete td;}static string ReadHtmlContent(const string& htmlpath){ifstream in(htmlpath, ios::in | ios::binary);if (!in.is_open()){return "404";}//获取文件大小struct stat filestat;stat(htmlpath.c_str(), &filestat);size_t fileSize = filestat.st_size;//读取一个文件的大小string ret;ret.resize(fileSize);in.read((char*)ret.c_str(), fileSize);return ret;}string SuffixToDesc(const string& suffix){auto iter = contentType.find(suffix);if (iter == contentType.end())return contentType[".html"];else return contentType[suffix];}void HandlerHttp(int sockfd){char readBuffer[4096];int n = recv(sockfd, readBuffer, sizeof(readBuffer) - 1, 0);if (n > 0){readBuffer[n] = 0;cout << readBuffer << endl;HttpRequest htq;htq.Deserialize(readBuffer);htq.Parse();bool ok = true;//将网页内容给读取下来,构建在response里面,然后发回给客户端string text = ReadHtmlContent(htq.filePath);cout << text << endl;if (text == "404"){ok = false;string errHtml = wwwroot;errHtml += "/";errHtml += "err.html";text = ReadHtmlContent(errHtml);}//构建响应的过程string response;string responseLine;if (ok){responseLine = "HTTP/1.0 200 OK";}else {responseLine = "HTTP/1.0 404 Not Found";}string blankLine = "\r\n";string responseHeader = "Content-Length: ";responseHeader += to_string(text.size());responseHeader += sep;responseHeader += "Content-Type: ";string s = SuffixToDesc(htq.suffix);responseHeader += s;responseHeader += sep;response += responseLine;response += sep;response += responseHeader;response += blankLine;response += text;write(sockfd, response.c_str(), response.size());}else if (n == 0) {cout << "client closed..." << endl;}else{cout << "read Error:" << strerror(errno) << endl;}close(sockfd);}bool start(uint16_t port){_listensock.CreateSocket();_listensock.Bind(port);lg(Info, "Bind socket success\n");_listensock.Listen();lg(Info, "listen socket success\n");while (1){string clientIp;uint16_t clientPort;int sockfd = _listensock.Accept(&clientIp, &clientPort);pthread_t tid;ThreadData* td = new ThreadData(sockfd, this);pthread_create(&tid, nullptr, ThreadRun, td);}}
private:Socket _listensock;unordered_map<string, string> contentType;
};

运行结果
请添加图片描述
请求一个不存在的资源
请添加图片描述

cookie:能实现http对登录用户的会话保持功能
什么是会话保持?当你第一次访问b站的时候,需要登录才能看视频,你登录后,关闭了浏览器,再进入浏览器再访问b站,发现你不需要再次登录了。
这是怎么做到的呢?
在这里插入图片描述
保存cookie文件通常由两种保存方法
1.内存级:浏览器也是进程,可以new/malloc,直接保存信息,丹尼关闭浏览器后,需要重新登录
2.文件级:即便把浏览器关掉了,关机了,仍然再次访问,不用再次登录

如何证明浏览器会保存信息到cookie文件?
在这里插入图片描述
在这里插入图片描述
可以看到有到期时间,这就是为什么过一段时间后,需要我们重新登录
我现在删除所有cookie
在这里插入图片描述
在这里插入图片描述
再进入就需要重新登录了
重新登录后,登录信息就会自动被浏览器保存到cookie中
在这里插入图片描述
我们自己写一个cookie,想看到我服务器给你一个set-cookie信息,浏览器会将该信息保存到cookie里面,浏览器每发起一次请求,会自动携带cookie给我(服务器)

更新HttpServer.hpp文件的HandlerHttp函数

void HandlerHttp(int sockfd){char readBuffer[4096];int n = recv(sockfd, readBuffer, sizeof(readBuffer) - 1, 0);if (n > 0){readBuffer[n] = 0;cout << readBuffer << endl;HttpRequest htq;htq.Deserialize(readBuffer);htq.Parse();bool ok = true;//将网页内容给读取下来,构建在response里面,然后发回给客户端string text = ReadHtmlContent(htq.filePath);if (text == "404"){ok = false;string errHtml = wwwroot;errHtml += "/";errHtml += "err.html";text = ReadHtmlContent(errHtml);}//构建响应的过程string response;string responseLine;if (ok){responseLine = "HTTP/1.0 200 OK";}else {responseLine = "HTTP/1.0 404 Not Found";}string blankLine = "\r\n";string responseHeader = "Content-Length: ";responseHeader += to_string(text.size());responseHeader += sep;responseHeader += "Content-Type: ";string s = SuffixToDesc(htq.suffix);responseHeader += s;responseHeader += sep;responseHeader += "Set-Cookie: name=wf1234";responseHeader += sep;responseHeader += "Set-Cookie: passwd=abc123";responseHeader += sep;responseHeader += "Set-Cookie: view=hello.html";responseHeader += sep;response += responseLine;response += sep;response += responseHeader;response += blankLine;response += text;write(sockfd, response.c_str(), response.size());}else if (n == 0) {cout << "client closed..." << endl;}else{cout << "read Error:" << strerror(errno) << endl;}close(sockfd);}

在这里插入图片描述
在这里插入图片描述

cookie会产生两个问题
1.cookie被盗取
2.个人信息被盗取

假如你访问了不好的网站,被植入了木马病毒,会自动扫描你的cookie文件,别人就会有了你的cookie文件,别人访问服务器就拿着你的cookie文件去访问,这样就等同于你了。这也是一般盗号的原理。有可能你的cookie信息里面有你的账号密码,别人就能拿着你的账号去诈骗等违法操作
如何解决个人信息被盗呢?
在这里插入图片描述
这种session+cookie的方案和上面只用cookie的方案有什么区别?
如果我再次把你的cookie文件盗走了,我也拿到了你的session id我也和你一样去访问b站,此时在服务器能否甄别出来你这个用户是非法的呢?很显然。黑客照样能和你一样访问
虽然不能解决这个问题,但是能解决你的私人信息不被泄露,引入session技术,最重要的点就是在于把曾经要让浏览器去维护私人的信息,统一放在了服务器去维护,哪有人又要说了,那服务器不会收到攻击吗?答案是确实会存在这种情况,一般企业里会有安全攻防工程师,并且我国信息安全的法律也是很完善的,所以攻击企业端一般没人做。比如说你敢攻击支付宝吗,里面有顶级的攻防工程师,比如说它们会故意漏出破绽,你去扫,一旦中陷阱,就会被反向追踪
cookie信息被盗取的问题能否解决呢?因为大多数用户是小白,你再怎么放,也防不了客户端的信息被盗走,答案是解决不了。我要知道一点session id是服务器统一管理分配的,它可以给你分配,那也可以销毁你的session id,比如说盗取你的黑客在缅北,上一秒你还在西安访问,下一秒你就在缅北访问,此时服务器就能甄别到你账号异常,就会销毁你的session id,你就需要重新登录了,这就是为什么你放假回老家从西安到成都,你再访问服务器就会显示账号异常,需要重新登录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/278318.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常见的实时操作系统(RTOS)(嵌入式和物联网操作系统)介绍

在嵌入式系统和物联网&#xff08;IoT&#xff09;设备中&#xff0c;实时操作系统&#xff08;RTOS&#xff09;是至关重要的&#xff0c;因为它们负责管理有限的硬件资源&#xff0c;并提供确保任务在特定时间内完成的机制。开源实时操作系统&#xff08;RTOS&#xff09;允许…

Java项目:60 ssm基于JSP的乡镇自来水收费系统+jsp

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 系统可以提供信息显示和相应服务&#xff0c; 其管理员管理水表&#xff0c;审核用户更换水表的请求&#xff0c;管理用户水费&#xff0c;包…

【04】WebAPI

WebAPI 和标准库不同,WebAPI 是浏览器提供的一套 API,用于操作浏览器窗口和界面 WebAPI 中包含两个部分: BOM:Browser Object Model,浏览器模型,提供和浏览器相关的操作DOM:Document Object Model,文档模型,提供和页面相关的操作BOM BOM 提供了一系列的对象和函数,…

3d导出stl格式模型破碎是什么原因,怎么解决?---模大狮模型网

在导出3D模型为STL格式时出现破碎(或称为碎片化)的情况通常是由于模型中存在几何上的问题造成的。以下是一些可能导致STL模型破碎的原因以及解决方法&#xff1a; 3d导出stl格式模型破碎的原因&#xff1a; 模型不封闭&#xff1a;STL格式要求模型必须是封闭的实体&#xff0c…

数字图像处理 使用C#进行图像处理九 实现傅里叶变换

一、简述 傅立叶变换将图像分解为其正弦和余弦分量。换句话说,它将图像从空间域变换到频率域。这个想法是任何函数都可以用无限正弦函数和余弦函数之和来精确近似。傅里叶变换是实现此目的的一种方法。 网上有很多关于傅里叶变换的文章,这里就不进行赘述了,这里主要结合代码…

Spring项目问题—前后端交互:Method Not Allowed

问题 前后端交互时出现Method Not Allowed问题 Ajax中使用的是get&#xff0c;方法仍然出现post方法报错 Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method POST not supported] 浏览器中没有报错&#xff0c;只是接收不到后端返…

监控微信的软件,什么软件可以监控微信聊天记录

有的老板会在后台发文&#xff1a; “能监控聊天记录么&#xff1f;” “聊天记录删除了能找回么” “监控聊天记录的安装包有吗” ...... 可见很多老板对员工的工作时的工作状态都不太放心。 针对监控微信这个事情&#xff0c;我们应该理性分析看待。 首先&#xff0c;需…

基于Java中的SSM框架实现在线通用旅游平台网站系统项目【项目源码+论文说明】

基于Java中的SSM框架实现在线通用旅游平台网站系统演示 摘要 近几年来&#xff0c;计算机网络的发展得到了飞速的提升&#xff0c;由此展开的一系列行业大洗牌也由此开始。早些年只是人们只是对于计算机和互联网有了些基础的认识&#xff0c;现在它正在悄悄的改变着我们生活的…

学会用AI写文案,一分钟就能做一条爆款短视频:方法简单可复制

在这个信息爆炸的时代,短视频已成为人们获取信息、娱乐和社交的重要方式。而一条爆款短视频,除了精彩的画面和音乐外,文案的作用也不容忽视。 学会用AI写短视频文案,能够让你在竞争激烈的市场中脱颖而出,快速吸引观众的注意力。本文将为你揭示如何利用AI快速写出爆款短视…

[善用佳软]推荐掌握小工具:Json解析的命令行工具jq

与我联系&#xff1a; 微信公众号&#xff1a;数据库杂记 个人微信: iiihero 我是iihero. 也可以叫我Sean. iiheroCSDN(https://blog.csdn.net/iihero) Sean墨天轮 (https://www.modb.pro/u/16258) 数据库领域的资深爱好者一枚。 水木早期数据库论坛发起人 db2smth就是俺&am…

【Redis知识点总结】(五)——Redis实现分布式锁

Redis知识点总结&#xff08;五&#xff09;——Redis实现分布式锁 setnxsetnx expiresetnx expire lua脚本set nx exset nx ex 随机值set nx ex 随机值 lua脚本set ex nx 随机值 lua脚本 锁续期RedissonRedLock 在Redis的众多应用场景中&#xff0c;分布式锁是Redis比…

使用BBDown下载bilibili视频的方法

一款命令行式哔哩哔哩下载器. Bilibili Downloader. 下载地址 https://github.com/nilaoda/BBDown 功能 番剧下载(Web|TV|App) 课程下载(Web) 普通内容下载(Web|TV|App) 合集/列表/收藏夹/个人空间解析 多分P自动下载 选择指定分P进行下载 选择指定清晰度进行下载 下载外挂字幕…

mybatis项目中配置sql提示

2023版的idea好像内置了这个功能。 第一步&#xff1a; 第二步&#xff1a;第一步完成后user会爆红&#xff0c;这时我们需要连接数据库。

Python使用 k 均值对遥感图像进行语义分割

本篇文章介绍K-means语义分割来估计 2000 年至 2023 年咸海水面的变化 让我们先看一下本教程中将使用的数据。这是同一地区的两张 RGB 图像,间隔 23 年,但很明显地表特性和大气条件(云、气溶胶等)不同。这就是为什么我决定训练两个独立的 k-Means 模型,每个图像一个。 首…

C# danbooru Stable Diffusion 提示词反推 Onnx Demo

目录 说明 效果 模型信息 项目 代码 下载 C# danbooru Stable Diffusion 提示词反推 Onnx Demo 说明 模型下载地址&#xff1a;https://huggingface.co/deepghs/ml-danbooru-onnx 效果 模型信息 Model Properties ------------------------- ----------------------…

C++ Qt开发:QTcpSocket网络通信组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QTcpSocket组件实现基于TCP的网络通信…

2023年蓝桥杯省赛——幸运数字

目录 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 思路 高级思路 总结 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 首先是我写了差不多一个小时的解法&#xff0c;裂开了&#xff0c;为什么我如此废物 思路 寻找第2023个在二进制、八…

c语言大小写字母的转换

通过ascll码表我们可以知道大写字母与小写字母相差32个数&#xff08;小写字母比大写字母大&#xff09;。因此&#xff0c;通过相加减32即可转换大小写字母。 #include <stdio.h>int main() {char ch c;char CH A;printf("%c\n", ch - 32);printf("%c…

中文版国产Figma简单好上手

在过去的两年里&#xff0c;国内外协同办公室发展迅速。一方面&#xff0c;它是由突如其来的疫情推动的&#xff0c;另一方面&#xff0c;它是科学技术不断进步的必然结果。在市场的推动下&#xff0c;市场上出现了越来越多的协同办公软件&#xff0c;使工作场所的工作更加高效…

南大通用数据库-Gbase-8a-学习-43-SQL长时间处于Writing to net状态排查

目录 一、问题截图 二、排查思路 1、Gbase8a SQL有几种状态 2、问题导致原因猜想 3、观察服务端&#xff08;集群端&#xff09;网络情况 4、观察客户端网络情况 5、排查客户端程序处理数据慢 5.1、send &#xff08;1&#xff09;声明 &#xff08;2&#xff09;作用…