协议就是一种约定,你客户端和服务器约定了某种方式去通讯
我们主机之间的连接通过硬件+代码可以实现,在这个连接好的基础上,再制定一种协议,通信双方通过这个协议的规则进行有条不紊的,有保障性的通信。
其实底层呢其实也就是一种大量的文本处理,这些约定好了的特殊的文本格式,使我们通信的时候更加的有保障。
我们在网络中访问一个网址的时候,我们一般会输入一段如下的字符串去访问
(https://soft.3dmgame.com/down/246856.html)
现在我们来认识一下这段网址
前面的https,指的是使用的协议名称
中间的//soft.3dmgame.com, 这个是域名(IP),域名也就是IP,浏览器会给我们自动解析
后面的/down/246856.html,是我们客户端请求的服务器上面的web资源的文件路径
注意嗷,我们如果要实现两个主机之间的通信是需要IP+端口号的, IP 确定互联网中唯一一台主机, 端口后确定该机器上给我提供服务的进程,这里的网址只有域名(IP), 没有端口号,那是因为,一般我们访问的服务,都是对所有人开放的,所以一般都是绑定的固定的端口号,这个端口号是默认知道的,比如https服务固定绑定的的就是443端口号。
好了,那么我们来复盘一下我们平时上网的操作,我们上网无非就是两件事
1.我们想要获取什么东西
2.我们想要上传什么东西
那么在这个资源没有被我们拿到的时候,在哪呢?
在服务器上,一般是Linux系统的
在这个服务器上有很多资源,也就是文件,当我们发送一个请求资源到本地时,服务器的进程打开你需要的文件, 通过我们请求的文件路径找到目标文件,读取改文件,再发送给客户端client。
如果用户想在url中包含url本身用来作为特殊字符的字符时,那么url在形成的时候,浏览器会自动进行编码encode.
一般服务器在收到之后,需要进行转回特殊字符
我们搜索完后
可以看到一个%3F的字符,为了对比我们在搜索一个hello字符
完成后可以看到请求里面有一个hello字符
如上的例子,就可以证实,一些特殊意义的字符,确实是通过转码处理了的
转义的规则:将需要转码的字符转为16进制,然后从左到右,去4位(不足四位直接处理),每2位作一位,前面加上%,编码成%XY格式
这个规则不做掌握的要求,知道即可,毕竟那么多已经编写好了,公司说不定也不放心我们去编写。
HTTP底层实现,依旧是通过TCP协议实现的,也就是说,使用HTTP协议时,计算机之间就已经完成了三次握手的事件。
在报文的角度看,http可以是基于行的文本协议!
它大致分为
1.请求行
2.请求报头
3.空行
4.请求正文
每一行都是以\r\n结束,但是我们如何看待呢?
仍然是将其看成线性结构,所谓的文本不就是把**\r\n**在视觉上让我们看起来是换行了的,其本质也就是一个字符。
给大家看看水代码
#include
#include
#include
#include
#include "Http_Server.hpp"
#include "Log.hpp"
#include "Util.hpp"
// 一般http都要有自己的web根目录
#define ROOT "./wwwroot"
// 如果客户端只请求了一个/,我们默认返回首页
#define HOMEPAGE "index.html"
void Usage(char *port)
{
std::cout << port << std::endl;
}
// 业务编写
void HandlerHttpRequest(int sock)
{
char buffer[10240];
ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s] = '\0';
// std::cout << buffer << std::endl;
// std::cout << "----------" << std::endl;
}
std::cout << buffer << std::endl;
std::vector vline;
Util::cutString(buffer, "\n", &vline);
for (auto e: vline)
{
std::cout << e << std::endl;
}
std::cout << "---------" << std::endl;
std::vector vblock;
Util::cutString(vline[0], " ", &vblock);
// for (auto e: vblock)
// {
// std::cout << e << std::endl;
// }
std::string file = vblock[1];
std::string target = ROOT;
// std::cout << "file" << file << std::endl;
if (file == "/")
file = "/index.html";
target += file;
std::cout << "target:" << target << std::endl;
std::string content;
std::ifstream in(target);
if (in.is_open())
{
std::string line;
while (std::getline(in, line))
{
content += line;
}
in.close();
}
// std::cout << content << std::endl;
std::string HttpResponse;
if (content.empty())
{
HttpResponse = "HTTP/1.1 302 NotFound\r\n";
HttpResponse += "Location:https://www.qq.com\r\n";
}
else
{
HttpResponse = "HTTP/1.1 200 OK\r\n";
HttpResponse += ("Content-Type: text/html\r\n");
HttpResponse += ("Content-Length: " + std::to_string(content.size()) + "\r\n");
HttpResponse += "Set-Cookie: 这是一个cookie\r\n";
}
HttpResponse += "\r\n";
HttpResponse += content;
// std::string HttpResponse = "HTTP/1.1 200 OK\r\n";
// HttpResponse += "\r\n";
// HttpResponse += "ZZL is dog!!!
";
send(sock, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(4);
}
std::unique_ptr httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest));
httpserver->Start();
return 0;
}
#pragma once
#include
#include
class Util
{
public:
static void cutString(std::string s, const std::string &sep, std::vector *out)
{
std::size_t start = 0;
while (start < s.size())
{
std::size_t pos = s.find(sep, start);
if (pos == std::string::npos)
{
break;
}
std::string sub = s.substr(start, pos - start);
out->push_back(sub);
start += sub.size();
start += sep.size();
}
if (start < s.size()) out->push_back(s.substr(start));
}
};
#pragma once
#include "Log.hpp"
#include "Sock.hpp"
#include
class HttpServer
{
using func = std::function;
Sock httpsock;
public:
HttpServer(uint16_t port, func HanderHttpRequest)
:_port(port)
,_ip("0.0.0.0")
,_HanderHttpRequest(HanderHttpRequest)
{
_listensock = httpsock.Socket();
httpsock.Bind(_listensock, _ip, _port);
httpsock.Listen(_listensock);
}
void Start()
{
while (true)
{
std::string clientIp;
uint16_t clientPort;
int serversock = httpsock.Accept(_listensock, clientIp, clientPort);
if (serversock < 0) continue;
//连接成功
if (fork() == 0)
{
close(_listensock);
_HanderHttpRequest(serversock);
close(serversock);
exit(-1);
}
close(serversock);
}
}
~HttpServer()
{
if (_listensock >= 0)
close(_listensock);
}
private:
std::string _ip;
uint16_t _port;
int _listensock;
func _HanderHttpRequest;
};
#pragma once
#include
#include
#include
#include
#include
#define DEBUG 0
#define NORMAL 1
#define WORNING 2
#define ERROR 3
#define FATAL 4
//映射
const char* gLevel[] = {
"DEBUG",
"NORMAL",
"WORNING",
"ERROR",
"FATAL"
};
#define DEBUG_SHOW
void logMessage(int level, char* format, ...)
{
#ifndef DEBUG_SHOW
if (level == DEBUG) return;
#endif
//标准
char stdBuffer[1024];
const time_t curt = time(nullptr);
struct tm* _curt = localtime(&curt);
snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%d年%d月%d日%d时%d分%d秒]", gLevel[level],
_curt->tm_year + 1900, _curt->tm_mon + 1, _curt->tm_mday, (_curt->tm_hour + 12 )% 24 + 12, _curt->tm_min, _curt->tm_sec);
//自定义
char logBuffer[1024];
va_list args;
va_start(args, format);
vsprintf(logBuffer, format, args);
FILE* fd = fopen("./LogCalServer", "a");
fprintf(fd, "%s-%s\n", stdBuffer, logBuffer);
fclose(fd);
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
class Sock
{
public:
Sock(){}
int Socket()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
exit(1);
}
logMessage(NORMAL, "creat sock success sock:%d", sock);
return sock;
}
void Bind(int sock, const std::string ip, const uint16_t port)
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
socklen_t len = sizeof addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
if (bind(sock, (struct sockaddr*)&addr, len) < 0)
{
exit(2);
}
logMessage(NORMAL, "bind success");
}
void Listen(int listensock)
{
if (listen(listensock, 20) < 0)
{
exit(3);
}
logMessage(NORMAL, "set listen sock success");
}
int Accept(int listensock, std::string& ip, uint16_t& port)
{
struct sockaddr_in retaddr;
socklen_t len = 0;
while (true)
{
int sock = accept(listensock, (struct sockaddr*)&retaddr, &len);
if (sock < 0)
{
logMessage(ERROR, "accpet fail again");
continue;
}
ip = inet_ntoa(retaddr.sin_addr);
port = ntohs(retaddr.sin_port);
logMessage(NORMAL, "accpet success client-ip:%s port:%d", ip.c_str(), port);
return sock;
}
}
~Sock(){}
};
宝宝的世界
这是一个linux学习网址
宝宝的世界
你好呀 客人!!
在请求行中的方法
这里我们在明确一下,我们平时的上网行为就两种
但是把数据拿下来和把数据提交,是需要指明方法的,这里的方法常用的有
GET:表单中用户提交的数据通过url传参,也就是说,通过GET上传的数据,会在url中体现出来,没有私密性
POST:表单中用户提交的数据通过转成http request中的一部分传参,它是通过正文提交参数的,不会回显,一般的私密性是有保障的
注意这里只是私密性,安全性需要通过加密和解密来保障。
常见的状态码,比如200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
301 永久重定向
302、307 临时重定向
Content-Type:数据类型(text/html等)
Content-Length:Body长度
Host:客户端告知服务器,所请求的资源时在哪个主机的哪个端口上;
User-Agent:声明用户的操作系统和浏览器版本信息
referer:当前页面是从哪个页面跳转过来的
Location:搭配3xx状态码使用,告诉客户端接下来要去哪里访问;
Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能
http特征:
说的是无状态,但是我们实际使用的时候,一般网站是会记录下我的状态的,这个是浏览器做的,不是http做的
HttpResponse += "Set-Cookie: 这是一个cookie\r\n";
为了安全,cookie是不直接保存用户名和密码的,一般会生成一个session id保护自己的私密信息。
Connection: keep-alive :长连接
Connection: close:短链接
一张完整的网页,是由非常多的资源构成的。
让客户端手动的向服务器发送HTTP请求
进行抓包的工具,主要是抓取HTTP的
原理:在我们进行网络请求的时候,先交给fiddler,再通过fiddler发送出去
我们利用上面的代码实现一个提交表单的操作,也就是我们平时的提价密码登陆的操作
我们点击登陆后,我们再使用fiddler工具抓包一下
我擦勒,大家发现没,我们输入的密码再发送过去的请求中体现了出来,woc,如果有人简单的抓一下包,这踏马不就是把我们的密码给别人看嘛,哈哈哈
HTTP携带数据的时候,是明文的
所以HTTP面对的问题,它是不安全的,所以我们需要引入下一个东西,HTTPS