当前位置: 首页 > news >正文

通信安全之数据加密

        数据安全的需求如今越来越重要,本篇简单举例给日常的TCP/UDP通信加密,至少能让想干坏事的崽犯罪的成本更高一些(如果会一些BPF的,可能难不住这些崽),能让我们的数据更安全一点。

经典TCP socket编程

下面为大家常见的TCP socket编程示例:

客户端
/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  
#include <sys/types.h>   
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
//#include "myssl.h"int main(int argc, char *argv[])
{// 定义相关变量int sockfd;struct sockaddr_in serv_addr;char buf[1024] = {0};const char *greeting = "Hello from client";if(argc < 2){printf("please input the ip of the server..\n");return -1;}//获取并初始化服务端地址serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8080);if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) <= 0){fprintf(stderr, "inet_pton failed[%d].\n", errno);return -1;}// 创建socketif((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){fprintf(stderr, "create socket failed[%d].\n", errno);return -1;}// 连接到服务器if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){fprintf(stderr, "connect failed[%d].\n", errno);return -1;}// 发送数据if(write(sockfd, greeting, strlen(greeting)) < 0){fprintf(stderr, "write failed[%d].\n", errno);return -1;}// 接收数据if(read(sockfd, buf, 1024) < 0){fprintf(stderr, "read failed[%d].\n", errno);return -1;}printf("received message:%s\n", buf);close(sockfd);
}
服务端
/* server.c */
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <sys/types.h>   
#include <sys/socket.h>  
#include <netinet/in.h>
#include <errno.h>
//#include "myssl.h"#define MAX_CNT 5int main(int argc, char *argv[])
{// 定义相关变量int server_fd, new_socket;int opt = 1; struct sockaddr_in serv_addr, cli_addr;socklen_t cli_addr_len;char buf[1024] = {0};const char *greeting = "Hello from server";// 创建socket文件描述符if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){fprintf(stderr, "create socket failed[%d].\n", errno);return -1;}// 设置socket选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {  fprintf(stderr, "set socket failed[%d].\n", errno);return -1;  }// 绑定socket到指定端口和地址memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(server_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){fprintf(stderr, "create bind failed[%d].\n", errno);return -1;}// 监听if(listen(server_fd, MAX_CNT) < 0){fprintf(stderr, "listen failed[%d].\n", errno);return -1;}// 接受客户端连接请求memset(&cli_addr, 0, sizeof(cli_addr));cli_addr_len = sizeof(cli_addr);if((new_socket = accept(server_fd, (struct sockaddr *)&cli_addr, (socklen_t *)&cli_addr_len)) < 0 ){fprintf(stderr, "accept failed[%d].\n", errno);return -1;}printf("Client [%s-%d] connected.\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));// 接收数据if(read(new_socket, buf, 1024) < 0){fprintf(stderr, "read failed[%d].\n", errno);return -1;}printf("received message:%s\n", buf);// 发送数据if(write(new_socket, greeting, strlen(greeting)) < 0){fprintf(stderr, "write failed[%d].\n", errno);return -1;}// 关闭socketclose(new_socket);close(server_fd);printf("server closed.\n");return 0;
}

开启两个终端,运行结果如下:

数据加密方式TCP socket编程

封装加密初始化程序

        我们创建两个文件,一个为myssl.h,一个为对应的实现文件myssl.c,如下:

/* myssl.h */#ifndef __MYSSL_H_
#define __MYSSL_H_#include <openssl/ssl.h>typedef enum{SSL_MODE_SERVER,SSL_MODE_CLIENT
}SSL_MODE;//对象方法枚举SSL *init_ssl(char *cert_path, char *key_path, SSL_MODE mode, int fd);//初始化函数#endif
/* myssl.c */
#include <openssl/ssl.h>
#include <stdio.h>
#include <stdlib.h>
#include "myssl.h"SSL *init_ssl(char * cert_path, char * key_path, SSL_MODE mode, int fd)
{// 声明套件变量const SSL_METHOD *method;SSL_CTX *ctx;SSL *ssl = NULL;// 添加支持的算法,加载错误信息OpenSSL_add_all_algorithms();SSL_load_error_strings();// 获取对象方法// 不同版本的openssl对应的API也是有区别的,需要注意if(mode == SSL_MODE_SERVER){method = SSLv23_server_method();}else if(mode == SSL_MODE_CLIENT){method = SSLv23_client_method();}else{printf("unkown method\n");return NULL;}// 创建CTXctx = SSL_CTX_new(method);if(!ctx){printf("create SSL CTX failed.\n");return NULL;}// 读取证书(公钥)和密钥(私钥)if((SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) <= 0)||(SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0)){printf("not found file\n");return NULL;}// 创建SSLssl = SSL_new(ctx);if(!ssl){printf("failed create SSL.\n");return NULL;}// 绑定套接字的文件描述符到SSL中if(SSL_set_fd(ssl, fd) == 0){printf("SSL set fd failed.\n");return NULL;}// 连接套接字if((mode == SSL_MODE_SERVER && SSL_connect(ssl) <= 0)||(mode == SSL_MODE_CLIENT && SSL_connect(ssl) <= 0)){printf("failed handshake with ssl.\n");return NULL;}return ssl;
}

 修改经典socket编程代码

        我们来对上面的客户端和服务端程序做些修改,以期使得变成加密的套接字通信。

