网络通信协议

应用层协议

负责应用程序之间的数据沟通(应用层的协议是程序员自己定义的)

自定制协议

举例网络版计算器:客户端将两个数字和一个运算符传输给服务端,然后服务端进行计算,最后将结果返回给客户端。 (这个属于数据结构化传输:使用结构体将多个数据对象的数据在内存中进行组织,最终进行数据传输)

在数据结构化传输中有两个专业名词:
序列化:将数据对象按照指定协议组织成为可持久化存储/数据传输的二进制数据串。
反序列化:将二进制数据串按照指定的协议进行解析得到各个数据对象。

调研 json,protobuf 序列化

知名协议(HTTP协议- - -超文本传输协议)
URL介绍
  • 我们通常浏览网页就需要用到HTTP协议,所说的网址也就是URL(统一资源定位符),我们先来了解一下:
    在这里插入图片描述
    查询字符串:客户端给服务端所提交的数据,需要进行 url 编码,url 编码是为了防止提交的数据中有特殊字符( 例如 / ? : 等等等),与 url 中的分隔符产生歧义。另外查询字符串由一个个键值对组成,并且键值对是 key = val 的形式,键值对之间以 & 进行间隔。

    url 编码(urlencode):将特殊字符的ASCII 值每个字节转换成十六进制数字的字符串(比如上面搜索C++时,+ 号的ASCII值为43,转换为十六进制为 2B),并且为了表示这个字节经过转义,因此在字符前面加上 % 进行标识。
    url 解码(urldecode):在查询字符串中遇到 % 时,则认为紧跟其后的两个字符需要进行解码:将两个字符分别转换成十六进制对应数字,第一个数字左移 4 位 + 第二个数字 。urldecode就是urlencode的逆过程。

HTTP协议格式协议格式传送门

首行

  • 请求首行:
    在这里插入图片描述

    请求首行三要素:请求方法、URL、协议版本,三个要素以空格进行间隔,并以 /r/n (回车换行)作为结尾。
    1、请求方法请求方法传送门

    • GET:获取资源,提交数据时数据在URL中
    • POST:向服务端提交数据,数据在正文中
    • HEAD:获取报文头部,没有正文

    注意 GET 与 POST 的区别:其实 GET 不仅仅是获取资源,也可以提交数据。但是因为GET是没有正文的,提交数据是通过 URL 中的查询字符串完成的,又因为URL的长度是有限制的,所以说GET提交数据显得比较鸡肋。而POST提交的数据放在正文中,没有大小限制。另外,在用户登录密某个账号输入密码时,都是POST请求方法,账号密码在正文中比较安全,不容易泄密。GET 则在URL中,容易泄密不安全。
    2、URL

    • 上面提到过,注意URL的长度是有限制的。

    3、协议版本

    • HTTP/0.9:短连接(客户端发送请求,服务端响应一次后关闭套接字通信结束)

    • HTTP/1.0:①默认是短连接,可以设置keep-alive变为长连接(客户端发送请求,服务端响应后两端并不会关闭套接字,如果发送第二次请求,就用原来创建的套接字通信,节省资源,直到客户端或服务器端决定将其关闭为止);②还增加了POST,HEAD请求方法

      头部中的Connection如果为 keep-alive 则设置为长连接;如果为 close 则设置为短连接。

    • HTTP/1.1:①多出了请求方法:PUT、DELETE、CONNECT等等。②:默认已经是长连接,并且长连接采用管线化传输:请求可以连续发送,响应是从第一个开始逐个开始响应。

    • HTTP/2.0:支持服务端主动给客户端发送数据。

  • 响应首行: 在这里插入图片描述

    响应首行三要素:协议版本、响应状态码、状态码描述,三个要素以空格进行间隔,并以 /r/n (回车换行)作为结尾。
    1、协议版本
    2、响应状态码响应状态码传送门

    • 1XX :信息,服务器收到请求,需要请求者继续执行操作
    • 2XX:成功,操作被成功接收并处理
    • 3XX:重定向,需要进一步的操作以完成请求
    • 4XX:客户端错误,请求包含语法错误或无法完成请求
    • 5XX:服务器错误,服务器在处理请求的过程中发生了错误
      在这里插入图片描述

    3、状态码描述

头部

  • 头部中都是一个个的key:val形式的键值对,并且每一个键值对都以 \r\n 作为结尾。

    Content-Length:正文长度
    Content-Type:正文数据类型,决定如何解析
    Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
    User-Agent: 声明用户的操作系统和浏览器版本信息
    referer: 当前页面是从哪个页面跳转过来的
    location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
    Connection:决定长连接还是短连接

cookie与session:
是用于记录信息确定用户身份的;cookie 是存放在客户端浏览器中的,session是存放在服务器当中的。
当客户端发起第一次登录请求,服务端收到这个请求后,会为客户端建立一个session会话(包含了用户信息)保存在服务端。服务端在响应头部中会加入set-cookie:xxx(这里的xxx其实就是session_id),将这个session会话的id(session_id)返回给客户端;客户端将这个set-cookie信息作为cookie保存下来,下一次请求相同的服务器的时候,会从cookie文件中读取到这些cookie信息发送给服务端。服务端通过请求头部中的cookie信息(包含session_id)找到对应的session会话,就知道这是哪一个用户发来的请求。

登录一次网站后访问网站其他页面不需要重新登录,典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

