项目-SERVER模块-Socket模块

Socket模块

  • 一、Socket模块是什么?
  • 二、代码实现
    • 1.成员变量
    • 2.构造、析构函数
    • 3.获取套接字文件描述符
    • 4.创建套接字
    • 5.绑定地址信息
    • 6.开始监听连接请求
    • 7.向服务器发起连接
    • 8.获取新连接
    • 9.接收数据
    • 10.非阻塞接收数据
    • 11.发送数据
    • 12.非阻塞发送数据
    • 13.关闭套接字
    • 14.创建一个服务端连接
    • 15.创建一个客户端连接
    • 16.设置套接字选项——开启地址端口重用
    • 17. 设置套接字阻塞属性——设置为非阻塞
    • 18.测试代码
    • 19.整体源代码


一、Socket模块是什么?

Socket模块是对套接字操作封装的⼀个模块,主要实现的socket的各项操作。
在这里插入图片描述

二、代码实现

1.成员变量

这行代码表示在类(class)的私有(private)部分声明了一个整型变量 _sockfd,用于存储某个类的套接字文件描述符。在类的私有部分声明的成员只能在该类的成员函数内部访问,外部无法直接访问。

private:int _sockfd;

2.构造、析构函数

这段代码展示了一个名为Socket的类的构造函数和析构函数的定义:

  1. Socket() : _sockfd(-1) {}: 这是一个无参数的构造函数,用于初始化Socket类的对象。在这个构造函数中,通过初始化列表将_sockfd成员变量设置为-1,表示套接字文件描述符的初始值为-1。

  2. Socket(int sockfd) : _sockfd(sockfd) {}: 这是一个带有整型参数的构造函数,用于初始化Socket类的对象并指定套接字文件描述符的值。在这个构造函数中,通过初始化列表将_sockfd成员变量设置为传入的参数sockfd的值。

  3. ~Socket() { Close(); }: 这是Socket类的析构函数,用于释放资源和清理工作。在析构函数中调用了Close()函数,该函数应该是Socket类的一个成员函数,用于关闭套接字。通过在析构函数中调用Close()函数,确保在对象被销毁时及时关闭相关资源,避免资源泄漏。

public:Socket() : _sockfd(-1) {}Socket(int sockfd) : _sockfd(sockfd) {}~Socket() { Close(); }

3.获取套接字文件描述符

这段代码定义了一个名为get_fd()的成员函数,用于获取类中私有成员变量_sockfd的值(套接字文件描述符)。该函数返回整型值,表示获取到的套接字文件描述符。

通过定义这样的成员函数,可以在类外部获取Socket类对象的套接字文件描述符,同时保持_sockfd作为私有成员的封装性。这样的设计方式遵循了面向对象编程的封装原则,将类的数据隐藏起来,只允许通过成员函数来进行访问和操作,从而提高了代码的安全性和可维护性。

    // 获取套接字文件描述符int get_fd() { return _sockfd; }

4.创建套接字

    // 创建套接字bool Create(){// 调用socket函数创建套接字// int socket(int domain, int type, int protocol)_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 检查套接字创建是否成功if (_sockfd < 0){// 套接字创建失败时输出日志信息INF_LOG("Socket creation failed");return false;}// 套接字创建成功return true;}

5.绑定地址信息

// 绑定地址和端口
bool Bind(const std::string &ip, uint16_t port)
{// 创建一个 sockaddr_in 结构体并设置相关参数struct sockaddr_in addr;addr.sin_family = AF_INET;  // 设置地址族为IPv4// 将端口号转换为网络字节顺序addr.sin_port = htons(port);// 将IP地址转换为网络字节顺序并填入结构体中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 计算地址结构体的长度socklen_t len = sizeof(struct sockaddr_in);// 调用bind函数将套接字和地址绑定int ret = bind(_sockfd, (struct sockaddr*)&addr, len);if (ret < 0){// 绑定失败时输出错误日志信息ERR_LOG("BIND ADDRESS FAILED!");return false;}// 绑定成功return true;
}