客户端
/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  
#include <sys/types.h>   
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include "myssl.h"int main(int argc, char *argv[])
{// 定义相关变量int sockfd;struct sockaddr_in serv_addr;char buf[1024] = {0};const char *greeting = "Hello from client";if(argc < 2){printf("please input the ip of the server..\n");return -1;}//获取并初始化服务端地址serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8080);if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) <= 0){fprintf(stderr, "inet_pton failed[%d].\n", errno);return -1;}// 创建socketif((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){fprintf(stderr, "create socket failed[%d].\n", errno);return -1;}// 连接到服务器if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){fprintf(stderr, "connect failed[%d].\n", errno);return -1;}// 初始化加密函数SSL *ssl = init_ssl("./cert.pem", "./key.pem", SSL_MODE_CLIENT, sockfd);// 发送数据if(SSL_write(ssl, greeting, strlen(greeting)) < 0)	//if(write(sockfd, greeting, strlen(greeting)) < 0){fprintf(stderr, "write failed[%d].\n", errno);return -1;}// 接收数据if(SSL_read(ssl, buf, 1024) < 0) //if(read(sockfd, buf, 1024) < 0){fprintf(stderr, "read failed[%d].\n", errno);return -1;}printf("received message:%s\n", buf);close(sockfd);SSL_shutdown(ssl);SSL_free(ssl);
}
服务端
/* server.c */#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <sys/types.h>   
#include <sys/socket.h>  
#include <netinet/in.h>
#include <errno.h>
//#include "myssl.h"#define MAX_CNT 5int main(int argc, char *argv[])
{// 定义相关变量int server_fd, new_socket;int opt = 1;struct sockaddr_in serv_addr, cli_addr;socklen_t cli_addr_len;char buf[1024] = {0};const char *greeting = "Hello from server";// 创建socket文件描述符if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){fprintf(stderr, "create socket failed[%d].\n", errno);return -1;}// 设置socket选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {  fprintf(stderr, "set socket failed[%d].\n", errno);return -1;  } // 绑定socket到指定端口和地址memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(server_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){fprintf(stderr, "create bind failed[%d].\n", errno);return -1;}// 监听if(listen(server_fd, MAX_CNT) < 0){fprintf(stderr, "listen failed[%d].\n", errno);return -1;}// 接受客户端连接请求memset(&cli_addr, 0, sizeof(cli_addr));cli_addr_len = sizeof(cli_addr);if((new_socket = accept(server_fd, (struct sockaddr *)&cli_addr, (socklen_t *)&cli_addr_len)) < 0 ){fprintf(stderr, "accept failed[%d].\n", errno);return -1;}		printf("Client [%s-%d] connected.\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));// 初始化加密函数SSL *ssl = init_ssl("./cert.pem", "./key.pem", SSL_MODE_SERVER, new_socket);// 接收数据if(SSL_read(ssl, buf, 1024) < 0) //if(read(new_socket, buf, 1024) < 0){fprintf(stderr, "read failed[%d].\n", errno);return -1;}printf("received message:%s\n", buf);// 发送数据if(SSL_write(ssl, greeting, strlen(greeting)) < 0) //if(write(new_socket, greeting, strlen(greeting)) < 0){fprintf(stderr, "write failed[%d].\n", errno);return -1;}// 关闭socketclose(new_socket);close(server_fd);SSL_shutdown(ssl);SSL_free(ssl);printf("server closed.\n");return 0;
}
Makefile

        为了方便总体编译,我们创建一个Makefile,内容如下:

