【Hello Network】TCP协议相关理解

news2024/11/24 23:12:01

作者:@小萌新
专栏:@网络
作者简介:大二学生 希望能和大家一起进步
本篇博客简介:补充下对于TCP协议的各种理解

TCP协议相关实验

  • TCP相关试验
    • 理解CLOSE_WAIT状态
    • 理解TIME_WAIT状态
    • 解决TIME_WAIT状态引起的bind失败的方法
    • 理解listen的第二个参数
    • SYN洪水攻击
  • TCP和UDP
    • TCP和UDP协议对比
    • 用UDP实现可靠传输(经典面试题)

TCP相关试验

理解CLOSE_WAIT状态

我们要理解CLOSE_WAIT状态 首先要知道什么时候会出现CLOSE_WAIT状态

在这里插入图片描述

当我们的客户端和服务器通信的时候 客户端调用close函数关闭其文件描述符 此时客户端在底层就会像服务器发送FIN报文 服务器给予FIN报文响应的ACK报文之后就会变成CLOSE_WAIT状态 而客户端会在第一次发送FIN报文的时候变为FIN_WAIT1状态 在收到ACK报文后变为FIN_WAIT2状态

在这里插入图片描述

我们可以编写一个简单的TCP服务器来模拟出该现象 不过事实上我们只需要写出服务器的代码就可以了 客户端的代码我们可以使用一些现成的工具比如说TELNET

服务器代码如下

  #include <iostream>    
  #include <cstring>    
  #include <unistd.h>    
  #include <sys/socket.h>    
  #include <sys/types.h>    
  #include <arpa/inet.h>    
  #include <netinet/in.h>    
  #include <pthread.h>    
  using namespace std;    
      
  const int PORT = 8081;    
  const int NUM = 5;    
      
      
  void* RUN(void* arg)    
  {    
    pthread_detach(pthread_self());    
    int fd = *(int*)arg;    
    delete arg;                                                                                                                                                                                                                                                                                                                                                                                                                                              
    while(1)    
    {    
      cout << "sockfd" << fd << "is severing the client" << endl;    
      sleep(1);    
    }    
      
    return nullptr;    
  }    
      
      
      
      
  int main()    
  {    
    int listen_socket = socket(AF_INET , SOCK_STREAM , 0);    
    if (listen_socket < 0)    
    {    
      cerr << "listen_socket error" << endl;    
      exit(1);    
    }    
      
    struct sockaddr_in local;    
    memset(&local , '\0' , sizeof(local));    
    local.sin_family = AF_INET;    
    local.sin_port = htons(PORT);    
    local.sin_addr.s_addr =  INADDR_ANY;    
      
    if (bind(listen_socket , (struct sockaddr*)&local , sizeof(local)) < 0)    
    {    
      cerr << "bind error" << endl;    
      exit(2);    
    }    
      
    if(listen(listen_socket , NUM) < 0)    
    {    
      cerr << "listen error" << endl;    
      exit(3);    
    }    
      
    // peer     
    struct sockaddr_in peer;    
    memset(&peer , '\0' , sizeof(peer));    
    socklen_t len = sizeof(peer);    
      
    for(;;)    
    {    
      int sockfd = accept(listen_socket , (struct sockaddr*)&peer , &len);    
      if (sockfd < 0)    
      {    
        cerr << "accept error" << endl;    
        continue;    
      }    
      
      cout << "get a new linl:" << endl;    
      int* p = new int(sockfd);    
      
      pthread_t tid;    
      pthread_create(&tid , nullptr , RUN , (void*)p);    
    }    
      
      
    return 0;    
  }  

我们使用netstat查看 可以发现两条连接 一条是客户端的连接 一条是服务器的连接

在这里插入图片描述

当我们现在将Telnet推出后客户端维护的连接状态会变为FIN_WAIT2 (因为它调用close函数到收到响应这段时间太短了 以至于我们捕捉不到FIN_WAIT1状态) 服务器的状态就会变为CLOSE_WAIT状态 因为我们在内部没有调用close函数 所以说服务器不会发起最后的第三次挥手
在这里插入图片描述