空行

  • 间隔头部与正文

正文

  • 客户端提交给服务端的数据或服务端响应给客户端的数据
一个简单的HTTP服务器

在编译执行之前需要将Linux的防火墙关掉,指令为:sudo systemctl stop firewalld.

//tcpSocket.hpp
#include<iostream>
#include<string>
#include<unistd.h>
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <netinet/in.h>
#define MAX_LISTEN 10
#define CHECK_RET(q) if((q) == false){return -1;}
class TcpSocket
{
  private:
    int _sockfd;
  public:
    TcpSocket()
      :_sockfd(-1)
    {}

    //1.创建套接字
    bool Socket(){
     _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
     if(_sockfd < 0)
     {
       perror("socket error");
       return false;
     }
     return true;
    }

     //2.绑定地址信息
     bool Bind(const std::string &ip,const uint16_t &port){
       struct sockaddr_in addr;
       addr.sin_family = AF_INET;
       addr.sin_port = htons(port);
       addr.sin_addr.s_addr = inet_addr(ip.c_str());
       socklen_t len = sizeof(struct sockaddr_in);
       int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
       if(ret < 0)
       {
         perror("bind error");
         return false;
       }
       return true;
     }

     //3.开始监听(服务端)
     bool Listen(int backlog = MAX_LISTEN){
       int ret = listen(_sockfd,backlog);
       if(ret < 0)
       {
         perror("listen error");
         return false;
       }
       return true;
     }

     //3.发起连接请求(客户端)
     bool Connect(const std::string &ip,const uint16_t &port){
       struct sockaddr_in addr;
       addr.sin_family = AF_INET;
       addr.sin_port = htons(port);
       addr.sin_addr.s_addr = inet_addr(ip.c_str());
       socklen_t len = sizeof(struct sockaddr_in);
       int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
       if(ret < 0)
       {
         perror("connect error");
         return false;
       }
       return true;
     }

     //4、获取新连接(服务端)
     bool Accept(TcpSocket *sock,std::string *ip = NULL,uint16_t *port = NULL){
       struct sockaddr_in addr;
       socklen_t len = sizeof(struct sockaddr_in);
       //获取新连接描述符,并且获取这个新连接对应的客户端地址
       int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
       if(newfd < 0)
       {
         perror("acccept error");
         return false;
       }
       sock->_sockfd = newfd;
       if(ip != NULL)
       {
         *ip = inet_ntoa(addr.sin_addr);
         //将网络字节序的整数IP转换为字符串
       }
       if(port != NULL)
       {
         *port = ntohs(addr.sin_port);
       }
       return true;
     } 

     //5.接收数据
     bool Recv(std::string &buf)
     {
       char tmp[4096] = {0};
       int rlen = recv(_sockfd,tmp,4096,0);
       if(rlen < 0)
       {
         perror("recv error");
         return false;
       }
       else if(rlen == 0){
         std::cerr << "peer shutdown\n";
         return false;
       }
         buf.assign(tmp,rlen);
         return true;
     }

     //6,发送数据
     bool Send(const std::string &buf)
     {
       int ret = send(_sockfd, &buf[0], buf.size(),0);
       if(ret < 0)
       {
         perror("send error");
         return false;
       }
       return true;
     }

     //7.关闭套接字
     bool Close(){
       close(_sockfd);
       return true;
     }
};
//http.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <sstream>
#include "tcpSocket.hpp"
int main(int argc ,char *argv[])
{
    if (argc != 3) {
        std::cerr << "./http ip port\n";
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    TcpSocket lst_sock;
    CHECK_RET(lst_sock.Socket());
    CHECK_RET(lst_sock.Bind(ip, port));
    CHECK_RET(lst_sock.Listen());

    while(1) {
        TcpSocket cli_sock;
        if (lst_sock.Accept(&cli_sock) == false) {
            continue;
        }
        std::string buf;
        if (cli_sock.Recv(buf) == false) {
            cli_sock.Close();
            continue;
        }
        std::cout << "req:[" << buf << "]\n";

        std::string text;
        text="<html><body><h1>Hello World</h1></body></html>";
        std::stringstream tmp;
        tmp << "HTTP/1.1 200 ok\r\n";
        tmp << "Content-Length: " << text.size() << "\r\n";
        tmp << "Connection: close\r\n";
        tmp << "Content-Type: text/html\r\n";
        tmp << "Location: http://www.taobao.com/\r\n";//重定向时要添加Location
        tmp << "\r\n";
        tmp << text;
        cli_sock.Send(tmp.str());//将tmp转换成为string对象
        cli_sock.Close();
    }
    lst_sock.Close();
    return 0;
}

输入ip:0.0.0.0 port:9000 进行编译。PS:0.0.0.0只能用于服务端,表示服务端在监听地址的时候可以监听当前主机上的所有网卡。所以 0.0.0.0 9000 表示当前主机上的所有网卡的9000端口都要进行监听,任意一块网卡的9000端口接收到数据都交给自己来处理。只能用于服务端,不能用于客户端(因为不知道发给哪一块网卡)。(其实直接:./http 192.168.139.128 9000 也可以)
此处我们使用 9000 端口号启动了HTTP服务器,虽然HTTP服务器一般使用80端口, 但这只是一个通用的习惯。并不是说HTTP服务器就不能使用其他的端口号。
在这里插入图片描述
在网页上访问服务器之后就会出现“Hello World”。
在这里插入图片描述
使用chrome测试我们的服务器时, 可以看到服务器打出的请求中还有一个 GET /favicon.ico HTTP/1.1 这样的请求:
在这里插入图片描述
在浏览器的标签头上面显示了一个图标,也就是我们常说的favicon.ico.

HTTP 与 HTTPS 的区别?
  • HTTP使用80端口,HTTPS使用443端口。

  • HTTPS在HTTP协议的基础上进行了一层ssl加密

    加密方式:对称加密、非对称加密
    使用对称加密,很容易被破解,因此协商对称加密算法的过程采用非对称加密;使用非对称加密,解密性能比较低,因此数据通信时采用对称加密。

    双方首先协商对称加密算法,为了防止协商过程被破解,因此对协商对称加密算法的过程进行非对称加密算法,这样就不会被破解了。服务端直接向客户端发送公钥存在中间公钥被劫持的可能,因此采用签名认证的方式。服务端先向客户端发送签名证书,证明自己的身份,但是证书有可能造价或被劫持,因此客户端拿到证书之后需要进行权威机构认证。
    加密过程:
    ①:服务端首先生成一对秘钥(公钥+私钥),服务端带着公钥去权威机构生成签名证书(证书包含:公钥信息,公司信息,机构信息),证书用来给客户端验证服务器是否可以信任。
    ②:通信的时候服务端先将证书发送给客户端,客户端进行证书解析,去权威机构进行公司认证,通过了以后才能进行通信。
    ③:通过身份验证之后,将使用公钥加密的对称加密算法和随机数发送给服务端。
    ④:服务端收到后用私钥将收到的加密信息进行解密,解密后也生成随机数然后发送到客户端。
    ⑤:双方根据两个随机数计算得到一种加密算法,然后双方再进行对称加密通信。

传输层协议

传输层:负责端与端之间的数据传输。

UDP协议

UDP协议格式

在这里插入图片描述
16位源端口、16位目的端口:负责端与端之间的数据传输。
16位UDP长度:整个数据报(UDP首部+UDP数据)的长度。其最小值是8(仅有首部)
16位UDP数据校验和:校验数据一致性(通过二进制反码求和算法),如果校验和出错, 就会直接丢弃;

UDP特性
  • 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。
  • 不可靠:没有确认机制,没有重传机制;并不关心数据是否已经到达。
  • 面向数据报:不能够灵活的控制读写数据的次数和数量。应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并。
UDP的缓冲区
  • UDP没有真正意义上的发送缓冲区(不是真正意义上的缓冲区指的是不堆积数据).。调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作。
  • UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报文的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃。
UDP使用注意事项
  • UDP数据报长度(UDP首部+UDP数据)为 16位,说明UDP整个数据报长度不能大于2^16,即64K。
  • 因为UDP数据报长度的限制,因此若数据过大,则需要用户在应用层进行数据分包,将大数据分割成一个个的小数据包(最大为64K - 8个字节,因为首部的大小为8个字节)。
  • UDP并不保证数据的有序到达,可能先放的数据后到了,因此有可能乱序,需要用户在应用层进行包序管理。
  • UDP在sendto发送数据的时候将数据放到缓冲区则会直接封装头部,将数据发送出去。(每一次的sendto发送的数据都会封装一个UDP头部)。
在传输层使用UDP协议的应用层协议
  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置 协议
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议

TCP协议

TCP协议格式在这里插入图片描述
  • 源端端口、目的端口:负责端与端之间数据传输。表示数据是从哪个进程来, 到哪个进程去;

  • 序号、确认序号:进行包序管理。

  • 长度:表示该TCP头部有多少个32位bit(有多少个4字节); 由于四位二进制数能够表示的最大十进制数字是15,所以TCP头部大长度是15 * 4 = 60 字节(此时选项长度为40字节);当没有“选项”时,TCP头部长度最小:为20字节。

  • 标志位:

    URG: 紧急指针是否有效
    ACK: 确认号是否有效
    PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
    RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
    SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
    FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段

  • 窗口大小:用于避免丢包的一种方式。

  • 校验和:校验数据一致性(二进制反码求和算法)

  • 紧急指针: 标识哪部分数据是紧急数据

  • 选项:通信双方协商一些信息的时候使用。

TCP特性(面向连接,可靠传输,面向字节流)
  • 面向连接
    三次握手四次挥手详解

    TCP连接管理中保活机制

    在这里插入图片描述

    如果已经建立了连接,但是客户端突然出现故障了怎么办?

    • 显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。所以TCP有一个连接保活机制,若通信双方长时间(7200s)无数据往来,则会每隔一段时间(75s)给对方发送一个保活探测数据报,要求对方进行回复,若多次(9次)保活探测请求没有得到回复,则会认为连接已经断开。连接断开在代码中的体现:1、recv返回0;2、send触发SIGPIPE异常。
  • 可靠传输

    1、确认应答机制 :发送的每一条数据都要求对方进行对方进行确认回复
    2、超时重传机制:等待确认回复超时,则重发数据(主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B; 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发; )
    这里是引用
    3、 协议字段中的序号(seq:数据发到哪里呢)与确认序号(ack=seq+1:下一条数据应该从哪里开始发送):确认序号一定是保证在这个序号之前的数据都已经收到了。第一条数据没有收到,就算收到了第二条数据,也不会对第二条数据进行确认回复,因为每一条回复都要保证在这之前的数据已经完全收到。另外,还可以实现包序管理。
    4、协议字段中的校验和:若数据不一致,则丢弃数据要求重传

  • 面向字节流

    tcp中send发送数据时,会将数据先放入发送缓冲区中,操作系统从缓冲区给你取出合适大小(MSS)的数据封装头部进行发送。如果发送的字节数太长, 会被拆分成多个TCP的数据包发出; 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了再发送出 去;

滑动窗口 (TCP可靠传输中避免丢包的一种机制)

由于确认应答策略, 所以对每一个发送的数据段, 都要给一个ACK确认应答.。收到ACK后再发送下一个数据段。 这样做有一个比较大的缺点, 就是性能较差.,尤其是数据往返的时间较长的时候。由于这样一发一收的方式性能较低, 那么我们一次连续发送多条数据,连续接收确认回复,这样 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了),如下图:
在这里插入图片描述
于是,为了提高性能、减少丢包,就有了滑动窗口。

  • 滑动窗口的实现:通过一个窗口后沿(保存序号的变量)和一个窗口前沿以及一个当前的发送序号;通过这几个序号就可以维护一个窗口。

  • 滑动窗口的过程:

    1、在TCP三次握手阶段,通信双方会协商一个数据叫做最大数据段大小(MSS),发送端在send数据的时候会将数据放到发送缓冲区,然后操作系统会从缓冲区中每次取出不大于MSS大小的数据,封装TCP头部发送到接收端的接收缓冲区中(MSS是选项数据里面的)。TCP在每次发送数据的时候,在报文头部中有一个字段叫做16位窗口大小。接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK通知发送端。窗口大小指的是发送端无需等待接收端确认应答而可以继续发送数据的大值(这个窗口大小不大于接受缓冲区中的剩余空间大小),窗口越大, 则网络的吞吐率就越高,传输效率就越高。 上图的窗口大小就是4000个字节(四个段).


    2、以上图为例,发送前四个段的时候, 发送端不需要等待任何ACK, 直接发送。
    3、前四个字段发送完毕后,当接收到所有的ACK回复之后,更新窗口位置,继续发送新的数据。

  • 发送窗口与接收窗口是如何移动的?

    这里是引用

    发送窗口:前沿减去后沿不能大于接收方返回的窗口大小
    ①:后沿变化只有两中可能:不动与前移(前移时取决于是否接收到接收端的确认回复)。PS:不能后移,因为不能撤销已经收到的确认。

    ②:前沿的移动,取决于接收方返回的窗口大小。(前移:没有收到新的确认,对方通知的窗口大小不变。不动:收到了新的确认但对方通知的窗口缩小了。后移:强烈不赞成,发生在对方通知的窗口缩小了,因为很可能发送方在收到这个通知之前已经发送了窗口中的许多数据,现在又要收缩窗口,不让发送这些数据就会产生一些错误)

    接收窗口:窗口大小不大于缓冲区剩余空间大小
    ①:后沿(当前接收数据的起始序号位置)的移动,取决于是否收到数据。用户recv从接收缓冲区取走数据会导致返回给发送端的窗口大小变大。

    ②:前沿的移动取决于缓冲区中空闲空间的大小

  • 滑动窗口的优点

    1、发送方可以连续发送多条数据,等待对方连续回复,提高了一定的传输性能。
    2、连续发送数据,连续接收数据,可以避免因为确认回复的丢失而导致数据重传(因为每一条确认序号,都需要保证在这之前的数据都已经安全到达)
    3、接收方根据自己接受缓冲区中的剩余空间大小,向对端返回窗口大小,进而限制发送方的发送速度,避免发送方发送数据过多,导致缓冲区满溢后,数据丢包。
    在这里插入图片描述

  • 丢包的两种情况

    ①:数据包已经抵达, ACK被丢了
    在这里插入图片描述
    这样就避免了ACK丢失了导致数据的重传。

    ②:数据包就直接丢了.
    在这里插入图片描述
    什么是快速重传机制?如下:
    当接收方接收到第二条数据,但是还没有接收到第一条数据时。则这时候认为数据可能丢失,因此接收端会连续发送三次对应数据的重传请求。当发送端连续收到三条重传请求,才会将相应数据进行重发,避免数据因为网络原因而延迟到达的重传。

  • 拥塞控制
    虽然TCP有了滑动窗口, 能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据, 在不清楚当前网络状态下, 贸然发送大量的数据, 很有可能造成大量的数据丢包导致重传。少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞。 为了避免这种情况,因此采用拥塞控制:

    传输数据的时候采用慢启动,快增长网络探测的方式进行数据传输。一开始发送的时候数据量比较小,收到回复确认之后,则发送数据大量提高(指数级别的提高)。但是,为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍. 此处引入一个叫做慢启动的阈值 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长 。