6.开始监听连接请求

// 开始监听连接请求
bool Listen(int backlog = MAX_LISTEN)
{// 调用listen函数开始监听连接请求int ret = listen(_sockfd, backlog);if (ret < 0){// 监听失败时输出错误日志信息ERR_LOG("SOCKET LISTEN FAILED!");return false;}// 监听成功return true;
}

7.向服务器发起连接

// 向服务器发起连接
bool Connection(const std::string &ip, uint16_t port)
{// 创建一个 sockaddr_in 结构体并设置相关参数struct sockaddr_in addr;addr.sin_family = AF_INET;  // 设置地址族为IPv4// 将端口号转换为网络字节顺序addr.sin_port = htons(port);// 将IP地址转换为网络字节顺序并填入结构体中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 计算地址结构体的长度socklen_t len = sizeof(struct sockaddr_in);// 调用connect函数发起连接请求int ret = connect(_sockfd, (struct sockaddr *)&addr, len);if (ret < 0){// 连接失败时输出错误日志信息ERR_LOG("CONNECT SERVER FAILED!");return false;}// 连接成功return true;
}

8.获取新连接

// 获取新连接
int Accept()
{// 调用accept函数接受新的连接// int accept(int sockfd, struct sockaddr *addr, socklen_t *len);int newfd = accept(_sockfd, NULL, NULL);if (newfd < 0){// 接受连接失败时输出错误日志信息ERR_LOG("SOCKET ACCEPT FAILED!");return -1;}// 返回新的文件描述符return newfd;
}

9.接收数据

// 接收数据
ssize_t Recv(void *buf, size_t len, int flag = 0)
{// 调用recv函数接收数据ssize_t s = recv(_sockfd, buf, len, flag);if (s <= 0){if (errno == EINTR || errno == EAGAIN){// 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0INF_LOG("Recv has not ready!");return 0;}// 其他接收失败的情况,记录错误日志信息并返回-1ERR_LOG("read failed!");return -1;}// 返回实际接收的数据长度return s;
}

10.非阻塞接收数据

// 非阻塞接收数据
ssize_t NonBlockRecv(void *buf, size_t len)
{// 调用Recv函数,设置MSG_DONTWAIT标志表示非阻塞接收return Recv(buf, len, MSG_DONTWAIT);
}

11.发送数据

// 发送数据
ssize_t Send(const void *buf, size_t len, int flag = 0)
{// 调用send函数发送数据// ssize_t send(int sockfd, void *data, size_t len, int flag);ssize_t ret = send(_sockfd, buf, len, flag);if (ret < 0){// 发送失败时记录错误日志信息并返回-1ERR_LOG("SOCKET SEND FAILED!!");return -1;}// 返回实际发送的数据长度return ret;
}

12.非阻塞发送数据

// 非阻塞发送数据
ssize_t NonBlockSend(void *buf, size_t len)
{// 调用Send函数,设置MSG_DONTWAIT标志表示非阻塞发送return Send(buf, len, MSG_DONTWAIT);
}

13.关闭套接字

// 关闭套接字
void Close()
{// 检查套接字是否有效,如果有效则关闭套接字if (_sockfd != -1)close(_sockfd);// 将套接字文件描述符设置为无效值_sockfd = -1;
}

14.创建一个服务端连接

// 创建一个服务端连接
bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false)
{// 1. 创建套接字 // 2. 绑定地址 // 3. 开始监听 // 4. 设置非阻塞 // 5. 启动地址重用// 如果创建套接字失败,则返回falseif (Create() == false) return false;// 如果需要设置为非阻塞模式,则调用NonBlock函数if (block_flag) NonBlock();// 绑定地址,如果绑定失败则返回falseif (Bind(ip, port) == false) return false;// 开始监听,如果监听失败则返回falseif (Listen() == false) return false;// 启动地址重用ReuseAddress();return true;
}

15.创建一个客户端连接

// 创建一个客户端连接
bool CreateClient(uint16_t port, const std::string &ip)
{// 1. 创建套接字 // 2. 指向连接服务器// 如果创建套接字失败,则返回falseif (Create() == false)  return false;// 连接服务器,如果连接失败则返回falseif (Connection(ip, port) == false)return false;return true;
}

16.设置套接字选项——开启地址端口重用

// 设置套接字选项——开启地址端口重用
void ReuseAddress()
{// 使用setsockopt函数设置SO_REUSEADDR和SO_REUSEPORT选项开启地址和端口重用int val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));
}

17. 设置套接字阻塞属性——设置为非阻塞

// 设置套接字阻塞属性——设置为非阻塞
void NonBlock()
{// 使用fcntl函数获取当前套接字的属性,并设置为非阻塞模式//int fcntl(int fd,int cmd,.../* arg */);int flag = fcntl(_sockfd, F_GETFL, 0);fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
}

18.测试代码

这段代码是一个简单的基于Socket的服务器端程序,它创建一个服务器Socket并监听指定端口(8500)。然后在一个无限循环中接受客户端连接,接收客户端发送的数据,并将数据原样发送回客户端,最后关闭与客户端的连接。注意:需要打开两个终端分别运行服务器端和客户端

//服务器端
#include"../source/server.hpp"
int main(){Socket lst_sock;lst_sock.CreateServer(8500);while(1){int newfd =lst_sock.Accept();if(newfd <0){continue;}  Socket cli_sock(newfd);char buf[1024]={0};int ret= cli_sock.Recv(buf, 1023);if(ret < 0){cli_sock.Close();continue;}cli_sock.Send(buf, ret);cli_sock.Close();}lst_sock.Close();return 0;
}
//客户端口
#include"../source/server.hpp"
int main()
{Socket cli_sock;cli_sock.CreateClient(8500,"127.0.0.1");std::string str="hello have a good day~";cli_sock.Send(str.c_str(),str.size());char buf[1024]={0};cli_sock.Recv(buf,1023);DBG_LOG("%s",buf);return 0;
}

测试结果:
在这里插入图片描述

19.整体源代码

