【Linux】网络编程套接字

一、预备知识

1.1 理解源IP地址和目的IP地址

在IP数据报的头部中,有两个IP地址,分别叫做源IP地址目的IP地址

       源IP地址和目的IP地址是网络通信中常用的两个概念,他们代表了通信中的两个节点。

       源IP地址是指发起通信的节点的IP地址,它标识了通信的发送方。在网络通信中,源IP地址用于识别数据包的来源,以便目的节点能够将恢复发送给正确的位置。

       目的IP地址是指接收通信的节点的IP地址,它标识了通信的接收方。在网络通信中,目的IP地址用于指定数据包的目的地,以便网络设备能够将数据包传递到正确的位置。

1.2 认识端口号

  • 我们上网无非只有两个动作:1.把远处的数据拉取到本地;2.把本地的数据发送到远端
  • 大部分的网络通信方式都是用户触发的,在计算机中,我们使用进程来表示用户:客户端服务,服务器服务
  • 把数据发送到目标主机中,不是目的,是手段;真正的目的是将数据发送到目标主机中的某一个服务(进程)
  • 网络通信的本质:其实是进程在帮我们进行网络通信,无论对于客户端还是服务器
  • IP(唯一的一台主机) + port(该主机上的唯一的一个进程) = 互联网中唯一的一个进程
  • 客户端进程 = 客户端IP + 客户端port = 客户端是互联网中唯一的一个进程;服务器进程 = 服务器IP + 服务器port = 服务器是互联网中唯一的一个进程

网络通信的本质实际上是进程间的通信。

端口号是传输层协议的内容:

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
  • IP地址 + 端口号能够标识网络上的某一个主机的某一个进程
  • 一个端口号只能被一个进程占用

1.3 理解端口号和进程ID

       我们之前在学习系统编程的时候,学习了pid标识唯一一个进程,此处我们的端口号也是唯一的表示一个进程。

       我们可不可以用进程ID来代替端口号?不可以,因为pid是随机改变的,每一次调用的时候,pid的值都会进行改变,但是常用的端口号是不会进行改变的。在操作系统中,每一个进程都有pid,但不是每一个进程都有port。

       最后,一个进程可以对应多个端口,而一个端口只能对应一个进程,不能被多个进程绑定。

1.4 理解源端口号和目的端口号

       传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁发的,要发给谁“。

1.5 认识TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

1.6 认识UDP协议

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

1.7 网络字节序

       我们在C语言学习中,内存中的多字节数据相对于内存地址由大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端和小端之分。网络数据流同样有大端和小端之分,那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
  • 接收主机把从网络中接收到字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序中保存的
  • 因此,网络数据流的地址应这样规定,先发出的数据是低地址,后发出的地址是高地址
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据
  • 如果当前发送的数据是小端,就需要先将数据转成大端;否则就忽略,直接发送即可

       为了使网络程序具有可移植性,使同样的C代码在大端计算机和小端计算机上编译后都能正常运行,可以调用一下库函数做网络字节序和主机字节序的转换。

#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表示network,l表示32位长整数,s表示16位短整数
  • 例如,htonl表示将32位的长整数从主机字节序中转换为网络字节序
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换,然后返回
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回

二、socket编程接口

2.1 socket 常见的 API

2.2 sockaddr 结构

       socket API是一层抽象的网络编程接口,适用于各种底层网络协议,比如IPv4和IPv6,以及之后的UNIX Domain Socket,然而,各种网络协议的地址并不相同。

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址使用sockaddr_in结构体表示,包括16位端口号和32位IP地址。
  • IPv4和IPv6类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
  • socket API可以都用struct sockaddr* 类型表示,在使用的时候需要强制类型转换成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4、IPv6以及UNIX Domain Socket各种了信息港的sockaddr结构体指针作为参数。

2.3 socket 结构体结构

2.3.1 sockaddr 结构

struct sockaddr
{
    __SOCKADDR_COMMON (sa_);
    char sa_addr[14];
};

