Lesson12 udptcp协议

news/2024/5/18 14:09:11 标签: tcp/ip, udp, 网络

netstat命令->查看网络状态

  •  n 拒绝显示别名,能显示数字的全部转化成数字
  • l 仅列出有在 Listen (监听) 的服務状态
  • p 显示建立相关链接的程序名
  • t (tcp)仅显示tcp相关选项
  • u (udp)仅显示udp相关选项
  • a (all)显示所有选项,默认不显示LISTEN相关

pidof命令->查看进程id

  •  pidof + 进程名 -> 查看进程id

 UDP协议

UDP协议端格式

1. 如何分离(封装) 

  •  固定长度的报头,就能将报头和有效载荷分离

 2.如何交付

  • 根据报头中的16位端口号,进行向上交付(进程bind了端口号)

 udp是如何正确的提取整个完整的报头

  • 固定长度的报头 -> 16位udp长度,这也解释了在应用层写端口号的时候,类型是uint16_t类型的

 3.理解udp报文本身

4. sendto/recvfrom/write/read/recv/send ...io类接口

 

  •  这些函数本质都是拷贝函数

5.udp 全双工 && 半双工

  • udp全双工的
  • 全双工: 一边读,一边写(可以同时发生)
  • 半双工: 只能一边读,或者一边写(不可以同时发生)

TCP协议

TCP协议端格式 

  • 4为首部长度虽然是4个bit位,但是单位是字节
  • 0000->1111 => 0 -> 15,即范围是0->60字节
  • 报头的范围: [20,60]
    • x * 4 = 20 推出 x = 5->0101
    • x * 4 = 60 推出 x = 15->1111

1. TCP是如何交付的

  • 根据报头中的16位端口号,进行向上交付(进程bind了端口号)

2. TCP是如何解包的

  •  先提取20字节

  • 根据标准报头,提取4位首部长度 * 4 = 20 - done

  • 读取[提取4位首部长度 * 4 - 20]字节数据,选项

  • 读完了报头,剩下的都是有效载荷

3.理解可靠性

  •  在网络不存在100%可靠的协议,无论那台主机都无法保证自己作为最新发送数据的一方
    发送出去的数据都能被对方收到
  • 但是在局部上能做到100%可靠,只有有匹配应答,就能保证刚发出去的消息,对方收到了
  • TCP协议的确认机制: 只要一个报文收到了对应的应答,就能保证我发出的数据对方收到了

4.序号和确认序号

  •  client一次可能向服务器发送多个报头,就会有一个问题,发送的顺序,不一定是接收顺序
  • 序号和确认序号,就是解决这个问题的 

序号和确认序号:

  1.  将请求和应答进行一一对应
  2. 确认序号,表示的含义:确认序号之前的数据已经全部收到的
  3. 允许部分确认丢失,或者不给应答
  4. 为什么要有两个字段数字
    1. 任何通信的一方,工作方式都是全双工的,在发送确认的时候,也可能携带新的数据
  5.  1000 2000 3000 -> 2000 1000 3000,发送和接收的顺序不一定一样,因为任何一方都会收到报文,但报文中会携带序号(可以排序)

TCP的接收缓冲区和发送缓冲区 

  • tcp有发送和接收缓冲区,当我们有clientserver,我们就要有两对接收和发送缓冲区

TCP的6个标记位

  • TCP的6个标记位都是标记报文类型
  • SYN: 该报文是一个链接请求报文
  • FIN: 该报文是一个断开链接请求的报文
  • ACK: 确认应答标志位,
    • 凡是该报文具有应答特征,该标志位都会被设置成1,大部分网络报文ACK都是被设置为1的
      除了一个链接请求报文
  •  RST: reset,连接重置
  • PSH: PUSH,督促对方尽快将数据进行向上交付
  • URG: 紧急标志位,16位紧急指针

1.理解TCP的连接

  •  因为有大量的client将来可能会链接server,所以server端一定会存在大量的连接
  • OS会通过先描述,再组织的方式管理这些连接
  • 所谓的连接:本质其实就是内核的一种数据结构类型,建立连接成功的时候,就是在内存中创建对应的连接对象,再对多个连接对象进行某种数据结构的组织
  •  维护连接是有成本的(内存+cpu)

2. 理解TCP的三次握手 

 

  •  这三次握手不一定都要保证成功

为什么需要三次握手

  • 站在client端,他认为只要发送了SYN给server,就建立了连接
  • 站在server端,他认为只要发送了SYN给client,就建立了连接
  • 但实际上需要收到对方的应答,才算是建立的连接,
  • 所以1次肯定不行,偶数次也不行,3次就刚好可以(客户端和服务端都会建立链接)
  • 这3次握手也能验证全双工,

 3. 理解四次挥手

  •  如果我们发生服务器中具有大量的close_wait状态的连接
    • 有可能是因为应用层写的时候有bug(没有关闭对应的连接sockfd)
  •  为什么会有TIME_WAIT状态,而不是直接就是CLOSED状态
    • 虽然4次挥手已经完成,但是主动断开连接的一方要维持一定的TIME_WAIT状态,
    • 在该状态下,连接其实已经结束了,但是地址信息ip和port依旧是被占用的,等待上面其他的发送结束(等待时间也是不确定的)