LIBS=-lssl -lcrypto
all:gcc -o server server.c myssl.c $(LIBS)gcc -o client client.c myssl.c $(LIBS)
clean:rm -rf server client
证书、密钥

        为方便测试,我们利用openssl生成密钥key.pem及公钥文件cert.pem,命令如下:

密钥

$openssl genpkey -algorithm RSA -out key.pem

公钥(利用密钥生成,生成之前需要根据提示输入一些地址公司等信息)

$sudo openssl req -new -x509 -key key.pem -out cert.pem -days 3650

注意openssl的版本程序中用到的和命令行的这个要保持一致,结果如下:

编译问题

        在终端执行make命令,如果出现找不到<openssl/ssl.h>,那么你的系统环境可能没有安装openssl开发库,我们可以通过下面命令安装:

sudo apt-get update  
sudo apt-get install libssl-dev

安装完毕后,<openssl/ssl.h>应该可以在/usr/include/openssl/目录中找到。

如果安装成功之后还是报找不到头文件的错误,通过下面命令检查定位头文件所在位置:

dpkg -L libssl-dev | grep ssl.h

如果你确定环境中有这个头文件而其他方法都不行的话,可以试试手动添加路径指定,如下:

gcc -o myprogram myprogram.c -I/usr/include/openssl -lssl -lcrypto

如果方法都试遍了,我们只能尝试卸掉重新安装。

sudo apt-get remove --purge libssl-dev  
sudo apt-get update  
sudo apt-get install libssl-dev

如果报找不到openssl的某些API 或相关问题,此时则需参考下面的流程对源码做出修改。

补充

        因为openssl每个跨级的版本(0.9/1.0/3.0)API的变动较大(有些遗弃有些修改),兼容性不好,我们编写的应用源代码可能会需要做些修改(如果使用的openssl变动了版本)。如果没有openssl的源码,系统中只有库文件,我们可以通过objdump命令来检索其用的哪些API,方便我们替换修改,举例:

$objdump -Tt libxxx.so | grep SSL*

类似返回如下结果:

参考

B站爱吃甜食的老猫的视频

相关文章:

通信安全之数据加密

数据安全的需求如今越来越重要&#xff0c;本篇简单举例给日常的TCP/UDP通信加密&#xff0c;至少能让想干坏事的崽犯罪的成本更高一些&#xff08;如果会一些BPF的&#xff0c;可能难不住这些崽&#xff09;&#xff0c;能让我们的数据更安全一点。 经典TCP socket编程 下面…...

花一分钟简单认识 CSS 中的规则 —— 级联层 @layer

layer 简介&#xff1a; 声明级联层时&#xff0c;越靠后优先级越高。不属于任何级联层的样式&#xff0c;将自成一层匿名级联层&#xff0c;并置于所有层之后 —— 级别最高。 用法一&#xff1a;在同一文件中 layer base, special; layer special {/* 优先 */li { color: …...

K8s学习三(Pod与探针)

深入学习Pod Pod配置文件 写一个自己的配置文件,nginx-po.yaml apiVersion: v1 #api文档版本 kind: Pod #资源类型对象&#xff0c;也可以配置为像Development&#xff0c;StatefulSet这一类的对象 metadata: # Pod相关的元数据&#xff0c;用于描述Pod的数据name: nginx-po…...

leetcode - 678. Valid Parenthesis String

Description Given a string s containing only three types of characters: ‘(’, ‘)’ and ‘*’, return true if s is valid. The following rules define a valid string: Any left parenthesis ( must have a corresponding right parenthesis ). Any right parenth…...

索尼相机照片清理软件

在使用索尼相机拍摄照片的时候有时我们需要同时拍摄JPG格式和RAW格式&#xff0c;这在后期选图的时候给我们带来一些麻烦。我们固然可以选用Br来管理照片&#xff0c;但是现在我们可以有一个更轻量的软件&#xff08;8.8MB&#xff09;来做到一部分功能。 我们将照片从SD卡导出…...

比赛记录:Codeforces Global Round 25 A~E (猜猜题场)

传送门:CF [前题提要]:其实这场打的不是很好.A题一个sb错误看不出来,50min后才过,B题上来就Wa了一发,C题用了没必要的线段树,D题刚开始被60诈骗,一直在想按位考虑.幸好赛时猜出了E,然后又猜出来D,本来掉大分变成上大分…但是这场前几题大都是猜猜题,所以本来不想写题解的.但是…...