理解TIME_WAIT状态

当客户端和服务器进行通信的时候 客户端调用close函数关闭对应的文件描述符 如果服务器收到后也调用close函数进行了关闭 那么此时双方将正常完成四次挥手

但是主动发起四次挥手的一方 在四次挥手之后并不会立即进入COLSED状态 而是会短暂的进入TIME_WAIT状态中等待一段时间

我们继续刚刚的试验 由于telnet推出后服务器没有调用close函数 所以说客户端的变为了TIME_WAIT 服务器的状态变为了CLOSE_WAIT

之后如果我们想要见到TIME_WAIT状态 我们就只需要方服务器调用close函数就可以 但是我们在服务器端的源码中并没有调用close函数 所以说我们要想办法关闭服务器打开的文件描述符

我们在前面一篇博客 TCP协议详解中说明了TCP连接异常断开的几种情况 其中我们说过 如果进程崩溃那么其实是和正常完成四次挥手没有区别的 而进程崩溃实际上就是操作系统给进行发送了信号 所以说我只需要我们使用键盘让OS向该进程发送信号就能够模拟服务器正常调用close函数的情况

再刨根问底一点 为什么操作系统向进程发送信号之后就相当于会调用close函数呢? 这里其实是因为文件描述符的生命周期是随进程的 而进程在被操作系统杀死后操作系统会回收资源 这些资源中肯定就包括文件描述符了

所以说此时我们先让telnet退出 紧接着让服务器退出 就能见到TIME_WAIT状态了

在这里插入图片描述

解决TIME_WAIT状态引起的bind失败的方法

我们在前面介绍过 四次挥手的时候主动发起挥手一方在四次挥手结束的时候会进入TIME_WAIT状态一段时间

如果服务器在有客户端的情况下主动退出了 就相当于是服务器先进行了四次挥手 那么服务器就将要进入TIME_WAIT一段时间

如果我们此时想要重新启动服务器绑定该端口号我们会发现我们绑定失败了

在这里插入图片描述

这是因为在TIME_WAIT期间 这个连接并没有被完全释放 这也就意味着我们的端口是被占用着的 此时服务器想要继续绑定该端口号就只能等待TIME_WAIT时间结束

当时我们服务器崩溃时最重要的就是让服务器重新启动并且是绑定原来的端口号 (因为一般一些服务的端口号都是固定的 如果你修改了端口号的话在网络中别人可能就不知道你了)如果想要让服务器崩溃后在TIME_WAIT期间也能立马重新启动 需要让服务器在调用socket函数创建套接字后 继续调用setsockopt函数设置端口复用 这也是编写服务器代码时的推荐做法

setsockopt函数

setsockopt函数可以设置端口复用 该函数的函数原型如下:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

返回值说明:

  • 设置成功返回0 失败返回-1 同时错误码会被设置

参数说明:

  • sockfd:需要设置的套接字对应的文件描述符
  • level:被设置选项的层次 比如在套接字层设置选项对应就是SOL_SOCKET
  • optname:需要设置的选项 该选项的可取值与设置的level参数有关
  • optval:指向存放选项待设置的新值的指针
  • optlen:待设置的新值的长度

我们这里要设置的就是监听套接字 将监听套接字在套接字层设置端口复用选项SO_REUSEADDR 该选项设置为非零值表示开启端口复用

int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

在这里插入图片描述

当我们主动发送信号让服务器崩溃之后我们立刻再次启动服务器 我们发现此时就不会出现bind error错误了

理解listen的第二个参数

在我们编写TCP套接字服务器的时候 在进行了套接字的创建和绑定之后需要调用listen函数将服务器变为监听状态 在这之后我们就能使用accept函数建立连接了

我们在调用listen函数的时候需要输入两个参数 第一个参数是我们要设置为监听状态的套接字 第二个参数是一个数字 它表示服务器可以建立的最大连接数

