网络套接字编程

news2024/9/27 15:35:17

       之前我们粗浅的认识了一下网络的一些知识,比如OSI七层模型,TCP/IP四层模型,那么我们具体怎么实现两台主机的交互呢?

        在学习这些之前,我们需要准备一些预备知识。

目录

预备知识

1:认识源IP地址和目的IP地址

2:认识端口号(port) 

3:理解端口号和进程ID

4:认识源port和目的port

认识TCP协议

认识UDP协议

网络字节序

SOCKET编程接口

简单的UDP程序

简单的UDP服务器

IP地址简单讲解

简单的UDP客户端

简单的TCP程序

TCP服务器

TCP客户端

守护进程

        会话,前台任务和后台任务

如何创建守护进程

        忽略异常信号

        不可选择组长

        关闭或者重定向默认打开的文件

TCP通讯流程

三次握手

服务器初始化

建立连接

四次挥手

断开连接

TCP协议和UDP协议对比


预备知识

1:认识源IP地址和目的IP地址

        在我们的IP数据包报头中,有两个IP地址,一个是源IP地址,一个是目的IP地址,而这些IP我们一般称之为公网IP,用来标示一台唯一的主机的。        

源IP地址:标示数据包的发送主机

目的IP地址:标示数据包的接收主机

        但是我们在日常生活中使用手机或者计算机,实际上并非只需要有对方的IP地址就能够相互交流,在日常生活中,我们都是通过微信,或者QQ之类的软件才能相互之间发送数据。

        而只有一个IP地址显然是不够用的,那么就轮到端口号出场了。

2:认识端口号(port) 

端口号 :端口号是传输层协议的内容,它是一个两字节16位的整数(uint16_t),用来标示一个进程,用来告诉操作系统这个数据要交给哪个进程来处理,并且一个端口号只能由一个进程占用。

         当我们使用 IP+port 就能标示网络上的某一台主机上的某一个进程,这样用户就能够成功建立通信。

        说到这里我们就应该明白,实际上网络通信的本质就是进程间通信。

        port 用来标示进程的唯一性,而 pid 也是用来标示进程的唯一性的,这两者有什么关联吗?

3:理解端口号和进程ID

        我们都知道在主机中,pid 是用来标示进程的唯一性的,这里的 port 号也是用来标示进程的唯一性的,那么为什么不直接用 pid 来表示 port 呢?

理由

1系统是系统,网络是网络,需要将系统和网络解耦

2:客户端每次都需要找到服务器进程,就决定了服务器的唯一性不能改变,而 pid 是能够轻易改变的

4:认识源port和目的port

        源port 就是指发送数据的进程,而目的 port 就是接收数据的进程。

        当我们了解了IP地址和 port 之后,我们就需要初步了解下网络的两个协议——TCP和UDP

认识TCP协议

        TCP协议,又叫传输控制协议,它是传输层的协议,具有以下特点

1.有连接——在IO之前需要先建立连接

2.可靠传输——当出现错误的时候能够有相应的策略应对

3.面向字节流——通过字节流进行IO

认识UDP协议

        UDP协议,又叫用户数据报协议,它也是传输层的协议,具有以下特点

1.无连接——IO之前不需要建立连接

2.不可靠传输——没有相应的错误应对策略

3.面向数据报——通过数据报进行IO

网络字节序

        我们都知道,在内存中,数据的存储分为大端和小端,而网络的数据量也有大小端之分。

        TCP/IP协议规定,任何一台主机,它的网络数据流都采用大端字节序。

        而为了使网络程序具有可移植性,有以下库函数做网络字节序和主机字节序的转换。

#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);//主机字节序转网络字节序——长整型

uint16_t htons(uint16_t hostshort);//主机字节序转网络字节序——短整型

uint32_t ntohl(uint32_t netlong);//网络字节序转主机字节序——长整型