setsockopt函数 

  • 4次挥手结束后TIME_WAITCLOSED状态的时间太长,该函数会缩短这个时间

确认应答(ACK)机制 

TCP将每个字节的数据都进行了编号. 即为序列号

  •  每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发.

超时重传机制

  • 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;
  • 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发
但是 , 主机 A 未收到 B 发来的确认应答 , 也可能是因为 ACK 丢失了

  •  主机B会收到很多重复数据. 那么TCP协议通过序列号就能够识别出那些包是重复的包, 并且把重复的丢弃掉.达到去重的效果

超时时间的影响

  • 这个时间的长短, 随着网络环境的不同, 是有差异的.
  • 如果超时时间设的太长, 会影响整体的重传效率;
  • 如果超时时间设的太短, 有可能会频繁发送重复的包;

解决方案 

TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间 

  • Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时间, 他都是500ms的整数倍.
  • 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
  • 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
  • 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

 连接管理机制

 

 

 滑动窗口

确认应答策略 ,
对每一个发送的数据段 , 都要给一个 ACK 确认应答 . 收到 ACK 后再发送下一个数据段 .
这样做有一个比较大的缺点,
就是 性能较差 . 尤其是数据往返的 时间较长 的时候

  •  窗口大小指的是无需等待确认应答而可以继续发送数据的最大值.
    上图的窗口大小就是4000个字节(四个段).
  • 发送前四个段的时候, 不需要等待任何ACK, 直接发送;

  •  滑动窗口: 在自己的发送缓冲区中,属于自己的发送缓冲区中的一部分
    • 一边想要给对方推更多的数据,另一边又想要对方来得及接收
  • 滑动窗口的上限 == 对方的接收能力
  • 本质: 让sender方,可以一次性发送对方接收数据的上限

滑动窗口的进一步理解

  • 滑动窗口的本质: 就是指针或下标 

  • 更新:win_start = 收到的应答报文中的确认序号,
            win_end=win_start + 收到报文中的窗口大小

  • 滑动窗口不一定都是右移也可以为0
  • 如果收到的不是开始的报文的应答,而是中间的,不影响
  • 滑动窗口如果一直右移滑动,是不存在越界问题的

 快重传 vs 超时重传

  • 快重传是有条件的, 
  • 这两个不是对立的,而是相互协作

拥塞控制

  • 虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据.
    但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
  • 因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵.
    在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.
  • TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

 

  • 拥塞窗口: 单台主机一次向网络中发送大量数据时,可能会引发网络拥塞的上限值
  • 滑动窗口的大小 = min(拥塞窗口,对方窗口大小(接收能力))

拥塞窗口变化大小

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

拥塞窗口变化原因

  • 它是指数级别增长,特点: 前期慢,后期快
  •  在网络拥塞时
    • 前期要让网络有一个缓一缓的机会
    • 中后期,网络恢复之后,需要尽快恢复通信 -> 避免影响通信效率

拥塞控制的总结

  • 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
    • TCP通信开始后, 网络吞吐量会逐渐上升;
    • 随着网络发生拥堵, 吞吐量会立刻下降;
  • 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方,
    但是又要避免给网络造成太大压力的折中方案.

延迟应答

  

  • 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
  • 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
  • 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;

延迟应答的时间

  • 数量限制: 每隔N个包就应答一次;
  • 时间限制: 超过最大延迟时间就应答一次

捎带应答

 

  •  ACK可以和服务器回应的 信息 一起回给客户端

 面向字节流

创建一个 TCP socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
  • 调用 write 时, 数据会先写入发送缓冲区中;
  • 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
  • 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
    然后应用程序可以调用
    read 从接收缓冲区拿数据;
  • 另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工

 由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:

  • 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
  • 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;

粘包问题

  • 首先要明确, 粘包问题中的 "包" , 是指的应用层的数据包 

  • 站在应用层的角度, 看到的只是一串连续的字节数据 
  • 明确两个包之间的边界,就可以解决这个问题 

TCP异常情况

  • 进程终止: 进程终止会释放文件描述符 , 仍然可以发送 FIN. 和正常关闭没有什么区别 .
    机器重启 : 和进程终止的情况相同 .
  • 机器掉电/网线断开 : 接收端认为连接还在 , 一旦接收端有写入操作 , 接收端发现连接已经不在了
    就会进行 reset .即使没有写入操作, TCP自己也内置了一个保活定时器 ,
    会定期询问对方是否还在 . 如果对方不在 , 也会把连接释放 .

