【Linux深入浅出】之全连接队列及抓包介绍

  • 理解listen系统调用函数的第二个参数
    • 简单实验
      • 实验目的
      • 实验设备
      • 实验代码
      • 实验现象
    • 全连接队列简单理解
      • 什么是全连接队列
      • 全连接队列的大小
  • 从Linux内核的角度理解虚拟文件、sock、网络三方的关系
    • 回顾虚拟文件部分的知识
    • struct socket结构体介绍
    • struct tcp_sock与struct udp_sock介绍
      • struct tcp_sock
        • struct inet_connection_sock结构体
        • struct inet_sock结构体
        • 总结
      • struct udp_sock
      • Tcp接收缓冲区与发送缓冲区
    • 分层介绍
  • tcp抓包介绍
    • Linux中使用tcp dump进行抓包并分析tcp过程
      • tcp dump的安装
      • tcp dump的简单使用
      • 实验
    • windows中使用wireshark进行抓包
      • wireshark的安装
      • 使用telnet作为客户端访问云服务器上的服务器程序
      • 设置wireshark过滤规则
      • 使用wireshark进行抓包

理解listen系统调用函数的第二个参数

listen函数是在进行TCP socket编程时的系统调用函数,它的功能是将普通套接字设置为监听状态,也就是将普通的套接字变成监听套接字,以便它能收到来自客户端的连接请求。

image-20250218101233322

第一个参数是我们之前创建的socket描述符,那么第二个参数应该如何理解呢?直接输出结论:backlog规定了全连接队列的最大长度,全连接队列是用于维护三次握手成功但是系统来不及接收的连接,backlog+1是这个队列的长度。

简单实验

实验目的

下面我们将做一个小实验,这个实验主要会验证如下几个点:

  • 三次握手成功建立连接,并不需要accept的参与,因为它是系统自动完成的,accept只是负责从全连接队列中取走已经建立好的连接。
  • backlog+1 = 全连接队列的长度。

因为accept函数会取走全连接队列中的连接,而且我们的实验就是模拟系统非常忙的情况,所以 TCP server端是不需要调用accept函数的。

实验设备

虚拟机一台,云服务器一台。

在同一台设备上会影响实验效果,因为TCP连接是双向的,从服务器->客户端,客户端->服务器都会维护一个连接,所以如果在一台设备上做实验,会有干扰。

