满足日常需求的应用程序都是在应用层
协议是一种约定。socket api
在读写程序的时候都是按照字符串的方式发送的,如果传输一些结构化数据该怎么办?
只要保证一端发送数据的时候,在另一端能够正确的解析。这就是应用层协议
HTTP协议(超文本传输协议)
像这样的字符串已经被URL当做特殊意义理解了,因此不能随意出现在URL当中
某个参数需要带特殊字符,就必须先对这些特殊字符进行转义
转义规则如下:
将需要转义的字符转为16进制,然后从右到左,取4位(不足4位直接处理)。每两位做一位,前面加上%
\n
分割,遇到空行表示header部分结束Content -Length
来标识Body的长度\n
分割,遇到空行表示header部分结束403状态码表示服务器理解请求,但拒绝执行请求。这通常是因为请求的资源对用户是禁止访问的,例如需要身份验证的页面或没有访问权限的页面。403状态码与401状态码的区别在于,401状态码表示未经身份验证的用户,而403状态码表示已经身份验证的用户,但没有访问权限。
302状态码表示重定向。当服务器收到客户端的请求后,会返回302状态码和一个Location头部,指示客户端重定向到另一个URL。这通常用于临时性的重定向,例如当一个网页被移到了新的URL上时,服务器可以返回302状态码和新的URL,以便客户端自动跳转到新的URL。
303状态码表示重定向,与302状态码类似。它通常用于POST请求后的重定向,以防止客户端重复提交表单数据。当服务器收到POST请求后,如果希望客户端重定向到另一个URL来获取结果,服务器会返回303状态码和一个Location头部,指示客户端进行GET请求以获取结果。这样可以防止客户端在刷新页面时重新提交表单数据。
实现一个最简单的服务器,返回客户端一个hello world
#include
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cout << "输入参数过少" << std::endl;
return 0;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(argv[1]);
local.sin_port = htons(atoi(argv[2]));
bind(sock, (struct sockaddr*)&local, sizeof(local));
listen(sock, 32);
while (true)
{
struct sockaddr_in client;
memset(&client, 0, sizeof(client));
socklen_t len = sizeof(client);
int n = accept(sock, (struct sockaddr*)&client, &len);
if (n < 0) continue;
char buf[1024];
const char* hello = "hello world
";
sprintf(buf, "HTTP/1.0 200 Ok\nContent-Length:%lu\n\n%s", strlen(hello), hello);
write(n, buf, sizeof(buf));
}
return 0;
}
负责把数据从发送端传输到接收端
cat /etc/services
一个进程可以绑定多个端口号,但是一个端口号不能绑定多个进程
netstat
是用来查看网络状态的重要工具
查看服务器的进程id
udp传输的过程类似于寄信:
面向数据报
应用层交给UDP多长的报文,UDP原样发送,既不会拆分也不会合并
用udp传输100个字节的数据:
如果发送端一次调用sendto,发送100个字节,那么接收端必须调用一次对应的recvfrom,接收100个字节,不能循环的调用10次recvfrom ,每次接收10个字节
UDP的缓冲区
Udp没有真正意义上的发送缓冲区,调用函数之后直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。UDP如果接收缓冲区满了之后,再达到的UDP数据就会被丢弃
UDP的socket既能读也能写,这个概念就叫做全双工
TCP全称为“传输控制协议”,要对数据的传输进行一个详细的控制
4位TCP报头长度:表示TCP报头有多少个32位bit (有多少个4字节),所以TCP头部最大长度是60字节 15 * 4
6 为标志位:
16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据有问题,这个不光包含TCP首部也包含TCP数据部分
16位紧急指针:标识那部分数据是紧急数据
TCP将每个字节的数据都进了编号,称为序列号:
每一个ACK都带有对应的确认序号列,意思是告诉发送者,收到了哪些数据,下一次从哪里开始发送
如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会重新发送
但是,主机A未收到主机B的确认应答,可能是因为ACK丢失了
因为主机B会收到很多重复数据,那么TCP协议需要识别哪些包是重复的包,并且把重复的包丢弃掉。这时候可以利用前面提到的序列号,就可以很容易做到去重的效果
超时时间如果确定?
找到一个最小的时间,保证“确认应答一定能在这个时间内返回”,但是这个事件段的长短,随着网络环境的不同也是有差异的。如果设置的时间太长,会影响整体的重传效率,如果设置的时间太短,有可能频繁发送重复的包
TCP为了保证无论在任何环境下都比较高性能的通信,因此会动态的计算这个时间
在正常情况下,TCP要经过三次握手建立连接和四次挥手断开连接
对每个发送的数据段,都要给一个ACK应答。收到ACK后再发送下一个数据段,这样做的一个比较大的缺点是,性能比较差尤其是数据段返回需要的时间比较长
既然这样一发一收的性能比较低,我们可以一次性发送多条数据,就可以大大提高性能(其实是将多个段的等待时间重叠在一起)
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录哪些数据没有应答,只有确认应答的数据,才能从缓冲区删掉
窗口越大,则网络的吞吐率越高
如果出现了丢包,如何重传?
这样需要分情况考虑:
情况一:数据报到了,但是ACK丢了
这种情况下,部分ACK丢了不要紧,可以通过后续的ACK进行确认
情况二:数据报丢了
如果发送端主机连续三次收到同样的ACK应答,就会将对应的数据重新发送。
被收到接收端操作系统的接收缓冲区内
这种机制被称为“高速重发控制”(也叫快重传)
接收端处理数据的速度是有限的,如果发送端发送的太快。导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包继而引起丢包重传一系列连锁反应
TCP 支持根据接收端的处理能力,来决定发送端的发送速度,这个机制叫做流量控制
虽然TCP有了滑动窗口,可以高效发送大量的数据,但是在刚开始时就发送大量的数据可能引发问题
网络上可能有很多计算机,可能当前的网络状态就已经比较拥堵,在不清楚网络状态的情况下,贸然发送大量数据可能雪上加霜
TCP引入慢启动机制,先发少量的数据探探路,摸清当前网络的拥堵情况,在确定按照多大的速度传输数据
发送开始的时候定义拥塞窗口大小为1, 每次收到一个ACK应答拥塞窗口加1。每次发送数据报的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为直接发送的窗口
这个地方有个慢启动的阈值,当拥塞窗口超过这个阈值的时候,不在按照指数方式增长,而是线性方式增长
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。如果接收端稍微等一会在应答,返回的窗口可能就很大
窗口越大,网络吞吐量就越高,目标是在保证网络不拥塞的情况下,尽量提高传输效率
每个包都延迟应答吗?
在延迟应答的基础上,我们发现很多情况下,客户端服务器在应用层也是“一发一收”的意味着客户端给服务器发送了一条消息,服务端也会返回一个消息。这个是会后ACK就可以搭顺丰车,和服务器返回的消息一起返回给客户端
创建一个TCP的socket,同时在内核中创建一个接收缓冲区和发送缓冲区
这里的“包”指的是应用层的数据报
在TCP协议头中,没有UDP一样的“报文长度”,但是有一个序号这样的字段。站在传输层的角度,TCP是一个报文一个报文发送过来的。按照序号排好放在缓冲区。站在应用层的角度看到的是一连串的数字应用程序看到这一连串的字节数据,就不知道那个部分开始啊到那个部分,是一个完整的数据报
如何解决粘包问题?
明确两个包之间的边界
进程终止:进程终止会释放文件描述符,仍然可以发送FIN。和正常关闭没什么区别
一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。即使没有写入操作,TCP也会内置一个保活定时器,会定期询问对方还在不在,也会连接释放
可靠性:
提高性能:
listen的第二个参数,是为了保证系统资源不浪费
TCP连接管理使用两个队列: