【计算机网络】简易UDP网络小程序

news2024/10/5 13:40:51

文章目录

  • 1. socket函数:创建套接字
  • 2. 服务端
    • 2.1 服务端创建套接字
    • 2.2 服务端绑定
    • 2.3 字符串IP和整数IP
    • 2.4 运行服务器
  • 3. 客户端
    • 3.1 客户端创建套接字
    • 3.2 启动客户端
  • 4. 本地测试
  • 5. INADDR_ANY

1. socket函数:创建套接字

我们把服务封装成一个类,当我们定义出一个服务器对象后需要马上初始化服务器,而初始化服务区需要做的第一件事就是创建套接字。

创建套接字我们需要用到socket函数

在这里插入图片描述
参数说明:

  • domain:创建套接字的域或者叫做协议家族,也就是套接字的类型。该参数就相当于struct sockaddr的前十六位。如果是本地通信就设为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或者AF_INET6(IPv6)。
  • type:创建套接字时所需的服务类型。其中最常见的套接字服务类型是SOCK_STREAMSOCK_DREAM。如果是基于UDP的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,如果是基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。可以指明为TCP或者UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你用的是哪种协议。

返回值说明:

  • 套接字创建成功返回文件描述符,创建失败返回-1,同时错误码会被设置。

socket属于什么接口?

网络协议栈是分层的,根据TCP/IP四层协议,自顶向下以此是应用层、传输层、网络层、数据链路层。而我们现在所写的代码都叫做用户级代码,也就是说我们是在应用层编写代码,因此我们调用的实际是下三层的接口,而传输层和网络层都是在操作系统内完成的,也就意味着我们在应用层调用的接口都叫做系统调用接口。

socker函数在底层做了什么?

socket函数是被进程所调用的,而每一个进程在系统层面上都有一个管理进程的PCB、文件描述符表(files_struct)以及对于打开的文件。而文件描述符表内包含了一个数组fd_array,其中数组的0、1、2下标默认被标准输入、标准输出以及标准错误所占用。
在这里插入图片描述
当我们调用socket函数创建套接字时,实际相当于我们打开了一个“网络文件”,打开后在内核层面上就形成了一个对应的struct file结构体,同时该结构体被连入到了该进程对应的文件双链表,并将该结构体的地址填入到了fd_array数组当中下标为3的位置,此时fd_array数组中下标为3的指针就指向了这个打开的“网络文件”,最后3号文件描述符作为socket函数的返回值返回给了用户。
在这里插入图片描述
其中每一个struct file结构体中包含的就是对应打开文件各种信息,比如文件的属性信息、操作方法以及文件缓冲区等。其中文件对应的属性在内核当中是由struct inode结构体来维护的,而文件对应的操作方法实际就是一堆的函数指针在内核当中就是由struct file_operations结构体来维护的。而文件缓冲区对于打开的普通文件对应的一般是磁盘,但对于现在打开的“网络文件”来说,这里的文件缓冲区对应的就是网卡。

在这里插入图片描述
对于一般的普通文件来说,当用户通过文件描述符将数据写到文件缓冲区中,然后再把数据刷到磁盘上就完成了数据的写入操作。而对于现在socket函数打开的“网络文件”来说,当用户将数据写到文件缓冲区之后,操作系统会定期将数据刷到网卡里面,而网卡是负责数据发送的,因此数据最终就被发送到了网络当中。

2. 服务端

2.1 服务端创建套接字

当我们在进行初始化服务器创建套接字时,就是调用socket函数创建套接字,创建套接字我们要传入的协议家族就是AF_INET,表明我们要进行的是网络通信,而我们需要的服务类型是SOCK_DGRAM,因为我们现在编写的UDP服务器是面向数据报的,而第三个参数设置为0即可。

#pragma once

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>

class UdpServer
{
public:
    bool InitServer()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        std::cout << "socket create success, sockfd: " << _sockfd << std::endl;
        return true;
    }

    ~UdpServer()
    {
        if (_sockfd >= 0)
            close(_sockfd);
    }

private:
    int _sockfd; // 文件描述符
};

上面那段代码在hpp头文件中,下面我们在源文件中包含它,并运行起来,看看结果。

#include "UdpServer.hpp"

int main()
{
    UdpServer* svr = new UdpServer();
    svr->InitServer();
    return 0;
}

运行结果如下:
在这里插入图片描述

2.2 服务端绑定

现在套接字已经创建成功了,但只是在系统层面上打开了一个文件,操作系统并不知道是要将数据写入到磁盘还是网卡,此时该文件还没有与网络关联起来。
由于现在编写的是不面向连接的UDP服务器,所以初始化的第二件事就是绑定。

bind函数

服务端绑定用到的是bind函数
在这里插入图片描述
参数说明:

  • sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度

返回值说明:

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

struct sockaddr_in结构体

在绑定时我们需要将网络相关的属性信息填充到一个结构体当中,然后将该结构体作为bind函数的第二个参数进行传入,这实际就是struct sockaddr_in结构体。

struct sockaddr_in当中的成员如下:

  • sin_family:表示协议家族
  • sin_port:表示端口号,是一个16位的整数
  • sin_addr:表示ip地址,是一结构体,该结构体当中只有一个32位的整数,ip地址实际存储在这个整数中。

剩下的字段一般不作处理。

如何理解绑定?

在进行绑定的时候需要将IP地址和端口号告诉对应的网络文件,此时就可以改变网络文件当中文件操作函数的指向,将对应的操作函数改为对应网卡的操作方法,此时读数据和写数据对应的操作对象就是网卡了,所以绑定实际上就是将文件和网络关联起来。

增加IP地址和端口号

由于绑定时需要用到IP地址和端口号,因此我们需要在服务器当中引入IP地址和端口号,在创建服务器对象时需要传入对应的IP地址和端口号,此时我们就可以根据传入的IP地址和端口号进行初始化。

class UdpServer
{
public:
    UdpServer(std::string ip, int port)
        : _sockfd(-1), _port(port), _ip(ip)
    {};

    bool InitServer()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        std::cout << "socket create success, sockfd: " << _sockfd << std::endl;
        return true;
    }

    ~UdpServer()
    {
        if (_sockfd >= 0)
            close(_sockfd);
    }

private:
    int _sockfd; // 文件描述符
    int _port; // 端口号
    std::string _ip; // IP地址
};

服务端绑定

套接字创建完毕之后我们就需要进行绑定了,但在绑定之前我们需要先定义一个struct sockaddr_in结构,将对应的网络属性信息填充到该结构当中。由于该结构体当中还有部分选填字段,因此我们最好在填充之前对该结构体变量里面的内容进行清空,然后再将协议家族、端口号、IP地址等信息填充到该结构体变量当中。

需要注意的是,在发送到网络之前需要将端口号设置为网络序列,由于端口号是16位的,因此我们需要使用前面说到的htons函数将端口号转为网络序列。此外,由于网络中传输的是整数IP,我们需要调用inet_addr函数将字符串转换成整数IP,然后再将转换后的整数IP进行设置。(inet_addr函数同时做了将字符串转换为整数,以及将主机字节序转换为网络字节序两件事)。

当网络信息填充完毕之后,在bind函数传入结构体地址时还需要将struct sockaddr_in* 强转为struct sockaddr* 类型后再进行传入。

    bool InitServer()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        std::cout << "socket create success, sockfd: " << _sockfd << std::endl;

        // 填充网络相关信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());

        // 绑定
        int ret = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if (ret < 0)
        {
            std::cerr << "bind error" << std::endl;
            return false;
        }
        std::cout << "bind success" << std::endl;
        
        return true;
    }

2.3 字符串IP和整数IP

IP地址的表现形式有两种,一种是点分十进制,另外一种是直接用一个32位整数表示。

整数IP存在的意义

如果我们在网络传输时直接以基于字符串的点分十进制进行IP地址的传送,俺么此时一个IP地址就至少需要15个字节,但实际并不需要耗费这么多字节。

IP地址实际可以划分为四个区域,其中每个区域的取值都是0~255,而这个范围的数字只需要8个比特位就能表示,因此我们实际只需要32个比特位就能够表示一个IP地址。其中这个32位的整数,每一个字节对应的就是IP地址中的某个区域,我们将IP地址的这种表示方法称之为整数IP,此时每一个IP地址只需要4个字节。

因为采用整数IP的方案表示一个IP地址只需要4个字节,并且在网络通信也能表示同样的含义,因此在网络通信时就没有用字符串IP而用的是整数IP,因为这样能够减少网络通信时数据的传送。

inet_addr函数

在进行字符串IP和网络IP的转换时,系统为我们提供了相应的转换函数,我们直接调用即可。
在这里插入图片描述
使用该函数,我们只需传入要进行转换的字符串IP,该函数返回的就是转换后的整数IP。

inet_ntoa函数

将整数IP转换为字符串IP函数为inet_ntoa
在这里插入图片描述
需要注意的是,传入inet_ntoa函数的参数类型是in_addr,因此我们在传参时不需要选中in_addr结构当中的32位的成员传入,直接传入in_addr结构体即可。

2.4 运行服务器

UDP服务器的初始化只需要创建套接字和绑定,当服务器初始化完毕之后就可以启动服务了。

服务器实际上就是在周而复始地为我们提供某种服务,服务器在运行起来之后就是一个死循环。由于UDP服务器是不面向连接的,因此只要UDP服务器启动后,就可以直接读取客户端发来的数据。

recvfrom

UDP服务器读取数据的函数叫做recvfrom
在这里插入图片描述
参数说明:

  • sockfd:对应操作的文件描述符,表示从该文件描述符索引的文件当中读取数据。
  • buf:读取数据的存放位置
  • len:最大读取数据的字节数
  • flags:读取方式,一般设置为0,表示阻塞读取
  • src_addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等
  • addrlen:调用时传入期望读取的src_addr结构体长度,返回时代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

返回值说明:

  • 读取成功时返回实际读取到的字节数,读取失败返回-1,同时错误码会被设置

注意:

  • 由于UDP是不面向连接的,因此我们除了读取到数据意外还需要获取到对端网络相关的属性信息,包括IP地址和端口号。
  • 在调用recvfrom读取数据时,必须将addrlen设置为你要读取的结构体对应的大小。
  • 由于recvfrom函数提供的参数也是struct sockaddr* 类型的,因此我们传入结构体地址时需要将struct sockaddr_in* 进行强转。

代码实现

现在服务端通过recvfrom函数读取客户端数据,我们可以先将读取到的数据当作字符串来看待,将读取到的数据的最后一个位置设置为 ‘\0’,此时我们就可以将读取到的数据进行输出,同时我们也可以将获取到的客户端的IP地址和端口号也一并进行输出。

需要注意的是,我们获取到的客户端的端口号此时是网络序列,我们需要通过ntohs函数将其转为主机序列后再进行打印输出。同时,我们获取到的客户端的IP地址是整数IP,此时我们需要通过inet_ntoa函数将其转换为字符串IP再进程输出。

    void start()
    {
#define SIZE 1024
        char buffer[SIZE];
        while (1) 
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t size = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
            if (size > 0)
            {
                buffer[size] = 0;
                int port = ntohs(peer.sin_port);
                std::string ip = inet_ntoa(peer.sin_addr);
                std::cout << ip << " : " << port << "# " << buffer << std::endl;
            }
            else
            {
                std::cerr << "recvfrom error" << std::endl;
            }
        }
    }