// Socket //
#define MAX_LISTEN 1024
#define DEFAULT_IP "0.0.0.0"
class Socket
{
private:int _sockfd;
public:Socket() : _sockfd(-1) {}Socket(int sockfd) : _sockfd(sockfd) {}~Socket() { Close(); }// 获取套接字文件描述符int get_fd() { return _sockfd; }// 创建套接字bool Create(){// 调用socket函数创建套接字// int socket(int domain, int type, int protocol)_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 检查套接字创建是否成功if (_sockfd < 0){// 套接字创建失败时输出日志信息INF_LOG("Socket creation failed");return false;}// 套接字创建成功return true;}// 绑定地址和端口bool Bind(const std::string &ip, uint16_t port){// 创建一个 sockaddr_in 结构体并设置相关参数struct sockaddr_in addr;addr.sin_family = AF_INET;  // 设置地址族为IPv4// 将端口号转换为网络字节顺序addr.sin_port = htons(port);// 将IP地址转换为网络字节顺序并填入结构体中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 计算地址结构体的长度socklen_t len = sizeof(struct sockaddr_in);// 调用bind函数将套接字和地址绑定int ret = bind(_sockfd, (struct sockaddr*)&addr, len);if (ret < 0){// 绑定失败时输出错误日志信息ERR_LOG("BIND ADDRESS FAILED!");return false;}// 绑定成功return true;}// 开始监听连接请求bool Listen(int backlog = MAX_LISTEN){// 调用listen函数开始监听连接请求int ret = listen(_sockfd, backlog);if (ret < 0){// 监听失败时输出错误日志信息ERR_LOG("SOCKET LISTEN FAILED!");return false;}// 监听成功return true;}// 向服务器发起连接bool Connection(const std::string &ip, uint16_t port){// 创建一个 sockaddr_in 结构体并设置相关参数struct sockaddr_in addr;addr.sin_family = AF_INET;  // 设置地址族为IPv4// 将端口号转换为网络字节顺序addr.sin_port = htons(port);// 将IP地址转换为网络字节顺序并填入结构体中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 计算地址结构体的长度socklen_t len = sizeof(struct sockaddr_in);// 调用connect函数发起连接请求int ret = connect(_sockfd, (struct sockaddr *)&addr, len);if (ret < 0){// 连接失败时输出错误日志信息ERR_LOG("CONNECT SERVER FAILED!");return false;}// 连接成功return true;}// 获取新连接int Accept(){// 调用accept函数接受新的连接// int accept(int sockfd, struct sockaddr *addr, socklen_t *len);int newfd = accept(_sockfd, NULL, NULL);if (newfd < 0){// 接受连接失败时输出错误日志信息ERR_LOG("SOCKET ACCEPT FAILED!");return -1;}// 返回新的文件描述符return newfd;}// 接收数据ssize_t Recv(void *buf, size_t len, int flag = 0){// 调用recv函数接收数据ssize_t s = recv(_sockfd, buf, len, flag);if (s <= 0){if (errno == EINTR || errno == EAGAIN){// 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0INF_LOG("Recv has not ready!");return 0;}// 其他接收失败的情况,记录错误日志信息并返回-1ERR_LOG("read failed!");return -1;}// 返回实际接收的数据长度return s;}// 非阻塞接收数据ssize_t NonBlockRecv(void *buf, size_t len){// 调用Recv函数,设置MSG_DONTWAIT标志表示非阻塞接收return Recv(buf, len, MSG_DONTWAIT);}// 发送数据ssize_t Send(const void *buf, size_t len, int flag = 0){// 调用send函数发送数据// ssize_t send(int sockfd, void *data, size_t len, int flag);ssize_t ret = send(_sockfd, buf, len, flag);if (ret < 0){// 发送失败时记录错误日志信息并返回-1ERR_LOG("SOCKET SEND FAILED!!");return -1;}// 返回实际发送的数据长度return ret;}// 非阻塞发送数据ssize_t NonBlockSend(void *buf, size_t len){// 调用Send函数,设置MSG_DONTWAIT标志表示非阻塞发送return Send(buf, len, MSG_DONTWAIT);}// 关闭套接字void Close(){// 检查套接字是否有效,如果有效则关闭套接字if (_sockfd != -1)close(_sockfd);// 将套接字文件描述符设置为无效值_sockfd = -1;}// 创建一个服务端连接bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false){// 1. 创建套接字 // 2. 绑定地址 // 3. 开始监听 // 4. 设置非阻塞 // 5. 启动地址重用// 如果创建套接字失败,则返回falseif (Create() == false) return false;// 如果需要设置为非阻塞模式,则调用NonBlock函数if (block_flag) NonBlock();// 绑定地址,如果绑定失败则返回falseif (Bind(ip, port) == false) return false;// 开始监听,如果监听失败则返回falseif (Listen() == false) return false;// 启动地址重用ReuseAddress();return true;}// 创建一个客户端连接bool CreateClient(uint16_t port, const std::string &ip){// 1. 创建套接字 // 2. 指向连接服务器// 如果创建套接字失败,则返回falseif (Create() == false)  return false;// 连接服务器,如果连接失败则返回falseif (Connection(ip, port) == false)return false;return true;}// 设置套接字选项——开启地址端口重用void ReuseAddress(){// 使用setsockopt函数设置SO_REUSEADDR和SO_REUSEPORT选项开启地址和端口重用int val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));}// 设置套接字阻塞属性——设置为非阻塞void NonBlock(){// 使用fcntl函数获取当前套接字的属性,并设置为非阻塞模式//int fcntl(int fd,int cmd,.../* arg */);int flag = fcntl(_sockfd, F_GETFL, 0);fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);}
};

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

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

