Linux——socket网络通信

news2024/9/19 11:08:55

一、什么是socket

Socket套接字 由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将 TCP/IP 协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了 Socket套接字。Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件sys/socket.h 中。

socket 其实就是一个网络通信的接口,或者说是标准,我们通过使用这个接口就能进行网络通信。在进行通信之前,我们需要确定对方主机的地址,也就是对方主机的 IP 地址;由于网络通信的消息是发送给对方主机上的某一个具体的进程的,比如用 QQ 发送消息给对方,对方也是用 QQ 这个应用接收消息的,所以我们需要指定将这个消息发送给对方主机上 QQ 这个应用,我们通过指定 QQ 这个进程对应的 Port端口号 完成。

注意socket 翻译过来的意思是 插座,有插座的话那么就存在 插头插头 看作 客户端插座 看成 服务端,将 插头 插到 插座 上的这个过程就类似于 客户端和服务端通信建立连接的过程。肯定是先要有 插座,才会有插头;实际应用中,应该是先启动客户端等待客户端的请求连接,再启动客户端建立连接

二、socket

1.字节序

字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指电脑内存中或在数字通信链路中,组成多字节的字的字节的排列顺序。

字节序存在 大端序(主机字节序)小端序(网络字节序)

  • 大端序低位字节放在低位地址,高位字节放在高位地址;
  • 小端序低位字节放在高位地址,高位字节放在低位地址;
// 有一个16进制的数, 有32位 (int): 0xab5c01ff
// 字节序, 最小的单位: char 字节, int 有4个字节, 需要将其拆分为4份
// 一个字节 unsigned char, 最大值是 255(十进制) ==> ff(16进制) 
                 内存低地址位                内存的高地址位
--------------------------------------------------------------------------->
小端:         0xff        0x01        0x5c        0xab
大端:         0xab        0x5c        0x01        0xff

注意: 对于 intlong 这种 4个字节,8个字节是一个整体的类型才会存在字节序问题,单字节类型是不会存在字节序问题的,比如字符串就不会有字节序问题

为什么要介绍这个字节序的问题?

在 socket 网络通信过程中,收发的数据,端口,IP地址都是大端序的。而我们主机平时使用的是小端序,所以我们需要在通信之前将对应的数据做相应的转换。

大端序 和 小端序对应的转换函数:

// u:unsigned
// 16: 16位, 32:32位
// h: host, 主机字节序
// n: net, 网络字节序
// s: short
// l: int

// 将一个短整形从主机字节序(小端序) -> 网络字节序(大端序)
uint16_t htons(uint16_t hostshort);	
// 将一个整形从主机字节序 -> 网络字节序
uint32_t htonl(uint32_t hostlong);	

// 将一个短整形从网络字节序(大端序) -> 主机字节序(小端序)
uint16_t ntohs(uint16_t netshort)
// 将一个整形从网络字节序 -> 主机字节序
uint32_t ntohl(uint32_t netlong);

2. IP 地址

IP地址实际本质上是一个整数,但是在形式上我们用一个 “点分十进制” 的字符串来描述,比如本地地址是 "127.0.0.1"

我们可以通过下面的函数,将 主机字节序(小端序)的 IP地址(字符串) 转换为 网络字节序(大端序,整数)

// 主机字节序的IP地址转换为网络字节序
// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
int inet_pton(int af, const char *src, void *dst); 

参数:

  • afIP地址族(包括:AF_INET IPv4格式的 IP 地址;AF_INET6 IPv6格式的 IP 地址);
  • srcIP 地址,一个点分十进制的字符串,比如 "127.0.0.1"
  • dst传出参数,对应的 src 被转换成 网络字节序(大端序) 之后,就将结果写到了 *dst 这个指针所指的内存处;

返回值:

  • 返回 1 1 1,表示转换成功;
  • 返回 0   o r   − 1 0\ or\ -1 0 or 1,表示失败;

通过下面这个函数,可以将 网络字节序(大端序)的 IP 地址(整数) 转换成 主机字节序(小端序)点分十进制的 IP 地址(字符串)

