Linux网络编程套接字

news2024/11/17 6:26:50

文章目录

  • 一、预备知识
    • 1. IP 地址
    • 2.端口号
    • 3. TCP 协议和 UDP 协议
    • 4.网络字节序
  • 二、socket 编程接口
    • 0. socket 常见 API
    • 1. socket 系统调用
    • 2. bind 系统调用
    • 3. recvfrom 系统调用
    • 4. sendto 系统调用
    • 5. listen 系统调用
    • 6. accept 系统调用
    • 7. connect 系统调用
  • 三、简单的 UDP 网络程序
    • 1. udp echo
    • 2.加入应用逻辑 -- 执行简单的 shell 命令
  • 四、简单的 TCP 网络程序
    • 1.单进程版本
    • 2.多进程版本
    • 3.多线程版本
    • 4.线程池版本
    • 5. TCP 简单总结

一、预备知识

1. IP 地址

  • IP 协议有两个版本,IPv4 和 IPv6 。在文章中凡是提到 IP 协议,没有特殊说明的,默认都是指 IPv4 。

  • 对于 IPv4 来说,IP 地址是一个 4 字节的32位整数。

  • 我们通常也使用 “点分十进制” 的字符串表示IP地址,例如 180.101.50.172,用点分割的每一个数字表示一个字节,范围是 [0, 255] 。

  • 公网 IP:通常用来唯一地标识互联网中的一台主机。

  • 源 IP 和目的 IP:对一个报文来讲,回答了从哪里来到哪里去的问题,最大的意义是指导一个报文该如何进行路径选择。

2.端口号

  • 端口号(port)是传输层协议的内容。它是一个 2 字节 16 位的整数,用来唯一地标识一台主机上的一个进程。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

进程具有独立性,进程间通信的前提工作:先得让不同的进程看到同一份资源,这份资源在这里就是网络

源端口号和目的端口号:描述数据是哪个进程发的,要发给哪个进程。

一个进程可以关联多个端口号,但是一个端口号不可以关联多个进程,这个可以由端口号的概念得出。

3. TCP 协议和 UDP 协议

我们需要先对 TCP 协议和 UDP 协议有一个直观的认识,后面再详细讨论。

  • TCP(Transmission Control Protocol,传输控制协议)
     ① 传输层协议。
     ② 有连接。
     ③ 可靠传输。
     ④ 面向字节流。

  • UDP(User Datagram Protocol,用户数据报协议)
     ① 传输层协议。
     ② 无连接。
     ③ 不可靠传输。
     ④ 面向数据报。

TCP 的可靠和 UDP 的不可靠都是中性词,客观的,没有谁好谁不好,只有谁更合适。

4.网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端和小端之分。网络数据流同样有大端和小端之分,那么为了避免网络通信中不同主机大小端不一致的问题,应如何定义网络数据流的地址呢?

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

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

在这里插入图片描述
以上函数的作用:
 ① 如果主机是小端字节序,这些函数将参数转换为大端字节序,然后返回。
 ② 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

注:这些函数名很好记,h 表示 host ,n 表示 network ,l 表示 32 位长整数,s 表示 16 位短整数。

举个例子:htonl函数表示将 32 位的长整数从主机字节序转换为网络字节序。

二、socket 编程接口

0. socket 常见 API

网络通信的标准方式有很多种,比如基于 IP 的网络通信(它对应的通信协议家族是 AF_INET,网络套接字),还有原始套接字、域间套接字。有很多种类的套接字,其实就是编程接口。这几种编程接口都是各自不同的体系,于是就会有不同套的编程接口,这样就会很麻烦,因此,干脆把不同套的编程接口统一为同一套编程接口,也就是下面的这一套。换言之,要使用不同种类的通信方式,只需要改变传入的参数即可。

// 创建 socket 文件描述符 (客户端 + 服务器, TCP/UDP)
int socket(int domain, int type, int protocol);

// 绑定端口号 (服务器, TCP/UDP)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 设置socket文件状态为监听状态 (服务器, TCP)
int listen(int sockfd, int backlog);

// 接受连接 (服务器, TCP)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 发起连接 (客户端, TCP)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

于是,为了支持不同种类的通信方式,struct sockaddr的结构就被设计出来了,它是一种通用结构。

在这里插入图片描述

IPv4 地址使用的结构体是struct sockaddr_in,我们后面经常用到它,其定义为:在这里插入图片描述

socket API 的参数都用struct sockaddr *类型表示,在使用时传入各种类型的struct sockaddr指针,强转成struct sockaddr *即可。

这样,API 内部只要取得某种struct sockaddr的首地址,不需要知道具体类型,就可以根据地址类型字段确定结构体中的内容。

1. socket 系统调用

socket的作用:为网络通信创建一个 socket 文件。

socket的参数:
 ① domain:指定协议家族。我们选择 AF_INET 。
 ② type:指定套接字类型。对于 TCP ,应选择 SOCK_DGRAM ;对于 UDP ,应选择 SOCK_STREAM 。
 ③ protocol:指定协议类型。在 TCP 和 UDP 中,我们设为 0 即可。