uint16_t ntohs(uint16_t netshort);//网络字节序转主机字节序——短整型

        虽然看上去比较难记,但实际上很好记,其中 h 表示 host ,也就是指主机,n 标示 net,也就是指网络,s 指 short,就是指16位的整数,l 是指 long ,就是指32位的整数。

SOCKET编程接口

        socket 这个东西想必大家都很陌生。

        在上面我们都知道,两个主机上的进程想要通信,就一定需要知道对方主机的 ip 和进程的 port,而 socket 实际上就是指 ip + port ;

 而 socket 也是有自身的编程接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器 )
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器 )
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听 socket (TCP, 服务器 )
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器 )
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端 )
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

        这些接口有点只能在TCP协议中用,有的TCP和UDP都能使用,有的只能在服务器上用,有点能在客户端上用,根据场景不同使用。 

        而在这些函数中,我们都看见一个参数类型是 struct sockaddr* 类型的,这个类型我们也需要了解。

        socket API 是一层抽象的网络编程接口,适用于各种底层网络协议。

        但是各种网络协议的地址各不相同。

         在上图中,我们发现最左边的sockaddr是函数中所需要传参的变量类型,而右边则是两种没有看到过的类型。

        实际上这是这套接口的发明者所用的一个小技巧,他将所有接收的变量类型定义为sockaddr,而不同协议则需要使用不同的 sockaddr,例如TCP和UDP协议都是使用的sockaddr_in的类型,而sockaddr_un 则有不同的协议使用。

        因此,我们在外部调用这类函数的时候,只需要根据自己使用的协议来使用不同的sockaddr类型,然后在里面填充数据后强转再传参,而具体的操作和判断就交给函数内部进行。

sockaddr结构

sockaddr_in结构

        当我们有了以上的预备知识后,我们便先来学习下UDP协议下的通信以及代码实现。

简单的UDP程序

        经过上面的预备知识,我们知道,要进行网络通信,那么一定是需要 IP地址 和 端口号 port 的,而且socket 套接字也是必不可少的,因此,我们想要编写一个UDP程序,这三者缺一不可。

简单的UDP服务器

我们首先以UDP服务器的编写为示范。 

        在定义好UDPSever的成员变量后,便是需要对这些变量初始化。

 

         这里我们依靠外面传参决定服务器使用几号端口进行通信,而 IP 地址,我们默认设为 0.0.0.0;

        至于为什么 IP 是 0.0.0.0,则在后面进行解答。

        在初始化完成员变量后,我们便是需要将服务器给初始化完成。

        在start函数中,我们首先创建了一个 socket 套接字。

        其中,我们传了三个参数:AF_INET,SOCK_DGREAM,0。

        先来了解这三个参数分别代表什么吧。

          首先第一个 domain 参数,表示我们是想进行网络通信还是本地通信。这里我们的domain 参数是 AF_INET,表示我们是网络通信,且IP地址是 IPv4 地址格式。

        而第二个参数 type 表示我们的socket 所提供的能力类型,即是使用流式套接字还是使用用户数据报套接字。

        而第三个参数protocol 则是表示我们采用TCP还是UDP,不过实际上我们前两个参数就已经决定我们使用什么协议了,因此我们这里默认给 0;

        而在拿到套接字之后,我们需要将 ip 地址和 port 号绑定给该套接字中,而要将 ip 地址 和 port 号两个变量绑定给套接字,我们需要使用bind函数。

         bind 函数需要三个参数:sockfd, addr, addrlen。

        这里需要提一嘴的是,实际上我们在上面所申请的 socket 实际上类似于文件描述符,如果说之前的socket函数的操作都属于文件范畴,那么这里的 bind 函数的操作就属于网络操作的范畴。

        它会告诉操作系统,这个套接字是属于哪个端口号的,这样我们才能成功的通过套接字来进行网络通信

        而addr 这个变量在预备知识我们已经了解过,我们并非是使用 sockaddr 这个类型,而是使用sockaddr_in 这个类型。

        预备知识中也说过,sockaddr_in这个类型需要我们手动填充,我们首先将协议定为AF_INET

        然后将 IP 地址通过 inet_addr 转换后,填入其中。

        关于IP地址这里需要简单讲解一下。

IP地址简单讲解

        IP地址分为点分十进制风格和整数风格。

       其中点分十进制是一个字符串,可读性好,而整数风格的IP地址则用于网络通信。

        因此我们的IP地址都是需要通过函数转换一下才能使用。

      

        此外,我们在前面的预备知识都了解过,网络也有自己的字节序,因此我们的port 端口号需要使用的话,也是需要通过相应的函数进行转化才能在网络上使用。

        当我们填充完该变量后,就只需要直接将该变量和socket直接绑定即可。       

        至此,我们的服务器就初始化完成了。

   

     接着我们就需要让服务器开始运行。

      

      在 start 函数中,我们使用buffer作为缓冲区,用recvfrom函数从套接字中接收数据,把数据放入buffer 中,并且用peer变量来接收客户端的 ip 地址 和 port 号。

        虽然服务器并不主动开始通信,但是服务器有时需要返回数据,到时候就需要使用客户端的 ip 地址 和 port 号。

        接着我们用 cip 和 cport 分别将peer 中的字段转换后接收,并且输出客户端发送的信息。

        一个简易版的UDP服务器就完成了。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>

using namespace std;



namespace UDP
{
   static const string defaultip = "0.0.0.0";
    class Sever
    {
    
    public:
        Sever(uint16_t port, string ip = defaultip)
            : _port(port), _ip(ip)
        {
        }

        void start()
        {
            //初始化套接字
            _socket = socket(AF_INET,SOCK_DGRAM,0);
            if(_socket == -1)
            {
                cerr<<"socket err!"<<endl;
                exit(-1);
            }
            cout<<"socket successed!"<<endl;
            //绑定IP 和 port
            struct sockaddr_in local;
            bzero(&local,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = inet_addr(_ip.c_str());
            local.sin_port = htons(_port);
            int n = bind(_socket,(struct sockaddr*)&local,sizeof(local));               
            if(n == -1)
            {
                cerr<<"bind failed!"<<endl;
                exit(-1);
            }
            cout<<"bind successed!"<<endl;


        }

        void run()
        {
            while(true)
            {
                char buffer[1024];
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                bzero(&peer,sizeof(peer));

                size_t n = recvfrom(_socket,buffer,sizeof(buffer),0,(sockaddr*)&peer,&len);
                if(n == -1)
                {
                    cout<<"recvfrom failed!"<<endl;
                    exit(-1);
                }
                cout<<"recvfrom successed!"<<endl;
                string cip = inet_ntoa(peer.sin_addr);
                uint16_t cport = ntohs(peer.sin_port);

                buffer[strlen(buffer) - 1] = 0;

                cout<<"Client["<<cip<<"]["<<cport<<"] # "<<buffer<<endl;
            }
        }
        ~Sever()
        {
        }

    private:
        int _socket;
        string _ip;
        uint16_t _port;
    };
}

          

        接着我们就需要写一个外部调用代码。

