「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

这篇具有很好参考价值的文章主要介绍了「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

「前言」文章是关于网络编程的socket套接字方面的,上一篇是网络编程socket套接字(三),这篇续上篇文章的内容,下面开始讲解! 

「归属专栏」网络编程

「主页链接」个人主页

「笔者」枫叶先生(fy)

「枫叶先生有点文青病」「句子分享」

Time goes on and on, never to an end but crossings.
时间一直走,没有尽头,只有路口。 
 ——克莱尔·麦克福《摆渡人》

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

目录

五、增加日志功能

六、Linux任务管理与守护进程

6.1 任务管理

6.1.1 进程组

6.1.2 作业概念

6.1.3 会话概念

6.1.4 操作

6.2 守护进程

6.2.1 概念

6.2.2 查看

6.2.3 创建守护进程

七、TCP协议通讯流程

7.1 三次握手

7.2 数据传输

7.3 四次挥手

7.4 TCP 和 UDP 对比


五、增加日志功能

文章续上篇文章的内容,给服务器增加日志功能,即把打印到显示台的内容,分等级打印到不同等级的文件里面

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

日志分为五个等级

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

 其中DEBUG、NORMAL、WARNING归类到一个文件里面,剩下的ERROR、FATAL归类到另一个文件里面

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

日志代码如下 

log.hpp 

#pragma once

#include <iostream>
#include <stdarg.h>
#include <ctime>
#include <unistd.h>

using namespace std;

#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

void logMessage(int level, const char *format, ...)
{
    // [日志等级] [时间] [pid] [message]

#define NUM 1024

    time_t now = time(nullptr);      // 获取当前时间
    tm *localTime = localtime(&now); // 将时间转换为结构体
    char timeStr[NUM];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", localTime); // 格式化时间为指定格式
    char logprefix[NUM];
    // 使用格式化后的时间字符串组装日志前缀
    snprintf(logprefix, sizeof(logprefix), "[%s][%s][pid: %d]",
             to_levelstr(level), timeStr, getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg);

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *err = fopen(LOG_ERR, "a");
    if (log != nullptr && err != nullptr)
    {
        FILE *curr = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            curr = log;
        if (level == ERROR || level == FATAL)
            curr = err;
        if (curr)
            fprintf(curr, "%s%s\n", logprefix, logcontent);

        fclose(log);
        fclose(err);
    }
}

 其他代码就不贴了,上传到Gitee了

Gitee:code_linux/code_202306_16/2_tcp/4_tcpthpool · Maple_fylqh/code - 码云 - 开源中国 (gitee.com)

测试结果,服务器运行,就已经把日志打印到文件里面了

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

六、Linux任务管理与守护进程

关闭shell之后,我们运行的进程也跟着销毁了。即我们运行的服务器也随之销毁,这显然是不合理了,所以这并不是服务器真正运行的样子。所以,下面要解决的就是这个问题,顺便介绍Linux任务管理与守护进程

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

关闭第一个shell,该进程的信息已经查不到了,说明该进程已经销毁了

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程  

6.1 任务管理

6.1.1 进程组

每个进程除了有一个进程ID之外,还属于一个进程组,进程组是一个或多个进程的集合。

进程组(Process Group)是一组具有相同进程组ID(PGID)的进程的集合。每个进程组都有一个唯一的PGID,用于标识进程组。

进程组的主要作用是将一组相关的进程组织在一起,以便可以对它们进行集体操作。例如,可以向进程组发送信号,以便同时影响组内的所有进程。进程组还可以用于实现作业控制,其中一个进程组被分配为前台作业,其他进程组被分配为后台作业。 

在Linux系统中,进程组的ID是由内核分配的,进程组的ID范围为正整数。进程组的ID为0的特殊进程组被称为“无效进程组”,用于标识没有有效进程组的进程。

需要注意的是,只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。

例如,这里的 PGID 就是进程组

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

6.1.2 作业概念

在Linux中,作业(Job)是指在终端或终端仿真器中运行的一个或多个命令的集合

作业分前台作业和后台作业:

  • 前台作业(Foreground Job):在终端中直接运行的命令或程序,默认情况下,前台作业会占用终端的控制权,并且会将输出直接显示在终端上
  • 后台作业(Background Job):在命令的末尾添加&符号,可以将命令放到后台运行,不会占用终端的控制权,并且会将输出重定向到一个文件或/dev/null