下面我们会通过一个试验来试验下listen的第二个参数是不是这个意思 试验步骤如下 :

  • 首先编写tcp套接字的服务器代码 关于我们这次的代码 我们只需要创建套接字 绑定 监听但是服务器初始化之后我们不调用accept函数建立连接
  • 为了方便验证 我们这里将listen的第二个参数设置为2

服务器代码如下

#include <iostream>    
using namespace std;    
#include <cstring>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/socket.h>    
#include <arpa/inet.h>    
#include <netinet/in.h>    
#include <pthread.h>    
using namespace std;    
    
    
int main()    
{    
  // create socket     
  int listen_sockfd = socket(AF_INET , SOCK_STREAM , 0);    
  if (listen_sockfd < 0 )    
  {    
    cerr << "socket error" << endl;    
    exit(1);    
  }    
    
  int opt = 1;    
  setsockopt(listen_sockfd , SOL_SOCKET , SO_REUSEADDR , &opt , sizeof(opt));    
    
  // bind     
  struct sockaddr_in local;    
  memset(&local , '\0' , sizeof(local));    
  local.sin_family = AF_INET;    
  local.sin_port = htons(8081);    
  local.sin_addr.s_addr = INADDR_ANY;    
    
  if (bind(listen_sockfd , (struct sockaddr*)&local , sizeof(local)) < 0 )    
  {    
    cerr << "bind error" << endl;    
    exit(2);    
  }    
                                                                                                                                                                                                                                                                                                                                                                    
  // listen     
  listen(listen_sockfd , 2);    
    
  for(;;)    
  {    
    ;    
  }    
  return 0;    
}  

在运行./sever服务之后 我们可以使用 netstat -nlpt命令 来查看处于监听状态的进程

在这里插入图片描述
接下来使用Postman向我们的服务器发送一个请求

关于怎么使用Postman大家可以参考这个视频

如何使用Postman

在这里插入图片描述

说明: 需要说明的是 我们这里之所以不选用浏览器(浏览器的就是基于TCP协议的) 是因为浏览器如果没有收到服务端的响应可能会重复发送 这个现象在我们介绍应用层的HTTP协议的时候有涉及

我们通过Postman发起请求之后可以使用下面的命令查看建立完毕的连接

netstat -npt | head -2 && netstat -npt | grep 8081

在这里插入图片描述

我们可以发现该连接现在处于ESTABLISHED状态 接下来我们继续时候Postman发送第二 三个请求

在这里插入图片描述

由于我们的服务器并没有调用accept函数获取已经建立好的连接 所以说服务器已经建立好的连接是三个

我们继续尝试下使用Postman发送第四个请求

我们查看当前服务器的网络连接之后发现了这样子的现象

在这里插入图片描述
此时我们并没有增加ESTABLISHED状态了 而是增加了一个SYN_RECV状态

而我们回看三次握手的场景

在这里插入图片描述

我们可以发现SYN_RECV状态出现在客户端给服务器发送建立连接请求但是服务器没有响应的时候

也就是说刚刚的Postman发送请求之后 我们服务器并没有回应客户端的请求

过一段时间之后我们继续服务建立连接的状态

在这里插入图片描述
我们可以发现在一段时间过后三次握手失败 该SYN_RECV连接就被自动释放了

而在Postman上表现为这样子

在这里插入图片描述

总结下上面的试验现象

  • 不论有多少的客户端请求连接 最终在我们的服务器端只会有三个连接建立成功
  • 当发来第四个SYN请求的时候 服务只是收到了却不做响应 等待三次握手失败后自动释放连接

listen的第二个参数

我们直接对于上面的现象给出解释

实际上我们在进行TCP连接管理的时候会用到两个连接队列

  • 全连接队列(accept队列) 全连接队列用于保存处于ESTABLISHED状态 但没有被上层调用accept取走的连接
  • 半连接队列 半连接队列用于保存处于SYN_SENT和SYN_RCVD状态的连接 也就是还未完成三次握手的连接