TCP小结

要保证可靠性, 同时又尽可能的提高性能 

可靠性:

  • 1.校验和,2.序列号(按序到达) 3.确认应答 4.超时重发 5.连接管理 6流量控制 7.拥塞控制 
提高性能:
  • 1.滑动窗口 2.快速重传 3.延迟应答 4.捎带应答

TCP/UDP对比

  •  TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景; 
  • UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播
  • TCP不 一定就优于 UDP,他们 之间的优点和缺点 , 不能简单 , 绝对的进行比较,需要看具体场景

理解 listen 的第二个参数

 

  •  首先accpet函数是从已经建立好连接中,获取对应的连接,它是不需要参与三次握手的

  •  但如果上层来不及调用accept函数,并且对端还来的大量的连接,那怎么处理这些连接?
    • 服务器本身要维护一个连接队列,不能没有,不能太长
    • 这个连接队列就和listen的第二个参数有关
  • 这个队列的长度 = listen的第二个参数 : ESTABLISHED -> SYN_RECV 

演示

 Sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

class Sock
{
private:
    // listen的第二个参数,意义:底层全连接队列的长度 = listen的第二个参数+1
    const static int gbacklog = 1;

public:
    Sock() {}
    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            exit(2);
        }
        int opt = 1;
        setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        return listensock;
    }
    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            exit(3);
        }
    }
    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            exit(4);
        }

    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }
    bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

main.cc

#include "Sock.hpp"

int main()
{
    Sock sock;
    int listensock = sock.Socket();
    sock.Bind(listensock, 8080);

    sock.Listen(listensock);

    while(true)
    {
        sleep(1);
    }
}

 Makefile

TcpServer:main.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f TcpServer

 

 


http://www.niftyadmin.cn/n/228503.html

相关文章

获美国企业认可,中国大飞机取得重大突破,已具备挑战波音的实力

日前消息指美国通用电气资本航空服务公司订购了20架中国商飞C919客机&#xff0c;这是中国大飞机首次获得美国航空企业的认可&#xff0c;代表着中国大飞机的重大突破&#xff0c;证明中国的大飞机已达到国际先进水平。通用电气资本航空服务公司是全球最大的飞机租赁公司&#…

Nestjs实战干货-概况-异常过滤器-Exception filters

异常过滤器 Nest 带有一个内置的异常层&#xff0c;负责处理应用程序中所有未处理的异常。当应用程序代码未处理异常时&#xff0c;该层会捕获该异常&#xff0c;然后自动发送适当的用户友好响应。 开箱即用&#xff0c;此操作由内置的全局异常过滤器执行&#xff0c;该过滤器…

python+vue 基于推荐算法的在线电影视播放网站

以广大影视剧迷们为研究对象&#xff0c;深入了解影视剧迷对在线视频观看视频的需求进行分析&#xff0c;形成系统需求分析设计一个符合影视剧迷们需求的在线视频网站。设计网站的前期工作包括对系统的各个功能进行详细分析&#xff0c;对数据库设计进行详细的描述&#xff0c;…

算法记录 | Day30 回溯算法

332.重新安排行程 思路&#xff1a; 1.确定回溯函数参数&#xff1a;定义全局遍历存放path&#xff0c; 2.终止条件&#xff1a;遍历完所有路径&#xff0c;机场个数&#xff0c;如果达到了&#xff08;航班数量1&#xff09;&#xff0c;即path的长度应当为字符串二维数组长…

【Python机器学习】——决策树

Python机器学习——决策树 文章目录 Python机器学习——决策树一、Python机器学习 决策树一、Python机器学习 决策树 决策树(Decision Tree) 在本章中,我们将向您展示如何制作“决策树”。决策树是一种流程图,可以帮助您根据以前的经验进行决策。 在这个例子中,一个人将尝…

SpringBoot高校毕业生就业信息管理系统 附带详细运行指导视频

文章目录一、项目演示二、项目介绍三、运行截图四、主要代码一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBoot框架开发的高校毕业生就业信息管理系统项目。首先&#xff0c;这是一个响应式的项目&#xff0c;代码简…

Java通过显示弹奏音乐的方式来实现继承的有关方法

目录 前言 一、Music.java类 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 二、Brass.java类 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 三、Wind.java类 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 四、Instrument.java类 1.1运行流程&…

新建论文三线表模板,一键格式刷

论文三线表模板写在最前面①表设计&#xff0c;新建表格样式②三线表上下线③三线表标题线④设置表格居中⑤设置表头格式容易出错的步骤写在最前面 论文写完啦&#xff0c;准备调整格式 之前建模也是三线表&#xff0c;但只能基于该文档模板&#xff0c;所以重新设置一下。 如…