socket的返回值:
 ① 成功,返回一个 socket 文件描述符。
 ② 错误,返回 -1 。

在这里插入图片描述在这里插入图片描述

2. bind 系统调用

bind的作用:将本地地址和一个 socket 文件进行绑定。

bind的参数:
 ① sockfd:传入 socket 文件描述符。
 ② addr:用于指定本端的 socket 信息。
 ③ addrlen:用于指定本端的 socket 信息的大小。

bind的返回值:
 ① 成功,返回 0 。
 ② 错误,返回 -1 。

在这里插入图片描述在这里插入图片描述

3. recvfrom 系统调用

recvfrom的作用:从一个 socket 文件接收数据。

recvfrom的参数:
 ① sockfd:传入 socket 文件描述符。
 ② buf:用于存放读到的数据的用户层缓冲区。
 ③ len:用户层缓冲区的大小。
 ④ flags:读的方式。我们这里默认设为 0 即可。
 ⑤ src_addr:输入输出型参数,用于获取对端的 socket 信息。
 ⑥ addrlen:输入输出型参数,用于获取对端的 socket 信息的大小。

recvfrom的返回值:
 ① 成功,返回接收的字节数(当对端退出时,返回 0)。
 ② 错误,返回 -1 。

在这里插入图片描述
在这里插入图片描述

4. sendto 系统调用

sendto的作用:从一个 socket 文件发送数据。

sendto的参数:
 ① sockfd:传入 socket 文件描述符。
 ② buf:用于发送数据的用户层缓冲区。
 ③ len:发送数据的长度。
 ④ flags:发送的方式。我们这里默认设为 0 即可。
 ⑤ dest_addr:目标对端的 socket 信息。
 ⑥ addrlen:目标对端的 socket 信息的大小。

sendto的返回值:
 ① 成功,返回发送的字节数。
 ② 错误,返回 -1 。

在这里插入图片描述
在这里插入图片描述

5. listen 系统调用

listen的作用:设置一个 socket 文件状态为监听状态,允许该 socket 文件被连接。

listen的参数:
 ① sockfd:传入 socket 文件描述符。
 ② backlog:设置连接队列的最大长度。

listen的返回值:
 ① 成功,返回 0 。
 ② 错误,返回 -1 。

在这里插入图片描述

6. accept 系统调用

accept的作用:从一个 socket 文件接受一个连接。

accept的参数:
 ① sockfd:传入处于 listen 状态的 socket 文件描述符。
 ② addr:输入输出型参数,用于获取对端的 socket 信息。
 ③ addrlen:输入输出型参数,用于获取对端的 socket 信息的大小。

accept的返回值:
 ① 成功,返回一个文件描述符。
 ② 错误,返回 -1 。

在这里插入图片描述
在这里插入图片描述

7. connect 系统调用

connect的作用:在一个 socket 文件上向目标发起连接。

connect的参数:
 ① sockfd:传入 socket 文件描述符。
 ② addr:目标对端的 socket 信息。
 ③ addrlen:目标对端的 socket 信息的大小。

connect的返回值:
 ① 成功,返回 0 。
 ② 错误,返回 -1 。

在这里插入图片描述

三、简单的 UDP 网络程序

在这里插入图片描述

一个服务器,必须得让客户端知道对应服务器的 socket 信息(IP + port)。
一般的服务器的 port ,必须是众所周知的,而且不能被轻易改变!

1. udp echo

程序说明:client 输入数据发送给 server ,server 接收数据后打印出来,并返回给 client 。

下面包含两个源文件:
 ① udp_server.cc:服务端。
 ② udp_client.cc:客户端。

  • udp_server.cc:
// udp_server.cc

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

// const uint16_t port = 8080;

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

// ./udp_server port
int main(int argc, char *argv[])
{
    if(argc != 2){
        Usage(argv[0]);  // 参数个数不对,打印说明
        return -1;
    }

    uint16_t port = atoi(argv[1]);  // atoi:字符串转整型

    //1. 创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);  // UDP -> SOCK_DGRAM
    if(sock < 0){
        std::cerr << "socket create error: " << errno << std::endl;
        return 1;
    }

    //2. 给该服务器绑定端口和IP(特殊处理)
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);  
    // 此处的端口号,是主机上的变量,是主机序列
    // 由于要在网络中传送,需要转为网络字节序
    
    //local.sin_addr.s_addr = inet_addr("xxx.xxx.xx.xxx");
    // a. 需要将人识别的点分十进制的字符串风格IP地址,转换为4字节整数IP
    // b. 将4字节整数IP由主机序列转换为网络序列
    // in_addr_t inet_addr(const char *cp); 能完成上面ab两个工作
    // 但是云服务器不允许用户直接bind公网IP
    // 另外,实际正常编写的时候,我们也不会指明IP
    
    // INADDR_ANY:不关心数据是从哪个IP上来的,只要访问的是这个端口,都会接收数据
    local.sin_addr.s_addr = INADDR_ANY;  // 最常用
    // 原因:
    // 如果bind的是确定的IP(主机),意味着只有发到该IP主机上面的数据才会交给网络进程
    // 但是,一般服务器可能有多张网卡,关联多个IP,
    // 我们需要的不仅仅是某个IP上面的数据,我们需要的是所有发送到该主机该端口上的数据!

    //服务器bind的本质是明确绑定的端口号,会被严格管理
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
        std::cerr << "bind error: " << errno << std::endl;
        return 2;
    }

    //3. 提供服务
    bool quit = false;
    #define NUM 1024
    char buffer[NUM];
    while(!quit)  // 服务器不断提供服务,死循环
    {
        struct sockaddr_in peer;  // 对端的socket
        socklen_t len = sizeof(peer);
        // 在这里,我们默认认为通信的数据是字符串
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
        if(cnt > 0)
        {
            buffer[cnt] = 0;  // 0 == '\0'
            std::cout << "client# " << buffer << std::endl;
            // 根据用户输入,构建一个新的返回字符串
            std::string echo_hello = buffer;
            echo_hello += "...";
            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&peer, len);
        }
        else
        {
            //TODO
        }
    }

    return 0;
}
  • udp_client.cc:
// udp_client.cc

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

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

// ./udp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return -1;
    }

    //1. 创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);  // UDP -> SOCK_DGRAM
    if(sock < 0){
        std::cerr << "socket error: " << errno << std::endl;
        return 1;
    }

    // 客户端不需要由用户显式地绑定
    // a. 首先,客户端必须也要有ip和port
    // b. 但是,客户端不需要显式地bind!
    // 因为一旦显式地bind,就必须明确client要和哪一个port关联
    // client指明的port有可能被占用,若被占用会导致client无法使用
    // server的端口会被严格地管理,跟客户端不一样
    // server要的是port必须明确,而且不变,但client只要有就行!
    // 一般是由OS自动给用户bind
    // 当client首次对外发送数据时,OS会自动bind,采用的是随机端口的方式

    //b.要给谁发
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    
    //2. 使用服务
    while(1)
    {
        //a.数据从哪里来?
        std::string message;
        std::cout << "输入# ";
        std::cin >> message;

        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
        
        //此处tmp就是一个“占位符”,参数不得不传
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&tmp, &len);
        if(cnt > 0)
        {
            // 在网络通信中,只有报文大小,或者说字节流中字节的个数
            // 没有C/C++字符串这样的概念(虽然我们可能经常会遇到类似的情况)
            buffer[cnt] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
        else{
            //TODO
        }
    }

    return 0;
}

运行测试:在这里插入图片描述

2.加入应用逻辑 – 执行简单的 shell 命令

程序说明:client 输入 shell 命令发送给 server ,server 接收命令后打印出来,并返回执行命令的结果给 client 。

popen函数的作用是执行传入的第一个参数 command ,执行完 command 后其结果会保存到一个文件,该函数会返回该文件的文件指针,第二个参数 type 表示以什么方式打开这个文件。popen函数的底层原理是先fork创建出子进程再pipe实现双方通信,让父进程通过文件指针拿到结果。

pclose函数的作用是关闭该文件。

下面包含两个源文件:
 ① udp_server.cc:服务端。
 ② udp_client.cc:客户端。

  • udp_server.cc:
// udp_server.cc

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

// const uint16_t port = 8080;

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

// ./udp_server port
int main(int argc, char *argv[])
{
    if(argc != 2){
        Usage(argv[0]);
        return -1;
    }

    uint16_t port = atoi(argv[1]);  // atoi:字符串转整型

    //1. 创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);  // UDP -> SOCK_DGRAM
    if(sock < 0){
        std::cerr << "socket create error: " << errno << std::endl;
        return 1;
    }

    //2. 给该服务器绑定端口和IP(特殊处理)
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    
    local.sin_addr.s_addr = INADDR_ANY;  // 最常用
    
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
        std::cerr << "bind error: " << errno << std::endl;
        return 2;
    }

    //3. 提供服务
    bool quit = false;
    #define NUM 1024
    char buffer[NUM];

    //Xshell
    while(!quit)  // 服务器不断提供服务,死循环
    {
        struct sockaddr_in peer;  // 对端的socket
        socklen_t len = sizeof(peer);
        // 在这里,我们默认认为通信的数据是字符串
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
        if(cnt > 0)
        {
            buffer[cnt] = 0;  // 0 == '\0',可以当做一个字符串命令
            FILE *fp = popen(buffer, "r");

            std::string echo_string;
            char line[1024] = {0};  // 使用fgets从文件一行一行地读取内容
            while(fgets(line, sizeof(line), fp) != NULL){
                echo_string += line;
            }
            // if(feof(fp)){  // 判断是否读到文件的末尾EOF
            //     //读取结果完成
            // }

            pclose(fp);
            std::cout << "client# " << buffer << std::endl;

            // 根据用户输入,构建一个新的返回字符串
            sendto(sock, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);
        }
        else
        {
            //TODO
        }
    }

    return 0;
}
  • udp_client.cc:
// udp_client.cc

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

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

// ./udp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return -1;
    }

    //1. 创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);  // UDP -> SOCK_DGRAM
    if(sock < 0){
        std::cerr << "socket error: " << errno << std::endl;
        return 1;
    }

    //b.要给谁发
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    
    //2. 使用服务
    while(1)
    {
        // //a.数据从哪里来?
        // std::string message;
        // std::cout << "输入# ";
        // std::cin >> message;
        std::cout << "MyShell $ ";
        char line[1024];
        fgets(line, sizeof(line), stdin);

        sendto(sock, line, strlen(line), 0, (struct sockaddr*)&server, sizeof(server));
        
        //此处tmp就是一个“占位符”,参数不得不传
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&tmp, &len);
        if(cnt > 0)
        {
            // 在网络通信中,只有报文大小,或者说字节流中字节的个数
            // 没有C/C++字符串这样的概念(虽然我们可能经常会遇到类似的情况)
            buffer[cnt] = 0;
            std::cout << buffer << std::endl;
        }
        else{
            //TODO
        }
    }

    return 0;
}

运行测试:在这里插入图片描述

四、简单的 TCP 网络程序

程序说明:client 输入数据发送给 server ,server 接收数据后打印出来,并返回给 client 。

下面包含两个源文件:
 ① tcp_server.cc:服务端。
 ② tcp_client.cc:客户端。

1.单进程版本

  • tcp_server.cc:
// tcp_server.cc

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

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(listen_sock < 0){
        std::cerr << "socket error: " << errno << std::endl; 
        return 2;
    }

    //2. bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[1]));
    local.sin_addr.s_addr = INADDR_ANY;

    if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error: " << errno << std::endl;
        return 3;
    }

    //3. 因为tcp是面向连接的,a.在通信前,需要建立连接 b.然后才能通信
    // 一定有人主动建立连接(客户端,需要服务),一定有人被动接受连接(服务器,提供服务)
    // 我们当前写的是一个server,不间断地等待客户端到来
    // 我们要不断地给客户端提供一个建立连接的功能
    // 设置套接字是listen状态,本质是允许客户端连接
    const int back_log = 5;  // 先设为5,这里先不解释
    if(listen(listen_sock, back_log) < 0){
        std::cerr << "listen error: " << errno << std::endl;
        return 4;
    }

    for( ; ; ){
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // accept
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
        if(new_sock < 0)
        {
            continue;
        }

        std::cout << "get a new link..." << std::endl;
        
        // 单进程版本,没人使用!
        // 提供服务,是一个死循环
        while(true)
        {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            ssize_t s = read(new_sock, buffer, sizeof(buffer)-1);
            if(s > 0)
            {
                buffer[s] = 0;  // 将获取的内容当成字符串
                std::cout << "client# " << buffer << std::endl; 

                std::string echo_string = ">>>server<<<, ";
                echo_string += buffer;

                write(new_sock, echo_string.c_str(), echo_string.size());
            }
            else if(s == 0){  // 读到EOF,表明对端退出了
                std::cout << "client quit..." << std::endl;
                break;
            }
            else{
                std::cerr << "read error" << std::endl;
                break;
            }
        }
    }

    return 0;
}
  • tcp_client.cc:
// tcp_client.cc

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

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " server_ip server_port" << std::endl;
}

// ./tcp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string svr_ip = argv[1];
    uint16_t svr_port = (uint16_t)atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 2;
    }

    // client无需显式地bind,client -> server
    // client -> connect!
    struct sockaddr_in server;
    bzero(&server, sizeof(server));  // 将一段空间清零,不推荐使用
    server.sin_family = AF_INET;
    server.sin_port = htons(svr_port);
    server.sin_addr.s_addr = inet_addr(svr_ip.c_str());
    
    //2. 发起连接
    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
        std::cerr << "connect server failed!" << std::endl;
        return 3;
    }

    std::cout << "connect success!" << std::endl;

    // 进行正常的业务请求
    while(true)
    {
        std::cout << "Please Enter# ";
        char buffer[1024];
        fgets(buffer, sizeof(buffer)-1, stdin);

        write(sock, buffer, strlen(buffer));

        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;            
        }
    }

    return 0;
}

运行测试:
在这里插入图片描述

2.多进程版本

  • tcp_server.cc:
// tcp_server.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}

void ServiceIO(int new_sock)
{
    // 提供服务,是一个死循环
    while(true)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        ssize_t s = read(new_sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;  // 将获取的内容当成字符串
            std::cout << "client# " << buffer << std::endl; 

            std::string echo_string = ">>>server<<<, ";
            echo_string += buffer;

            write(new_sock, echo_string.c_str(), echo_string.size());
        }
        else if(s == 0){  // 读到EOF,表明对端退出了
            std::cout << "client quit..." << std::endl;
            break;
        }
        else{
            std::cerr << "read error" << std::endl;
            break;
        }
    }
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(listen_sock < 0){
        std::cerr << "socket error: " << errno << std::endl; 
        return 2;
    }

    //2. bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[1]));
    local.sin_addr.s_addr = INADDR_ANY;

    if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error: " << errno << std::endl;
        return 3;
    }

    //3. 设置套接字是listen状态,本质是允许客户端连接
    const int back_log = 5;
    if(listen(listen_sock, back_log) < 0){
        std::cerr << "listen error: " << errno << std::endl;
        return 4;
    }

    // 在Linux中父进程忽略子进程的SIGCHLD信号,子进程会自动退出释放资源
    signal(SIGCHLD, SIG_IGN);

    for( ; ; ){
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // accept
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
        if(new_sock < 0)
        {
            continue;
        }
        
        // 打印对端的socket信息			
        uint16_t cli_port = ntohs(peer.sin_port);  // 网络序列转主机序列
        std::string cli_ip = inet_ntoa(peer.sin_addr);  // 作用与inet_addr函数相反

        std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port << "]# " << new_sock << std::endl;
        
        // 多进程版本
        pid_t id = fork();
        if(id < 0){
            continue;
        }
        else if(id == 0){  // 曾经被父进程打开的fd,会被子进程继承
            // child       // 无论父子进程,强烈建议关闭掉不需要的fd
            close(listen_sock);
            
            // 不设置信号的另一种做法
            // if(fork() > 0) exit(0);  //退出的是子进程,向后走的是孙子进程,父孙进程没关系

            ServiceIO(new_sock);
            close(new_sock);  //如果不关闭不需要的fd,会造成fd泄漏
            exit(0);
        }
        else{
            // parent,不需要等待child
            // waitpid(id, nullptr, 0);  // 阻塞式等待,但几乎不阻塞,因为子进程很快退出
            close(new_sock);            
        }
    }

    return 0;
}
  • tcp_client.cc:
// tcp_client.cc

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

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " server_ip server_port" << std::endl;
}

// ./tcp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string svr_ip = argv[1];
    uint16_t svr_port = (uint16_t)atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 2;
    }

    // client无需显式地bind,client -> server
    // client -> connect!
    struct sockaddr_in server;
    bzero(&server, sizeof(server));  // 将一段空间清零,不推荐使用
    server.sin_family = AF_INET;
    server.sin_port = htons(svr_port);
    server.sin_addr.s_addr = inet_addr(svr_ip.c_str());
    
    //2. 发起连接
    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
        std::cerr << "connect server failed!" << std::endl;
        return 3;
    }

    std::cout << "connect success!" << std::endl;

    // 进行正常的业务请求
    while(true)
    {
        std::cout << "Please Enter# ";
        char buffer[1024];
        fgets(buffer, sizeof(buffer)-1, stdin);

        write(sock, buffer, strlen(buffer));

        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;            
        }
    }

    return 0;
}

运行测试:
在这里插入图片描述

3.多线程版本

  • tcp_server.cc:
// tcp_server.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}

void ServiceIO(int new_sock)
{
    // 提供服务,是一个死循环
    while(true)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        ssize_t s = read(new_sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;  // 将获取的内容当成字符串
            std::cout << "client# " << buffer << std::endl; 

            std::string echo_string = ">>>server<<<, ";
            echo_string += buffer;

            write(new_sock, echo_string.c_str(), echo_string.size());
        }
        else if(s == 0){  // 读到EOF,表明对端退出了
            std::cout << "client quit..." << std::endl;
            break;
        }
        else{
            std::cerr << "read error" << std::endl;
            break;
        }
    }
}

void *HandlerRequest(void *args)
{
    pthread_detach(pthread_self());
    int sock = *(int*)args;
    delete (int*)args;

    ServiceIO(sock);
    close(sock);
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(listen_sock < 0){
        std::cerr << "socket error: " << errno << std::endl; 
        return 2;
    }

    //2. bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[1]));
    local.sin_addr.s_addr = INADDR_ANY;

    if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error: " << errno << std::endl;
        return 3;
    }

    //3. 设置套接字是listen状态,本质是允许客户端连接
    const int back_log = 5;
    if(listen(listen_sock, back_log) < 0){
        std::cerr << "listen error: " << errno << std::endl;
        return 4;
    }

    for( ; ; ){
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // accept
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
        if(new_sock < 0)
        {
            continue;
        }
        
        // 打印对端的socket信息			
        uint16_t cli_port = ntohs(peer.sin_port);  // 网络序列转主机序列
        std::string cli_ip = inet_ntoa(peer.sin_addr);  // 作用与inet_addr函数相反
        
        std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port << "]# " << new_sock << std::endl;

        // 多线程版本
        // 曾经被主线程打开的fd,新线程能看到和共享
        pthread_t tid;
        int* pram = new int(new_sock);
        pthread_create(&tid, nullptr, HandlerRequest, (void*)pram);
    }

    return 0;
}
  • tcp_client.cc:
// tcp_client.cc

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

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " server_ip server_port" << std::endl;
}