我们在这里谈及Listen第二个参数的原因是 在linux操作系统中 全连接队列的长度就等于Listen的第二个参数+1 所以说我们服务器的Listen参数设置为2 此时服务器的全连接队列长度就为3 也就是说此时能够建立ESTABLISHED状态的连接就为3个

而当全连接队列满了之后服务器此时就不能够建立ESTABLISH连接了 此时如果客户端请求建立连接那么服务器不会进行SYN+ACK响应 而是会将该连接放在半连接队列中 状态变为SYN_RECV 在一段事件后如果三次握手还没有成功则会自动释放连接

此外如果半连接队列也满了 服务器会自动拒绝所有连接请求

如果被accept了全连接队列减少一个连接吗?

如果全连接中的队列被accept之后全连接队列会减少的

我们可以把服务器想象成一个饭店 这个饭店可以提供128人吃饭(服务器可以接收的连接数默认是128)

但是这个饭店太火爆了 几乎去的时候人都是满的 (参考下海底捞) 于是饭店老板便在饭店外面放了X+1个椅子(X为Listen的第二个参数 ) 坐在饭店椅子上的人就可以认为他们正处于EXTABLISHED状态 当饭店里面有人吃饭完饭店服务员就是使用accept函数让他们进来吃饭 此时我们就可以认为全连接队列少了一个连接 (此时在旁边站着的 也就是半连接队列的人就可以坐上椅子 变为EXTABLISHED状态)

为什么底层要维护连接队列?

其实我们在服务器压力较大的时候才能看出来连接队列的作用 我们上面的例子就能很好的说明原因 如果饭店没有满员的话服务员完全可以直接使用accept函数让人进去吃饭 没有必要让别人坐在外面的椅子上了

事实上 在服务器运行的时候我们就预先在服务器上面预先创建了多个线程 当主线程从底层accept上来连接之后这些连接就会交由其他线程处理

在服务器不繁忙的情况下连接一旦在底层建立好就会交由我们的服务线程处理了

可是当服务器繁忙的时候如果没有连接队列 我们的服务器就会拒绝其他的连接请求

如果拒绝之后我们的线程有处理完毕手上的连接了 但是此时没有任何新的连接请求发过来 那么此时该线程就会处于闲置状态

所以说连接队列的存在是必要的 它能够保证我们的服务器满负荷工作

连接队列长度问题

全连接队列的长度我们一般设置为5

事实上全连接的队列的长度不能太长也不能太短

如果说全连接的队列长度太长

  • 这就意味着位于队列尾部的连接需要较长的时间才能享受到服务 此时客户端的请求需要较长时间才能收到响应
  • 服务器维护这些连接也需要成本 如果能够维护很长的连接不如直接扩建“饭店”了

当然服务器全连接的长度也不能太短 比如说如果全连接队列的长度只有一 那么在连接数很大的情况下和没有连接队列没什么两样

全连接队列的长度

全连接队列的长度由两个值决定

  • 用户层调用listen时传入的第二个参数backlog
  • 系统变量net.core.somaxconn 默认值为128

事实上我们全连接队列的长度就等于这两个队列长度的较小值+1

SYN洪水攻击

在这个小节中我们会讲解SYN洪水攻击是什么以及应用层和传输层防范SYN洪水攻击的方式

正常三次握手方式

在这里插入图片描述

如上图所示

  • 首先客户端会向服务器发送SYN连接请求 服务器收到之后会响应SYN+ACK给客户端并且将该连接放入半连接队列中
  • 此时如果客户端接收到了服务器发送的SYN+ACK报文并回复ACK给服务器 那么服务器就会将该连接从半连接队列中放到全连接队列中
  • 此时上层就可以通过调用accept函数 从全连接队列当中获取建立好的连接了

SYN洪水攻击

在这里插入图片描述

异常情况:

  • 客户端在发起连接建立请求后突然死机或掉线 那么服务器发出的SYN+ACK就得不到对应的ACK应答
  • 这时候服务器由于收不到对方的ACK就会触发超时重传机制 如果经过多次重发之后还是收不到ACK应答此时服务器就会关闭这个连接 我们将该时间称为SYN timeout
  • 在SYN timeout时间内这个连接一直维护在半连接队列中