注:一个前台作业可以由多个进程或线程组成,一个后台作业也可以由多个进程或线程组成

前台任务只能有一个,后台任务可以有多个或者没有 

默认情况下,我们登录 Xshell后,bash会默认占据前台任务也就是命令行解释器shell(即占用终端的控制权)

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

比如,我们随便运行一个不会退出的程序,比如上面的服务端程序

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

该进程任务自动切换为前台任务,shell自动切换为后台任务,我们输入的命令就无效了

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

Linux提供了一些作业控制命令来管理和控制作业:

  • jobs:查看当前终端中运行的作业列表。
  • fg:将后台作业切换到前台运行。
  • bg:将后台作业切换到后台继续运行。
  • kill:终止指定作业的运行。

作业与进程组的区别:

如果作业中的某个进程又创建了子进程,则子进程不属于作业。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在,也就是这个被创建的子进程还没有终止,那么它将自动变为后台进程组

6.1.3 会话概念

在Linux中,会话(Session)是指从用户登录到系统开始,到用户退出系统结束的整个时间段。

也就是说一个用户进行登录Linux,Linux系统就会分配一个会话给我们,直到我们主动退出这个会话。在一个会话中,用户可以与系统进行交互,执行命令、操作文件、启动程序等

6.1.4 操作

先创建几个进程组,为了方便直接用 sleep 代替应用程序 

在命令的末尾添加&符号,可以将命令放到后台运行,不会占用终端的控制权,此时命令行依旧生效

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程  

 查看一下进程信息,进程组的PGID相同代表的是在同一个进程组

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

使用 jobs:查看当前终端中运行的作业列表,例如

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

其中,前面的序号就是任务编号,用于辨别多个任务

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程  

使用 fg 命令可以将后台作业切换到前台运行,后面带上作业的编号

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

由于1号作业被提至前台运行,所以其运行状态也由S变成了S+,+ 就是代表是前台任务

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

注意:前台进程只能有一个,当一个进程变成前台进程后,bash会自动变为后台进程,此时bash就无法进行命令行解释了 

将一个前台进程放到后台运行可以使用Ctrl+Z,但使用Ctrl+Z后该进程就会处于停止状态(Stopped)

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

bg:将后台作业切换到后台继续运行。 可以让某个停止的作业在后台继续运行(Running)

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

6.2 守护进程

6.2.1 概念

在Linux系统中,守护进程(Daemon Process)是在后台运行的一种特殊类型的进程。它与用户交互的终端分离,通常在系统启动时自动启动,并在系统运行期间持续运行,直到系统关闭或手动停止。守护进程也称精灵进程,本质是孤儿进程的一种

 守护进程通常用于执行一些需要持续运行的任务,比如网络服务、系统监控、定时任务等。与普通进程不同,守护进程没有终端与之关联,也没有用户交互。它们在后台默默地运行,执行系统任务,并通过日志文件记录运行状态和输出信息。

6.2.2 查看

可以用 ps axj命令查看系统中的进程:

  • 参数a表示不仅列出当前用户的进程,也列出所有其他用户的进程。
  • 参数x表示不仅列出有控制终端的进程,也列出所有无控制终端的进程。
  • 参数j表示列出与作业控制相关的信息

TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

6.2.3 创建守护进程

创建守护进程一般不喜欢使用系统提供的,因为有未定义行为,一般都是自己写

daemon函数是系统提供的

创建守护进程的过程可以分为以下几个步骤:

  1. 让调用进程忽略掉异常的信号

  2. 创建子进程:使用fork()系统调用创建一个子进程。

  3. 脱离终端(核心):使用setsid()系统调用使子进程脱离终端,成为一个新的会话组长。

  4. 关闭文件描述符:关闭所有文件描述符,以防止守护进程与终端或其他进程的关联。可以使用close()系统调用来关闭文件描述符。

  5. 重定向标准输入输出、错误:将标准输入、输出和错误重定向到/dev/null或日志文件中。可以使用dup2()系统调用来重定向文件描述符。

  6. 设置工作目录(可选):将工作目录切换到根目录,以防止守护进程运行时影响其他目录。可以使用chdir()系统调用来切换工作目录。

代码如下:

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

#define DEV "/dev/null"