// ./tcp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string svr_ip = argv[1];
    uint16_t svr_port = (uint16_t)atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 2;
    }

    // client无需显式地bind,client -> server
    // client -> connect!
    struct sockaddr_in server;
    bzero(&server, sizeof(server));  // 将一段空间清零,不推荐使用
    server.sin_family = AF_INET;
    server.sin_port = htons(svr_port);
    server.sin_addr.s_addr = inet_addr(svr_ip.c_str());
    
    //2. 发起连接
    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
        std::cerr << "connect server failed!" << std::endl;
        return 3;
    }

    std::cout << "connect success!" << std::endl;

    // 进行正常的业务请求
    while(true)
    {
        std::cout << "Please Enter# ";
        char buffer[1024];
        fgets(buffer, sizeof(buffer)-1, stdin);

        write(sock, buffer, strlen(buffer));

        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;            
        }
    }

    return 0;
}

运行测试:
在这里插入图片描述

4.线程池版本

上面的多进程版本和多线程版本的网络程序都是有问题的:创建进程或线程无上限,而且当客户端连接来了,我们才给客户端创建进程或线程。

实际上,最好是线程池版本。

下面包含四个文件:
 ① Task.hpp:任务的声明和定义。
 ② thread_pool.hpp:线程池的声明和定义。
 ③ tcp_server.cc:服务端。
 ④ tcp_client.cc:客户端。

  • Task.hpp:
// Task.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <unistd.h>

namespace ns_task
{
    class Task
    {
    private:
        int sock_;

    public:
        Task() : sock_(-1) {}
        Task(int sock) : sock_(sock)
        {
        }

        int Run()
        {
            // 实际上不应该是长服务,应该是短服务
            // 相当于一请求一响应就完了

            // // 提供服务,是一个死循环
            // while(true)
            // {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            ssize_t s = read(sock_, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0; // 将获取的内容当成字符串
                std::cout << "client# " << buffer << std::endl;

                std::string echo_string = ">>>server<<<, ";
                echo_string += buffer;

                write(sock_, echo_string.c_str(), echo_string.size());
            }
            else if (s == 0)
            { // 读到EOF,表明对端退出了
                std::cout << "client quit..." << std::endl;
                // break;
            }
            else
            {
                std::cerr << "read error" << std::endl;
                // break;
            }
            // }

            close(sock_);
        }

        ~Task() {}
    };
}
  • thread_pool.hpp:
// thread_pool.hpp

#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

namespace ns_threadpool
{
    const int g_num = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int num_;
        std::queue<T> task_queue_; // 该成员是一个临界资源

        pthread_mutex_t mtx_;
        pthread_cond_t cond_;

        static ThreadPool<T> *ins;

    private:
        // 构造函数必须得实现,但是必须得私有化
        ThreadPool(int num = g_num) : num_(num)
        {
            pthread_mutex_init(&mtx_, nullptr);
            pthread_cond_init(&cond_, nullptr);
        }

        ThreadPool(const ThreadPool<T> &tp) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;

    public:
        static ThreadPool<T> *GetInstance()
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            if (ins == nullptr) //双判定,减少锁的争用,提高获取单例的效率!
            {
                pthread_mutex_lock(&lock);
                // 当前单例对象还没有被创建
                if (ins == nullptr)
                {
                    ins = new ThreadPool<T>();
                    ins->InitThreadPool();
                    std::cout << "首次加载对象" << std::endl;
                }
                pthread_mutex_unlock(&lock);
            }

            return ins;
        }

        void Lock()
        {
            pthread_mutex_lock(&mtx_);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&mtx_);
        }
        void Wait()
        {
            pthread_cond_wait(&cond_, &mtx_);
        }
        void Wakeup()
        {
            pthread_cond_signal(&cond_);
        }
        bool IsEmpty()
        {
            return task_queue_.empty();
        }

    public:
        // 在类中要让线程执行类内成员方法,是不可行的,原因:隐含的参数this
        // 所以必须让线程执行静态方法
        static void *Routine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T> *)args;

            while (true)
            {
                tp->Lock();
                while (tp->IsEmpty())
                {
                    // 任务队列为空
                    tp->Wait();
                }
                // 该任务队列中一定有任务了
                T t;
                tp->PopTask(&t);
                tp->Unlock();

                t.Run();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < num_; ++i)
            {
                pthread_create(&tid, nullptr, Routine, (void *)this);
            }
        }
        void PushTask(const T &in)
        {
            Lock();
            task_queue_.push(in);
            Unlock();
            Wakeup();
        }
        void PopTask(T *out)
        {
            *out = task_queue_.front();
            task_queue_.pop();
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&cond_);
        }
    };

    template <class T>
    ThreadPool<T> *ThreadPool<T>::ins = nullptr;
} // namespace ns_threadpool
  • tcp_server.cc:
// tcp_server.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Task.hpp"
#include "thread_pool.hpp"