此时服务器虽然需要短暂维护这些异常连接 但这种情况毕竟是少数 不会对服务器造成太大影响

但是如果有用户恶意大量模拟这些情况

  • 此时服务器就需要一个非常大的半连接队列 并且实际上这些连接都是无用的
  • 当半连接队列被占满之后 新来的连接都会被服务器直接拒绝
  • 我们把这种发送大量的SYN 但是却不对服务器的 SYN+ACK请求进行响应的行为 叫做SYN洪水攻击

如何防范SYN洪水攻击呢?

防范SYN洪水攻击的时候肯定不光要考虑传输层 还要考虑应用层

应用层:

  • 我们应用层可以建立黑名单机制 如果一个ip在短时间内发送了大量的SYN请求我们就可以将这个ip封禁 对于此ip发送的SYN请求一律置之不理

传输层:

  • 增大半连接队列长度 但是其实这个方案能够起到的效果很少 因为都说了是SYN洪水肯定 肯定能够发送大量的SYN请求如果我们增大长度对方只需要发送更多的SYN请求就好了
  • 减少超时重发的次数 同样的这个也是治标不治本的做法

传输层最有效的方式应该是引入syncookie机制 当服务器收到一个连接建立请求后 会根据这个SYN包计算出一个cookie值 将其作为将要返回的SYN+ACK包的初始序号 然后将这个连接放到一个暂存队列当中

在这里插入图片描述

当服务器收到客户端的ACK响应时 会提取出当中的cookie值进行对比 对比成功则说明是一个正常连接 此时该连接就会从暂存队列当中移到全连接队列供上层读取

引入了syncookie机制的好处:

  • 引入了syncookie机制之后 这些异常连接就不会堆积到半连接队列中了 而是会放在暂存队列中
  • 对于正常的SYN请求 一般会立刻对于服务器的SYN+ACK进行ACK应答 所以说会很快建立正常连接成功
  • 而异常连接或者说是SYN洪水攻击 不会对于服务器的SYN+ACK请求进行ACK应答 所以说异常连接都会堆积到队列中

TCP和UDP

TCP和UDP协议对比

在对比他们之间我们首先要知道TCP和UDP协议是什么

TCP协议

TCP协议叫做传输控制协议(Transmission Control Protocol)它是一种面向连接的 可靠的 基于字节流的传输层协议

TCP协议是面向连接的 如果两台主机之间想要进行通信就需要先建立连接 连接建立成功之后进行数据传输

其次TCP协议是保证可靠的协议 如果数据在传输的过程中出现了丢包 乱序等问题 TCP协议都有自己的相解决方案

UDP协议

UDP协议叫做用户数据报协议(User Datagram Protocol) 它是一种无需连接的 不可靠的 基于数据包的传输层协议

使用UDP协议的时候无需建立连接 如果两台主机之间想要进行通信 那么直接将数据发送给对端主机就可以了

上面的描述其实也就概括了UDP协议是不可靠的 如果数据在传输的过程中出现了丢包 乱序等问题 UDP协议都不知道 更别说解决了

TCP/UDP对比

TCP协议是可靠的 UDP协议是不可靠的 但是这并不意味着TCP协议比UDP协议要好 UDP协议不可靠也就意味着UDP协议足够简单 TCP可靠也就意味着TCP所做的准备工作也就越多 但是需要注意的是TCP所做的准备工作越多并不意味着TCP越慢 事实上它们的效率是差不多的

  • TCP常用于需要可靠传输的情况 比如说文件传输 重要更新等场景
  • UDP常用于对高速传输和实时性较高的通信领域 比如说QQ 视频传输等 此外UDP也可以用于广播

UDP协议和TCP协议不存在孰优孰劣这种说法 只有在某个场景中使用哪个协议更合适

用UDP实现可靠传输(经典面试题)

这里有一道经典的面试题:

使用UDP来实现可靠传输