        我们在UDPSever.cc中写下如下代码,就能够成功运行了。

#include"UDPSever.hpp"
#include<memory>


using namespace UDP;

int main(int args,char* argv[])
{
    if(args != 2)
    {
        cout<<"./UDPSever port"<<endl;
        exit(-1);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<UDP::Sever> usp(new Sever(port));
    usp->start();
    usp->run();
    return 0;
}

        写完服务器,接着就是客户端了。

简单的UDP客户端

        一个客户端和服务器都是需要相同的成员变量,但是不同的是客户端的IP地址不能为0.0.0.0;

而必须是服务器的IP地址,这样才能使得客户端成功连接到服务器。

        而之后的操作,客户端和服务器基本一致。

        客户端的初始化相比服务器而言,少了绑定操作,因为客户端一般是主动向其他主机发送连接请求,只需要通过函数发送数据即可。 

 

        然后就是客户端的运行了。

         我们也是和服务器一样的操作,不过不同的是,客户端是将数据送出去,而非拿进来。

       

#include <iostream>
#include <string>
#include <stdlib.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>

using namespace std;

namespace UDP
{
    class Client
    {
    public:
        Client(string ip, uint16_t port)
            : _ip(ip), _port(port)
        {
        }
        void start()
        {
            _socket = socket(AF_INET, SOCK_DGRAM, 0);
            if (_socket == -1)
            {
                cout << "socket failed!" << endl;
                exit(-1);
            }
            cout << "socket successed!" << endl;
        }

        void run()
        {
            struct sockaddr_in peer;
            peer.sin_family = AF_INET;
            peer.sin_addr.s_addr = inet_addr(_ip.c_str());
            peer.sin_port = htons(_port);


            while (true)
            {
                    char buffer[1024];
                    cout<<"Enter # "<<endl;
                    fgets(buffer,sizeof(buffer),stdin);
                    sendto(_socket,buffer,sizeof(buffer),0,(sockaddr*)&peer,sizeof(peer));
            }
        }
        ~Client()
        {
        }

    private:
        int _socket;
        string _ip;
        uint16_t _port;
    };
}

        此外就是客户端的外部调用了。

#include"UDPClient.hpp"
#include<memory>

using namespace UDP;

int main(int args,char* argv[])
{
    if(args != 3)
    {
        cout<<"./UDPClient ip port"<<endl;
        exit(-1);
    }
    string ip = argv[1];
    uint16_t port =  atoi(argv[2]);
    unique_ptr<Client> upc(new Client(ip,port));
    upc->start();
    upc->run();

}

        这样我们的简单的UDP客户端就完成了。

        我们来看看成果吧!

         由于笔者是在一台电脑上进行通信,因此这里客户端输入的 IP 地址为 127.0.0.1,即本地回流,表明接收的IP地址是本机。

        此外,客户端上输入的端口号应该和服务器的端口号相同,因为客户端输入的IP地址和端口号都是服务器的IP地址和端口号。

        那么这里就有一个问题,既然我们客户端所有初始化的 IP 地址 和 端口号都是服务器的,那么服务器又是怎么知道客户端的 IP 地址和 端口号的呢?

        实际上,客户端在发送数据之前,操作系统会自动选择一个可用的本地IP地址和端口号来和目标主机建立连接,因此不用我们手动建立连接。

       

简单的TCP程序

        

TCP服务器

        在做完一个简单的UDP协议服务器和客户端后,自然就要试试写一个TCP服务器了。

        

        实际上TC服务器和UDP服务器的代码十分相似,只有个别不同,我们直接看代码。

       在代码中我们能够看到,TCP服务器的初始化和UDP服务器的初始化是十分相似的,只有两点不同。

1:socket函数创建套接字的时候,第二个参数是SOCK_STREM。

2:初始化时有listen函数,来设置套接字的状态。

        

        关于为何socket函数第二个参数是SOCK_STREM,这是因为TCP协议是面向字节流的,因而使用SOCK_STREM。

        而listen函数也是因为TCP协议是面向连接的,listen函数使套接字处于监听状态。

        listen函数一共两个参数,第一个就是需要改变状态的socket,第二个参数表明最多允许backlog个客户端处于连接等待状态,连接到更多请求就会忽略。

        将服务器初始化后,我们就需要写运行代码。

        

        在运行代码中,我们发现,有一个accept函数,我们先来看一下这个函数是干嘛的。、

         对于accept函数,它的三个参数我们都不陌生,第一个就是进入监听状态的listensocket,后面两个参数我们在UDP协议中使用recvfrom的时候就已经见识到了。

        重点在于accept函数的返回值。

         根据手册的描述,accept函数返回值也是一个socket套接字!

        那么这个返回的socket和我们设为监听的listensocket又有什么区别呢?

        实际上,listensocket的作用并不是用来通信的,是用来监听是否有新的连接到来。

        而真正进行通信的实际上是accept函数返回的socket

        紧接着,我们就能够使用seviceIO函数,在函数内部进行真正的通信。

        我们的函数内部使用read函数从socket里面接收信息,从而成功通信。

        然而,我们这里有一串代码需要详细的讲一下:

         这串代码从字面意思上来看,就是父进程创建一个子进程,然后在子进程里面创建一个孙子进程,然后将子进程关闭,由孙子进程进行通信,然后父进程回收子进程,这是为什么呢?

        首先我们都知道,子进程在退出的时候,若是父进程不等待,就会导致子进程变成僵尸进程,从而导致资源泄露等问题。

        但是,如果我们的父进程等待子进程,就会造成串行的问题,因为等待是阻塞的,这样我们的服务器就不能对多个客户端请求进行处理。

        因此,我们这里创建了一个孙子进程,然后将子进程退出,这样孙子进程就会被1号进程领养,当孙子进程退出时,会自动回收资源,而父进程在子进程退出时就已经回收它的资源了。

        这样就不会出现串行的问题,也不会出现资源泄露的问题。

        当然,这里有多种写法,我们可以使用多线程的写法,就不用关心这么多了。

TCP客户端

        对于客户端,实际上也和UDP协议相差不多。

        首先是客户端初始化的时候,创建套接字。

        其次便是运行。 

         我们发现TCP的客户端比UDP的客户端多一个connect的步骤,实际上也是因为TCP是面向连接的,因此需要用connect与服务器建立连接。

        我们先来看看connect的描述。

        实际上connect的具体用法相信不用说都能够明白了,重点是这一串描述。

         这句话说:如果连接或者绑定成功了,返回0,错误返回1.

        也就是说,TCP客户端的隐式绑定是在connet的时候发生的,这和UDP客户端不同,UDP绑定是在客户端初次sendto的时候绑定的。

        不过需要注意的是,客户端所保存的ip和port都是服务器的。

        接着我们就能顺利运行起来了。

         

       

守护进程

        目前,我们已经将TCP和UDP的服务器和客户端分别写了一遍,但是,真正的服务器并不是像我们这样,将程序一关就结束了,这个时候就需要讲一讲守护进程了。

        在了解守护进程前需要一些预备知识。

        会话,前台任务和后台任务

                

        当我们打开xshell连接到服务器时,服务器内部会创建一个会话,然后再在会话里面创建一个bash,也就是命令行解释器,其中,bash就是一个前台任务。

                在会话中,只能有一个前台任务,但是可以有多个后台任务。 

        而我们的后台任务可以创建多个。

        我们创建两串进程组,分别是sleep 10000| sleep 20000| sleep 30000 和 sleep 40000| sleep 50000| sleep 60000,然后通过 & 将它们放到后台去。并且通过jobs指令查看到他们都在后台中。

        而这两串进程组就是后台任务。

        然后我们通过 ps 命令查看这些后台任务的时候,发现一件事。

         这些后台任务有一个PGID,并且这两个进程组的PGID各不相同。

        实际上PGID指的是该进程组的组长的PID,我们可以看到都有对应的进程ID。

        此外,我们发现PGID旁边还有一个SID,而且这两个进程组的SID都一样,SID又是什么呢?

                                        SIG:指该会话(session)的ID

         此时,若是我们将后台进程变为前台任务就会发现我们的命令行解释器bash失效了。

         通过fg + 后台任务号,我们将2号后台任务提到前台中,发现bash命令都不能用了,然后ctrl+z将2号任务暂停,才能够使用bash,然后使用bg + 后台任务号让2号任务继续运行起来。

        

        而我们发现,前台任务和后台任务都是受用户的退出和注销的影响的,而想避免这种影响,就必须自成会话,自成进程组,和终端设备无关,这就是守护进程。

如何创建守护进程

        创建守护进程需要我们自己写一个函数。

        创建守护进程一共三步:

       1.忽略异常信号

       2.不可选择组长

       3.关闭或者重定向进程默认打开的文件

        忽略异常信号

       有时候当一个客户端在写,而服务器未接收时,服务器会收到信号从而导致服务器崩溃,因此需要忽略异常信号。

        不可选择组长

         创建守护进程最重要的就是setsid函数,他会将调用该函数的进程设置为守护进程,但是前提是该进程不能是组长。

         该函数的描述中就确定不能是组长,否则会报错。

        关闭或者重定向默认打开的文件

        在这里我先隆重讲解一下传说中的文件黑洞——/dev/null

        想要创建一个守护进程,就必须将该进程的三个文件描述符全部重定向到该文件。

                /dev/null是一个文件黑洞,任何放入其中的文件都会被丢弃。

       说完三个需要注意的点,我们直接看代码。

#pragma once

#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;
#define DEV "/dev/null"

void daemonSelf()
{
    // 1.忽略异常信号
    signal(SIGPIPE, SIG_IGN);
    // 2.让自己不成为组长
    if (fork() != 0)
        exit(0);
    pid_t n = setsid();
    assert(n != -1);
    // 3.关闭或者重定向进程默认打开的文件
    int fd = open(DEV,O_RDWR);
    if(fd >= 0)
    {
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);

        close(fd);
    }
    else 
    {
        close(0);
        close(1);
        close(2);
    }
}