using namespace ns_threadpool;
using namespace ns_task;

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(listen_sock < 0){
        std::cerr << "socket error: " << errno << std::endl; 
        return 2;
    }

    //2. bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[1]));
    local.sin_addr.s_addr = INADDR_ANY;

    if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error: " << errno << std::endl;
        return 3;
    }

    //3. 设置套接字是listen状态,本质是允许客户端连接
    const int back_log = 5;
    if(listen(listen_sock, back_log) < 0){
        std::cerr << "listen error: " << errno << std::endl;
        return 4;
    }

    for( ; ; ){
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // accept
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
        if(new_sock < 0)
        {
            continue;
        }
        
        // 打印对端的socket信息			
        uint16_t cli_port = ntohs(peer.sin_port);  // 网络序列转主机序列
        std::string cli_ip = inet_ntoa(peer.sin_addr);  // 作用与inet_addr函数相反
        
        std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port << "]# " << new_sock << std::endl;

        // 线程池版本(单例模式)
        //1. 构建一个任务
        Task t(new_sock);
        //2. 将任务push到后端的线程池即可
        ThreadPool<Task>::GetInstance()->PushTask(t);
    }

    return 0;
}
  • tcp_client.cc:
// tcp_client.cc

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

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " server_ip server_port" << std::endl;
}

// ./tcp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string svr_ip = argv[1];
    uint16_t svr_port = (uint16_t)atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);  // TCP -> SOCK_STREAM
    if(sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 2;
    }

    // client无需显式地bind,client -> server
    // client -> connect!
    struct sockaddr_in server;
    bzero(&server, sizeof(server));  // 将一段空间清零,不推荐使用
    server.sin_family = AF_INET;
    server.sin_port = htons(svr_port);
    server.sin_addr.s_addr = inet_addr(svr_ip.c_str());
    
    //2. 发起连接
    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
        std::cerr << "connect server failed!" << std::endl;
        return 3;
    }

    std::cout << "connect success!" << std::endl;

    // 进行正常的业务请求
    while(true)
    {
        std::cout << "Please Enter# ";
        char buffer[1024];
        fgets(buffer, sizeof(buffer)-1, stdin);

        write(sock, buffer, strlen(buffer));

        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;            
        }
    }

    return 0;
}

运行测试:
在这里插入图片描述

5. TCP 简单总结

  1. 创建 socket 的过程,socket(),本质是打开文件 – 仅仅有系统相关的内容。
  2. bind(),struct sockaddr_in -> IP, port,本质是 IP + port 和文件信息进行关联。
  3. listen(),本质是设置该 socket 文件状态为监听状态,允许客户端来连接。
  4. connect(),本质是发起连接,系统层面:就是构建一个请求报文发送过去。网络层面:发起 TCP 连接的三次握手。
  5. accept(),获取新连接到应用层,是以 fd 为代表的。
  6. 读/写,本质就是进行网络通信,但是对于用户来讲,相当于在进行正常的文件读写。
  7. close(),关闭文件,系统层面:释放曾经申请的文件资源、连接资源等。网络层面:通知对方,连接已经关闭了,其实就是在进行四次挥手。

当有很多连接连上服务器时,OS 中会存在大量的连接,于是 OS 要管理这些已经建立好的连接。如何管理呢?先描述再组织

所谓的连接,在 OS 层面上,其实就是一个描述连接的文件结构体。

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

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

相关文章

Cert Manager 申请SSL证书流程及相关概念-二

中英文对照表 英文英文 - K8S CRD中文备注certificatesCertificate证书certificates.cert-manager.io/v1certificate issuersIssuer证书颁发者issuers.cert-manager.ioClusterIssuer集群证书颁发者clusterissuers.cert-manager.iocertificate requestCertificateRequest证书申…

用栈实现队列

题目&#xff1a;232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09;这题跟我们之前写过的 用队列实现栈 很像&#xff0c;感兴趣的可以自行了解一下。题目内容准备工作这题明确说明了需要用栈来实现队列&#xff0c;介于C语言没有队列的库&#xff0c;所以在此之前我们…