我们提到传输层的可靠协议就能想到TCP协议了 所以说这道面试题的本质其实是在考查你对于TCP协议的了解程度

当我们知道了这道面试题的时候我们首先应该问面试官具体的应用场景 之后根据场景的需要往TCP协议上套

比如说:

  • 引入序列号 保证数据按序到达
  • 引入确认应答 确保对端接收到了数据
  • 引入超时重传 如果隔一段时间没有应答 就进行数据重发
  • … …

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/499602.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式 03 富文本处理插件和图片文件上传

01.使用富文本编辑器来上传图片文件。 02.最开始在html文件中去使用相关富文本的插件。 引入相关文件 <link href"/js/kindeditor-4.1.10/themes/default/default.css" type"text/css" rel"stylesheet"> <script type"text/java…

Redis面试题(上)

1.什么是 Redis&#xff1f; Redis 是一种基于内存的数据库&#xff0c;对数据的读写操作都是在内存中完成&#xff0c;因此读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列、分布式锁等场景。 Redis 提供了多种数据类型来支持不同的业务场景&#xff0c;比如 Strin…

Python机器学习入门 -- 支持向量机学习笔记

文章目录 前言一、支持向量机简介二、支持向量机的数学原理1. 距离解算2. 目标函数3. 约束下的优化求解4. 软间隔优化5. 核函数变换 三、Python实现支持向量机1. 惩罚力度对比2. 高斯核函数3. 非线性SVM 总结 前言 大部分传统的机器学习算法都可以实现分类任务&#xff0c;但这…

干货丨你真的了解反应持续时间吗?

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐~ 在今天的推文里&#xff0c;要给大家分享的是一种灵活、免费的心理科学工具——反应持续时间&#xff0c;快来一起看看哦~ 01 导读 简单按键的反应持续时间是一种容易获得但未被充分利用…

C++相比于C语言增加的8个小特性(详解)

C相比于C语言增加的8个小特性&#xff08;详解&#xff09; 文章目录 C相比于C语言增加的8个小特性&#xff08;详解&#xff09;一、命名空间二、C输入和输出三、缺省参数四、函数重载五、引用六、内联函数七、auto关键字八、指针空值nullptr总结 一、命名空间 c的命名空间是…

从一到无穷大 #8 Arrow,Parquet and ORC

文章目录 引言ArrowParquetNested EncodingRepetition LevelsDefinition Levels 列化压缩 ORC 引言 以我的机器为例来做一个简单的计算&#xff1a; 执行cat /proc/cpuinfo |grep MHz|uniq可以看到目前机器中CPU频率&#xff0c;得到值 2494.140MHZ&#xff5e;2494140000HZ&…

【算法】——全排列算法讲解

前言&#xff1a; 今天&#xff0c;我给大家讲解的是关于全排列算。我会从三个方面去进行展开&#xff1a; 首先&#xff0c;我会给大家分析关于全排列算法的思想和定义&#xff1b;紧接着通过手动实现出一个全排列代码来带大家见见是怎么实现的&#xff1b;最后我会给出两道题…

ESP32单片机入门篇

目录 一、ESP32单片机的基本概念 1.双核架构 2. Wi-Fi和蓝牙功能 3. 集成多种外设 4. 支持多种操作系统 二、开发环境 1. Arduino IDE 2. ESP-IDF 三、开发语言 四、注意事项 五、代码例程 &#xff08;1&#xff09;点亮LED灯 1. 电路图 2. 代码 3. 代码注释 …

【精品】Java-Stream流详解

Java-Stream流详解 如何学会JDK8中的Stream流&#xff0c;用它来提高开发效率&#xff1f;创建不可变的集合&#xff08;Immutable 不可变的&#xff09;场景方法 初试 Stream 流Stream 流的思想Stream 流的作用Stream 流的使用步骤Stream 流的中间方法Stream 流的终结方法 如何…

STM32:利用PWM波控制飞盈电调过程和注意事项