为了提高TCP传输性能的机制
  • 延迟应答机制

    接收端接收到数据后,并不会立即进行ACK确认回复(如果立即ACK回复,可能用户调用recv将数据从缓冲区取走,导致发送端接收缓冲区后沿向前移动,此时就会导致返回给发送端的窗口值变小),而是延迟一会儿进行ACK确认回复,尽最大的可能保证窗口的大小。窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输 效率;


    在这里插入图片描述

  • 捎带应答机制

    接收方每接收一条数据就要进行确认回复,而确认回复就是一个TCP报头。所以,通过在即将发送的数据头部中添加确认信息,将上次的确认回复捎带上,可以减少纯报头的确认回复。

面向字节流带来的TCP粘包问题
  • 产生原因:

    首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包。
    ①:发送端引起粘包问题:TCP发送端引起的粘包问题是由TCP协议本身造成的,因为TCP在传输层对数据边界并不敏感,若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个合适大小的数据包(MSS)封装头部后一次发送出去,这样接收方就收到了粘包数据。
    ②:发送端引起的粘包问题:接收方不及时接收缓冲区的包,造成多个包接收导致一次性recv多条数据包,造成粘包。

  • 如何解决:
    粘包的本质原因tcp在传输层对数据边界不敏感,因此需要用户在应用层进行数据的边界管理。

    ①:对于定长的包, 保证每次都按固定大小读取即可.
    ②:对于变长的包,可以在包和包之间使用明确的分隔符(特殊字符,数据中不能含有这个分隔符)如果出现了特殊字符,即人为的将粘包分开。缺点:如果本身数据就含有特殊字符,就会造成冲突。
    ③:对于变长的包,在不定长数据的应用层头部中添加数据长度字段,服务端接收到数据后,先是解析数据包应用层头部,然后根据数据包应用层头部中的数据长度字段截取数据包,这样接收端就知道了每次recv(从接收缓冲区中取出)多少数据。
    注意:UDP是不会存在粘包问题的,因为由于UDP发送的时候,没有经过Negal算法优化,不会将多个小包合并一次发送出去;另外,在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。
    面向字节流:传输比较灵活,应为数据都在缓冲区中如何发送接收都由用户自己定,但是存在粘包问题。
    面向数据报:数据只能整条收发,不够灵活,但是不存在粘包问题。