Open3D 网格整形(Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 如果我们想要根据少量的约束来变形一个三角形网格,我们可以使用网格变形算法。Open3D中提供了一种ARAP整形方法,该方法的最终的目的是优化下面这个误差方程: 这里的 R i R_i R<

初识 Bootstrap4(前端开发框架)

初识 Bootstrap&#xff08;前端开发框架&#xff09;参考Bootstrap特点获取目录结构jQuery 与 Popper准备工作包含 jQuery 与 Poppermetabox-sizing基本模板无注释版本注释版本参考 项目描述Bootstrap 官方教程https://getbootstrap.net/docs/getting-started/introduction/百…

Streamlit如何展示3D模型?

Streamlit 是一个非常好的创建 web demo 的库&#xff0c;但是对于单目深度估计很难找到可以展示 3D 模型的东西。 正如我刚刚在 Jupyter Notebook 中使用 obj2html 库可视化 3D 模型所做的那样&#xff0c;我创建了一个演示&#xff1a;HuggingFacae Spaces Monocular Depth …

【算法基础】归并排序

目录 一、归并排序的思想 二、归并排序的步骤 三、归并的方式 四、代码模板 一、归并排序的思想 归并排序和快速排序一样&#xff0c;都是分治的思想。它是将一个无序的数组一分为二&#xff0c;最后再合二为一&#xff0c;将两个有序数组合并成为一个有序数组。 时间复杂…

2023兔年第一篇文章【CSS】之CSS列表详解【CSS基础知识详解】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 本文章收录于专栏 【CSS】 【CSS专栏】已发布文章 &#x1f4c1;【CSS基础认知】 &#x1f4c1;【CSS选择器全解指南】 &#x1f4c1…

ARM S5PV210的iNand

一、iNand介绍 1、iNand/eMMC/SD Card/MMC Card 的关联 (1) 最早出现的是 MMC 卡&#xff0c;卡片式结构&#xff0c;按照 MMC 协议设计。&#xff08;相较于 NandFlash 芯片来说&#xff0c;MMC 卡有 2 个优势&#xff1a;第一是卡片化&#xff0c;便于拆装&#xff1b;第二是…

【Java开发】Spring Cloud 06 :分布式配置管理-Nacos Config

在微服务架构中&#xff0c;我们会使用一个分布式的“配置中心”来管理所有的配置文件和配置项&#xff0c;本章节将介绍 Nacos 配置中心的特性&#xff0c;以及这些特性在微服务体系中所发挥的作用。在 Spring Boot 应用中&#xff0c;我们习惯于使用传统的配置管理方式&#…

【蓝桥杯】简单数论——GCDLCM

GCD 最大公约数Greatest Common Divisor(GCD)&#xff1a;整数a和b的GCD是指能同时整除a和b的最大整数&#xff0c;记为gcd(a,b)。由于-a的因子和a的因子相同&#xff0c;因此gcd(a, b) gcd(al, |bl)。编码时只关注正整数的最大公约数。 GCD性质 (1) gcd(a, b) gcd(a, ab) g…

活动星投票教师创课大赛网络评选微信的投票方式线上免费投票

“教师创课大赛”网络评选投票_小程序不记名选举投票_投票微信创建链接_微信公众号投票制作小程序投票活动如何做&#xff1f;很多企业在运营当中&#xff0c;都会通过投票活动来进行推广&#xff0c;从而达到吸粉、增加用户粘度等效果。而此类投票活动&#xff0c;通过小程序就…

【C进阶】通讯录2.0(文末附原码)

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;C语言进阶 ⭐代码仓库&#xff1a;C Advanced 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff0c;你们…

webpack搭建的React脚手架项目与vite进行兼容化开发

React项目兼容vite开发 问题描述 前言&#xff1a;之前在做Vue开发项目的过程中&#xff0c;是使用vite搭建的项目&#xff0c;不论是启动速度还是热更新&#xff0c;都非常的丝滑&#xff0c;可以极大弥补我电脑的短板&#xff0c;也提升了我的开发体验&#xff01;。 由于…

马帮对接打通金蝶云星空订单

马帮对接打通金蝶云星空获取订单列表接口与销售出库新增接口接入系统&#xff1a;马帮马帮ERP旗下有马帮ERP3.0、马帮ERP亚马逊专用版、马帮WMS仓储管理系统、马帮云仓、马帮TMS、跨境分销、马帮供应链SCM管理系统等产品&#xff0c;为跨境电商卖家提供高效管理方法和解决方案&…

【操作系统】—— 如何使用VMware虚拟机(带你快速了解)

&#x1f4dc; “作者 久绊A” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴。 &#x1f341; 操作系统【带你快速了解】对于电脑来说&#xff0c;如果说…

java多态/类的组合2022023

多态 Java引用变量有两个类型&#xff1a;一个是编译时类型&#xff0c;一个是运行时类型。编译时类型由声明该变量时使用的类型决定&#xff0c;运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致&#xff0c;就可能出现所谓的多态&#xff08;Polymor…

LeetCode刷题复盘笔记—一文搞懂贪心算法之55. 跳跃游戏问题(贪心算法系列第四篇)

今日主要总结一下可以使用贪心算法解决的一道题目&#xff0c;55. 跳跃游戏 题目&#xff1a;55. 跳跃游戏 Leetcode题目地址 题目描述&#xff1a; 给定一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长…

Python流程控制语句之选择语句

前言 在生活中&#xff0c;我们总是要做出许多选择&#xff0c;程序也是一样。比如下面的例子&#xff1a; 如果输入的用户名和密码正确&#xff0c;提示登录成功&#xff0c;否则&#xff0c;提示登录失败。如果考试成绩大于等于60分&#xff0c;则及格&#xff0c;否则不及…

在甲骨文云容器实例(Container Instances)上部署firefox

甲骨文云推出了容器实例&#xff0c;这是一项无服务器计算服务&#xff0c;可以即时运行容器&#xff0c;而无需管理任何服务器。 今天我们尝试一下通过容器实例部署firefox。 Step1. 创建容器实例 在甲骨文容器实例页面&#xff0c;单击"创建容器实例"&#xff0c…

[漏洞分析] CVE-2022-2602 io_uring UAF内核提权详细解析

本文首发于华为安全应急响应中心公众号&#xff1a; https://mp.weixin.qq.com/s/w_u0FoiFdU0KM397UXJojw 文章目录漏洞简介环境搭建漏洞原理文件引用计数与飞行计数引用计数飞行计数发送过程scm_send接收过程unix_gc垃圾处理机制io_uring原理(仅限漏洞)io_uring_setupio_urin…