#include <arpa/inet.h>
// 将大端的整形数, 转换为小端的点分十进制的IP地址        
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

  • afIP地址族(包括:AF_INET IPv4格式的 IP 地址;AF_INET6 IPv6格式的 IP 地址);
  • src网络字节序的整型(大端序) IP 地址;
  • dst传出参数src 转换成的 主机字节序(小端序)的 点分十进制的 IP 地址
  • size:修饰 dst,表示 dst 指向的内存最多可以存入 size 个字节的数据;

返回值:

  • 成功: 指针指向第三个参数对应的内存地址, 通过返回值也可以直接取出转换得到的 IP字符串;
  • 失败:返回 NULL

3.sockaddr 结构

在这里插入图片描述

// 在写数据的时候不好用
struct sockaddr {
	sa_family_t sa_family;       // 地址族协议, ipv4
	char        sa_data[14];     // 端口(2字节) + IP地址(4字节) + 填充(8字节)
}

typedef unsigned short  uint16_t;
typedef unsigned int    uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

struct in_addr
{
    in_addr_t s_addr;
};  

// sizeof(struct sockaddr) == sizeof(struct sockaddr_in)
struct sockaddr_in
{
    sa_family_t sin_family;		/* 地址族协议: AF_INET */
    in_port_t sin_port;         /* 端口, 2字节-> 大端  */
    struct in_addr sin_addr;    /* IP地址, 4字节 -> 大端  */
    /* 填充 8字节 */
    unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
               sizeof (in_port_t) - sizeof (struct in_addr)];
};  

4.套接字函数

使用套接字通信函数需要引入头文件 <arpa/inet.h>

1.创建套接字

// 创建一个套接字
int socket(int domain, int type, int protocol);

参数:

  • domain:使用的地址族协议。AF_INET,使用 IPv4 格式的 IP地址;AF_INET6,使用 IPv6 格式的 IP 地址;
  • typeSOCK_STREAM,使用流式传输的协议,TCP协议;SOCK_DGRAM,使用报文传输的协议,UDP协议;
  • protocal:一般写 0 0 0,使用默认协议。SOCK_STREAM 默认使用的是 TCP 协议;SOCK_DGRAM 默认使用的是 UDP 协议;

返回值:

  • 成功,返回可用于套接字通信的 文件描述符
  • 失败,返回 − 1 -1 1

该函数执行成功的返回值是一个 文件描述符通过这个文件描述符可以操作内核中的某一块内存,网络通信就是基于这个文件描述符来完成的

2.绑定

// 将文件描述符和本地的IP与端口进行绑定   
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd:用于监听的文件描述符,是通过调用 socket() 函数的返回值;
  • addr:要绑定的 IP 地址端口信息 需要在这个结构体中初始化,IP 地址 和 端口信息需要转换为 网络字节序(大端序)
  • addrlen:参数 addr 指向内存的大小,即 sizeof (struct addr)

返回值:

  • 成功返回 0 0 0
  • 失败返回 − 1 -1 1

3.监听

// 给监听的套接字设置监听
int listen(int sockfd, int backlog);

参数:

  • sockfd:文件描述符,通过调用 socket()函数的返回值,在监听之前必须先绑定 bind()
  • backlog:能够同时处理的最大连接数,最大值为 128 128 128

返回值:

  • 成功返回 0 0 0
  • 失败返回 − 1 -1 1

4.获取连接

// 等待并接受客户端的连接请求, 建立新的连接, 会得到一个新的文件描述符(通信的)		
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

  • sockfd:监听的文件描述符;
  • addr:建立连接的客户端的信息;
  • addrlen:用于存储 addr 指向内存的大小;

返回值:

  • 成功返回一个文件描述符,用于和建立连接的这个客户端进行通信;
  • 失败返回 − 1 -1 1

注意: 这个函数是一个阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞在这里;当检测到有新的客户端连接请求时,阻塞解除,新连接就建立了,得到的返回值也是一个文件描述符,基于这个文件描述符就可以和客户端通信了。

5.接收数据