如何用UDP实现可靠传输

参考TCP的可靠性机制,在应用层实现类似的逻辑

  • 引入序列号,保证数据顺序。
  • 引入确认应答,确保对端收到了数据。
  • 引入超时重传, 如果隔一段时间没有应答, 就重发数据。
基于TCP协议的应用层协议
  • HTTP
  • HTTPS
  • SSH
  • FTP
  • Telnet

网络层协议

网络层功能:负责地址管理与路由选择。

IP协议

IP协议格式

在这里插入图片描述

  • 4位版本号(version): IPV4/IPV6
  • 4位头部长度(header length): IP头部的长度是多少个32bit, 也就是 length * 4 的字节数. 4bit表示大 的数字是15, 因此IP头部最大长度是60字节
  • 8位服务类型(Type Of Service): 3位优先权字段(已经弃用), 4位TOS字段, 和1位保留字段(必须置为0). 4位 TOS分别表示: 小延时, 大吞吐量, 高可靠性, 小成本. 这四者相互冲突, 只能选择一个. 对于 ssh/telnet这样的应用程序, 小延时比较重要; 对于ftp这样的程序, 大吞吐量比较重要.
  • 16位总长度(total length): IP数据报整体占多少个字节(IP头部+IP数据),所以UDP数据最大长度:64K-20字节-8字节。
  • 16位标识(id):标识分片的IP数据报数据哪一个UDP数据段 . 如果IP报文在数据链路层被分片了, 那么每一个片里面的这个 id都是相同的. (在链路层有一个信息叫MTU,叫做最大传输单元,决定了每一个数据帧的最大长度。)PS:TCP不会进行数据分片,因为TCP在传输层协商的MSS就是通过MTU计算得到的,因此网络层数据分片主要针对的是UDP数据报。
  • 3位标志字段: 第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到). 第二位置为1表示禁 止分片, 这时候如果报文长度超过MTU, IP模块就会丢弃报文. 第三位表示"更多分片", 如果分片了的话, 后一个分片置为1, 其他是0. 类似于一个结束标记.。
  • 13位分片偏移(framegament offset): 是分片相对于原始IP报文开始处的偏移. 其实就是在表示当前分片 在原报文中处在哪个位置. 实际偏移的字节数是这个值 * 8 得到的. 因此, 除了后一个报文之外, 其他报 文的长度必须是8的整数倍(否则报文就不连续了).。因为UDP数据段大小最大为64K,2 ^ 13 乘以 8 = 64k,所以是以8字节为单位。
  • 8位生存时间(Time To Live, TTL): 数据报到达目的地的大报文跳数. 一般是64. 每次经过一个路由, TTL -= 1, 一直减到0还没到达, 那么就丢弃了. 这个字段主要是用来防止出现路由循环 。
  • 8位上层协议:保存上层传输层使用的协议,用于数据分用的时候选择哪个上层协议进行解析。
  • 16位头部校验和: 使用CRC进行校验, 来鉴别头部是否损坏.
  • 32位源地址和32位目标地址: 表示发送端和接收端.
  • 选项字段(不定长, 最多40字节)
IP地址

IP地址分为两个部分, 网络号和主机号