相关文章

20240301作业

1.使用fwrite、fread将一张随意的bmp图片&#xff0c;修改成德国的国旗 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> int main(int argc, const char *argv[]) {FILE* fp fopen("./gaoda.bmp","…

Java毕业设计 基于SpringBoot vue 社区团购系统

Java毕业设计 基于SpringBoot vue 社区团购系统 SpringBoot vue 社区团购系统 功能介绍 前端用户: 首页 图片轮播 商品信息 商品分类展示 搜索 商品详情 点我收藏 添加到购物车 立即购买 我要开团 去参团 评论 公告资讯 资讯详情 登录 注册 个人中心 更新信息 点我充值 我的订…

git之远程操作

一.分布式版本控制系统 分布式版本控制系统通常也有⼀台充当“中央服务器”的电脑&#xff0c;但这个服务器的作⽤仅仅是⽤来⽅便“交换”⼤家的修改&#xff0c;没有它⼤家也⼀样⼲活&#xff0c;只是交换修改不⽅便⽽已。有了这个“中央服务器”的电脑&#xff0c;这样就不怕…

ChatGPT学习第四周

&#x1f4d6; 学习目标 ChatGPT实践操作 通过实际操作和练习&#xff0c;加深对ChatGPT功能的理解。 项目&#xff1a;创建一个ChatGPT应用案例 设计一个基于ChatGPT的小项目&#xff0c;将理论应用于实践。 ✍️ 学习活动 学习资料 《万字干货&#xff01;ChatGPT 从零完…

达梦数据库查询语句内存溢出问题解决

背景&#xff1a;达梦数据库使用过程中&#xff0c;某天突然服务宕机&#xff0c;导致各类后端服务无法注册到nacos上&#xff0c;重启之后nacos正常启动&#xff0c;可执行一条两千多条数据量的连表查询时间很长&#xff0c;甚至会报错&#xff0c;经查看日志发现在查询过程中…

【C语言】常见的动态内存管理错误

前言 上一篇介绍了C语言中 动态内存管理函数&#xff0c;本片讲解的是 在我们使用动态内存管理时 常见的错误&#xff0c;一起来看看吧~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 1.对NULL指针的解引⽤操作 错…

什么是前端框架中的数据绑定(data binding)?有哪些类型的数据绑定?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

YOLOv5白皮书-第Y4周:common.py文件解读

YOLOv5白皮书-第Y4周:common.py文件解读 YOLOv5白皮书-第Y4周:common.py文件解读0.导入需要的包和基本配置1.基本组件1.1 autopad1.2 Conv1.3 Focus1.4 Bottleneck1.5 BottleneckCSP1.6 C31.7 SPP1.8 Concat1.9 Contract、Expand 2.重要类2.1 非极大值抑制&#xff08;NMS&…

vue3中的基本语法

目录 基础素材 vue3的优化 使用CompositionAPI理由 1. reactive() 函数 2. ref() 函数 2.1. ref的使用 2.2. 在 reactive 对象中访问 ref 创建的响应式数据 3. isRef() 函数 4. toRefs() 函数 5. computed() 5.1. 通过 set()、get()方法创建一个可读可写的计算属性 …

函数——递归6(c++)

角谷猜想 题目描述 日本一位中学生发现一个奇妙的 定理&#xff0c;请角谷教授证明&#xff0c;而教授 无能为力&#xff0c;于是产生了角谷猜想。 猜想的内容&#xff1a;任给一个自然数&#xff0c; 若为偶数则除以2&#xff0c;若为奇数则乘 3加1&#xff0c;得到一个新的…

PyTorch深度学习快速入门