2.3.2 sockaddr_in 结构

struct sockaddr_in
{
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;
    struct in_addr sin_addr;

    unsigned char sin_zero[sizeof(struct sockaddr) -
        __SOCKADDR_COMMON_SIZE -
        sizeof (in_port_t) -
        sizeof (struct in_addr_t)];
};

2.3.3 in_addr 结构

typedef uint32_t in_addr_t;

struct in_addr
{
    in_addr_t s_addr;
};

三、简单的UDP网络程序

3.1 服务器:

3.1.1 初始化服务器

3.1.1.1 创建套接字

        在服务端中,我们需要先来创建一个套接字(文件描述符),这一步是必须创建的,在创建套接字之前,我们先来学习一下socket函数。

3.1.1.1.1 socket函数

函数的原型:

函数的功能:

        用于创建套接字

函数的参数:

  • Domain:该参数表示套接字使用的协议簇,协议簇在:Linux/socket.h“中详细的定义,常用的协议簇有:
    • AF_UNIX(本地通信)
    • AF_INET(TCP/IP——IPv4)
    • AF_INET6(TCP/IP——IPv6)
  • type:该参数指的是套接字了信息港,常用的类型是:
    • SOCK_STREAM(TCP流)
    • SOCK_DGRAM(UDP数据报)
    • SOCK_RAW(原始套接字)
  • protocol:该参数在使用时,一般都置为0,就是说在已经确定套接字使用的协议簇和类型时,这个参数的值就为0。但是有时候创建初始套接字时,在domain参数未知情况下,即使不清楚协议类型时,protocol参数可以用来确定协议的种类。

函数的返回值:

  • 当套接字创建成功时,返回套接字的文件描述符
  • 失败返回“-1”,错误代码则写入“errno”中

因此,我们根据socket函数,我们可以写出一下代码:

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
           
if(sockfd < 0) // 如果创建套接字失败
{
    exit(1);
}

// 如果创建套接字成功之后,我们需要继续进行
3.1.1.2 填充struct sockaddr_in结构体

       我们在绑定服务器之前,我们需要知道服务器的IP地址和端口号,因为需要让客户端知道服务端的位置。我们在用户栈中创建出struct sockaddr_in结构体,在创建结构体之后,我们需要进行清零操作。在清零操作的时候,我们可以了解一个新函数:bzero函数。之后我们需要了解一个结构体内的类型:总共有四个类型。之后,我们需要填充这四个类型的变量。

3.1.1.2.1 bzero函数

函数的原型:


函数的功能:

        该函数可以将内存块(字符串)中的前n个字节清零
函数的参数:

  • s:指向所要清零的内存(字符串)的地址的指针
  • n:该参数为需要清零的字节数
3.1.1.2.2 struct sockaddr_in结构体类型

       在这个结构体类型中,总共有四种类型:sin_addr,sin_family,sin_port,sin_zero。在这四种类型中,我们可以进行一一对应。

  • sin_addr:对应的是32位IP地址,我们需要将字符串类型的IP地址转换为四字节整形的IP地址,我们需要将其填充进行结构体中
  • sin_family:对应的是我们使用哪种协议簇,在Linux中,我们一般使用AF_INET
  • sin_port:对应的是16位端口号(使用网络字节序)
  • sin_zero:是为了让sockaddr与sockaddr_in两种数据结构保持大小相同而保留的空字节

       但是,我们知道,现在是在用户栈中创建的结构体类型,这一步只是填充结构体类型,我们还没有进行绑定操作。

       在套接字通信的时候,我们需要将服务器的IP地址和端口号进行过绑定,但是,我们需要先进行获取端口号和IP地址。在获取端口号和IP地址时,我们需要关心网络字节序,因为这些数据是要发送到网络中,我们也需要通过网络来进行接收这些数据。在基础知识中,我们讲解了有关网络字节序的知识,知道网络中的存储方式是大端存储,所以我们需要将主机序列转换成网络序列。

3.1.1.2.3 如何将字符串类型的IP地址转换为四字节整形类型的IP地址?

       首先来看,如何将四字节整形类型的IP地址转换为字符串类型的IP地址:我们可以使用一个结构体来创建IP地址,结构体类型中的内容是四个整形类型的整数,之后,我们将四字节整形的IP地址进行强制类型转换,然后利用结构体中的成员,然后利用将整形转换成字符串的函数进行转换,然后将四个子串进行拼接起来。

       其次,如何将字符串类型的IP地址转换为四字节整形类型的IP地址:我们需要使用substr函数将字符串进行剪切出四个子串,然后将四个子串转换为整形类型,最后将四个整形组合成IP的结构体。

       但是,在系统中,我们不需要这么麻烦进行转换,系统中会提供一些接口来帮助我们完成这些任务:

inet_addr函数

函数的原型:

函数的功能:

        将点分十进制字符串类型的IP地址转换成四字节整形的IP地址,并转换成网络字节序列

函数的参数:

  • cp:指向所要转换的字符串的IP地址的指针

函数的返回值:

  • 如果正确执行将返回一个无符号长整数型数
  • 如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE
inet_ntoa函数

函数的原型:

函数的功能:

        将网络地址转换为点分十进制格式的字符串类型

函数的参数:

  • in:存放有IP地址的结构体类型

函数的返回值:

  • 该函数的返回值是一个字符串,这个字符串是点分十进制的IP地址。

       在上述工作完成后,我们就可以进行填充sockaddr_in结构体类型了,代码如下:

struct sockaddr_in local;  // 在用户栈上创建
bzero(&local, sizeof local); // 将结构体清空

local.sin_family = AF_INET;
local.sin_port = htons(_port);

local.sin_addr.s_addr = inet_addr(_ip.c_str());
3.1.1.3 绑定服务器

       因为服务器是要被客户端进行访问,所以我们需要知道服务器的IP地址和端口号,这样我们客户端就可以根据IP地址和端口号来访问服务器。

bind函数

函数的原型:

函数的功能:

       服务端用于将把用于通信的地址和端口号绑定到socket上。

函数的参数:

  • sockfd:需要绑定的socket
  • addr:需要传递addr_in结构体,存放服务端用于通信的地址和端口
  • addrlen:表示addr结构体的大小

函数的返回值:

  • 成功则返回0 ,失败返回-1,错误原因存于 errno 中。
  • 如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错,否则一般不会返回错误。
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof local);

if(n < 0)
{
    exit(1);
}

3.1.2 启动服务器

       根据常识,我们可以得知,服务器是需要死循环的:服务器会一直运行,直到管理者不想运行。所以在启动服务器中,我们需要使用死循环来进行。

3.1.2.1 接收数据

       我们服务器在接收数据时,我们需要知道是哪一个客户端发送数据给服务器的,还要了解一下接收函数。因此,我们需要一个缓冲区来存储我们所接收到的数据,创建一个struct sockaddr_in结构体来存储客户端的IP地址和端口号。

recvfrom函数

函数的原型:

函数的功能:

  recvfrom() 函数是一个系统调用,用于从套接字接收数据。该函数通常与无连接的数据报服务(如 UDP)一起使用,但也可以与其他类型的套接字使用。与简单的 recv() 函数不同,recvfrom() 可以返回数据来源的地址信息。

函数的参数:

  • sockfd:一个已经打开的套接字的文件描述符
  • buf:一个指针,指向用于存放接收到的数据的缓冲区
  • len:期望收到的数据的大小,缓冲区的大小(以字节为单位)
  • flags:控制接收行为的标志。通常可以设置为0,但以下是一些可用的标志:
    • MSG_WAITALL:尝试接收全部请求的数据,函数可能会阻塞,直到收到所有数据。
    • MSG_PEEK:查看即将接收的数据,但不从套接字缓冲区中删除它。
    • 其他一些标志还可以影响函数的行为,但在大多数常规应用中很少使用
  • src_addr:输出型参数。一个指针,指向一个sockaddr结构,用于保存发送数据的源地址
  • addrlen:输出型参数。一个值。它应该设置为src_addr缓冲区的大小。当recvform()函数返回时,该值会被修改为实际地址的长度(以字节为单位)。