// 发送数据的函数
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);

参数:

  • fd:用于通信的文件描述符,调用 accept() 函数的返回值;
  • buf:要发送的的数据;
  • len:要发送数据的长度;
  • flags:特殊的属性,一般不使用,默认为 0 0 0

返回值:

  • 大于 0 0 0,实际接收数据的字节数;
  • 等于 0 0 0,对方断开了连接;
  • − 1 -1 1,接收数据失败了;

注意: 如果连接没有断开,接收端接收不到数据,接收数据的函数会阻塞等待数据到达,数据到达后函数解除阻塞,开始接收数据,当发送端断开连接,接收端无法接收到任何数据,但是这时候就不会阻塞了,函数直接返回0。

6.发送数据

// 发送数据的函数
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);

参数:

  • fd: 通信的文件描述符, accept() 函数的返回值;
  • buf: 传入参数, 要发送的字符串;
  • len: 要发送的字符串的长度;
  • flags: 特殊的属性, 一般不使用, 指定为 0 0 0

返回值:

  • 大于 0 0 0:实际发送的字节数,和参数 len 是相等的;
  • − 1 -1 1:发送数据失败;

7.连接

// 成功连接服务器之后, 客户端会自动随机绑定一个端口
// 服务器端调用accept()的函数, 第二个参数存储的就是客户端的IP和端口信息
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd: 通信的文件描述符, 通过调用 socket() 函数就得到;
  • addr: 存储了要连接的服务器端的地址信息: IP 和 端口,这个 IP 和端口也需要转换为网络字节序(大端序)然后再赋值;
  • addrlen: addr 指针指向的内存的大小 sizeof(struct sockaddr)

返回值:

  • 连接成功返回 0 0 0
  • 连接失败返回 − 1 -1 1

三、TCP 通信

TCP 是一个面向连接的、安全的、流式传输协议,是传输层的协议。

TCP 通信的大致流程如下:

在这里插入图片描述

服务端的通信流程

1.创建用于监听的套接字 lfd

int lfd = socket();

2.将监听得到的文件描述符 lfd 和本地端口 和 IP 地址绑定

    bind();

3.设置监听,监听客户端的连接

   listen();

4.等待并接受客户端的连接请求,建立新的连接,会返回一个文件描述符 cfd(用于通信的),没有客户端连接就一直阻塞

   int cfd = accept();

5.通信,进行收发数据,读写操作默认都是阻塞的

   // 接收数据
read(); / recv();
// 发送数据
write(); / send();

6.断开连接,关闭套接字

   close();

在服务端,有两种文件描述符:

  • 用于 监听 的文件描述符:

    • 只需要有一个即可;
    • 只负责检测客户端的连接请求,检测到之后调用 accept() 就能建立新的连接;
  • 用于 通信 的文件描述符:

    • 负责和建立连接的客户端进行通信;
    • 如果有 k k k 个客户端和服务端建立了连接,那么用于通信的文件描述符就有 k k k 个,每一个客户端都与服务端有一个对应的用于通信的文件描述符;

文件描述符对应的内存结构:

  • 一个文件描述符对应两块内存,分别是 读缓冲区写缓冲区
  • 读数据: 通过 文件描述符 将内存中的数据读出,这块内存就是读缓冲区;
  • 写数据: 通过 文件描述符 将数据写入某块内存中,这块内存就是写缓冲区;

用于监听的文件描述符:

  • 客户端的连接请求会发送到服务器端监听的文件描述符的读缓冲区中;
  • 读缓冲区中有数据, 说明有新的客户端连接;
  • 调用 accept() 函数, 这个函数会检测监听文件描述符的读缓冲区:
    • 检测不到数据, 该函数阻塞;
    • 如果检测到数据, 解除阻塞, 新的连接建立;

用于通信的文件描述符:

  • 客户端和服务端都有用于通信的文件描述符;

  • 发送数据:调用 write() / send() 函数,将数据写到内核:

    • 数据并没有被发送出去, 而是将数据写入到了通信的文件描述符对应的写缓冲区中;
    • 内核检测到通信的文件描述符写缓冲区中有数据, 内核会将数据发送到网络中;
  • 接收数据:调用 read() / recv() 函数, 从内核读数据:

    • 数据进入到通信的文件描述符的读缓冲区中;
    • 数据进入到内核, 必须使用通信的文件描述符, 将数据从读缓冲区中读出即可;

客户端的通信流程

在单线程的情况下,客户端用于通信的文件描述符只有一个,没有用于监听的文件描述符。

1.创建一个用于通信的套接字

int cfd = socket();

2.连接服务端,这时需要知道服务端绑定的 IP 地址 和 端口号

connect();

3.开始通信,进行收发数据

// 接收数据
read(); / recv();
// 发送数据
write(); / send();

4.断开连接,关闭文件描述符

close();

四、实现代码

1.最初版

此时只能是一个客户端 跟 一个服务端进行通信

服务端代码server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>


int main(){

    //1. 创建监听的套接字
    int lfd = socket(AF_INET,SOCK_STREAM,0);

    if(lfd == -1){ //监听套接字创建失败
       perror("socket");
       return -1;
    }

    //2. 绑定本地的 IP : Port

    /**
     * 初始化 saddr 绑定 IP 和 Port 信息
    */
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET; //IPv4
    saddr.sin_port = htons(9999); //Port 需要转换成大端序
    saddr.sin_addr.s_addr = INADDR_ANY;


   int ret = bind(lfd,(struct sockaddr*)(&saddr),sizeof(saddr));

   if(ret == -1){ //绑定失败
      perror("bind");
      return -1;
   }

   //3. 设置监听
   ret = listen(lfd , 128);

   if(ret == -1){  //监听失败
      perror("listen");
      return -1;
   }

   //4. 阻塞并等待客户端的连接
   struct sockaddr_in caddr;
   int caddr_len = sizeof(caddr);

   int cfd = accept(lfd,(struct sockaddr*)(&caddr),&caddr_len);

   if(cfd == -1){ //连接失败
      perror("accept");
      return -1;
   }

   /**
    *连接建立成功,打印客户端的 IP 和 Port 信息
     注意:需要将信息由大端序 转为 小端序 
   */

    char ip[32];

    printf("客户端的IP : %s , 端口Port : %d\n",inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ip,sizeof(ip)),ntohs(caddr.sin_port));

    //5. 开始进行通信

    char buf[1024];

    while(1){
        //接受数据
        int len = recv(cfd,buf,sizeof(buf),0);

        if(len > 0){ //还有数据
            printf("Client say : %s\n",buf);
            send(cfd,buf,sizeof(buf),0);
        }
        else if(len == 0){ //说明客户端已经断开了连接
            printf("客户端已经断开了连接...!\n");
            break;
        }
        else{  //len == -1 说明读取数据失败
            perror("recv");
            break;
        }
    }

    //6.关闭文件描述符

    close(lfd);
    close(cfd);

    return 0;
}

客户端代码client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>


int main(){

    //1. 创建通信的套接字
    int fd = socket(AF_INET,SOCK_STREAM,0);

    if(fd == -1){ //监听套接字创建失败
       perror("socket");
       return -1;
    }

    //2. 连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET; //IPv4
    saddr.sin_port = htons(9999); //Port 需要转换成大端序

    inet_pton(AF_INET,"10.0.8.14",&saddr.sin_addr.s_addr);

    int ret = connect(fd,(struct sockaddr*)(&saddr),sizeof(saddr));

    if(ret == -1){ //连接失败
       perror("connect");
       return -1;
    }

    //3. 开始进行通信
    int num = 0;

    while(1){
        char buf[1024];
        //发送数据
        sprintf(buf,"hello socket communication ... %d \n",num++);
        send(fd,buf,strlen(buf) + 1,0);

        //接收数据
        memset(buf,0,sizeof buf);
        int len = recv(fd,buf,sizeof(buf),0);

        if(len > 0){ //还有数据
            printf("Server say : %s\n",buf);
        }
        else if(len == 0){ //说明服务端已经断开了连接
            printf("服务端已经断开了连接...!\n");
            break;
        }
        else{  //len == -1 说明读取数据失败
            perror("recv");
            break;
        }

        sleep(1); // 每隔 1s 再发一次数据
    }

    //6.关闭文件描述符

    close(fd);

    return 0;
}