STM32&#xff1a;利用PWM波控制电调过程和注意事项 在进行模型控制的过程中&#xff0c;如四旋翼无人机等&#xff0c;需要用到电机&#xff0c;这些电机需要通过电调来控制电机的转速。在电调模块中带有的说明书一般都是利用遥控器进行控制&#xff0c;有些情况需要自己通过…

【自然语言处理】【大模型】CodeGeeX:用于代码生成的多语言预训练模型

CodeGeeX&#xff1a;用于代码生成的多语言预训练模型 《CodeGeeX: A Pre-Trained Model for Code Generation with Multilingual Evaluations on HumanEval-X》 论文地址&#xff1a;https://arxiv.org/pdf/2303.17568.pdf 相关博客 【自然语言处理】【大模型】CodeGeeX&#…

二叉排序树

二叉排序树 文章目录 二叉排序树创建遍历删除完整代码 假如给你一个数列 (7, 3, 10, 12, 5, 1, 9)&#xff0c;要求能够高效的完成对数据的查询和添加。 使用数组 数组未排序&#xff1a; 优点&#xff1a;直接在数组尾添加&#xff0c;速度快。 缺点&#xff1a;查找速度慢. 数…

[图形学] 射线和线段之间的最小距离

1 说在前面 本文的主要内容来自于Unity引擎中Spline功能的一个函数&#xff0c;一开始我难以理解这几个向量运算的作用和几何意义&#xff0c;经过一番思考后总结如下&#xff1a; 该段代码实际上更像是两个直线之间寻找最短距离&#xff0c;然后判断该距离对应的点在其中一条…

STM32利用USB的HID与QT上位机通信

之前使用kingst的逻辑分析仪&#xff0c;打开上位机软件&#xff0c;插上带usb的硬件就可以通信&#xff0c;也不需要打开串口什么的&#xff0c;感觉很方便&#xff0c;于是借用一个周末研究下这个技术。本文主要是用于记录自己学习的过程&#xff0c;顺便分享下学习感悟。 首…

大数据周会-本周学习内容总结012

开会时间&#xff1a;2023.05.07 16:00 线下会议 目录 01【es数据同步至mysql】 1.1【在es中插入数据后能够同步到mysql中】 1.2【修改与删除es中的数据】 02【nifi】 2.1【Nifi的单机及分布式集群部署】 2.2【nifi集群&#xff0c;getFile简单使用nifi】 2.3【nifi使用…

如何利用Requestly提升前端开发与测试的效率,让你事半功倍?

痛点 前端测试 在进行前端页面开发或者测试的时候&#xff0c;我们会遇到这一类场景&#xff1a; 在开发阶段&#xff0c;前端想通过调用真实的接口返回响应在开发或者生产阶段需要验证前端页面的一些 异常场景 或者 临界值 时在测试阶段&#xff0c;想直接通过修改接口响应来…

Nuvoton NK-980IOT开发板 u-boot 编译

前言 最近搭建了 Nuvoton NK-980IOT开发板 的开发编译环境&#xff0c;记录一下 u-boot 的 编译流程 Nuvoton NK-980IOT开发板 资源还是比较的丰富的&#xff0c;可以用于 嵌入式Linux 或者 RT-Thread 的学习开发 开发板上电比较的容易&#xff0c;两根 USB 线即可&#xff0…

进程与线程(二)

进程同步、进程互斥 同步亦称直接制约关系&#xff0c;是指为完成某种任务而建立的两个或多个进程&#xff0c;这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于他们之间的相互合作。 操作系统要提供“进程同步机制”来解决异…

Oracle的学习心得和知识总结(二十四)|Oracle数据库DBMS程序包解密方法及SQL Developer和Unwrapper的安装与使用

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

android 隐藏底部虚拟按键

方法一 滑动屏幕 可重新显示出来 protected void hideBottomUIMenu() { //隐藏虚拟按键&#xff0c;并且全屏 if (Build.VERSION.SDK_INT <11 && Build.VERSION.SDK_INT < 19) { // lower api View v this.getWindow().getDecorView(); v.setSyst…