         首先第一步,我们直接忽略掉异常信号SIGPIPE;

        其次,我们fork一下,让子进程调用setsid,这样就不是组长进程了;

        最后,我们打开文件黑洞,然后将文件都重定向到里面,否则就关闭它们。

        这样就完成了,然后在服务器的调用中使用该函数即可。

        然后我们看看表现。

 

         我们发现确实这个进程不是前台任务了,而且它的PID,PGID和SID都是它自己,说明它确实已经成为了守护进程了!

TCP通讯流程

        

        TCP的客户端和服务器的通信流程用一句话就是三次握手和四次挥手。

三次握手

       

服务器初始化

1.通过socket函数创建文件描述符。

2.调用bind函数,将该文件描述符和ip与port绑定,若port号被占用,绑定失败。

3.通过listen函数,声明该文件描述符作为服务器的文件描述符,为accept做准备。

4.阻塞等待accept返回,等待客户端连接。

建立连接

1.当客户端初始化后,通过connect向服务器发起连接请求。

2.connect发送SYN段并且阻塞等待服务器应答(第一次)。

3.服务器收到SYN段会返回SYN-ATK表示同意建立连接(第二次)。

4.客户端收到SYN-ATK段从connect函数返回,并且应答一个ATK段(第三次)。

        当客户端和服务器通过三次握手建立连接后,两边可以重复通过write和read函数进行通信。直到调用close函数关闭文件描述符。

四次挥手

断开连接

1.当客户端调用close关闭连接时,客户端向服务器发送FIN段(第一次)。

2.服务器收到FIN段后,会回应ACK段,同时read返回0(第二次)。

3.read返回后,服务器就知道客户端断开连接,会调用close断开连接,同时发送FIN(第三次)。

4.客户端收到FIN,再返回一个ACK给服务器(第四次)。