实现效果:

在这里插入图片描述

在这里插入图片描述
最后是客户端先断开连接:

在这里插入图片描述
服务端也断开连接:

在这里插入图片描述

2.多线程版

对于每一个客户端的连接,服务端都生成一个子线程去跟客户端进行通信。这样就可以多个客户端,连接同一个服务端。

我们需要将服务端的代码改成多线程的版本。

server_mutl_thread.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>


//信息结构体

struct sock_info{
    struct sockaddr_in addr;
    int cfd;
};

struct sock_info infos[512];

void* work(void* arg);



int main()
{
    // 0. 初始化信息结构体数组
    size_t n = sizeof(infos) / sizeof(infos[0]);

    for(size_t i = 0;i < n;i++){
        bzero(&infos[i] , sizeof(infos[i]));
        infos[i].cfd = -1;
    }


    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if (lfd == -1)
    { // 监听套接字创建失败
        perror("socket");
        return -1;
    }

    // 2. 绑定本地的 IP : Port

    /**
     * 初始化 saddr 绑定 IP 和 Port 信息
     */
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;   // IPv4
    saddr.sin_port = htons(9999); // Port 需要转换成大端序
    saddr.sin_addr.s_addr = INADDR_ANY;

    int ret = bind(lfd, (struct sockaddr *)(&saddr), sizeof(saddr));

    if (ret == -1)
    { // 绑定失败
        perror("bind");
        return -1;
    }

    // 3. 设置监听
    ret = listen(lfd, 128);

    if (ret == -1)
    { // 监听失败
        perror("listen");
        return -1;
    }

    // 4. 阻塞并等待客户端的连接
    int len = sizeof(struct sockaddr_in);

    while (1)
    {
        struct sock_info* pinfo = NULL;

        for(size_t i = 0;i < n;i++){
            if(infos[i].cfd == -1){
                pinfo = &infos[i];
                break;
            }
        }

        int cfd = accept(lfd, (struct sockaddr *)(&(pinfo->addr)), &len);
        pinfo->cfd = cfd;

        if (cfd == -1)
        { // 连接失败
            perror("accept");
            break;
        }

        //创建子线程
        pthread_t tid;
        pthread_create(&tid,NULL,work,pinfo);
        //分离 子线程 跟 父线程
        pthread_detach(tid);
    }

    close(lfd);

    return 0;
}

void *work(void *arg)
{

    /**
    *连接建立成功,打印客户端的 IP 和 Port 信息
     注意:需要将信息由大端序 转为 小端序
   */

    struct sock_info * pinfo = (struct sock_info*) arg;


    char ip[32];

    printf("客户端的IP : %s , 端口Port : %d\n", inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(pinfo->addr.sin_port));

    // 5. 开始进行通信

    char buf[1024];

    while (1)
    {
        // 接收数据
        int len = recv(pinfo->cfd, buf, sizeof(buf), 0);

        if (len > 0)
        { // 还有数据
            printf("Client say : %s\n", buf);
            send(pinfo->cfd, buf, sizeof(buf), 0);
        }
        else if (len == 0)
        { // 说明客户端已经断开了连接
            printf("客户端已经断开了连接...!\n");
            break;
        }
        else
        { // len == -1 说明读取数据失败
            perror("recv");
            break;
        }
    }

    // 6.关闭文件描述符

    close(pinfo->cfd);
    pinfo->cfd = -1; // 置为 -1 表示这块内存已经是可用的了

    return NULL;
}

实现效果:

在这里插入图片描述

在分别启动四个客户端。

客户端 1 1 1

在这里插入图片描述
客户端 2 2 2

在这里插入图片描述
客户端 3 3 3

在这里插入图片描述
客户端 4 4 4

