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

网络编程UDP—socket实现(C++)

网络编程UDP—socket实现

  • 前言
  • UDP客户端和服务端
  • UDP使用场景
  • UDP socket C++代码示例
    • 服务端接收数据示例(bind+recvfrom 阻塞式接收信息):
      • bind 绑定-监听 函数
        • 为什么一般都是监听所有网络接口呢?
        • 为什么需要用inet_addr进行转换?
      • socket函数
      • sockaddr_in结构体
      • recvfrom 函数
    • 客户端发送数据示例:
      • sendto 发送 函数

前言

  • UDP通信需要哪些必要信息

    • IP地址
      • 用于定位通讯双方
    • 端口号
      • 用于标识通信的具体应用或服务。
      • 传输层通信都需要端口号的。
  • 网络要求

    • 双方必须是可以进行ip通信的
      • UDP依赖IP协议栈(IPv4或IPv6)完成路由、传输
    • 双方需要用同一协议

UDP客户端和服务端

  • 客户端

  • 构造数据报:包含目标IP、目标端口、数据内容。

  • 发送数据报:使用套接字 sendto() 函数将数据发送到目标地址。

  • 等待响应(如果有):接收服务端返回的数据。

  • 服务端

  • 创建监听套接字:绑定到指定IP和端口。

  • 等待数据:通过 recvfrom() 函数接收数据。

  • 处理请求:解析数据内容并执行相应操作。

  • 返回响应:将结果数据发送回客户端。

UDP使用场景

UDP适用于以下需要高效传输但容忍数据丢失的场景:

  • 实时通信:
    • 视频通话、语音通话(如VoIP)。
  • 在线游戏:
    • 游戏中快速同步状态。
  • 流媒体传输:
    • 实时视频、音频传输。
  • 广播/组播:
    • 数据包同时发送给多个主机(如局域网中发现服务)。
  • 轻量级请求/响应:
    • DNS查询、简单的远程控制。

UDP socket C++代码示例

服务端接收数据示例(bind+recvfrom 阻塞式接收信息):

  • 使用场景
    • 简单服务端,适用于单个套接字的接收
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <cstring>int main() {int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP套接字if (sock_fd < 0) {perror("Socket creation failed");return -1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080); // 监听端口server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("Bind failed");close(sock_fd);return -1;}char buffer[1024];struct sockaddr_in client_addr;socklen_t addr_len = sizeof(client_addr);// 会阻塞等待 直到 接收信息int bytes_received = recvfrom(sock_fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &addr_len); // 接收数据if (bytes_received > 0) {buffer[bytes_received] = '\0';std::cout << "Received message: " << buffer << std::endl;}close(sock_fd);return 0;
}

bind 绑定-监听 函数

  • 功能:

    • bind 函数用于将套接字绑定到特定的IP地址和端口号,通常用于服务端监听套接字。
      • 服务器先运行监听特定的IP地址和端口号;然后客户端再
  • 函数声明:

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

参数说明:

  1. sockfd:
    • 套接字描述符,由 socket 函数返回。
  2. addr:
    • 指向 sockaddr 结构体,表示要绑定的地址和端口。
      • 通常使用 sockaddr_in,需强制转换为 sockaddr。
  3. addrlen:
    • addr 的长度(使用 sizeof(sockaddr_in))。

返回值:
成功:返回 0。
失败:返回 -1,并设置 errno。

示例:

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);        // 绑定端口号
server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用IP地址if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("Bind failed");close(sock_fd);
}
为什么一般都是监听所有网络接口呢?
server_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用IP地址

这行代码中的 INADDR_ANY 是一个常用的常量,它代表了一个特殊的 IP 地址,即 0.0.0.0。当你将它设置为服务器套接字的地址时,表示该服务器将 监听所有网络接口。

  • 理解什么是网络接口?

    • 网络接口指计算机或设备上 每一个可以用于发送或接收数据的网络连接通道。
      • 可以先理解为网卡,但是网络接口还包括一些虚拟网卡、本地回环接口(127.0.0.1)、甚至 VPN接口等,总的就是软硬 网络通道。
  • 服务器为什么一般监听所有网络接口?

    • 因为理论上我们希望只要是服务器这个端口号接收的,不管是哪一个网络接口,都交给服务器应用程序处理;
    • 除非我们就只想让服务器处理从某个网络接口 接收的数据,才设置某一个网络接口的ip地址。例如:
      • 只想让服务器接受本地(同一台机器上的应用程序)发出的数据
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
为什么需要用inet_addr进行转换?

因为实际是采用网络字节序进行传输的,而用字符串形式十进制格式(如 “192.168.1.1”)只是为了人类方便阅读,所以需要转为网络字节序。

  • inet_addr作用

    • 用于将一个点分十进制表示的 IPv4 地址(例如 “192.168.1.1”)转换为网络字节序的二进制格式。
  • inet_addr 将一个 IPv4 地址的点分十进制字符串(如 “192.168.1.1”)转换为网络字节序的 32 位整数。例如:

    • “192.168.1.1” 在十进制中是:192 * 256^3 + 168 * 256^2 + 1 * 256^1 + 1 * 256^0
    • 对应的二进制表示是:11000000 10101000 00000001 00000001
  • 值得注意的是,inet_addr 已经不推荐使用,特别是在现代网络编程中,因为它对无效地址的处理可能不够清晰(比如返回 -1 会被误认为是有效的地址)。推荐使用 inet_pton 函数来替代,它更加健壮和安全。inet_pton 允许支持不同的地址族(IPv4 和 IPv6),并且不会出现类似 inet_addr 那样的错误返回值。

socket函数

  • 作用

    • 用于创建套接字socket,套接字是网络通信的基础,用于在客户端和服务端之间建立通信。
    • 确定协议族(IPv4还是)、TCP还是UDP
  • 函数声明

    int socket(int domain, int type, int protocol);
    

参数说明:

  1. domain:指定通信的协议族(地址类型)。
  • AF_INET:IPv4。
  • AF_INET6:IPv6。
  • AF_UNIX:本地通信(不使用网络)。
  1. type:指定套接字的类型。
  • SOCK_STREAM:TCP(面向连接,保证数据可靠性)。
  • SOCK_DGRAM:UDP(无连接,适合快速传输)。
  1. protocol:通常指定为 0,表示使用默认协议。
  • 如果 type 是 SOCK_DGRAM,默认使用 UDP 协议。
  • 如果 type 是 SOCK_STREAM,默认使用 TCP 协议。

返回值:
成功:返回套接字描述符(非负整数)。
失败:返回 -1,并设置 errno。

示例:

int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {perror("Socket creation failed");
}

sockaddr_in结构体

  • 功能

    • 用于表示 IPv4 地址和端口信息,通常在网络通信中用于绑定或指定目标地址。
  • 定义:

struct sockaddr_in {short sin_family;        // 地址族(必须为 AF_INET)unsigned short sin_port; // 端口号(网络字节序,需要使用 htons() 转换)struct in_addr sin_addr; // IPv4 地址char sin_zero[8];        // 填充字节,保持与 struct sockaddr 的大小一致(不使用,置 0)
};

字段说明:

  1. sin_family:
    • 必须设置为 AF_INET(IPv4协议)。
  2. sin_port:
    • 16位端口号,必须用 htons() 将主机字节序转换为网络字节序。
  3. sin_addr:
    • 一个 struct in_addr 结构体,表示IPv4地址。
      • 可以用 inet_addr() 或 inet_aton() 转换字符串形式的IP地址。
      • 也可以设置为 INADDR_ANY,表示绑定到本地所有可用IP。
  4. sin_zero:
    • 填充字段,不使用,应设置为 0。

示例:

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;                // IPv4
server_addr.sin_port = htons(8080);              // 端口号(转换为网络字节序)
server_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 目标IP地址
memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero)); // 填充为0

recvfrom 函数

  • 作用:一个用于从套接字接收数据的函数。
  • 函数声明:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  1. sockfd:要读取数据的套接字文件描述符。
  2. buf:指向接收数据的缓冲区。
  3. len:缓冲区的大小。如果接收的数据超过该大小,数据会被截断。
  4. flags:设置标志,通常为 0。
    控制接收行为的标志。常用的标志包括:
    • MSG_PEEK:查看数据但不从队列中移除数据。
    • MSG_WAITALL:接收指定大小的完整数据,直到所有数据都接收到才返回。
    • MSG_DONTWAIT:非阻塞操作,如果没有数据可接收则立即返回。
    • MSG_TRUNC:如果接收的消息太大,超过缓冲区的大小,将丢弃多余部分并返回 EMSGSIZE 错误。
  5. src_addr:接收数据源的地址,通常可以为 NULL,如果不需要知道源地址。
    • 创建一个指针,用来接收数据源的地址
  6. addrlen:地址长度,如果 src_addr 不是NULL,它将被修改为实际的地址长度。

函数返回值:

  • 成功时:返回接收到的字节数。如果没有数据到达,且没有设置非阻塞标志,则会阻塞直到有数据可读;如果设置了 MSG_DONTWAIT 或套接字为非阻塞模式,它将立即返回 0 表示没有数据。
  • 失败时:返回 -1,并设置 errno 以指示错误。常见错误包括:
    • EAGAIN 或 EWOULDBLOCK:非阻塞模式下没有数据可接收。
    • EBADF:sockfd 不是有效的套接字。
    • ECONNREFUSED:目标主机拒绝连接(仅在某些类型的套接字中出现)。
    • EINVAL:无效的地址长度或参数。

客户端发送数据示例:

#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <cstring>int main() {int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP套接字if (sock_fd < 0) {perror("Socket creation failed");return -1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080); // 目标端口号server_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 目标IP地址const char* message = "Hello, UDP!";sendto(sock_fd, message, strlen(message), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 发送数据close(sock_fd);return 0;
}

sendto 发送 函数

  • 功能:

    • 用于通过UDP套接字发送数据报到指定地址和端口
  • 函数声明

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

参数说明:

  1. sockfd:
    • 套接字描述符,由 socket 函数返回。
  2. buf:
    • 指向要发送的数据的缓冲区。
  3. len:
    • 要发送的数据长度(字节数)。
  4. flags:
    • 传输标志,通常设置为 0。
  5. dest_addr:
    • 指向一个 sockaddr 结构体,表示目标地址。
      • 通常传入 sockaddr_in,需要通过强制类型转换为 sockaddr。
  6. addrlen:
    • dest_addr 的长度(使用 sizeof(sockaddr_in))。
      返回值:
      成功:返回实际发送的字节数。
      失败:返回 -1,并设置 errno。

示例:

const char *message = "Hello, UDP!";
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("192.168.1.1");sendto(sock_fd, message, strlen(message), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

相关文章:

网络编程UDP—socket实现(C++)

网络编程UDP—socket实现 前言UDP客户端和服务端UDP使用场景UDP socket C代码示例服务端接收数据示例&#xff08;bindrecvfrom 阻塞式接收信息&#xff09;&#xff1a;bind 绑定-监听 函数为什么一般都是监听所有网络接口呢&#xff1f;为什么需要用inet_addr进行转换&#x…...

系统思考—冰山模型

“卓越不是因机遇而生&#xff0c;而是智慧的选择与用心的承诺。”—— 亚里士多德 卓越&#xff0c;从来不是一次性行为&#xff0c;而是一种习惯。正如我们在日常辅导中常提醒自己&#xff1a;行为的背后&#xff0c;隐藏着选择的逻辑&#xff0c;而选择的根源&#xff0c;源…...

MySQL 中存储金额数据一般使用什么数据类型

在 MySQL 中存储金额数据时&#xff0c;应该谨慎选择数据类型&#xff0c;以确保数据的精度和安全性。以下是几种常用的数据类型及其适用性&#xff1a; DECIMAL 类型&#xff1a; 描述&#xff1a;DECIMAL 类型是专门为存储精确的小数而设计的。它可以指定小数点前后的数字位数…...

Excel中一次查询返回多列

使用Excel或wps的时候&#xff0c;有时候需要一次查询返回多列内容&#xff0c;这种情况可以选择多次vlookup或者多次xlookup&#xff0c;但是这种做法费时费力不说&#xff0c;效率还有些低下&#xff0c;特别是要查询的列数过多时。我放了3种查询方法&#xff0c;效果图&…...

Java中各种数组复制方式的效率对比

在 Java 中&#xff0c;数组复制是一个常见的操作&#xff0c;尤其是在处理动态数组&#xff08;如 ArrayList&#xff09;时。Java 提供了多种数组复制的方式&#xff0c;每种方式在性能和使用场景上都有所不同。以下是对几种主要数组复制方式的比较&#xff0c;包括 System.a…...

STM32 FLASHdb

FlashDB是一款超轻量级的嵌入式数据库&#xff0c;专注于为嵌入式产品提供数据存储方案。以下是对STM32 FlashDB的详细介绍&#xff1a; 一、主要特性 资源占用极低&#xff1a;FlashDB的内存占用几乎为0&#xff0c;非常适合资源有限的嵌入式系统。支持多分区、多实例&#…...

【漏洞复现】Struts2(CVE-2024-53677)任意文件上传逻辑绕过漏洞

文章目录 前言一、漏洞描述二、漏洞详情三、影响版本四、危害描述五、漏洞分析六、漏洞复现七、修复建议前言 Struts2框架是一个用于开发Java EE网络应用程序的开放源代码网页应用程序架构。它利用并延伸了Java Servlet API,鼓励开发者采用MVC架构。Struts2以WebWork优秀的设…...

图的最短路径(C++实现图【4】)

目录 1. 最短路径 1.1单源最短路径--Dijkstra算法 代码实现 1.2 单源最短路径--Bellman-Ford算法 代码实现 1.3 多源最短路径--Floyd-Warshall算法 代码实现 1. 最短路径 最短路径问题&#xff1a;从在带权有向图G中的某一顶点出发&#xff0c;找出一条通往另一顶点的最短路径&…...

Pandas01

文章目录 内容简介1 常用数据分析三方库2 Jupyter notebook3 Series的创建3.1 通过Numpy的Ndarray 创建一个Series3.2 通过列表创建Series 4 Series的属性和方法4.1 常用属性4.2 常用方法4.3 布尔值列表筛选部分数据4.4 Series 的运算 5 DataFrame的创建通过字典创建通过列表[元…...

opencl 封装简单api

这是cl代码 kernel.c __kernel void add_one(__global float *output,__global float* pnum) {int xget_global_id(0);output[x]pnum[0]; } c代码 #include <CL/cl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include<st…...

超快速的路径优化IKD-SWOpt:SHIFT Planner 中增量 KD 树滑动窗口优化算法详解

IKD-SWOpt&#xff1a;SHIFT Planner 中增量 KD 树滑动窗口优化算法详解 今天本博主王婆卖瓜自卖自夸&#x1f604;&#xff0c;介绍自己paper中的算法&#xff0c;本算法已经持续开源中(部分关键内容)Github&#xff0c;之前很多读者朋友一直说要详细讲讲路径优化算法&#x…...

精读DeepSeek v3技术文档的心得感悟

最近宋大宝同学读完了DeepSeekv3的文档&#xff0c;心中颇多感慨&#xff0c;忍不住想在这里记录一下对这款“业界有望启示未来低精度训练走向”的开源大模型的观察与思考。DeepSeek v3的亮点绝不仅仅是“Float8”或“超长上下文”这么简单&#xff0c;而是贯穿了从数值精度、注…...

【Java数据结构】LinkedList与链表

认识LinkedList LinkedList就是一个链表&#xff0c;它也是实现List接口的一个类。LinkedList就是通过next引用将所有的结点链接起来&#xff0c;所以不需要数组。LinkedList也是以泛型的方法实现的&#xff0c;所以使用这个类都需要实例化对象。 链表分为很多种&#xff0c;比…...

uniapp——微信小程序,从客户端会话选择文件

微信小程序选择文件 文章目录 微信小程序选择文件效果图选择文件返回数据格式 API文档&#xff1a; chooseMessageFile 微信小程序读取文件&#xff0c;请查看 效果图 选择文件 /*** description 从客户端会话选择文件* returns {String} 文件路径*/ const chooseFile () &g…...

【CSS in Depth 2 精译_098】17.3:CSS 动画延迟技术与填充模式设置 + 17.4:通过 CSS 动画传递意图的秘诀

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第五部分 添加动效 ✔️【第 17 章 动画】 ✔️ 17.1 关键帧17.2 3D 变换下的动画设置 17.2.1 添加动画前页面布局的构建17.2.2 为布局添加动画 17.3 动画延迟与填充模式 ✔️17.4 通过动画传递意图…...

Oracle考试多少分算通过?

OCP和OCM认证的考试及格分数并不是固定的&#xff0c;而是根据考试的难度和考生的整体表现来确定。对于OCP认证&#xff0c;考生需要全面掌握考试要求的知识和技能&#xff0c;并在考试中表现出色才有可能通过。而对于OCM认证&#xff0c;考生则需要在每个模块中都达到一定的水…...

在云服务器中编译IDF(ESP32库)

登录云服务器 使用gitee从github上导入仓库 地址GitHub - espressif/esp-idf: Espressif IoT Development Framework. Official development framework for Espressif SoCs. 然后在云服务器中创建目录~/esp 进入路径后使用git clone 下载项目 进入编程指南ESP-IDF 编程指南…...

Oracle 日常巡检

1. 检查服务器状态 1.1. CPU使用情况 1.1.1. top top 命令是 Linux 和 Unix 系统中用于显示实时系统状态的工具&#xff0c;特别是对于监控 CPU 和内存的使用非常有用。 在命令行中输入 top&#xff0c;top 会显示一个实时更新的界面&#xff0c;其中包含系统的关键指标&am…...

机器学习常用术语

目录 概要 机器学习常用术语 1、模型 2、数据集 3、样本与特征 4、向量 5、矩阵 6、假设函数与损失函数 7、拟合、过拟合与欠拟合 8、激活函数(Activation Function) 9、反向传播(Backpropagation) 10、基线(Baseline) 11、批量(Batch) 12、批量大小(Batch Size)…...

springboot507基于Springboot教学管理系统(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装教学管理系统软件来发挥其高效地信息处理的作用&#xff0c…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...