        通过四次挥手,客户端和服务器就断开了连接。

TCP协议和UDP协议对比

TCP特点

1.字节流。

2.面向连接。

3.可靠传输。          

UDP特点

1.用户数据报。

2.无连接。

3.不可靠传输。 

        也许这里大家看到UDP是不可靠的时候,就会认为UDP不好用,其实这里的不可靠是个中性词,不可靠意味着UDP简单,快速,而TCP可靠也意味着TCP复杂。

        因此大部分时间都是通过具体环境使用两种协议。 

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

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

相关文章

matlab字符串的操作方法

一个字符串是存储在一个行向量中的文本&#xff0c;这个行向量中的每一个元素代表一个字符&#xff0c;字符串可以由0个或多个字符组成。下面是一些字符串的操作方法 &#xff08;1&#xff09;字符串的创建 MATLAB中创建字符串非常简单&#xff0c;将字符串中的字符放到一对…

Jmeter接口之间的动态关联(同一线程组和跨线程组)

目录 前言&#xff1a; 动态关联提取cookie 1.同一线程组里的请求之间进行动态关联 2.跨线程组之间的动态关联 前言&#xff1a; 在进行接口测试时&#xff0c;有时候需要将一个接口的返回值作为参数传递给另一个接口&#xff0c;这就需要用到动态关联。JMeter提供了丰富的…

Haproxy负载均衡集群(时间能回答少年的所有不解)

文章目录 一、Haproxy的基础了解1.常用的负载均衡调度器2.Haproxy 应用相比较其他的优缺点3.Haproxy的特性4.LVS、Nginx、HAproxy的区别 二、Haproy负载均衡部署1.实验前准备2.实验的具体操作步骤步骤一&#xff1a;部署haproxy负载均衡调度器步骤二&#xff1a;Nginx节点服务器…

MySQL——深入理解

前言 MySQL——深入理解主要包括MySQL的存储引擎、索引以及索引对数据库操作的性能优化、SQL优化、视图、存储过程、存储函数、触发器、锁、innoDB引擎的结构原理和数据库的相关管理操作。在这篇文章中&#xff0c;荔枝也是且学且整理&#xff0c;希望能帮助到有需要的小伙伴吧…

Esxi6给虚拟机磁盘扩容

需求 因为最初磁盘规划没想好&#xff0c;导致磁盘给的太小&#xff0c;很快磁盘被用满了&#xff0c;所以需要把该磁盘扩容。 我们这里尝试将/home分区由原来的45GB增加50GB&#xff0c;所以首先我们需要将整个虚拟机扩容50GB&#xff0c;然后再把这50GB全部分给/home分区&a…

前端网址收藏

1.图标库 ByteDance IconPark

WebGL的一些Bug

一、TypeErrpr,Cannot set properties of undefined("setting 1") at _JS_WebRequest_Create(...) 类似这样的问题 解决方案1&#xff1a;可能是BestHTTP插件导致的&#xff0c;打开BestHttp插件目录&#xff0c;找到BestHTTP/Plugins/WebGL目录文件夹下的&#xff…

COMSOL晶体材料损伤断裂模拟基于Voronoi维诺图泰森多边形建模

在外部荷载及内力效应的作用下&#xff0c;晶体材料将发生断裂破坏&#xff0c;按晶体材料断裂时裂纹扩展路径的差异&#xff0c;可将晶体的断裂分为穿晶断裂及沿晶断裂两种断裂形式。 穿晶断裂中裂纹穿过晶体的晶粒内部&#xff0c;断裂面较为粗糙&#xff1b;沿晶断裂中裂纹…

python读取广州-湛江天气csv文件并做可视化仪表盘

1.读取广-湛.csv文件 import pandas as pd data pd.read_csv(广-湛天气.csv) data 2.去除多余字符 #去除多余字符 data[[最高温度,最低温度]] data[[最高温度,最低温度]].apply(lambda x: x.str.replace(,).replace(, 0)) data.head() 3.删除2023年数据,并计算平均温度保存到…

【文件 part 1 - 文件的概念】

一、文件的概念 文件用来存放程序、文档、音频、视频数据、图片等数据的。 文件就是存放在磁盘上的&#xff0c;一些数据的集合。 在windows下可以通过写字板或记事本打开文本文件对文件进行编辑保存。写字板和记事本是微软程序员写的程序&#xff0c;对文件进行打开、显示、读…

2023虎啸奖揭榜 | AI加码,数说故事再度荣膺两项大奖

近日&#xff0c;第十四届虎啸奖颁奖典礼圆满落幕&#xff01;本届获奖名单已正式公布。自2018年起&#xff0c;数说故事已连续6年获奖&#xff0c;今年再度斩获“年度AI&大数据服务公司”大奖&#xff0c;旗下数说雷达是本届虎啸奖唯一荣获“年度最佳营销效果监测评估系统…

新手如何组装一台电脑

新手如何组装一台电脑 首先&#xff0c;我们要先了解一台电脑的基本构成由哪些&#xff1f; CPU显卡主板散热器磁盘内存电源机箱显示器 通常我们需要根据自己对电脑的定位&#xff0c;根据需求和资金确定CPU和显卡 CPU CPU主要有AMD和Intel。 Intel芯片单核能力足够强&…

大数据模型交易行业类型及数据挖掘工具

大数据模型交易平台拥有大量大数据人工智能项目案例资源&#xff0c;涉及行业领域包括农业、电力、电信、地质、医疗、环保、政务等行业。各行业通过模型预测可以获知预测风险率&#xff0c;可以找到应对风险措施同时也可以及时解决相关问题。 政务大数据模型 教育大数…

AutoCV第十课:3D基础

3D基础 前言 手写 AI 推出的全新保姆级从零手写自动驾驶 CV 课程&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程我们来学习下 nuScenes 数据集的可视化。 课程大纲可看下面的思维导图。 1. nuScenes数据集 明确下我们本次学习的目的&#xff1a;将…

ThinkPHP3.2.3通过局域网手机访问项目

折腾一上午&#xff0c; 试了nginx&#xff0c; 试了修改Apache的httpd.conf 试了关闭代理 试了手动配置网络 试了关闭防火墙 试了添加防火墙入站出站规则 问了五个ChatGPT 都没解决。 记录一下 wampserver3.0.4 Apache2.4.18 PHP 5.6.19 MySQL 5.7.11 所有服务启…

交换机上云MACC方式

步骤1、尝试ping通114.114.114.114 步骤2、尝试ping cloud.ruije.com.cn 若不通&#xff0c;配置dns&#xff1a;ip name-server 223.5.5.5 步骤3、设备开启cwmp功能 Ruijie#conf t Ruijie(config)#cwmp Ruijie(config-cwmp)#acs url http://118.190.126.198/service/tr069s…

Jmeter对数据库批量增删改查

目录 前言&#xff1a; 一、主要配置元件介绍 二、共有元件数据配置如下 前言&#xff1a; JMeter可以通过JDBC请求实现对数据库的批量增删改查。JDBC请求模拟了一个JDBC请求&#xff0c;它是连接池中的一个虚拟用户。JDBC请求可以定义SQL语句和预编译参数&#xff0c;…

【100个高大尚求职简历】简历模板+修改教程+行业分类简历模板 (涵盖各种行业) (简历模板+编辑指导+修改教程)

文章目录 1 简历预览2 简历下载 很多人说自己明明投了很多公司的简历&#xff0c;但是都没有得到面试邀请的机会。自己工作履历挺好的&#xff0c;但是为什么投自己感兴趣公司的简历&#xff0c;都没有面试邀请的机会。反而是那些自己没有投递的公司&#xff0c;经常给自己打电…

一文详解!教你如何在Jmeter里添加Get请求

目录 前言&#xff1a; 第一步&#xff0c;添加线程组 第二步&#xff0c;添加HTTP请求 第三步&#xff0c;添加监视器 前言&#xff1a; 前提条件&#xff1a;Jmeter已安装且已配置好&#xff1b;运行Jmeter&#xff0c;打开界面。 在JMeter中添加一个GET请求非常简…

使用uniapp的扩展组件,在微信小程序中出现报错如何解决

在 vue-cli 项目中可以使用 npm 安装 uni-ui 库 &#xff0c;或者直接在 HBuilderX 项目中使用 npm 。 注意 cli 项目默认是不编译 node_modules 下的组件的&#xff0c;导致条件编译等功能失效 &#xff0c;导致组件异常 需要在根目录创建 vue.config.js 文件 &#xff0c;增…