Windows系统安装OpenSSH结合VS Code远程ssh连接Ubuntu【内网穿透】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-AwzyR2lkHKjD9HYl {font-family:"trebuchet ms",verdana,arial,sans-serif;f…...

Svg Flow Editor 原生svg流程图编辑器(五)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;一&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;二&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;三&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;四&#xf…...

数字晶体管选型参数,结构原理,工艺与注意问题总结

🏡《总目录》 目录 1,概述2,工作原理2.1,AND 门(与门),2.2,OR 门(或门):2.3,NOT 门(非门):2.4,NAND 门(与非门):2.5,NOR 门(或非门):3,结构特点3.1,TTL(Transistor-Transistor Logic)晶体管...

lua学习笔记9(字典的学习)

print("********************字典的学习***********************") a{["凌少"]"傻逼",["我"]"天才",["age"]24,["daihao"]114514,["8848"]20000} --访问单个变量 print(a["凌少"])…...

第六篇: 3.5 性能效果 (Performance)- IAB/MRC及《增强现实广告效果测量指南1.0》

​​​​​​​ 翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇 广告效果测量定义和其他矩阵之- 3.1 广告印象&#xff08;AD Impression&#xff09;第三篇 广告效果测量定义和其他矩阵之- 3.2 可见性 &#xff08;Viewability…...

mysql学习笔记NO.2

Java操作数据库、表笔记 1.创建数据库 创建数据库的步骤如下&#xff1a; 导入所需的Java数据库连接驱动&#xff08;如MySQL驱动&#xff09;。使用JDBC连接到数据库。执行SQL语句创建数据库。 import java.sql.Connection; import java.sql.DriverManager; import java.…...

C++11:lambda表达式 包装器

C11&#xff1a;lambda表达式 & 包装器 lambda表达式包装器functionbind lambda表达式 在C98中&#xff0c;如果想对一个结构体数组使用sort排序&#xff0c;那么我们就需要自己些仿函数。 比如以下结构体&#xff1a; struct Goods {string _name; // 名字double _pric…...

Node.js HTTP/2 CONTINUATION 拒绝服务漏洞(CVE-2024-27983)

Node.js 是开源、跨平台的 JavaScript 运行时环境。CONTINUATION泛洪攻击被发现存在于多个HTTP/2协议实现中。 在受影响版本中&#xff0c;由于Node.js针对HTTP/2协议的实现不当&#xff0c;未正确处理多个CONTINUATION帧的情况&#xff0c;在node::http2::Http2Session::~Htt…...

YOLOV8 + 双目测距

YOLOV8 双目测距 1. 环境配置2. 测距流程和原理2.1 测距流程2.2 测距原理 3. 代码部分解析3.1 相机参数stereoconfig.py3.2 测距部分3.3 主代码yolov8-stereo.py 4. 实验结果4.1 测距4.2 测距跟踪4.3 测距跟踪分割4.4 视频展示 相关文章 1. YOLOv5双目测距&#xff08;python&…...

前端:SVG绘制流程图

效果 代码 html代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>SVG流程图示例</title><style>/* CSS 样式 */</style><script src"js/index.js"></script…...

【Linux系列】如何确定当前运行的是 RHEL 9 还是 RHEL 8?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

vscode开发java的插件和配置

推荐插件 .vscode/extensions.json {"recommendations": ["redhat.fabric8-analytics","ms-azuretools.vscode-docker","vscjava.vscode-java-pack","eamodio.gitlens","obkoro1.korofileheader","redhat.j…...

Mysql启动报错:本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止

Mysql启动报错&#xff1a;本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止 文章目录 Mysql启动报错&#xff1a;本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止1. 备份mysql的data文件夹2. 重新构建 Wind…...

WPF程序添加托盘图标

程序添加托盘图标 UI层 //添加handycontrol的引用xmlns:hc"https://handyorg.github.io/handycontrol"//添加NotifyIcon图标 实现单击 双击 二级菜单点击功能<hc:NotifyIconText"通知"Token"Info"><hc:NotifyIcon.ContextMenu><…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...

leetcode_69.x的平方根

题目如下 &#xff1a; 看到题 &#xff0c;我们最原始的想法就是暴力解决: for(long long i 0;i<INT_MAX;i){if(i*ix){return i;}else if((i*i>x)&&((i-1)*(i-1)<x)){return i-1;}}我们直接开始遍历&#xff0c;我们是整数的平方根&#xff0c;所以我们分两…...