注意:如果调用recvform函数读取数据失败,我们可以打印一条提示信息,但是不要让服务器退出,服务器不能因为读取某一个客户端的数据失败就退出。

引入命令行参数

鉴于构造服务器需要传入IP地址和端口号,这里引入命令行参数。此时当我们运行服务器器时后面跟上对应的IP地址和端口号即可。

我们可以先不传入IP地址,使用127.0.0.1这个地址,这个地址等价于localhost即本地主机,我们使用127.0.0.1这个地址时可以称为本地环回,相当于我们一会先在本地测试一些能否正常通信,然后再进行网络通信的测试。

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    std::string ip = "127.0.0.1"; // 本地环回
    int port = atoi(argv[1]);
    UdpServer* svr = new UdpServer(ip, port);
    svr->InitServer();
    svr->start();
    return 0;
}

此时运行程序,就可以看到套接字创建成功、绑定成功了,现在服务器就是在等待客户端发送数据。
在这里插入图片描述
我们可以通过netstat命令查看当前网络的状态

netstat命令常用选项说明:

  • -n:以数字格式显示网络地址和端口,而不进行主机名或服务名的解析
  • -l:显示监控中服务器的socket
  • -t:显示TCP传输协议的连线情况
  • -u:显示UDP协议的连线情况
  • -p:显示正在使用socket的程序识别码和程序名称

在这里插入图片描述
如果去掉 -n 选项,原本显示IP地址的地方就变成了对应的域名服务器
在这里插入图片描述

  • Proto:表示协议的类型
  • Recv-Q:表示网络接收队列
  • Send-Q:表示网络发送队列
  • Local Address:表示本地地址
  • Foreign Address:表示外部地址
  • State:表示当前的装填
  • PID:表示该进程的进程ID
  • Program name:表示该进程的程序名称

其中Foreign Address写成 0.0.0.0:* 表示任意IP地址、任意端口号的程序都可以访问当前进程。

3. 客户端

3.1 客户端创建套接字

同样地,我们把客户端也封装成一个类,当我们定义出一个客户端对象后也是需要对其进行初始化,而客户端在初始化是也需要创建套接字,之后客户端发送数或接收数据也就是对这个套接字进行操作。

客户端创建套接字时选择的协议家族也是AF_INET,需要的服务类型也是SOCK_DGRAM,当客户端被析构时也可以选择对应的套接字。与服务端不同的是,客户端在初始化时只需要创建套接字就行了,不需要程序员进行绑定。

class UdpClient
{
public:
    bool InitClient()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        return true;
    }

    ~UdpClient()
    {
        if (_sockfd > 0)
            close(_sockfd);
    }
private:
    int _sockfd;
}

客户端要不要进行bind?

client必须要进行bind,要有自己的IP和port,说不要bind是错的,只不过client不需要程序员去进行bind。服务端的port为了客户端更快地找到,它是固定不变的,但是客户端有很多,如果都要让客户处去显式地绑定一个port,程序员可能选的port是一样的。为了避免重复,port由操作系统自动生成并隐式地进行绑定。
所以,在UDP通信中写客户端时,只需要创建套接字即可,不需要bind,bind由操作系统自己完成!

3.2 启动客户端

增加服务端IP地址和端口号

作为一个客户端,它必须知道它要访问的服务端的IP地址和端口号,因此在客户端类当中需要引入服务端的IP地址和端口号,此时我们就可以根据传入的服务端的IP地址和端口号对对应的成员进行初始化。

class UdpClient
{
public:
    UdpClient(std::string server_ip, int server_port)
        : sockfd(-1), _server_ip(server_ip), _server_port(server_port)
    {}

    bool InitClient()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        return true;
    }

    ~UdpClient()
    {
        if (_sockfd > 0)
            close(_sockfd);
    }
private:
    int _sockfd;
    int _server_port; // 服务端端口号
    std::string _server_ip // 服务端IP地址
}

sendto函数