PyTorch深度学习快速入门 1.PyTorch环境配置及安装2.python编辑器的选择、安装、配置&#xff08;pycharm、JupyTer安装&#xff09;3.为什么torch.cuda.is_available()返回false4.python学习中两大法宝函数&#xff08;也可用在pytorch&#xff09;5.pycharm和jupyter&#xf…

golang goroutine 如何退出?

上一讲说到调度器将maingoroutine推上舞台&#xff0c;为它铺好了道路&#xff0c;开始执行runtime.main函数。这一讲&#xff0c;我们探索maingoroutine以及普通goroutine从执行到退出的整个过程。 //Themaingoroutine. funcmain(){ //gmaingoroutine&#xff0c;不再是g0了 …

Python列表中添加删除元素不走弯路

1.append() 向列表中添加单个元素&#xff0c;一般用于尾部追加 list1 ["香妃", "乾隆", "贾南风", "赵飞燕", "汉武帝"]list1.append("周瑜") print(list1) # [香妃, 乾隆, 贾南风, 赵飞燕, 汉武帝, 周瑜]…

STM32标准库——(14)I2C通信协议、MPU6050简介

1.I2C通信 I2C 通讯协议(Inter&#xff0d;Integrated Circuit)是由Phiilps公司开发的&#xff0c;由于它引脚少&#xff0c;硬件实现简单&#xff0c;可扩展性强&#xff0c; 不需要USART、CAN等通讯协议的外部收发设备&#xff0c;现在被广泛地使用在系统内多个集成电路(IC)间…

计算机网络_2.2物理层下面的传输媒体

2.2物理层下面的传输媒体 一、传输媒体的分类二、导向型传输媒体1、同轴电缆2、双绞线3、光纤&#xff08;1&#xff09;光纤通信原理&#xff08;2&#xff09;光纤组成&#xff08;4&#xff09;多模光纤与单模光纤对比&#xff08;5&#xff09;光纤的波长与规格&#xff08…

数据可视化基础与应用-02-基于powerbi实现连锁糕点店数据集的仪表盘制作

总结 本系列是数据可视化基础与应用的第02篇&#xff0c;主要介绍基于powerbi实现一个连锁糕点店数据集的仪表盘制作。 数据集描述 有一个数据集&#xff0c;包含四张工作簿&#xff0c;每个工作簿是一张表&#xff0c;其中可以销售表可以划分为事实表&#xff0c;产品表&am…

Linux时间同步(PPS、PTP、chrony)分析笔记

1 PPS(pulse per second) 1.1 简介 LinuxPPS provides a programming interface (API) to define in the system several PPS sources. PPS means "pulse per second" and a PPS source is just a device which provides a high precision signal each second so t…

每日一题 2673使二叉树所有路径值相等的最小代价

2673. 使二叉树所有路径值相等的最小代价 题目描述&#xff1a; 给你一个整数 n 表示一棵 满二叉树 里面节点的数目&#xff0c;节点编号从 1 到 n 。根节点编号为 1 &#xff0c;树中每个非叶子节点 i 都有两个孩子&#xff0c;分别是左孩子 2 * i 和右孩子 2 * i 1 。 树…

Java缓存简介

内存访问速度和硬盘访问速度是计算机系统中两个非常重要的性能指标。 内存访问速度&#xff1a;内存是计算机中最快的存储介质&#xff0c;它的访问速度可以达到几纳秒级别。内存中的数据可以直接被CPU访问&#xff0c;因此读写速度非常快。 硬盘访问速度&…

学习和工作的投入产出比(节选)

人工智能统领全文 推荐包含关于投入、产出、过剩、市场关注、案例、结果和避雷等主题的信息&#xff1a; 投入与产出&#xff1a; 投入和产出都有直接和间接两类常见形式。常见的四种组合是&#xff1a;直接投入、直接产出、间接投入、间接产出。 过剩&#xff1a; 过剩是一个重…