函数的返回值:

  • 在成功的情况下,recvfrom()函数返回接收到的字节数
  • 如果没有数据可读或者套接字已经关闭,那么返回值为0
  • 出错时,返回-1,并设置全局变量errno以指示错误类型
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof peer;

ssize_t n = recvfrom(_sockfd, buffer, sizeof buffer - 1, 0, 
                                (struct sockaddr*)&peer, &len);

if(n > 0)
{
    buffer[n] = 0;
}
3.1.2.2 发回数据

       在接收数据后,我们可以收到客户端发送的数据,和客户端的IP地址和端口号。我们可以利用sendto函数将接收到的数据发送回去。

sendto函数

函数的原型:

函数的功能:

  sendto() 函数是一个系统调用,用于发送数据到一个指定的地址。它经常与无连接的数据报协议,如UDP,一起使用。不像 send() 函数只能发送数据到一个预先建立连接的远端,sendto() 允许在每次发送操作时指定目的地址。

函数的参数:

  • sockfd:一个已经打开的套接字的文件描述符
  • buf:一个指针,指向要发送的数据的缓冲区
  • len:要发送数据的大小(以字节为单位)
  • flags:控制发送行为的标志。通常可以设置为0,一些可用的标志包括:
    • MSG_CONFIRM:在数据报协议下告诉网络层该数据已经被确认了
    • MSG_DONTROUTE:不查找路由,数据报将只发送到本地网络
    • 其他标志可以影响函数的行为,但再大多数常规应用中很少使用
  • dest_addr:指向sockaddr结构的指针,该结构包含目标地址和端口信息
  • addrlen:dest_addr缓冲区的大小(以字节为单位)

函数的返回值:

  • 成功时,sendto()返回实际发送的字节数
  • 出错时,返回-1,并设置全局变量errno以指示错误类型
sendto(_sockfd, buffer, strlen(buffer) - 1, 0, (struct socket *)&peer, len);
3.1.2.3 一些知识点
3.1.2.3.1 netstart指令

        netstat指令:-anup 查看网络服务,选项:a:表示all,所有网络服务显示出来;u:表示的是UDP;n:number,能把能显示出数字,全部显示出数字;p:表示进行process,选项顺序无关。

3.1.2.3.2 本地循环地址

       127.0.0.1 本地环回,可以实现本地通信,常用于进行代码测试。我们在绑定服务器的IP地址时,不可以绑定一个确定的公网IP,因为在一个计算机上会有好几个不同的IP地址,我们可以根据不同的IP地址访问同一个端口,所以,我们强烈不推荐绑定公网IP,因此,我们最好绑定0地址。

3.3 将服务器代码进行拼接(完整代码)

#pragma once

#include <iostream>
#include <string>
#include <errno.h>
#include <cstring>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "InetAddr.hpp"

// 设置错误码
enum
{
    SOCKET_ERROR = 0,
    USAGE_ERROR
};

const static int defaultfd = -1;

