
在写应用层协议。
协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?(需要序列化和反序列化)
约定:要传的一共有三个区域,用:分割,前两个是int,后一个是char
协议定制:有几个字段,每个字段什么含义
将这个结构体变成字符串行为叫序列化。
data d={10,20,‘+’} => “10:20:+”
将这个字符串转为结构行为叫反序列化。
“10:20:+” => data d= { 10,20,‘+’ }
而 上面就是在用户发送和网路之间加了一层软件,可以在里面做各种操作。
字符串整体的长度对方怎么知道?
我们需要定制的时候,序列化之后,需要将长度,我们设置为4字节将长度放入序列化之后的字符串的开始。——自描述长度的协议
sudo yum install -y jsoncpp-devel
1.Value对象,万能对象
2.json是基于kv
3.json有两套操作方法
4.序列化的时候,会将所有数据内容,转化为字符串
void serialize(std::string *out)
{
Json::Value root;
root["x"] = x_;
root["y"] = y_;
root["op"]=op_;
//方法一:
Json::FastWriter fw;
//方法二:
//Json::StyledWriter fw;
*out =fw.write(&root);
}
方法一(推荐):

方法二:

// 反序列化
bool deserialize(std::string &in)
{
Json::Value root;
Json::Reader rd;
rd.parse(in,root);
x_ = root["x"].asInt();
y_ = root["y"].asInt();
op_ =root["op"].asInt();
return true;
}
https://www.baidu.com/
a.域名—>必须转化为ip
b.访问网络服务,服务端必须具有port
网络通信的本质:socket ,IP+port
使用确定协议的时候,一般显示的时候,会缺省端口号,所以,浏览器访问指定的url的时候,浏览器必须给我们自动添加port,浏览器如何得知,url匹配的port是谁?特定的众所周知服务,端口号必须是确定的!
httpserver->80
httpsServer->443 sshd->22
用户自己写的网络服务bind端口是时候,[1024,n]
平时我们俗称的“网址”,其实就是说的URL
查阅文档
查看视频
上面都是以网页的形式呈现的 .html文件
http——获取网页资源的,视频,音频等也都是文件!!
.html文件就是http是向特定的服务器申请特定的“资源”的,获取到本地,进行展现或者某种使用的!


样例请求:
GET / HTTP/1.1
Host: 114.55.229.240:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

如何协议的request or response;
报头+有效载荷
1.http如何保证自己的报头:按行读取,直到读取到空行
2.你又如何保证,你能读取到完整的正文?
报头能读取完毕,请求或者响应属性中,一定要包含正文的长度!

/不是根目录,web更目录,可以设置为根目录

程序运行在tcp目录下,通过读取请求,将请求路径前加上wwwroot就是正在请求的相对路径了。
std::string getPath(std::string http_request)
{
std::size_t pos = http_request.find(CRLF);
if(pos==std::string::npos)return "";
std::string request_line = http_request.substr(0,pos);
//GET /a/b/c
std::size_t first = request_line.find(SPACE);
if(first == std::string::npos)return "";
std::size_t second = request_line.rfind(SPACE);
if(first==second)return "";
std::string path = request_line.substr(first+SPACE_LEN,second-first-SPACE_LEN);
if(path.size()==1&&path[0]=='/') path+= HOME_PATH;
return path;
}
std::string readFile(const std::string& recource )
{
std::ifstream in (recource);
if(!in.is_open()) return "open error";
std::string content;
std::string line;
while(std::getline(in,line))content+=line;
in.close();
return content;
}
void handlerHttpRequest(int sock, const std::string clientIp, uint16_t clientPort)
{
std::cout<<clientIp<<": "<<clientPort<<std::endl;
char buffer[1024];
ssize_t s = read(sock,buffer,sizeof(buffer));
if(s>0)
{
std::cout<<buffer;
}
std::string path = getPath(buffer);
std::cout<<path<<std::endl;
std::string recource = ROOT_PATH;
recource+=path;
std::string html = readFile(recource);
std::string response;
response = "HTTP/1.0 200 OK\r\n";
response+="Content-Type: text/html\r\n";
response+="Conetent-Length: "+std::to_string(html.size())+"\r\n";
response+="\r\n";
response+=html+CRLF;
send(sock,response.c_str(),response.size(),0);
}
我们的网络行为无非有两种:
1.我想把远端的资源拿到你的本地:GET /index.html http/1.1
2.我们想把我们的属性字段,提交到远端 GET or POST
注意下面提交方式是用get
<form name="input" action="/a/b/c.html" method="get">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
form>