实验代码

  1. TcpServer.cc:

    #include <iostream>
    #include <string>
    #include <cerrno>
    #include <cstring>
    #include <cstdlib>
    #include <memory>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/wait.h>
    #include <unistd.h>const static int default_backlog = 1;enum
    {Usage_Err = 1,Socket_Err,Bind_Err,Listen_Err
    };#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)class TcpServer
    {
    public:TcpServer(uint16_t port) : _port(port), _isrunning(false){}// 都是固定套路void Init(){// 1. 创建socket, file fd, 本质是文件_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){exit(0);}int opt = 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));// 2. 填充本地网络信息并bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = htonl(INADDR_ANY);// 2.1 bindif (bind(_listensock, CONV(&local), sizeof(local)) != 0){exit(Bind_Err);}// 3. 设置socket为监听状态,tcp特有的if (listen(_listensock, default_backlog) != 0){exit(Listen_Err);}}void ProcessConnection(int sockfd, struct sockaddr_in &peer){uint16_t clientport = ntohs(peer.sin_port);std::string clientip = inet_ntoa(peer.sin_addr);std::string prefix = clientip + ":" + std::to_string(clientport);std::cout << "get a new connection, info is : " << prefix << std::endl;while (true){char inbuffer[1024];ssize_t s = ::read(sockfd, inbuffer, sizeof(inbuffer)-1);if(s > 0){inbuffer[s] = 0;std::cout << prefix << "# " << inbuffer << std::endl;std::string echo = inbuffer;echo += "[tcp server echo message]";write(sockfd, echo.c_str(), echo.size());}else{std::cout << prefix << " client quit" << std::endl;break;}}}void Start(){_isrunning = true;while (_isrunning){sleep(1);}}~TcpServer(){}private:uint16_t _port;int _listensock; // TODObool _isrunning;
    };using namespace std;void Usage(std::string proc)
    {std::cout << "Usage : \n\t" << proc << " local_port\n"<< std::endl;
    }
    // ./tcp_server 8888
    int main(int argc, char *argv[])
    {if (argc != 2){Usage(argv[0]);return Usage_Err;}uint16_t port = stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);tsvr->Init();tsvr->Start();return 0;
    }
    
  2. TcpClient.cc

    #include <iostream>
    #include <string>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>int main(int argc, char **argv)
    {if (argc != 3){std::cerr << "\nUsage: " << argv[0] << " serverip serverport\n"<< std::endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (clientSocket < 0){std::cerr << "socket failed" << std::endl;return 1;}sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(serverport);                  // 替换为服务器端口serverAddr.sin_addr.s_addr = inet_addr(serverip.c_str()); // 替换为服务器IP地址int result = connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));if (result < 0){std::cerr << "connect failed" << std::endl;::close(clientSocket);return 1;}while (true){std::string message;std::cout << "Please Enter@ ";std::getline(std::cin, message);if (message.empty())continue;send(clientSocket, message.c_str(), message.size(), 0);char buffer[1024] = {0};int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);if (bytesReceived > 0){buffer[bytesReceived] = '\0'; // 确保字符串以 null 结尾std::cout << "Received from server: " << buffer << std::endl;}else{std::cerr << "recv failed" << std::endl;}}::close(clientSocket);return 0;
    }
    

实验现象

  1. 验证:即使服务器端没有调用accpet函数,三次握手也能建立连接成功:

    image-20250218104617555

    image-20250218111030075

    • 结论:accept系统调用函数并不参与三次握手,它只负责从下层取走连接(socket文件描述符)。
  2. 验证Tcp全连接最多维护backlog+1个连接:

    image-20250218110201620

  • 可以看到,我们在虚拟机中同时运行了多个TcpClient客户端,但最终只有两个成功建立连接,这是因为全连接的大小不够了所以不会接收来自客户端的连接,只有上层调用accept拿走在全连接队列中已经建立的连接,全连接的空间才会腾出来。

全连接队列简单理解

什么是全连接队列

全连接队列就是我们内核(传输层)中某个结构体中维护的一个队列,每一个listen套接字都有一个全连接队列:

image-20250218111735714

在Linux内核中所谓的连接和全连接队列都是struct结构体,后面我们会结合Linux内核重点介绍。

  • 注意:全连接队列的大小并不是代表TcpServer服务器只能同时处理这么多,而是表示它来不及处理(来不及调用accept)的连接的最大数量。

全连接队列的大小

全连接队列的本质其实是生产者消费者模型,全连接队列作为生产者一直生产连接,而上层的accept作为消费者一直从全连接队列中取走连接。

全连接队列的大小不能太大,也不能太小

  • 如果backlog为0:会增加服务器的闲置率,如果有全连接队列,那么可能只需要等待一会,服务器就有空闲可以把连接取走处理了,如果backlog为0,直接就是三次握手建立不成功,用户就以为你的服务已经崩溃,短时间内就不会访问了。
  • 如果backlog过大:那么处于全连接队列结尾的用户就可能需要等待很久来能享受到服务,这样用户体验不好,还不如直接连接失败,而且维护连接也会占用内存,用多余的内存去给服务器处理数据可能效率更高。

过去,常用的默认值可能是5或者50左右,但是现代Linux系统的默认值通常要高得多。

通常这个值有一个上限,我的Linux系统中为4096:

image-20250218113737436

  • /proc/sys/net/core/somaxconn:这个内核参数定义了系统范围内每个端口的最大监听队列长度。它设置了listen()函数中backlog参数的上限值。
  • tcp_max_syn_backlog:这个文件中规定的是未完成连接请求的最大数量(半连接队列)。

从Linux内核的角度理解虚拟文件、sock、网络三方的关系

回顾虚拟文件部分的知识

我们都知道在运行服务器程序后,系统会给这个进程创建一个task_struct结构体,它是用来描述进程的,这个结构体中又会有一个file_struct*的指针,它指向了一个file_struct的对象,这个file_struct结构体是用来管理打开的文件的,它里面有一个文件描述符表,这个文件描述符表中的每一个下标都指向struct file*对象。

但是我们的网络socket是怎么和struct file这个结构体挂上联系的呢?这是我们今天要解决的问题之一,因为文件描述表中的文件描述符不仅仅有普通文件的还有网络套接字文件。

image-20250218114925683

struct socket结构体介绍

struct socket {socket_state		state;unsigned long		flags;struct proto_ops	*ops;struct fasync_struct	*fasync_list;struct file		*file;struct sock		*sk;wait_queue_head_t	wait;short			type;unsigned char		passcred;
};

struct socket结构体是我们网络socket的入口,它是一个通用的套接字类型。

  • short type:表示套接字的类型,是流式套接字还是数据报式的套接字:

    image-20250218120610357

  • struct proto_ops *ops:它是一个保存各种函数方法的类型,可以通过type字段让其指向不同的方法。

    image-20250218120414776

  • struct file*:指向虚拟文件层的struct file对象,但是我们不是需要通过socket文件描述符找到struct socket嘛,怎么顺序反过来,别担心,其实struct file对象中也有一个开放字段是可以指向struct socket对象的,它就是void*类型的private_data字段。

所以经过对struct socket结构体的学习,我们上面的图可以继续完善:

image-20250218121338553

并且调用socket系统调用的同时就创建了struct socketstruct file并在文件描述表中申请了空间,然后还让struct socketstruct file互相指向。

Tcp SocketUdp Socket岂不是没有区别了,既然调用socket都会创建struct socket的话,别急,我们继续往下学习。

struct tcp_sock与struct udp_sock介绍

struct tcp_sock

image-20250218133948690

tcp_sock结构体中有很多关于tcp的字段,譬如:

  • int tcp_header_len:即将要发送的TCP报文的头部的长度,以字节为单位。
  • rcv_nxt, snd_nxt: 分别表示期望接收的下一个序列号和发送方即将发送的下一个序列号。
  • snd_ssthresh, snd_cwnd: 慢启动阈值和拥塞窗口大小,是拥塞控制的重要参数。

但我们更想知道,它的第一个字段struct inet_connection_sock是什么:

  • struct inet_connection_sock inet_conn;:光看其名称,这个结构体肯定与连接有关。
struct inet_connection_sock结构体

这个结构体是描述的TCP与连接相关的属性,里面包括了全连接队列。全连接队列中不仅维护三次握手已经建立好的连接,也会维护只进行了二次或者一次的半连接,但是半连接的生命周期一般很短

image-20250218134723123

  • struct request_sock_queue icsk_accept_queue;:这个字段就是我们之前一直在谈的全连接队列,它由listensock维护,用于管理监听套接字上的半连接(SYN_RCVD状态)和全连接(ESTABLISHED状态但未被accept()接受)队列。

    image-20250218134936143

但我们最好奇的是它的第一个属性字段:

  • struct inet_sock inet:这是一个 struct inet_sock 类型的成员,包含了通用的因特网套接字信息。tcp_sock 以此为基础,添加TCP特定的信息。
struct inet_sock结构体

image-20250218123449300

struct inet_sock结构体中存储的是与网络通信相关的信息,例如:

  • _u32 daddr:外部IPv4地址。
  • _u32 rcv_saddr:本地IPv4地址。
  • _u32 dport:目的端口号。

我们进行Tcp网络通信,调用bind系统调用函数bindIP地址和端口号,不就是在往这个struct inet_sock结构体中写数据吗?

我们惊奇的发现这个inet_sock结构体的第一个字段的类型居然是struct sock,我们之前不是在struct socket里面见过这个字段吗,让struct socket指向它,不就可以通过通用套接字访问到Tcpsock了吗?

所以我们预测udp_sock中一定也存在struct sock字段,而且一定是在最前面。

image-20250218135232593

总结

看了这么多结构体,我们可以画图总结一下它们的关系了:

后续只需要通过socket中的struct sock*字段,通过强制类型转化,我们就可以访问到struct sockstruct inet_sockstruct inet_connection_sockstruct tcp_sock结构体的内容,因为它们的初始地址都是相同的,这样通过结构体嵌套,我们就实现了C风格的多态。

那么当我们客户端和服务器经过三次握手后,建立了一个新的连接,内核会帮助我们做哪些事情呢?

  1. 最最重要的是创建struct inet_connection_sock,这表示一个新的连接,里面的inet_sock字段存储着这个连接相关的属性字段(IP地址、端口号)。

  2. 然后就是struct tcp_sock对象,三次握手完成,内核实际上已经为这个新建立的连接创建了完整的struct tcp_sock结构体。它不仅包含了inet_connection_sock中的所有字段,还添加了许多TCP特有的属性和方法,例如序列号管理、窗口缩放、重传机制等。每当一个新的TCP连接被接受(即完成了三次握手),就会创建一个tcp_sock实例来管理这个连接的状态和行为。

  3. 除此之外,内核还会将这个连接(队尾的nextstruct sock*指向新连接的struct sock)加入listen套接字的全连接队列中(做类似链表的操作),然后需要将队列的元素个数加1。

    image-20250220112433080
    内核中会有实现上述功能的方法:

    image-20250218160530154

如果全连接队列中没有空间了,三次握手根本就不会完成,也就不会创建上述的结构体。

当调用accept函数时,它会做如下事情:

  1. 创建struct socket对象(三次握手完成时并没有创建这个通用的套接字类型),然后从全连接队列中取出队头连接的struct sock*,然后赋值给struct socketstruct sock*变量,就相当与让struct socket指向了struct tcp_sock,因为struct tcp_sock的最开始的字段是struct sock*

  2. 创建struct file,并在文件描述符表中开辟一个新的空间指向这个struct file对象。

  3. 最后,让struct filestruct socket互相引用。

  4. 返回文件描述符给上层。

    内核中的方法sock_map_fd就是实现类似功能的。

    image-20250220113021007

自此之后,我们就可以通过socket fd找到struct file,然后通过struct file中的private_data字段找到struct socket对象,而通用套接字的sk又指向struct tcp_sock的首地址空间的struct sock对象。然后通过强制类型转换,可以访问tcp这个连接相关的任何信息,包括报文、拥塞控制属性、滑动窗口属性、确认应答相关属性(序号、确认序号)。

struct udp_sock

image-20250218194218690

由于udp协议比tcp协议要简单,所以udp_sock结构体的字段也要少一些。

而且由于udp是无连接的协议,所以它没有连接相关的字段,它的第一个结构体对象就直接是struct inet_sock结构体。这和tcpinet_sock是一样的,因为网络套接字部分两者有很多相同的部分,所以可以复用。

对于udp_sock就是这样:

Tcp接收缓冲区与发送缓冲区

我们前面不是一直谈到TCP存在接收缓冲区和发送缓冲区吗?它们在内核中是否有体现呢?

当然有,在struct sock结构体中,存在着这两个字段:

image-20250218200122058

它们就是接收缓冲区与发送缓冲区,每个连接都有单独的struct sock,也就意味着有单独的接收缓冲区与发送缓冲区。

sk_buff_head是这个缓冲区的类型,它是一个类似队列的结构体:

image-20250218200326278

struct sk_buff是描述报文的,也就是解析出来或者即将发送的应用层的报文:

image-20250218200618750

分层介绍

自此之后,虚拟文件、socket、网络三者的关系我们就清楚了,我们也清楚了如何通过文件描述符找到关于套接字的各种信息。

它们自上而下是有层次的,可以分为虚拟文件层、通用套接字层和网络套接字层。其中通用套接字就像是一个基类,它提供了一种通用的方式来创建各种类型的套接字,但是当网络真的建立起来,又会有其它细微的不同。

tcp抓包介绍

Linux中使用tcp dump进行抓包并分析tcp过程

tcp dump的安装

ubuntu下

sudo apt update
sudp apt install -y tcpdump

通过检查版本号验证是否安装成功

tcpdump --version

image-20250220121020961

tcp dump的简单使用

  1. 捕获所有网络接口中的报文:

    sudo tcpdump -i any tcp
    
    • -i:interface是接口的意思,any代表任何,-i any的意思就是捕获所以网络接口中的报文。
    • tcp:只捕获tcp报文。

    image-20250220121732786

  2. 捕获指定网络接口的报文:

    sudo tcpdump -i [本机某网络接口名称] tcp
    

    我们可以通过命令ifconfig查看本主机的所有网络接口:

    image-20250220122051444

    image-20250220122247575

  3. 捕获指定源IP的报文:

    sudo tcpdump src host 192.168.0.1 and tcp
    

    上述命令的含义是:捕获源IP地址为192.168.0.1的到达本主机的tcp报文:

    image-20250220122953630

    • 现在的一般后端服务器都会使用反向代理来实现负载均衡技术,在大型应用或服务中,通常会部署多个反向代理服务器以提高性能、增加可用性和提供冗余。所以可能就会出现多次ping qq.com这个相同的域名,得到来自不同公网IP服务器的回复,这也不用惊讶,所以上面的实验存在一定的运气的成分。

      image-20250220123330174

      • 上面的显示的公网IP可能是反向代理服务器的IP地址,而不是后端服务真正的公网IP。
  4. 捕获指定目的IP地址的报文:

    sudo tcpdump dst host 192.168.0.1 and tcp
    

    上面命令的含义是捕获目的IP地址为192.168.0.1tcp报文:

    image-20250220125705218

    • 注意这个目的IP为什么是iZt8qyfqyfs47mZ呢?云服务提供商使用类似的随机字符串作为实例ID或设备标识:

      image-20250220134359259

      你也可以去云服务网站的控制台修改这个实例名称。

    • 但是如果我们希望它显示IP地址,而不是显示云服务器的实例名称该怎么办呢?加上选项-n即可:

      image-20250220134659966

  5. 捕获特定端口号的TCP报文:

    • 使用port关键字可以捕获特定端口号的报文,例如捕获80端口的TCP报文(通常是http请求):

      sudo tcpdump port 80 and tcp
      

      image-20250220135146901

实验

使用tcpdump工具,一般以捕获特定端口的形式居多,代码和上述的验证listen系统调用函数的第二个参数的代码一样,简单的tcp echo服务器:

  1. 服务器不给客户端发送数据,也不accept接受连接:

    image-20250220135716488

    • 将客户端在虚拟机上运行,观察抓包现象:

      1. 三次握手:

        image-20250220161715672

        • 因为三次握手是没有发送数据的,所以length为0。
      2. 当我们虚拟机客户端给服务器发送数据报文,但是服务器收到该报文,发送的ack报文数据为0,没有发送数据报文,我们有理由相信,服务器根本没有将这个连接拿上来给用户,但是三次握手肯定成功了,并且ack报文是OS自动发送的,不需要用户参与:

        image-20250220162304996

        image-20250220162559707

        • 看了一下代码果然没有将连接拿上来。
  2. accept函数注释取消后继续实验:

    • 三次握手部分(依旧正常):Flags中的S代表SYN标志位,win是窗口大小(用于滑动窗口中确定窗口大小),可以看到双方还协商了mss的大小。

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 服务器接收数据,发送数据:

      image-20250220163401186

      现在收发数据都正常了。

      image-20250220163505417

    • 四次挥手部分,客户端主动退出:

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

      • 就只有客户端给服务器发送了FIN报文,服务器OS自动给它回复了一个ACK报文,服务器并没有断开连接,我们有理由相信,服务器端忘记close关闭socket描述符了。

        image-20250220163807463

  3. 服务器端在客户端关闭连接后也要正常关闭连接,修改代码后继续测试四次挥手的过程:

    image-20250220165028109

    • 不是说四次挥手吗,为什么只有三次呢,我们有理由相信,在客户端给服务器发送FIN报文后,服务器立马就给客户端发送了FIN报文,并且这个时间和系统自动发送ACK报文的时间几乎是同时,所以触发了捎带应答,如果我们让服务器sleep上1s再关闭socketfd,就可以看到四次挥手:

      image-20250220170319069

    • sleep后的结果:

      image-20250220170355643

windows中使用wireshark进行抓包

wireshark的安装

wireshark-4.4.3-x64.exe

下载好之后,直接安装即可,没有太多要注意的地方。

使用telnet作为客户端访问云服务器上的服务器程序

默认windows上telnet服务是没有打开的,我们可以手动打开,打开telnet教程

设置wireshark过滤规则

  1. 首先选择你想捕获哪个网卡的流量(上行和下行):

    image-20250220174448817

  2. 选择好之后,顶部工具栏点捕获,点开始,就可以开始捕获该网卡的流量:

    image-20250220174611588

  3. 默认是捕获经过该网络接口的流量:

    image-20250220174732358

  4. 在顶部可以设置过滤规则,我们设置ip为服务器ip,只关心服务器所在的端口号8888

    ip.addr == 121.40.68.117 && tcp.port == 8888
    

    顶部过滤栏是绿色说明语法没有问题:

    image-20250220175032517

使用wireshark进行抓包

  1. 启动服务器程序:

    image-20250220175112957

  2. 启动windows上的telnet服务:

    telnet [服务器公网ip] [端口号]
    

    image-20250220175243523

    • 进入这个界面就代表启动成功了。
  3. 观察报文:

    1. 三次握手:

      image-20250220175444404

    2. telnet发送1字节的数据:

      image-20250220180035091

      点击某一个包,下面可以看到更详细的信息:

      image-20250220180214708

  4. 四次挥手,telnet输入ctrl ]进入命令行模式,然后点quit就可退出:

    image-20250220180532792