网络号: 保证相互连接的两个网段具有不同的标识; 每一个路由器组建的网络都会有一个固定的网络号。
主机号: 同一网段内, 主机之间具有相同的网络号, 但是必须有不同的主机号;当每一个主机通过网线连接到这个路由器上的时候,路由器都会为每一个主机动态分配一个具有当前网络标识的IP地址。
PS:不同的子网其实就是把网络号相同的主机放到一起.。如果在子网中新增一台主机, 则这台主机的网络号和这个子网的网络号一致, 但是主机号必须不能和子网中的其他主机重复。
DHCP(自动分配IP):能够自动地给子网内新增主机节点分配IP地址的一门技术,一般的路由器都有DHCP功能,因此路由器也可以看做是一个DHCP服务器。

网段的划分

早期提出一种划分网络号和主机号的方案, 把所有IP 地址分为五类, 如下图所示:
在这里插入图片描述
这种划分方案具有局限性,如果大多数组织都申请B类网络地址, 导致B类地址很快就 分配完了, 而A类却浪费了大量地址,针对这种情况提出了新的划分方案, 称为CIDR(Classless Interdomain Routing):

  • 引入一个额外的子网掩码(subnet mask)来区分网络号和主机号;
  • 子网掩码也是一个32位的正整数. 通常用一串 “0” 来结尾;
  • 将IP地址和子网掩码进行 “按位与” 操作, 得到的结果就是网络号;
  • 子网掩码取反可以得到当前这个网络中的最大主机号;
  • 网络号和主机号的划分与这个IP地址是A类、B类还是C类无关;

练习题传送门,必须会做

特殊的IP地址(不能分配给主机 ):

  • 将IP地址中的主机地址全部设为0, 就成为了网络号, 代表这个局域网;
  • 将IP地址中的主机地址全部设为1, 就成为了广播地址, 当一台主机向广播地址发送数据时,路由器收到之后就会发送到整个局域网当中,每一个主机都会收到这个数据。
  • 127.0.0.1 IP地址为本地虚拟回环网卡地址,用于本地网络测试。
IP地址数量限制

我们知道, IP地址(IPv4)是一个4字节32位的正整数. 那么一共只有 2的32次方 个IP地址, 大概是43亿左右. 而TCP/IP 协议规定, 每个主机都需要有一个IP地址.这意味着, 一共只有43亿台主机能接入网络么? 实际上, 由于一些特殊的IP地址的存在, 数量远不足43亿; 另外IP地址并非是按照主机台数来配置的, 而是每一个网卡 都需要配置一个或多个IP地址. CIDR在一定程度上缓解了IP地址不够用的问题(提高了利用率, 减少了浪费, 但是IP地址的绝对上限并没有增加), 仍然 不是很够用. 这时候有三种方式来解决:

  • 动态分配IP地址: 只给接入网络的设备分配IP地址. 因此同一个MAC地址的设备, 每次接入互联网中, 得到 的IP地址不一定是相同的;
  • NAT地址转换技术。
  • Pv6: IPv6并不是IPv4的简单升级版. 这是互不相干的两个协议, 彼此并不兼容; IPv6用16字节128位来表 示一个IP地址;但是目前IPv6还没有普及;
私有IP地址和公网IP地址

我们一般使用私网ip作为局域网内部的主机标识,使用公网ip作为互联网上通信的标识。

如果一个组织内部组建局域网,IP地址只用于局域网内的通信,而不直接连到Internet上,理论上 使用任意的IP地址都可以,但是RFC 1918规定了用于组建局域网的私有IP地址:

  • 10 .* .* .* ,前8位是网络号,共16,777,216个地址
  • 172 . 16 . * . * 到172.31 . * . * ,前12位是网络号,共1,048,576个地址
  • 192.168 . * . * ,前16位是网络号,共65,536个地址
  • 包含在以上这个范围中的, 都称为私有IP, 其余的则称为全局 IP(或公网IP);
路由
  • 路由表可以使用 route 命令查看。
  • 如果目的IP命中了路由表, 就直接转发即可。
  • 路由表中的后一行,主要由下一跳地址和发送接口两部分组成,当目的地址与路由表中其它行都不匹配时,就按缺省路由条目规定的接口发送到下一跳地址。

在这里插入图片描述

IP数据报需从主机A上传送到主机B上,主机A首先查找路由表;
if(目的主机是与自己在同一个网段内)
{
主机A查询自己的ARP表;
if(有该目的IP地址对应的MAC地址的记录)
{
将该MAC地址作为目的MAC地址,封装数据帧,传送给主机B;
}
else
{
发送一个ARP请求广播给网段内的所有主机,来查询该目的IP地址的MAC地址;
收到ARP请求报文的各个主机如果发现该IP地址是自己的IP地址,则返回一个ARP应答报文告诉主机A自己的MAC地址;
如果发现不是自己的IP地址,则丢弃该报文。
主机A收到这个应答报文后,就按照返回的MAC地址,将IP数据包封装成帧,然后发送到主机B上;
(补充:一般为了减少网络中的报文量,通信双方会维护一个各自的ARP表,把一次通信中获得IP MAC地址对保存在缓冲的ARP表中,但是ARP表有一个老化机制,删除一段时间内不用的IP MAC地址对。)
}
}
else if(发现了能与目的网络号相匹配的表目)
{
则把报文发给该表目指定的下一站的路由器或直接连接的网络接口;
报文发送到下一站时,数据帧的目的MAC地址是下一个站路由器或者网络接口的MAC地址(由MAC地址可以得出源IP地址变为下一站路由器或者网络接口的IP地址),而IP头部的目的IP地址是主机B的IP地址;
这里要指出的是:ARP请求报文以下一站路由器或网络接口的IP地址为目的IP地址,寻找真的目的MAC地址。换句话,ARP请求报文只负责IP数据报传输过程中每一跳中的目的MAC地址查询。
}
else
{
寻找标为“默认”的表目,把报文发送给该表目指定的下一站路由器;
报文发送到下一站时,数据帧的目的MAC地址是下一个站路由器的MAC地址(由MAC地址可以得出源IP地址变为下一站路由器或者网络接口的IP地址),而IP头部的目的IP地址是主机B的IP地址。
}