GET http://114.55.229.240:1025/a/b/c.html?user=zhangsan&passwd=123456 HTTP/1.1
Host: 114.55.229.240:1025
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://114.55.229.240:1025/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
在HTTP中GET会以明文方式将我们对应的参数信息,拼接到url中。
<form name="input" action="/a/b/c.html" method="post">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
form>


POST http://114.55.229.240:1025/a/b/c.html HTTP/1.1
Host: 114.55.229.240:1025
Connection: keep-alive
Content-Length: 27
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://114.55.229.240:1025
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://114.55.229.240:1025/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
user=zhangsan&passwd=123456
POST方式提交参数,会将参数以明文的方式,拼接到http的正文中进行提交!
1.GET通过url传参
2.POST通过正文传承
3.GET方式传参不私密
4.POST方法因为通过正文传参数,所以,相对比较私密一些
5.GET通过url传参,POST通过正文传参,所以一般一些比较大的内容都是通过POST传参的
常见Header:
Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
Referer: 当前页面是从哪个页面跳转过来的;
Location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
HTTP的状态码:

301:永久重定向
302:临时重定向

void handlerHttpRequest(int sock, const std::string clientIp, uint16_t clientPort)
{
std::cout << clientIp << ": " << clientPort << std::endl;
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer));
if (s > 0)
{
std::cout << buffer;
}
std::string response = "HTTP/1.1 302 临时重定向\r\n";
response += "Location: https://www.baidu.com\r\n";
response += "\r\n";
send(sock, response.c_str(), response.size(), 0);
}
访问自己的网站就跳转到百度搜索。
http协议特点之一:无状态
用户需要:用户保持
一旦登陆,会有各种会哈保持策略
这个策略就是——cookie策略

服务器返回响应,报头添加:
Set-Cookie: 内容
void handlerHttpRequest(int sock, const std::string clientIp, uint16_t clientPort)
{
std::cout << clientIp << ": " << clientPort << std::endl;
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer));
if (s > 0)
{
std::cout << buffer;
}
test1
std::string path = getPath(buffer);
std::cout<<path<<std::endl;
std::string recource = ROOT_PATH;
recource+=path;
std::string html = readFile(recource);
std::string response;
response = "HTTP/1.0 200 OK\r\n";
response+="Content-Type: text/html\r\n";
response+="Conetent-Length: "+std::to_string(html.size())+"\r\n";
response+="Set-Cookie: this is my cookie content;\r\n";
response+="\r\n";
response+=html;
send(sock,response.c_str(),response.size(),0);
///test1_end
}
下一次浏览器发送请求,就会在报头添加Cookie
POST http://114.55.229.240:1026/a/b/c.html HTTP/1.1
Host: 114.55.229.240:1026
Connection: keep-alive
Content-Length: 20
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://114.55.229.240:1026
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://114.55.229.240:1026/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: this is my cookie content; this is my cookie content
user=111&passwd=2222
方案二:cookie+session

Connection:keep-alive
Connection:closed
用户所看到的完整的网页内容——背后可能是无数次http请求
http底层主流采用的就是tcp协议!
HTTPS也是⼀个应⽤层协议.是在HTTP协议的基础上引⼊了⼀个加密层.
HTTP协议内容都是按照⽂本的⽅式明⽂传输的.这就导致在传输过程中出现⼀些被篡改的情况

http提供的服务端口是:80
https提供的服务端口是:443
所有的加密,都是为了防止中间人篡改。
加密就是把明⽂要传输的信息)进⾏⼀系列变换,⽣成密⽂.
解密就是把密⽂再进⾏⼀系列变换,还原成明⽂.
在这个加密和解密的过程中,往往需要⼀个或者多个中间的数据,辅助进⾏这个过程,这样的数据称为密钥
加密解密到如今已经发展成⼀个独⽴的学科:密码学.
⽽密码学的奠基⼈,也正是计算机科学的祖师爷之⼀,艾伦·⻨席森·图灵
臭名昭著的"运营商劫持"