void daemonSelf(const char *currPath = nullptr)
{
    // 1. 让调用进程忽略掉异常的信号
    signal(SIGPIPE, SIG_IGN);

    // 2. 创建子进程
    if (fork() > 0)
        exit(0);
    // 子进程 -- 守护进程,精灵进程,本质就是孤儿进程的一种!
    // 3.脱离终端:使用setsid()系统调用使子进程脱离终端,成为一个新的会话组长。
    pid_t n = setsid();
    assert(n != -1);

    // 4. 关闭文件描述符 或 重定向标准输入输出、错误
    int fd = open(DEV, O_RDWR);
    if (fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);

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

    // 5. 可选:进程执行路径发生更改

    if (currPath)
        chdir(currPath);
}

/dev/null

/dev/null是Linux操作系统中的一个特殊文件,它会丢弃所有写入它的数据,并在从中读取时返回文件结束条件。它通常被用作丢弃不需要的输出或测试程序在遇到写入错误时的行为。/dev/null 形象称为黑洞,或文件黑洞

setsid函数

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

creates a session and sets the process group ID:创建会话并设置进程组ID 

返回值:函数调用成功后,将返回调用进程的(新)会话ID。出现错误时,返回(pid_t)-1,错误码被设置

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

如果调用进程不是进程组的组织,setsid()将创建一个新会话

如何让自己不是进程组组长??

创建子进程:使用fork()系统调用创建一个子进程,让父进程直接退出

注意:创建子进程成立新会话后,子进程自己就成了进程组,与终端设备无关

测试

给服务端加上该代码,进行测试

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

编译运行服务端

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

查看进程信息

发现该进程的TPGID为-1,代表的是守护进程,TTY显示的是,也就意味着该进程已经与终端去关联了 

PPID为1,说明OS领养了守护进程,守护进程本质是孤儿进程的一种

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

现在把自己的终端关掉,重新连接,该进程依旧可以查到,说明进程已经守护进程化了,这就是服务器正确的运行方式

七、TCP协议通讯流程

TCP协议通讯流程这里只是浅谈,后序再详谈,这里只有简单认识。

TCP协议的客户端/服务器程序的一般流程: 

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

7.1 三次握手

三次握手就是客户端向服务端发起连接的过程(简单了解,后序详谈)

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

 服务器初始化

  • 调用socket, 创建文件描述符;
  • 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  • 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
  • 调用accecpt, 并阻塞, 等待客户端连接过来

 建立连接的过程

  • 调用socket, 创建文件描述符;
  • 调用connect, 向服务器发起连接请求;
  • connect会发出SYN段并阻塞等待服务器应答; (第一次)
  • 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
  • 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次) 

这个建立连接的过程, 通常称为 三次握手

7.2 数据传输

双方建立好连接之后就可以进行数据传输了

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

 数据传输的过程

  •  建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方
  • 可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
  • 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
  • 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
  • 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;
  • 客户端收到后从read()返回, 发送下一条请求,如此循环下去

注意:用 read 读取数据是有问题的,你不能保证数据读取完了,或者数据只读取了一部分,又或者数据没有及时读取,这些问题后序再谈,这就是为什么说 TCP是面向字节流。

7.3 四次挥手

如果不想通信了,双方就要断开连接

「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

断开连接的过程 

  • 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
  • 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
  • read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
  • 客户端收到FIN, 再返回一个ACK给服务器; (第四次) 

这个断开连接的过程, 通常称为 四次挥手

在学习socket API时要注意应用程序和TCP协议层是如何交互的 

  • 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段
  • 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段 

注:以上概念先了解,后序再谈

7.4 TCP 和 UDP 对比

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流 vs 数据报

到目前为止,我们通过代码知道 TCP是有连接 和 UDP是无连接,而可靠和不可靠传输、面向字节流和面向数据报暂时体会不到,后序谈原理的时候就可以理解了

--------------------- END ----------------------文章来源地址https://www.toymoban.com/news/detail-497659.html

「 作者 」 枫叶先生
「 更新 」 2023.6.23
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