class UdpServer
{

public:
    UdpServer(uint16_t port)
        : _sockfd(defaultfd), _port(port), _isrunning(false)
    {
    }
    // 初始化服务器  需要IP地址和端口号
    void initUdpServe()
    {
        // 文件描述符        套接字类型  TCP/UDP
        // 1. 创建对应的套接字——必须要做的
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) // 如果打印失败
        {
            LOG(FATAL, "error: %s, error_num: %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket success, socket: %d\n", _sockfd);

        // 2.0 填充sockaddr_in结构体
        // 用户栈上
        struct sockaddr_in local;    // 系统提供的数据类型,local是一个变量,在用户栈上开辟空间
        bzero(&local, sizeof local); // 进行清零操作

        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // port要经过网络传输到对面,先到网络,将主机序列转换为网络序列

        // 字符串风格的IP地址转换为点分十进制的IP地址
        // 主机序列转换为网络序列
        // in_addr_t inet_addr(const char* cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // IP地址  198.128.0.3  将字符串的形势转换为4字节的IP地址
        local.sin_addr.s_addr = INADDR_ANY;
        // 2. 绑定服务器  ———— 端口号和IP地址
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0) // 绑定失败
        {
            LOG(FATAL, "error: %s, error_num: %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }

        LOG(INFO, "socket bind success\n");
    }
    // 启动服务器
    void Start()
    {
        // 一直运行,直到管理者不想运行.服务器都是死循环
        // UDP是面向数据报的协议
        _isrunning = true;
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof peer;
            // 1. 我们需要先收到数据

            ssize_t n = recvfrom(_sockfd, buffer, sizeof buffer - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                InetAddr addr(peer);
                buffer[n] = 0;
                LOG(DEBUG, "get message from [%s : %d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
            }
            // 2. 我们将收到的数据发回
            // sendto ---
            sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
        }
        _isrunning = false;
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;    // 创建的套接字,文件描述符
    uint16_t _port; // 服务器所用的端口号
    // std::string _ip; // 暂时这样写,这个地方不是必须的
    bool _isrunning;
};

3.2 客户端:

       客户端的代码就比较简单了,有两个步骤:首先创建套接字,然后直接进行通信。

3.2.1 创建套接字

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if(sockfd < 0)
{
    std::cerr << "socket error" << std::endl;
}

3.2.2 直接进行通信

       在直接通信之前,我们需要思考一个小知识点:(客户端需不需要绑定端口号)客户端需不需要显式地绑定端口号?在进行服务器端的编写时,我们需要将服务器与端口号进行绑定。客户端需要绑定,但不是显式地绑定。当UDP客户端首次发送数据时,操作系统会自动地绑定。一个端口号只能与一个进程相关联。

发送数据:

       我们需要先将服务端的IP地址和端口号填充到sockaddr_in结构体中,然后直接进行通信:

std::string message;
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(serverip.c_str());

std::string message;
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(serverip.c_str());

接收数据:

struct sockaddr_in peer;
socklen_t len = sizeof peer;
char buffer[1024];

ssize_t n = recvfrom(sockfd, buffer, sizeof buffer - 1, 0, (struct sockaddr *)&peer, &len);
if (n > 0)
{
    buffer[n] = 0;
    std::cout << "server echo#" << buffer << std::endl;
}

3.2.3 完整的代码

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

void Usage(std::string proc)
{
    std::cout << "Usage: \n\t"
              << proc << "local_ip local_port\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 服务器的ip地址和端口号是需要客户端知道的
    std::string serverip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    // 2. 客户端要不要bind?客户端是要绑定的,因为客户端要有自己的IP和port,要不要显示的bind
    // 客户端不需要用显示的绑定,如何bind;当UDP客户端首次发送数据时,OS会自动的绑定
    // 什么时候绑定的?  首次发送数据时,需要进行绑定
    // 一个端口号只能与一个进程相关联

    std::string message;
    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(serverip.c_str());
    // 2. 直接进行通信
    while (true)
    {
        std::cout << "Place enter:";
        std::getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof server);

        struct sockaddr_in peer;
        socklen_t len = sizeof peer;
        char buffer[1024];

        ssize_t n = recvfrom(sockfd, buffer, sizeof buffer - 1, 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "server echo#" << buffer << std::endl;
        }
    }

    return 0;
}

四、服务器中接收的数据是谁发送的

#pragma once

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

class InetAddr
{
private:
    void GetAddress(std::string *ip, uint16_t *port)
    {

        *port = ntohs(_addr.sin_port);
        *ip = inet_ntoa(_addr.sin_addr);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在WSL Ubuntu中启用root用户的SSH服务

在 Ubuntu 中&#xff0c;默认情况下 root 用户是禁用 SSH 登录的&#xff0c;这是为了增加系统安全性。 一、修改配置 找到 PermitRootLogin 行&#xff1a;在文件中找到 PermitRootLogin 配置项。默认情况下&#xff0c;它通常被设置为 PermitRootLogin prohibit-password 或…

老生常谈问题之什么是缓存穿透、缓存击穿、缓存雪崩?举个例子你就彻底懂了!!

老生常谈问题之什么是缓存穿透、缓存击穿、缓存雪崩&#xff1f;举个例子你就彻底懂了&#xff01;&#xff01; 缓存穿透发生场景解决方案 缓存击穿解决方案 缓存雪崩发生场景解决方案 总结三者区分三者原因三者解决方案 想象一下&#xff0c;你开了一家便利店&#xff0c;店里…

FastAPI教程I

本文参考FastAPI教程https://fastapi.tiangolo.com/zh/tutorial 第一步 import uvicorn from fastapi import FastAPIapp FastAPI()app.get("/") async def root():return {"message": "Hello World"}if __name__ __main__:uvicorn.run(&quo…

从我邮毕业啦!!!

引言 时间过的好快&#xff0c;转眼间就要从北邮毕业了&#xff0c;距离上一次月度总结又过去了两个月&#xff0c;故作本次总结。 PS: https://github.com/WeiXiao-Hyy/blog整理了后端开发的知识网络&#xff0c;欢迎Star&#xff01; 毕业&#x1f393; 6月1号完成了自己的…

Windows server 2016.2019 .NET Framework 3.5安装包、安装步骤

windows server2019 操作系统 安装 sqlserver2008时提示缺少 .NET Frameword 3.5&#xff0c; 在功能里选择 .NET Frameword 3.5安装报错&#xff0c; 下载安装包&#xff0c;下载地址 https://download.csdn.net/download/qq445829096/89450429这里指定备份源路径 安装包解…

多供应商食品零售商城系统的会员营销设计和实现

在多供应商食品零售商城系统中&#xff0c;会员营销是提升用户粘性和增加销售的重要手段。一个有效的会员营销系统能够帮助平台更好地了解用户需求&#xff0c;提供个性化服务&#xff0c;进而提高用户满意度和忠诚度。本文将详细探讨多供应商食品零售商城系统的会员营销设计与…

2毛钱不到的2A同步降压DCDC电压6V频率1.5MHz电感2.2uH封装SOT23-5芯片MT3520B

前言 2A&#xff0c;2.3V-6V输入&#xff0c;1.5MHz 同步降压转换器&#xff0c;批量价格约0.18元 MT3520B 封装SOT23-5 丝印AS20B5 特征 高效率&#xff1a;高达 96% 1.5MHz恒定频率操作 2A 输出电流 无需肖特基二极管 2.3V至6V输入电压范围 输出电压低至 0.6V PFM 模式可在…

MySQL进阶-索引-使用规则-索引失效情况一(索引列运算,字符串不加引号,头部模糊匹配)

文章目录 1、索引列运算1.1、查询表tb_user1.2、查看tb_user的索引1.3、查询 phone177999900151.4、执行计划 phone177999900151.5、查询 substring(phone,10,2) 151.6、执行计划 substring(phone,10,2) 15 2、字符串不加引号2.1、查询 phone177999900152.2、执行计划 phone177…

JAVA-矩阵置零

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 思路&#xff1a; 找到0的位置&#xff0c;把0出现的数组的其他值夜置为0 需要额外空间方法&#xff1a; 1、定义两个布尔数组标记二维数组中行和列…

axios之CancelToken取消请求

从 v0.22.0 开始&#xff0c;Axios 支持以 fetch API 方式—— AbortController 取消请求 此 API 从 v0.22.0 开始已被弃用&#xff0c;不应在新项目中使用 官网链接 1. 背景 最近项目中遇到一个场景&#xff0c;当连续触发一个请求时&#xff0c;如果是同一个接口&#xf…

【仿真建模-anylogic】开发规范

Author&#xff1a;赵志乾 Date&#xff1a;2024-06-28 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 0. 说明 实际模型开发过程中&#xff0c;对遇到的问题进行总结归纳出以下开发规范&#xff0c;仅供参考&#xff01; 1. 强制性规范 1…

加密与安全_Java 加密体系 (JCA) 和 常用的开源密码库

文章目录 Java Cryptography Architecture (JCA)开源国密库国密算法对称加密&#xff08;DES/AES⇒SM4&#xff09;非对称加密&#xff08;RSA/ECC⇒SM2&#xff09;散列(摘要/哈希)算法&#xff08;MD5/SHA⇒SM3&#xff09; 在线生成公钥私钥对&#xff0c;RSA公私钥生成参考…

单目操作符

目录 ! --- 逻辑反操作 & --- 取地址操作符 * --- 间接访问操作符&#xff08;解引用操作符&#xff09; sizeof --- 操作数的类型长度&#xff08;单位为字节&#xff09; ~ --- 对一个数的补码二进制按位取反 前置和前置-- 后置和后置-- (类型) --- 强制类型转换…

《GPT模型揭秘:数据驱动AI的核心概念与GPT系列对比分析》

DS&#xff1a;《What Are the Data-Centric AI Concepts behind GPT Models?通过三个数据为中心的人工智能目标(训练数据开发、推理数据开发和数据维护)揭示GPT模型背后的数据为中心的人工智能概念》解读—GPT-1/GPT-2/GPT-3系列对比(语料大小参数量解码层数上下文长度隐藏层…

RabbitMQ中java实现队列和交换机的声明

java实现队列和交换机的声明 在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时&#xff0c;队列和交换机是程序员定义的&#xff0c;将来项目上线&#xff0c;又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来&#xff0c;…

重塑客户体验!VoLTE、VoNR引领新时代企业服务变革

试想一下&#xff0c;当你拨打客服咨询或售后电话时&#xff0c;没有漫长的等待&#xff0c;瞬时在手机中看到清晰的客服人员的脸&#xff0c;你说一句&#xff0c;ta说一句&#xff0c;你们流畅的沟通&#xff0c;仿佛线下面对面交流…… 这是VoLTE&#xff08;Voice over LT…

微服务部署上线过程总结

目录 一、找到适合自己的部署方式 二、开始部署&#xff0c;先安装需要的环境 2.1 梳理一下都需要安装什么软件 2.2 配置数据库环境 2.3 配置redis 2.4 配置nacos 2.5 配置rabbitmq 2.6 配置docker环境 三、环境配置好了&#xff0c;开始部署后端 3.1 梳理后端都…

Vue3学习笔记<->nginx部署vue项目(3)

安装nginx vue项目通常部署到nginx上&#xff0c;所以先安装一个nginx。为了方便安装的是windows版nginx&#xff0c;解压就能用。 项目参考上一篇文章《Vue3学习笔记&#xff1c;-&#xff1e;创建第一个vue项目》《Vue3学习笔记&#xff1c;-&#xff1e;创建第一个vue项目》…

微信视频号里面的视频怎么下载,分享4个视频号视频下载方法!可长期使用

如何在微信视频号里下载视频,虽然互联网上微信视频号视频下载方法千千万&#xff0c;奈何总有一些方法不起任何作用. 如何解决这一问题&#xff0c;今天就分享3个可以下载微信视频号的视频方法仅供参考。 1:提取器助手 手机搜索提取器助收/扫码获取视频号下载小助手二维码。该…

unity VR Interaction Framework 创建新手势

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、新建物体&#xff0c;并添加必要组件二、添加抓取点三、查看手势的可视化样式四、制作新的手势1.点击编辑2.根据需求调节手指关节3.保存手势4. 使用创建的手势5.运行 总结 前言…