因为http的内容是明⽂传输的,明⽂数据会经过路由器、wifi热点、通信服务运营商、代理服务器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双⽅察觉,这就是 中间⼈攻击 ,所以我们才需要对信息进⾏加密。
对称加密其实就是通过同⼀个"密钥",把明⽂加密成密⽂,并且也能把密⽂解密成明⽂
⾮对称加密要⽤到两个密钥,⼀个叫做"公钥",⼀个叫做"私钥".
公钥和私钥是配对的.最⼤的缺点就是运算速度⾮常慢,⽐对称加密要慢很多.
HTTPS⼯作过程中涉及到的密钥有三组.
其实⼀切的关键都是围绕这个对称加密的密钥.其他的机制都是辅助这个密钥⼯作的.


UDP传输的过程类似于寄信。
UDP的socket既能读,也能写,这个概念叫做全双工。
TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制;

可靠性问题:
1.什么是不可靠:丢包,乱序,校验失败
2.怎么确认一个报文是丢了还是没丢?
我们发的出去的消息,我们如何得知对方是否收到,只要得到应答就意味着我刚刚发的消息100%收到了。
在长距离交换的时候,永远有一条最新的数据是没有应答的!但是,只要发送消息有对方的应答,我们就认为我们发送的消息,对方是收到的。
我们如果收到了应答,我们确认是没丢,否则就是不确定。这个机制就是确认应答机制。
建立一个共识:tcp进行通信的时候,绝对不要忘记,发送出去的报文一定会携带tcp报头。


为什么TCP要有两组序号?
因为TCP协议是全双工的,主机B给主机A应答同时给主机B发送消息,也需要序号,保证数据可靠送达,还得确认之前得到主机A发送的消息,就需要两组序号。
IO类函数,本质其实都是拷贝函数。

如果发送方发的太快了,导致接受方来不及接受,如何解决?
需要让发送方知道接受方的接受能力!
接受方的接受能力,哪一个指标表示?
接受方的接受缓冲区的剩余空间的大小决定。
发送方如何知道接受方的接受能力?
发送方会得到接受方的应答,而应答本质就是包含TCP报头,tcp报头可以有保存接受方接受能力的属性。
报头中填充的是自己的还是对方的?
自己的,因为报文是发送给对方的

为什么有这6个标记位?
报文也是有类别的。

但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了

因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
那么, 如果超时的时间如何确定?
最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
但是这个时间的长短, 随着网络环境的不同, 是有差异的.
如果超时时间设的太长, 会影响整体的重传效率;
如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间
Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传.
如果仍然得不到应答, 等待 4500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

listen的第二个参数,叫做底层的全连接队列的长度,算法是:n+1,表示在不accept的情况下你最多能够维护多少个连接,再来一个就会无法完成三次握手,多来的只能维护在SYN_RCVD。
四次挥手时,主动断开连接的一方,会维护连接一个状态TIME_WAIT,进程挂了,但端口号被占用着,如果想重启服务bind会显示端口占用,需要等待TIME_WAIT结束。解决TIME_WAIT状态引起的bind失败的方法:

使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符
int opt =1;
setsockopt(listsock_,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
setsockopt(listsock_,SOL_SOCKET,SO_REUSEPORT,&opt,sizeof(opt));
发送出去的数据,在没有得到应答(1.发送成功2.发送失败)的情况下,必须被保留,以便于支持超时重传。保留的数据在发送缓冲区中。将发送缓冲区划分如下:

对于已经发送,但没有得到响应的区域就是滑动窗口。

扩展:
如何理解缓冲区和滑动窗口?
缓冲区就是个char sendBuffer[16*1024]
滑动窗口就是:
int start_index
int end_index
所谓的滑动就是++
tcp的发送缓冲区其实是被设计成为环状结构的!
滑动窗口大小由谁决定?
目前:是由对方的接受的能力决定!是发送方收到接受方的TCP数据报头中的窗口大小
滑动窗口一定会向右移动吗?滑动窗口可以变大吗?可以变小吗?
没发之前接受能力是4KB,发送之后,对方没有读取,返回应答的报头接受能力小于4KB,滑动窗口的end_index就不动,start_index++。所以滑动窗口不一定向右移动,且滑动窗口可能会减小,也可能增大。
情况一:数据包已经抵达, ACK被丢了

这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认
情况二: 数据包就直接丢了

这种机制被称为 “高速重发控制”(也叫 “快重传”)
流量控制接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);

接收端如何把窗口大小告诉发送端呢? 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;
那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?
实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

此处引入一个概念程为拥塞窗口
发送开始的时候, 定义拥塞窗口大小为1;
每次收到一个ACK应答, 拥塞窗口加1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快.
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.
此处引入一个叫做慢启动的阈值
当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长

当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1