数据链路层

链路层负责相邻设备之间的数据传输。

以太网协议

“以太网” 不是一种具体的网络, 而是一种技术标准; 负责相邻设备之间的数据传输用到的协议是以太网协议

以太网协议帧格式在这里插入图片描述
  • 源地址和目的地址是指网卡的硬件地址(也叫MAC地址), 长度是48位,是在网卡出厂时固化的;
  • 帧协议类型字段有三种值,分别对应IP、ARP、RARP;
  • 帧末尾是CRC校验码
MAC地址
  • MAC地址用来识别数据链路层中相连的节点; 长度为48位, 即 6 个字节. 一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19) 在网卡出厂时就确定了, 不能修改. mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址, 可能会冲突; 也有些网卡支持用户配置mac地址)

MAC地址与IP地址的区别?

  • IP地址描述的是路途总体的 起点 和 终点; MAC地址描述的是路途上的每一个区间的起点和终点;

在网络通讯时,源主机的应用程序知道目的主机的IP地址和端口号,却不知道目的主机的硬件地址; 数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃; 因此在通讯前必须获得目的主机的MAC地址,但是如何获得目的主机的MAC地址呢?于是就有了ARP协议(可以看见以太网帧头部就有一个类型字段选择ARP)

ARP协议

PS:ARP不是一个单纯的数据链路层的协议, 而是一个介于数据链路层和网 络层之间的协议;

ARP数据报的格式 在这里插入图片描述
ARP协议的作用
  • ARP协议建立了主机 IP地址 和 MAC地址 的映射关系.,通过设备的IP地址获取其MAC地址。
ARP协议的工作流程

在这里插入图片描述

  • 源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”, 并将这个请求广播到本地网段(与源主机相邻的所有主机)。
  • 目的主机接收到广播的ARP请求,发现其中的IP地址与本机相符,则发送一个ARP应答数据包给源主机,将自己的硬件地址填写在应答包中;
  • 每台主机都维护一个ARP缓存表,可以用arp -a命令查看。缓存表中的表项有过期时间(一般为20分钟),如果20分钟内没有再次使用某个表项,则该表项失效,下次还要发ARP请求来获得目的主机的硬件地址。为什么表项会过期?因为是通过设备的IP地址获取其MAC地址,而IP地址是动态分配的

MTU

以太网帧中的数据长度规定最小46字节,最大1500字节,数据的长度不够46字节,要在后面补填充位; 最大值1500称为以太网的大传输单元(MTU),不同的网络类型有不同的MTU;

MTU叫做最大传输单元,限制了一条数据(网络层封装后的报文)的最大长度。
在这里插入图片描述

MTU对TCP的影响
  • TCP在传输层进行三次握手建立连接的时候,就会协商MSS(通过MTU计算得出),然后取双方中较小的一个MSS进行数据传输。MSS的值就是在TCP首部的40字节变长选项中。保证协商的MSS在进行封装以后在网络层不会被分片。
MTU对UDP的影响
  • UDP并不会协商MSS,只要UDP携带的数据大小小于64K -20字节-8字节就可以发送, 但是在网络层封装IP头部之后的数据报若大于MTU,则就会在网络层进行分片,封装IP报头形成多个IP数据报.。但是这多个IP数据报有任意一个丢失, 都会引起接收端网络层重组失败,整个UDP报文都会被丢弃。那么这就意味着, 如果UDP数据报在网络层被分片, 整个数据被丢失的概率就大大增加了。所以用户最好在应用层进行分包的时候就分割成合适的大小。
MTU对IP协议的影响
  • 由于数据链路层MTU的限制, 对于较大的IP数据包要进行分包.

典型协议和技术

DNS协议

DNS是应用层协议 ,进行域名解析,通过便于记忆的域名获取到相应的服务器IP地址。

域名的层级划分:
  • 顶级域名(.com/.org/.gov/.edu/.cn/.us)
  • 二级域名(.baidu.com/.qq.com)
  • 三级域名(.news.baidu.com)
域名服务器的层级划分:
  • 根域名服务器
  • 顶级域名服务器
  • 二级域名服务器
  • 三级域名服务器
域名解析流程

在这里插入图片描述
以访问ww.baidu.com为例剖析域名解析流程

  • 1、浏览器缓存 :浏览器会缓存DNS记录一段时间。
  • 2、系统缓存(Windows下的hosts文件 :C:\Windows\System32\drivers\etc\hosts)如果在浏览器缓存里没有找到需要的记录,浏览器就会访问系统缓存中的是否存在记录。
  • 3、如果系统缓存没有找到所需要的记录,浏览器就会访问本地DNS服务器。
  • 4、如果本地DNS服务器没有找到所需要的记录,走到这里就会有两种方法:
迭代法
1、如果本地DNS服务器没有找到所需要的记录,就会访问根域名服务器 2、如果根域名服务器没有找到所需要的记录,就会将.com顶级域名服务器地址返回给本地DNS服务器,告诉本地DNS服务器去访问.com顶级域名服务器。如果根域名服务找到了,就直接返回给本地DNS服务器,本地DNS服务器再返回给浏览器,则解析成功,进行访问。

3、接着,本地DNS服务器就去访问.com顶级域名服务器,如果.com顶级域名服务器没有找到所需要的记录,就会将.baidu.com二级域名服务器地址返回给本地DNS服务器,告诉本地DNS服务器去访问.baidu.com二级域名服务器。如果.com顶级域名服务器找到了,就直接返回给本地DNS服务器,本地DNS服务器再返回给浏览器,则解析成功,进行访问。
. . . . . . .一直迭代
如果最后未找到所需要的,则解析失败
在这里插入图片描述

递归法
1、如果本地DNS服务器没有找到所需要的记录,就会访问根域名服务器。 2、如果根域名服务器没有找到所需要的记录,就会访问.com顶级域名服务器。如果找到了则层层返回。 3、如果.com顶级域名服务器没有找到所需要的记录,就会访问.baidu.com二级域名服务器。如果找到了则层层返回。 如果.baidu.com二级域名服务器没有找到所需要的记录,就会继续向下访问。如果找到了则层层返回。

. . . . . . 一直迭代
如果最后未找到所需要的,则解析失败。
在这里插入图片描述

浏览器中输入url后发生的事情
  • 1、域名解析。
  • 2、浏览器建立一个TCP客户端,根据HTTP协议格式,组织HTTP请求。
  • 3、经过各层相应的封装,然后路由选择将数据发送到服务器。
  • 4、服务端收到请求后,根据HTTP协议格式进行解析请求,组织响应数据,将结果返回给客户端。
  • 5、浏览器(客户端)收到响应后,对响应进行解析,最终通过HTML将数据渲染成为页面展示给用户进行浏览。

ICMP协议

网络层协议,用于进行网络探测。用于网络探测的ping工具,就是基于ICMP协议实现的。A主机给B主机发送一个请求应答报文,如果B主机应答,则说明这条路是通的。
PS:ping 是基于ICMP协议实现的,ICMP是网络层协议,网络层不涉及端口号

NAT/NAPT技术

NAT(Network Address Translation)

NAT技术(网络地址转换技术)是当前解决IP地址不够用的主要手段, 因为内部服务器的多个私网IP可以使用同一个公网IP访问外部服务器。NAT是将IP 数据包头中的IP 地址转换为另一个IP 地址的过程。在实际应用中,NAT 主要用于实现私有网络访问公共网络的功能。NAT的基本工作原理是将私有IP对外通信时转换为共有IP。路由器往往都具备NAT设备的功能
在这里插入图片描述
NAT路由器将源地址从10.0.0.10替换成全局的IP 202.244.174.37; NAT路由器收到外部的数据时, 又会把目标IP从202.244.174.37替换回10.0.0.10;

NAPT(Network Address Port Translation)

由于NAT实现是私有IP和NAT的公共IP之间的转换,那么,如果局域网内, 有多个主机都访问同一个外网服务器, 那么对于服务器返回的数据中, 目的IP都是相同的. 那么NAT路由器如何判定将这个数据包转发给哪个局域网的主机? 这时候NAPT来解决这个问题了.。使用IP+port来建立这个关联关系 。

NAPT就是在NAT地址转换的基础上增加一个映射表,发送的地址与转换后的地址映射,通过这个映射表就可以知道收到的哪个回复应该发送给哪个局域网的主机
这种关联关系也是由NAT路由器自动维护的. 例如在TCP的情况下, 建立连接(发出SYN)时, 就会生成这个表项; 在断开连接(发出FIN)后, 就 会删除这个表项

在这里插入图片描述

由于NAT依赖这个转换表,所以NAT/NAPT 缺点:

  • 无法从NAT外部向内部服务器建立连接
  • 转换表的生成与转换操作都会产生性能开销
  • 通信过程中,如果NAT路由器重启了,所有的TCP连接都会断开

如何解决NAT/NAPT潜在的问题(主要解决这个转换表带来的的性能开销)?

  • IPV6:IPV6应用范围非常大,以至于每台设备都可以配置一个工友IP地址,就不用高那么多花里胡哨的地址转换了。
  • NAT穿透技术:客户端主动从NAT设备获取公有IP地址,然后自己建立端口映射条目,然后利用这个条目对外通信,就不需要NAT设备来进行转换了。

代理

什么是代理?

客户端像代理服务器发送请求, 代理服务器将请求转发给真正要请求的服务器; 服务器返回结果后, 代理服务器又把结果回传给客户端.。(比如:玩儿游戏时的加速器,翻墙)

NAT和代理服务器的区别
  • 从应用上讲, NAT 解决的是 IP 不足的问题。 代理服务器则是更贴近具体应用, 比如通过代理服务器进行翻墙, 另外像迅游这样的加速器, 也是使用代理服务器.。
  • 从底层实现上讲, NAT是工作在网络层, 直接对IP地址进行替换.;代理服务器往往工作在应用层.,要求源端先将数据发送给自己,再把请求发送给目标服务器
  • 从使用范围上讲, NAT一般在局域网的出口部署, 代理服务器可以在局域网做, 也可以在广域网做, 也可以跨网。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值