UDP客户端发送数据的函数叫做sendto
在这里插入图片描述
参数说明:

  • sockfd:对应操作的文件描述符,表示将数据写入该文件描述符索引的文件当中
  • buf:待写入数据的存放位置
  • len:期望写入数据的字节数
  • flags:写入的方式。一般设置为0,表示阻塞写入。
  • dest_addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addlen:传入dest_addr结构体的长度

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

注意:

  • 由于UDP是不面向连接的,因此除了待发送的数据以外还需要指明对端网络相关的信息,包括IP地址和端口号等。
  • 由于sendto函数提供的参数也是struct sockaddr* 类型的,因此我们在传入结构体地址时需要将struct sockaddr_in* 类型进行强转

启动客户端函数

现在客户端要发送数据给服务端,我们可以让客户端获取用户输入,不断将用户输入的数据发送给服务端。

需要注意的是,客户端中存储的服务端的端口号此时是主机序列,我们需要调用htons函数将其转换为网络序列后再设置进struct sockaddr_int结构体。同时,客户端中存储的服务端的IP地址是字符串IP,我们需要通过调用inet_addr函数将其转换为整数IP后再设置进struct sockaddr_in结构体。

    void start()
    {
        std::string msg;
        struct sockaddr_int peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(_server_port);
        peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());

        while (1)
        {
            std::cout << "Please Enter# ";
            getline(std::cin, msg);
            sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
        }
    }

引入命令行参数

构造客户端时需要传入对应服务器的IP地址和端口号,我们这里也可以引入命令行参数。当我们运行客户端时直接在后面跟上对应服务器端的IP地址和端口号即可。

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    int server_port = atoi(argv[2]);

    UdpClient* clt = new UdpClient(server_ip, server_port);
    clt->InitClient();
    clt->start();

    return 0;
}

4. 本地测试

现在服务端和客户端的代码都已经编写完毕,我们可以先进行本地测试,此时服务器绑定的是本地环回,现在我们运行服务器时指定端口号为8081,再运行客户端,此时客户端要访问的服务器的IP地址就是本地环回127.0.0.1,服务器的端口号是8081。
在这里插入图片描述
此时我们再用netstat命令查看网络信息,可以看到服务端端口号为8081,客户端端口号为56577。这里客户端能被netstat命令查看到,说明客户端也已经绑定成功了。
在这里插入图片描述

5. INADDR_ANY

现在我们已经经过了本地测试,接下来就需要进行网络测试了,那是不是直接让服务端绑定我的公网IP,此时这个服务端就能够被外网访问了呢?
理论上是这样的,但是我用的是云服务器,云服务器的IP地址是由对应的云厂商提供的,这个IP地址并不一定是真正的公网IP,这个IP地址是不能直接被绑定的,如果要让外网访问,我们需要将IP地址绑定为INADDR_ANY,这个一个宏值,它对应的值就是0。

绑定INADDR_ANY的好处

当一个服务器的宽带足够大时,一台机器接收数据的能力就约束了这台机器的IO效率,因此一台服务器底层可能装有多张网卡,此时这台服务器就有多个IP地址,但一台服务器上端口号为8081的服务只有一个。这台服务器在接收数据时,这里的多张网卡在底层实际都收到了数据,如果这些数据也都想访问端口号为8081的服务。此时如果服务端在绑定的时候是指明绑定的某一个IP地址,那么此时服务器在接收数据的时候就只能从绑定IP对应的网卡接收数据。而如果服务端绑定的是INADDR_ANY,那么只要是发送给端口号为8081的服务的数据,系统都可以将数据自底向上交给该服务器。
在这里插入图片描述
因此服务端绑定INADDR_ANY这种方案也是强烈推荐的方案,所有服务器具体在操作的时候也是用的这种方案。

当前,如果想让外网访问服务器,但你又指向绑定某一个IP地址,那么就不能使用云服务器,此时可以选择使用虚拟机或者你自定义安装的Linux操作系统,那个IP地址就是支持你绑定的,而云服务器是不支持的。

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

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