在这里插入图片描述

五、参考内容

  • 本篇博客是对于 套接字 Socket 的整理。

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

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

相关文章

继续深挖,Jetpack Compose的State快照系统

Jetpack Compose 有一种特殊的方式来表示状态和传播状态变化&#xff0c;从而驱动最终的响应式体验&#xff1a;状态快照系统&#xff08;State snapshot system&#xff09;。这种响应式模型使我们的代码更加强大和简洁&#xff0c;因为它允许组件根据它们的输入自动重组&…

Docker安装及Docker构建简易版Hadoop生态

一、首先在VM创建一个新的虚拟机将Docker安装好 更新系统&#xff1a;首先打开终端&#xff0c;更新系统包列表。 sudo apt-get update sudo apt-get upgrade下图是更新系统包截图 安装Docker&#xff1a;使用以下命令在Linux上安装Docker。 sudo apt-get install -y docker.i…

离谱事件解决方法2 无法定位程序输入点XXX于动态链接库XXX.dll

事情经过&#xff1a; 本人一只acmer&#xff0c;使用sublime编写代码&#xff0c;但是前两天在打开cpp类型的文件的时候显示报错如下&#xff1a; 这里的dll文件就是动态链接库&#xff0c;它并不是一个可执行文件&#xff0c;里面存放的是程序的函数实现过程&#xff08;公用…

postgresql基于postgis常用空间函数

1、ST_AsGeoJSON 图元转geojson格式 select ST_AsGeoJSON(l.geom) from g_zd l limit 10 2、 ST_Transform 坐标转换 select st_transform(l.shape, 3857) from sde_wf_cyyq l limit 10select st_astext(st_transform(l.shape, 3857)) from sde_wf_cyyq l limit 103、st_aste…

创建本地镜像

通过前面文章的阅读&#xff0c;读者已经了解到所谓的容器实际上是在父镜像的基础上创建了一个可读写的文件层级&#xff0c;所有的修改操作都在这个文件层级上进行&#xff0c;而父镜像并未受影响&#xff0c;如果读者需要根据这种修改创建一个新的本地镜像&#xff0c;有两种…

【位运算进阶之----左移(<<)】

今天我们来谈谈左移这件事。 ❤️简单来说&#xff0c;对一个数左移就是在其的二进制表达末尾添0。左移一位添一个0&#xff0c;结果就是乘以2&#xff1b;左移两位添两个0&#xff0c;结果就乘以2 ^ 2&#xff1b;左移n位添n个0&#xff0c;结果就是乘以2 ^ n&#xff0c;小心…

shopee店铺如何注册?卖家需要准备哪些材料?

shopee是这两年发展迅速的东南亚电商平台&#xff0c;国内也是有越来越多的卖家入驻开店。目前&#xff0c;Shopee入驻的门槛是比较低的&#xff0c;卖家账号注册也比较简单。如果你想入驻Shopee&#xff0c;但是又不知道要这么注册卖家账号&#xff0c;那么就要往下看了。 申请…

ch3_1汇编语言程序的源程序

mark 一下&#xff0c; 2023.Aug.15 从湖北返回学习&#xff0c;参加了一场学术会议&#xff0c; 看来做学术确实是需要交流的&#xff0c; 尤其该领域的多交流&#xff0c; 还是需要至少一年参加一次学术会议. &#xfeff; 不至于让自己太孤陋寡闻&#xff0c; 局限于自…

小程序如何手动变更会员卡等级

有时候需要商家手动变更会员卡等级&#xff0c;以让会员获取更多的福利和特权。下面就介绍一些小程序手动变更会员卡等级的常见方法和策略。 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要更改等级的会员卡。也支持对会员卡按卡号、手机号和等级进行…

宝塔计划任务读取文件失败

想挂计划任务 相关文章【已解决】计划任务读取文件失败 - Linux面板 - 宝塔面板论坛 对方反馈的是执行下面的命令 chattr -ai /var/spool/cron 后来发现直接没有这个文件夹&#xff0c;然后通过mkdir命令创建文件夹&#xff0c;成功在宝塔创建了计划任务 后面发现任务虽然添…