到了这里,关于「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 网络编程套接字(Socket)

    认识IP地址, 端口号, 网络字节序等网络编程中的基本概念; 学习socket api的基本用法; 能够实现一个简单的udp客户端/服务器; 能够实现一个简单的tcp客户端/服务器(单连接版本, 多进程版本, 多线程版本); 理解tcp服务器建立连接, 发送数据, 断开连接的流程; 通俗易懂地说,源

    2024年01月21日
    浏览(57)
  • 网络编程—Socket套接字详解

    目录 一、网络编程 1.1、为什么需要网络编程? 1.2、什么是网络编程 1.3、发送端和接收端 ​编辑1.4、请求和响应 ​编辑1.5、客户端和服务端  二、Socket套接字  2.1、概念 2.2、分类  2.2.1、流套接字  2.2.2、数据报套接字  2.2.3、原始套接字  2.3、Socket编程注意事项  1.1、为什

    2024年02月16日
    浏览(54)
  • [JAVAee]网络编程-套接字Socket

    目录 基本概念 发送端与接收端 请求与响应 ​编辑客户端与服务器 Socket套接字  分类 数据报套接字 流套接字传输模型   UDP数据报套接字编程 DatagramSocket API DatagramPacket API InetSocketAddress API 示例一: 示例二: TCP流数据报套接字编程 ServerSocket API Socket API 示例一:   网络编程指的

    2024年02月13日
    浏览(56)
  • 网络编程『socket套接字 ‖ 简易UDP网络程序』

    🔭个人主页: 北 海 🛜所属专栏: Linux学习之旅、神奇的网络世界 💻操作环境: CentOS 7.6 阿里云远程服务器 在当今数字化时代,网络通信作为连接世界的桥梁,成为计算机科学领域中至关重要的一部分。理解网络编程是每一位程序员必备的技能之一,而掌握套接字编程则

    2024年02月04日
    浏览(57)
  • 【网络通信】socket编程——TCP套接字

    TCP依旧使用代码来熟悉对应的套接字,很多接口都是在udp中使用过的 所以就不会单独把他们拿出来作为标题了,只会把第一次出现的接口作为标题 通过TCP的套接字 ,来把数据交付给对方的应用层,完成双方进程的通信 在 tcpServer.hpp 中,创建一个命名空间 yzq 用于封装 在命名

    2024年02月13日
    浏览(51)
  • 【网络编程】网络编程概念,socket套接字,基于UDP和TCP的网络编程

    前言: 大家好,我是 良辰丫 ,今天我们一起来学习网络编程,网络编程的基本概念,认识套接字,UDP与TCP编程.💞💞💞 🧑个人主页:良辰针不戳 📖所属专栏:javaEE初阶 🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。 💦期待大家三连,关注

    2023年04月20日
    浏览(61)
  • Linux网络编程- 原始套接字(Raw Socket)

    原始套接字(Raw Socket)提供了一种机制,允许应用程序直接访问底层传输协议,绕过操作系统提供的传输层接口。这种套接字通常用于实现新的协议或对现有协议进行低级别的操作。 以下是对原始套接字的详细介绍: 定义与用途 : 原始套接字是直接基于网络层(如IP)的。

    2024年02月07日
    浏览(55)
  • 【JavaEE初阶】 网络编程基础与Socket套接字

    用户在浏览器中,打开在线视频网站,如腾讯看视频,实质是通过网络,获取到网络上的一个视频资源。 与本地打开视频文件类似,只是视频文件这个资源的来源是 网络 。 相比本地资源来说,网络提供了更为丰富的网络资源: 所谓的网络资源,其实就是在网络中可以获取

    2024年02月05日
    浏览(65)
  • 【Python】Python 网络编程 ( Socket 套接字简介 | Socket 套接字使用步骤 | Socket 套接字服务端与客户端开发 )

    Socket 套接字 是一种 进程之间的 通信机制 , 通过套接字可以在 不同的进程之间 进行数据交换 ; 在 网络编程 中 , Socket 套接字 主要用于 客户端 与 服务器 之间的 通信 , 大部分 网络相关的应用程序 , 都使用到了 Socket 套接字技术 ; 套接字有两种类型 : 流套接字 : 提供了一个可

    2024年02月15日
    浏览(153)
  • 【Linux网络】网络编程套接字 -- 基于socket实现一个简单UDP网络程序

    我们把数据从A主机发送到B主机,是目的吗?不是,真正通信的不是这两个机器!其实是这两台机器上面的软件(人) 数据有 IP(公网) 标识一台唯一的主机 ,用谁来标识各自主机上客户或者服务进程的唯一性呢? 为了更好的表示一台主机上服务进程的唯一性,我们采用 端口号

    2024年02月12日
    浏览(158)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包