相关文章

计算机网络最基础知识介绍

OSI和TCP/IP是很基础但又非常重要的知识,很多知识点都是以它们为基础去串联的,作为底层,掌握得越透彻,理解上层时会越顺畅。今天这篇网络基础科普,就是根据OSI层级去逐一展开的。 01 计算机网络基础 01 计算机网络的分类 按照网络的作用范围:广域网(WAN)、城域网(MA…

【Kafka源码走读】Admin接口的客户端与服务端的连接流程

注&#xff1a;本文对应的kafka的源码的版本是trunk分支。写这篇文章的主要目的是当作自己阅读源码之后的笔记&#xff0c;写的有点凌乱&#xff0c;还望大佬们海涵&#xff0c;多谢&#xff01; 最近在写一个Web版的kafka客户端工具&#xff0c;然后查看Kafka官网&#xff0c;…

Python基础教程:sklearn机器学习入门

1. sklearn基础介绍 sklearn&#xff08;全名为scikit-learn&#xff09;是一个建立在NumPy、SciPy和matplotlib等科学计算库的基础上&#xff0c;用于机器学习的Python开源库。它提供了丰富的工具和函数&#xff0c;用于处理各种机器学习任务&#xff0c;包括分类、回归、聚类…

线性表的顺序存储和链式存储—Python数据结构(二)

线性表 定义&#xff1a; 线性表的定义是描述其逻辑结构&#xff0c;而通常会在线性表上进行的查找、插入、删除等操作。 线性表作为一种基本的数据结构类型&#xff0c;在计算机存储器中映象(表示)一般有两种形式&#xff0c;一种是顺序映象&#xff0c;一种是链式映象。 线…

接口漏洞-WebService-wsdl+SOAP-Swagger+HTTP-WebPack

什么是接口&#xff1f; 接口就是位于复杂系统之上并且能简化你的任务&#xff0c;它就像一个中间人让你不需要了解详细的所有细节。像谷歌搜索系统&#xff0c;它提供了搜索接口&#xff0c;简化了你的搜索任务。再像用户登录页面&#xff0c;我们只需要调用我们的登录接口&am…

Jupyter 安装和使用

安装Jupyter 使用pip工具进行安装&#xff0c;在命令提示窗口输入命令如下&#xff1a; pip install jupyter notebook 使用Jupyter 在命令提示窗口输入如下命令&#xff0c;启动浏览器页面&#xff1a; jupyter notebook 修改jupyter的工作路径/存储路径 由于默认工作路…

去括号问题(C++处理)

继http://t.csdn.cn/kIcUT后的文章 题目描述 当老师不容易&#xff0c;尤其是当小学的老师更难:现在的小朋友做作业喜欢滥用括号。 虽然不影响计算结果&#xff0c;但不够美观&#xff0c;容易出错&#xff0c;而且可读性差。但又不能一棒子打死&#xff0c;也许他们就是将来的…

【Linux从入门到精通】进程的控制(进程退出+进程等待)

本篇文章主要讲述的是进程的退出和进程等待。希望本篇文章的内容会对你有所帮助。 文章目录 一、fork创建子进程 1、1 在创建子进程中操作系统的作用 1、2 写时拷贝 二、进程终止 2、1 常见的进程退出 2、2 进程的退出码 2、2、1 运行结果正确实例 2、2、2 运行结果不正确实例…

防御第二天-防火墙演示实验

1.上课思维导图 2.防火墙演示实验 防火墙FW1&#xff1a;原用户名&#xff1a;admin 原密码&#xff1a;Admin123 配地址&#xff1a;<USG6000V1>sy [USG6000V1]int g0/0/0 [USG6000V1-GigabitEthernet0/0/0]ip add 192.168.18.2 24 打开所有权限[USG6000V1-Gig…

C++(一):基本数据类型

基本数据类型 基本内置类型定义变量type field value;type field(value);type field{value};type field {value}; 数学常量及函数静态类型转换 static_cast格式化字符串std::stringstreamstd::string引入三方库 fmt/core.h 字符运算auto 关键字枚举类型数据类型定义别名判断是…

牛客网刷题之链表(一)

链表 NB1 删除链表峰值NB2 牛群排列去重NB3 调整牛群顺序NB4 牛群的重新分组NB5 牛群的重新排列NB6 合并两群能量值&#xff08;合并有序单链表&#xff09;NB7 牛群的能量值&#xff08;单链表相加&#xff09; 以下题全部出自牛客网。 题目题目考察的知识点链表&#xff1a; …

Element Plus 日期选择器

计算开始日期到结束日期的总天数 结构 <el-form-item label"计划开始时间" required prop"StartTime"><el-date-pickertype"date"v-model"ruleForm.StartTime":disabled-date"StartTime"placeholder"计划开始…

pytorch工具——使用pytorch构建一个分类器

目录 分类器任务和数据介绍训练分类器的步骤在GPU上训练模型 分类器任务和数据介绍 训练分类器的步骤 #1 import torch import torchvision import torchvision.transforms as transformstransformtransforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.…

SpringCloud学习路线(8)—— Docker

一、Docker的开始 &#xff08;一&#xff09;项目部署问题&#xff1a; 依赖关系复杂&#xff0c;容易出现兼容性问题开发、测试、生产环境有差异 &#xff08;二&#xff09;Docker如何解决问题&#xff1f; 1、依赖兼容问题 &#xff08;1&#xff09;将应用的Libs&…

垃圾回收之三色标记法(Tri-color Marking)

关于垃圾回收算法&#xff0c;基本就是那么几种&#xff1a;标记-清除、标记-复制、标记-整理。在此基础上可以增加分代&#xff08;新生代/老年代&#xff09;&#xff0c;每代采取不同的回收算法&#xff0c;以提高整体的分配和回收效率。 无论使用哪种算法&#xff0c;标记…

(已解决)RuntimeError: Java gateway process exited before sending its port number

今天用Pycharm远程使用pysaprk解释器时&#xff0c;跑代码出现了这个错误&#xff1a; RuntimeError: Java gateway process exited before sending its port number 找了好多博客都没解决问题&#xff0c;有说重装spark的&#xff0c;有说本地配Java_home的&#xff0c;后面我…

[C语言刷题]杨氏矩阵、返回型参数

本文包含知识点 杨氏矩阵极其解法函数return多个值的四种方法 题目&#xff1a; 杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于…

js 在浏览器窗口关闭后还可以不中断网络请求

有个需求&#xff0c;我们需要在用户发送数据过程中&#xff0c;如果用户关闭了网页(包括整个浏览器关闭)&#xff0c;不要中断数据传递 目前XMLHttpRequest对象是不支持的 http服务器 为了测试效果我们用nodejs写了个http服务器代码 文件名为httpServer.js如下&#xff0c;…

获取大疆无人机的飞控记录数据并绘制曲线

机型M350RTK&#xff0c;其飞行记录文件为加密的&#xff0c;我的完善代码如下 gitgithub.com:huashu996/DJFlightRecordParsing2TXT.git 一、下载安装官方的DJIFlightRecord git clone gitgithub.com:dji-sdk/FlightRecordParsingLib.git飞行记录文件在打开【我的电脑】&am…

Windows nvm 安装后webstrom vue项目编译报错,无法识别node

1 nvm安装流程 卸载原先nodejs用管理员权限打开exe安装nvmnvm文件夹和nodejs文件夹 都授权Authenticated Users 完全控制nvm list availablenvm install 16.20.1nvm use 16.20.1输入node和npm检查版本命令&#xff0c;正常显示确认系统变量和用户变量都有nvm 和nodejs 2 bug情…