红色的报文为超时重传。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DB-GPT V0.7.1 版本更新:支持多模态模型、支持 Qwen3 系列,GLM4 系列模型 、支持Oracle数据库等

V0.7.1版本主要新增、增强了以下核心特性 &#x1f340;DB-GPT支持多模态模型。 &#x1f340;DB-GPT支持 Qwen3 系列&#xff0c;GLM4 系列模型。 &#x1f340; MCP支持 SSE 权限认证和 SSL/TLS 安全通信。 &#x1f340; 支持Oracle数据库。 &#x1f340; 支持 Infini…

2025五一数学建模竞赛A题完整分析论文(共45页)(含模型、可运行代码、数据)

2025年五一数学建模竞赛A题完整分析论文 摘 要 一、问题分析 二、问题重述 三、模型假设 四、符号定义 五、 模型建立与求解 5.1问题1 5.1.1问题1思路分析 5.1.2问题1模型建立 5.1.3问题1参考代码 5.1.4问题1求解结果 5.2问题2 5.2.1问题2思路分析 …

[学习]RTKLib详解:pntpos.c与postpos.c

文章目录 RTKLib详解&#xff1a;pntpos.c与postpos.cPart A: pntpos.c一、概述二、整体工作流程三、主要函数说明1. pntpos()2. satposs()3. estpos()4. rescode()5. prange()6. ionocorr()7. tropcorr()8. valsol()9. raim_fde()10. estvel() 四、函数调用关系图&#xff08;…