Markdown初级使用指南

前言 大家好&#xff0c;我是艾老虎尤&#xff0c;我在一篇官方的文章中&#xff0c;我了解到了markdown&#xff0c;原本我写博客一直是使用的富文本编译器&#xff0c;之前我也有同学叫我使用MD&#xff0c;但是我嫌它复杂&#xff0c;就比如说一个标题&#xff0c;我在富文…

SFM structure from motion

struction就是空间三维点的位置 motion 就是相机每帧的位移 https://www.youtube.com/watch?vUhkb8Zq-dnM&listPL2zRqk16wsdoYzrWStffqBAoUY8XdvatV&index9

单片机学习-蜂鸣器电子元件

蜂鸣器是有什么作用的&#xff1f; 蜂鸣器 是 一种 一体化结构 的电子训响器&#xff0c;可以发出声音的电子元器件 蜂鸣器分类&#xff1f; ①压电式蜂鸣器&#xff08;图左&#xff09; 称&#xff1a; 无源蜂鸣器 ②电磁式蜂鸣器&#xff08;图右&#xff09; 称&#xf…

ISIS路由协议

骨干区域与非骨干区域 凡是由级别2组建起来的邻居形成骨干区域&#xff1b;级别1就在非骨干区域&#xff0c;骨干区域有且只有一个&#xff0c;并且需要连续&#xff0c;ISIS在IP环境下目前不支持虚链路。 路由器级别 L1路由器只能建立L1的邻居&#xff1b;L2路由器只能建立L…

SpringCloud学习笔记(十)_SpringCloud监控

今天我们来学习一下actuator这个组件&#xff0c;它不是SpringCloud之后才有的&#xff0c;而是SpringBoot的一个starter&#xff0c;Spring Boot Actuator。我们使用SpringCloud的时候需要使用这个组件对应用程序进行监控与管理 在SpringBoot2.0版本中&#xff0c;actuator可以…

TensorFlow中slim包的具体用法

TensorFlow中slim包的具体用法 1、训练脚本文件&#xff08;该文件包含数据下载打包、模型训练&#xff0c;模型评估流程&#xff09;3、模型训练1、数据集相关模块&#xff1a;2、设置网络模型模块3、数据预处理模块4、定义损失loss5、定义优化器模块 本次使用的TensorFlow版本…

电商项目part07 订单系统的设计与海量数据处理

订单重复下单问题&#xff08;幂等&#xff09; 用户在点击“提交订单”的按钮时&#xff0c;不小心点了两下&#xff0c;那么浏览器就会向服务端连续发送两条创建订单的请求。这样肯定是不行的 解决办法是,让订单服务具备幂等性。什么是幂等性&#xff1f;幂等操作的特点是&a…

Vue2向Vue3过度Vue3组合式API

目录 1. Vue2 选项式 API vs Vue3 组合式API2. Vue3的优势3 使用create-vue搭建Vue3项目1. 认识create-vue2. 使用create-vue创建项目 4 熟悉项目和关键文件5 组合式API - setup选项1. setup选项的写法和执行时机2. setup中写代码的特点3. <script setup>语法糖 6 组合式…

Cpp学习——编译链接

目录 ​编辑 一&#xff0c;两种环境 二&#xff0c;编译环境下四个部分的 1.预处理 2.编译 3.汇编 4.链接 三&#xff0c;执行环境 一&#xff0c;两种环境 在程序运行时会有两种环境。第一种便是编译环境&#xff0c;第二种则是执行环境。如下图&#xff1a; 在程序运…

wxpython:wx.html2 是好用的 WebView 组件

wxpython : wx.html2 是好用的 WebView 组件。 pip install wxpython4.2 wxPython-4.2.0-cp37-cp37m-win_amd64.whl (18.0 MB) Successfully installed wxpython-4.2.0 cd \Python37\Scripts wxdemo.exe 取得 wxPython-demo-4.2.0.tar.gz wxdocs.exe 取得 wxPython-docs-4.…