【科研绘图系列】R语言绘制世界地图(map plot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理画图输出图片系统信息介绍 【科研绘图系列】R语言绘制世界地图(map plot) 加载R包 library(ggmap) library(RColorBrewer) library(pals) …

在pycharm profession 2020.3上安装使用xlwings

之前写了一篇文章在win7和python3.8上安装xlwings-CSDN博客 今天安装了pycharm profession 2020.3&#xff0c;自带Terminal&#xff0c;所以试一下安装xlwings。 一、新建一个python项目 二、安装xlwings 三、输入安装命令 pip3.exe install -i https://pypi.tuna.tsinghu…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】4.3 数据脱敏与安全(模糊处理/掩码技术)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL数据脱敏实战&#xff1a;从模糊处理到动态掩码的全流程解析4.3 数据脱敏与安全&#xff1a;模糊处理与掩码技术深度实践4.3.1 数据脱敏的核心技术体系4.3.1.1 技…

坚鹏:平安保险集团《保险行业发展趋势与AI应用方法及案例》培训

坚鹏&#xff1a;平安保险集团《保险行业发展趋势与AI应用方法及案例》培训圆满成功 中国平安保险&#xff08;集团&#xff09;股份有限公司是全球领先的综合金融生活服务集团&#xff0c;2024年位列《财富》世界500强第16位&#xff0c;连续多年蝉联全球保险品牌价值榜首。截…

NetSuite 2025.1 学习笔记

目录 领域、新功能统计表 值得注意功能摘要 最有价值功能详解 1. 领域、新功能统计表 2. 值得注意功能 3. 最有价值功能 3.1 Customer 360 目前的Customer 360在加入了几个新的控件后&#xff0c;变得完整了&#xff0c;相比较过去&#xff0c;真正有了实用感。 3.2 CSV Im…

Messenger.Default.Send 所有重载参数说明

Messenger.Default.Send 是 MVVM 框架中实现消息传递的核心方法,其重载参数主要用于控制消息的发送范围和接收条件。以下是其所有重载形式及参数说明: ‌1. 基本消息发送‌ Send<TMessage>(TMessage message) ‌参数说明‌: TMessage:消息类型(泛型参数),可以是任…

代码异味(Code Smell)识别与重构指南

1、引言:什么是“代码异味”? 在软件开发中,“代码异味(Code Smell)”是指那些虽然不会导致程序编译失败或运行错误,但暗示着潜在设计缺陷或可维护性问题的代码结构。它们是代码演进过程中的“信号灯”,提示我们某段代码可能需要优化。 1.1 ✅ 为什么关注代码异味? 预…

K8S有状态服务部署(MySQL、Redis、ES、RabbitMQ、Nacos、ZipKin、Sentinel)

K8S部署MySQL ①、创建配置 ②、创建存储卷 ③、创建服务 指定配置文件 指定存储卷 ④、同样的方式创建mysql-slaver服务&#xff08;配置文件和mysql-master不同&#xff09; ⑤、进行主从同步关联 进入master服务中 进入从库的终端 K8S部署Redis…

正则表达式与文本三剑客grep、sed、awk

目录 一、正则表达式 1.1、字符匹配 1.2、次数匹配 1.3、位置锚定 1.4、分组或其他 二、扩展正则表达式 三、grep 四、awk 4.1、常用命令选项 4.2、工作原理 4.3、基础用法 4.4、内置变量 4.5、模式 4.6、条件判断 4.7、awk中的循环语句 4.8、数组 4.9、脚本 …

Matlab/Simulink的一些功能用法笔记(4)

水一篇帖子 01--MATLAB工作区的保护眼睛颜色设置 默认的工作区颜色为白色 在网上可以搜索一些保护眼睛的RGB颜色参数设置 在MATLAB中按如下设置&#xff1a; ①点击预设 ②点击颜色&#xff0c;点击背景色的三角标符号 ③点击更多颜色&#xff0c;找到RGB选项 ④填写颜色参数…

Qt国际化实战--精通Qt Linguist工具链

概述 在全球化的今天,软件产品需要支持多种语言和地区,以满足来自世界各地用户的需求。Qt框架提供了一套完整的工具集来帮助开发者实现应用程序的国际化(i18n)和本地化(l10n),其中最核心的就是Qt Linguist工具链 关于国际化与本地化 国际化(i18n): 指的是设计和开发…

0基础 | STM32 | STM32F103C8T6开发板 | 项目开发

注&#xff1a;本专题系列基于该开发板进行&#xff0c;会分享源代码 F103C8T6核心板链接&#xff1a; https://pan.baidu.com/s/1EJOlrTcProNQQhdTT_ayUQ 提取码&#xff1a;8c1w 图 STM32F103C8T6开发板 1、黑色制版工艺、漂亮、高品质 2、入门级配置STM32芯片(SEM32F103…

【SF顺丰】顺丰开放平台API对接(注册、API测试篇)

1.注册开发者账号 注册地址&#xff1a;顺丰企业账户中心 2.登录开发平台 登录地址&#xff1a;顺丰开放平台 3.开发者对接 点击开发者对接 4.创建开发对接应用 开发者应用中“新建应用”创建应用&#xff0c;最多创建应用限制数量5个 注意&#xff1a;需要先复制保存生产校验…

AI Agent开发第48课-DIFY中利用AI动态判断下一步流程-DIFY调用API、REDIS、LLM

开篇 之前我们在《AI Agent开发第47课-DIFY处理多步流程慢?你确认用对了?》中讲述了DIFY的设计中在整合多步LLM时如避免过多调用LLM的良好设计,并给出了AI工作流的相应设计手法。今天我们要在上一篇的基础上把“上门维修预约”这个流程进一步按照实际业务需求加入用户在整个…

剥开 MP4 的 千层 “数字洋葱”:从外到内拆解通用媒体容器的核心

在当今数字化时代&#xff0c;MP4 格式随处可见&#xff0c;无论是在线视频、手机拍摄的短片&#xff0c;还是从各种渠道获取的音频视频文件&#xff0c;MP4 都占据着主流地位。它就像一个万能的 “数字媒体集装箱”&#xff0c;高效地整合和传输着各种视听内容。接下来&#x…

JavaScript性能优化实战:深入探讨性能瓶颈与优化技巧

JavaScript性能优化实战:深入探讨性能瓶颈与优化技巧 引言 在当今快速发展的Web世界中,性能已经成为衡量应用质量的关键指标。随着Web应用复杂度的不断提升,JavaScript作为前端开发的核心语言,其性能优化变得尤为重要。本文旨在全面深入地探讨JavaScript性能优化的各个方…

无忧AI综合插件,可实现图色识别、机器视觉、图像编辑等多种功能

说明: 无忧AI综合插件(vu.dll)是一款功能强大的AI脚本插件&#xff0c;主要用于按键精灵、易语言、Python、C/C等辅助制作工具&#xff0c;具有图像识别、文本识别、键盘鼠标、内存操作、AI视觉等多种功能。 其官网地址 无忧 - AI图色综合插件 http:www.voouer.com/Plugin 功…