Android netty的使用
导入netty依赖
implementation 'io.netty:netty-all:4.1.107.Final'
使用netty
关闭netty
/*** 关闭*/private void closeSocket() {LogUtils.i(TAG, "closeSocket");if (nettyManager != null) {nettyManager.close();nettyManager = null;}if (nettyExecutor != null) {try {nettyExecutor.shutdown();nettyExecutor.shutdownNow();} catch (Exception e) {}}}
创建netty
private void initSocket() {closeSocket(); //关闭之前的连接nettyManager = new NettyManager(ip, port, dataTimeOut, readTimeOut, whiteTimeOut);nettyExecutor = Executors.newSingleThreadExecutor();nettyExecutor.execute(new Runnable() {@Overridepublic void run() {nettyManager.connect(); //连接}});nettyManager.setOnNettyListener(new NettyManager.OnNettyListener() {@Overridepublic void onConnectSuccess() {LogUtils.i(TAG, "onConnectSuccess");}@Overridepublic void onConnectFail(int connectFailNum) {LogUtils.i(TAG, "onConnectFail >>" + connectFailNum);if (connectFailNum >= 5) { //重连次数达到阈值 则重设nett的ipresetNeetyIpAndConnect();}}@Overridepublic void onReceiveData(byte[] bytes) {LogUtils.i(TAG, "onReceiveData");parseReceiveData(bytes);}@Overridepublic void onNeedReCreate() {initSocket();}});}/*** 重新设置ip地址 断开重设ip不需要调用 nettyManager.reConnect(); 因为会自动重连*/private void resetNeetyIpAndConnect() {ip = (String) MmkvUtils.getInstance().decode(Content.SP_KEY_SOCKET_TALK_IP, "");port = (int) MmkvUtils.getInstance().decode(Content.SP_KEY_SOCKET_TALK_PORT, 0);LogUtils.i(TAG, "resetNeetyIpAndConnect>>ip:" + ip + " port:" + port);nettyManager.setIp(ip);nettyManager.setPort(port);}/**
连接中时 重设ip 需要调用nettyManager.reConnect();
*/
private void resetIp(){String ip = (String) MmkvUtils.getInstance().decode(Content.SP_KEY_SOCKET_TALK_IP, "");int port = (int) MmkvUtils.getInstance().decode(Content.SP_KEY_SOCKET_TALK_PORT, 0);nettyManager.setIp(ip);nettyManager.setPort(port);nettyManager.reConnect(); //重连ip}
}
有时候不需要netty进行数据粘包处理的情况,直接返回原始响应数据则使用 具体参数看完整代码
nettyManager = new NettyManager(ip, port, dataTimeOut, readTimeOut, whiteTimeOut, 0, 0, 0, 0);
完整代码
package com.baolan.netty;import android.util.Log;import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.internal.StringUtil;public class NettyManager {private static final String TAG = "bl_netty";private ChannelFuture channelFuture;private String ip;private int port;private int dataTimeOut; //收发超时private int readTimeOut; // 读超时时间private int whiteTimeOut; // 写超时时间private ChannelFutureListener channelFutureListener;private NioEventLoopGroup nioEventLoopGroup;private Bootstrap bootstrap;private int connectFailNum = 0; //重连次数private int lengthFieldOffset = 3; //长度域偏移。就是说数据开始的几个字节可能不是表示数据长度,需要后移几个字节才是长度域private int lengthFieldLength = 2; //长度域字节数。用几个字节来表示数据长度。private int lengthAdjustment = 1; //数据长度修正。因为长度域指定的长度可以使header+body的整个长度,也可以只是body的长度。如果表示header+body的整个长度,那么我们需要修正数据长度。private int initialBytesToStrip = 0; //跳过的字节数。如果你需要接收header+body的所有数据,此值就是0,如果你只想接收body数据,那么需要跳过header所占用的字节数。public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}public NettyManager(String ip, int port, int dataTimeOut, int readTimeOut, int whiteTimeOut) {this(ip, port, dataTimeOut, readTimeOut, whiteTimeOut, 3, 2, 1, 0);}/*** @param ip 连接的地址* @param port 连接的端口* @param dataTimeOut 收发超时* @param readTimeOut 读超时时间* @param whiteTimeOut 写超时时间*/public NettyManager(String ip, int port, int dataTimeOut, int readTimeOut, int whiteTimeOut, int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip) {this.ip = ip;this.port = port;this.dataTimeOut = dataTimeOut;this.readTimeOut = readTimeOut;this.whiteTimeOut = whiteTimeOut;this.lengthFieldOffset = lengthFieldOffset;this.lengthFieldLength = lengthFieldLength;this.lengthAdjustment = lengthAdjustment;this.initialBytesToStrip = initialBytesToStrip;Log.i(TAG, "create ip>>" + ip);Log.i(TAG, "create port>>" + port);Log.i(TAG, "create dataTimeOut>>" + dataTimeOut);Log.i(TAG, "create readTimeOut>>" + readTimeOut);Log.i(TAG, "create whiteTimeOut>>" + whiteTimeOut);//进行初始化//初始化线程组nioEventLoopGroup = new NioEventLoopGroup();bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class).group(nioEventLoopGroup);bootstrap.option(ChannelOption.TCP_NODELAY, true); //无阻塞bootstrap.option(ChannelOption.SO_KEEPALIVE, true); //长连接bootstrap.option(ChannelOption.SO_TIMEOUT, dataTimeOut); //收发超时bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); //收发超时bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();if (lengthFieldOffset > 0) { //为0时 不处理应答数据pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65530, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip));}pipeline.addLast("decoder", new ByteArrayDecoder()) //接收解码方式.addLast("encoder", new ByteArrayEncoder()) //发送编码方式.addLast(new ChannelHandle(NettyManager.this))//处理数据接收.addLast(new IdleStateHandler(readTimeOut, whiteTimeOut, 0)); //心跳 参数1:读超时时间 参数2:写超时时间 参数3: 将在未执行读取或写入时触发超时回调,0代表不处理;读超时尽量设置大于写超时代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接}});}private void create() {if (StringUtil.isNullOrEmpty(ip) || port == 0 || dataTimeOut == 0 || readTimeOut == 0 || whiteTimeOut == 0) {//TODO 设置回调通知service 重连if (onNettyListener != null) {onNettyListener.onNeedReCreate();}return;}if (channelFuture != null && channelFuture.channel().isActive()) {return;}//开始建立连接并监听返回try {channelFuture = bootstrap.connect(new InetSocketAddress(ip, port));channelFuture.addListener(channelFutureListener = new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) {boolean success = future.isSuccess();Log.i(TAG, "connect success>>" + success);if (success) {connectFailNum = 0;Log.i(TAG, "connect success !");if (onNettyListener != null) {onNettyListener.onConnectSuccess();}} else { //失败connectFailNum++;future.channel().eventLoop().schedule(new Runnable() {@Overridepublic void run() {reConnect();}}, 5, TimeUnit.SECONDS);if (onNettyListener != null) {onNettyListener.onConnectFail(connectFailNum);}}}}).sync();} catch (Exception e) {Log.i(TAG, "e>>" + e);e.printStackTrace();}}/*** 发送数据** @param sendBytes*/public void sendData(byte[] sendBytes) {if (channelFuture == null) {return;}Log.i(TAG, "sendDataToServer");if (sendBytes != null && sendBytes.length > 0) {if (channelFuture != null && channelFuture.channel().isActive()) {Log.i(TAG, "writeAndFlush");channelFuture.channel().writeAndFlush(sendBytes);}}}public void receiveData(byte[] byteBuf) {Log.i(TAG, "receiveData>>" + byteBuf.toString());if (onNettyListener != null && byteBuf.length > 0) {onNettyListener.onReceiveData(byteBuf);}}public void connect() {Log.i(TAG, "connect ");if (channelFuture != null && channelFuture.channel() != null && channelFuture.channel().isActive()) {channelFuture.channel().close();//已经连接时先关闭当前连接,关闭时回调exceptionCaught进行重新连接} else {create(); //当前未连接,直接连接即可}}public void reConnect() {Log.i(TAG, "reConnect");if (StringUtil.isNullOrEmpty(ip) || port == 0 || dataTimeOut == 0 || readTimeOut == 0 || whiteTimeOut == 0) {//TODO 设置回调通知service 重连if (onNettyListener != null) {onNettyListener.onNeedReCreate();}return;}connect(); //当前未连接,直接连接即可}public void close() {if (channelFuture != null && channelFutureListener != null) {channelFuture.removeListener(channelFutureListener);channelFuture.cancel(true);}}private OnNettyListener onNettyListener;public void setOnNettyListener(OnNettyListener onNettyListener) {this.onNettyListener = onNettyListener;}public interface OnNettyListener {/*** 连接成功*/void onConnectSuccess();/*** 连接失败*/void onConnectFail(int connectFailNum);/*** 接收到数据** @param bytes*/void onReceiveData(byte[] bytes);/*** 参数丢失 需重新创建*/void onNeedReCreate();}
}
package com.baolan.netty;import android.util.Log;import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;public class ChannelHandle extends SimpleChannelInboundHandler<ByteBuf> {private static final String TAG = "bl_netty";private NettyManager nettyManager;public ChannelHandle(NettyManager nettyManager) {this.nettyManager = nettyManager;}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);//成功Log.i(TAG,"channelActive");}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {super.channelInactive(ctx);//连接失败Log.i(TAG,"channelInactive 连接失败");if (nettyManager != null) {nettyManager.reConnect(); //重新连接}}/*** 心跳检测 当设置时间没有收到事件 会调用* @param ctx* @param evt* @throws Exception*/@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {super.userEventTriggered(ctx, evt);if (evt instanceof IdleStateEvent) {IdleStateEvent idleStateEvent = (IdleStateEvent) evt;if (idleStateEvent.state().equals(IdleState.WRITER_IDLE)) { //写超时,此时可以发送心跳数据给服务器Log.i(TAG, "userEventTriggered write idle");if (nettyManager == null){return;}}else if (idleStateEvent.state().equals(IdleState.READER_IDLE)){ //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连Log.i(TAG, "userEventTriggered read idle");ctx.channel().close();}}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {super.exceptionCaught(ctx, cause);Log.i(TAG, "exceptionCaught");cause.printStackTrace();ctx.close();if (nettyManager != null) {nettyManager.reConnect(); //重新连接}}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {Log.i(TAG, "channelRead0");if (nettyManager == null){return;}}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {super.channelRead(ctx, msg);Log.i(TAG, "channelRead >>"+msg.getClass());if (nettyManager == null){return;}if(msg instanceof byte[]){nettyManager.receiveData((byte[]) msg); //收到数据处理}}
}
相关文章:
Android netty的使用
导入netty依赖 implementation io.netty:netty-all:4.1.107.Final使用netty 关闭netty /*** 关闭*/private void closeSocket() {LogUtils.i(TAG, "closeSocket");if (nettyManager ! null) {nettyManager.close();nettyManager null;}if (nettyExecutor ! null) {…...
苹果电脑启动磁盘是什么意思 苹果电脑磁盘清理软件 mac找不到启动磁盘 启动磁盘没有足够的空间来进行分区
当你一早打开苹果电脑,结果系统突然提示: “启动磁盘已满,需要删除部分文件”。你会怎么办?如果你认为单纯靠清理废纸篓或者删除大型文件就能释放你的启动磁盘上的空间,那就大错特错了。其实苹果启动磁盘的清理技巧有很…...
【Java SE】多态
🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. 多态1.1 多态是什么1.2 多态的意义1.3 多态的实现条件 2. 重写2.1 重写的概念2.2 重写的规则2.3 重写与重…...
Yarn vs npm的大同小异Yarn是什么?
Yarn vs npm的大同小异&Yarn是什么? 一、Yarn、npm是什么?二、Yarn vs npm:特性差异总结 一、Yarn、npm是什么? npm是Node.js的包管理器,是由Chris Korda维护。 npm,它全称为Node Package Manager,是…...
1.Godot引擎|场景|节点|GDS|介绍
Godot介绍 Godot是一款游戏引擎 可以通过在steam商城免费下载 初学者和编程基础稍差的推荐学习使用GDScript,和python有些相似 Godot节点 Godot的开发思想——围绕节点 节点的特征与优势 最常用基本的开发组件大部分都具有具体的功能,如图片…...
springboot3 redis 实现分布式锁
分布式锁介绍 分布式锁是一种在分布式系统中用于控制不同节点上的进程或线程对共享资源进行互斥访问的技术机制。 在分布式环境中,多个服务可能同时访问和操作共享资源,如数据库、文件系统等。为了保持数据的一致性和完整性,需要确保在同一…...
2024年第十四届MathorCup数学应用挑战赛A题思路分享(妈妈杯)
A题 移动通信网络中PCI规划问题 物理小区识别码(PCI)规划是移动通信网络中下行链路层上,对各覆盖小区编号进行合理配置,以避免PCI冲突、PCI混淆以及PCI模3干扰等现象。PCI规划对于减少物理层的小区间互相干扰(ICI),增加物理下行控制信道(PDCCH)的吞吐量有着重要的作用,尤其…...
运动听歌哪款耳机靠谱?精选五款热门开放式耳机
随着人们对运动健康的重视,越来越多的运动爱好者开始关注如何在运动中享受音乐。开放式蓝牙耳机凭借其独特的设计,成为了户外运动的理想选择。它不仅让你在运动时能够清晰听到周围环境的声音,保持警觉,还能让你在需要时与他人轻松…...
Kubernetes学习笔记12
k8s核心概念:控制器: 我们删除Pod是可以直接删除的,如果生产环境中的误操作,Pod同样也会被轻易地被删除掉。 所以,在K8s中引入另外一个概念:Controller(控制器)的概念,…...
Qt Designer 控件箱中的控件介绍及布局比列分配
控件箱介绍 Qt Designer的控件箱(Widget Box)包含了各种常用的控件,用户可以通过拖放的方式将这些控件添加到窗体设计器中,用于构建用户界面。以下是一些常见控件箱中的控件及其功能的讲解: 1.基本控件&#…...
蓝桥集训之三国游戏
蓝桥集训之三国游戏 核心思想:贪心 将每个事件的贡献值求出 降序排序从大到小求和为正是即可 #include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N 100010;int a[N],b[N],c[N];…...
MySQL知识整理
MySQL知识整理 基础第一讲:基础架构:一条SQL查询语句是如何执行的?架构尽量减少长连接的原因和方案为什么尽量不要依赖查询缓存 索引第四讲:深入浅出索引(上)第五讲:深入浅出索引(下…...
代码随想录算法训练营第36天| 435. 无重叠区间、 763.划分字母区间*、56. 合并区间
435. 无重叠区间 力扣题目链接 代码 示例代码 class Solution { public:// 按照区间右边界排序static bool cmp (const vector<int>& a, const vector<int>& b) {return a[1] < b[1];}int eraseOverlapIntervals(vector<vector<int>>&a…...
SpringBoot整合Nacos
文章目录 nacosnacos下载nacos启动nacos相关配置demo-dev.yamldemo-test.yamluser.yaml 代码pom.xmlUserConfigBeanAutoRefreshConfigExampleValueAnnotationExampleDemoApplicationbootstrap.yml测试结果补充.刷新静态配置 nacos nacos下载 下载地址 一键傻瓜试安装即可,官…...
vue3 浅学
一、toRefs 问题: reactive 对象取出的所有属性值都是⾮响应式的 解决: 利⽤ toRefs 可以将⼀个响应式 reactive 对象的所有原始属性转换为 响应式的 ref 属性 二、hook函数 将可复⽤的功能代码进⾏封装,类似与vue2混⼊。 三、ref:获取元素或者组件 let …...
三小时使用鸿蒙OS模仿羊了个羊,附源码
学习鸿蒙arkTS语言,决定直接通过实践的方式上手,而不是一点点进行观看视频再来实现。 结合羊了个羊的开发思路,准备好相应的卡片素材后进行开发。遇到了需要arkTS进行解决的问题,再去查看相应的文档。 首先需要准备卡片对应的图片…...
如何使用 ArcGIS Pro 制作热力图
热力图是一种用颜色表示数据密度的地图,通常用来显示空间分布数据的热度或密度,我们可以通过 ArcGIS Pro 来制作热力图,这里为大家介绍一下制作的方法,希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的POI数…...
SpringBoot之集成Redis
SpringBoot之集成Redis 一、Redis集成简介二、集成步骤2.1 添加依赖2.2 添加配置2.3 项目中使用 三、工具类封装四、序列化 (正常都需要自定义序列化)五、分布式锁(一)RedisTemplate 去实现场景一:单体应用场景二&…...
mybatis-plus与mybatis同时使用别名问题
在整合mybatis和mybatis-plus的时候发现一个小坑,单独使用mybatis,配置别名如下: #配置映射文件中指定的实体类的别名 mybatis.type-aliases-packagecom.jk.entity XML映射文件如下: <update id"update" paramete…...
MySQL基础知识——MySQL日志
一条查询语句的执行过程一般是经过连接器、 分析器、 优化器、 执行器等功能模块, 最后到达存储引擎。 那么, 一条更新语句的执行流程又是怎样的呢? 下面我们从一个表的一条更新语句进行具体介绍: 假设这个表有一个主键ID和一个…...
uniapp 地图分幅网格生成 小程序基于map组件
// 获取小数部分 const fractional function(x) {x Math.abs(x);return x - Math.floor(x); } const formatInt function(x, len) {let result x;len len - result.length;while (len > 0) {result 0 result;len--;}return result; }/*** 创建标准分幅网格* param …...
python项目练习——22、人脸识别软件
功能分析: 人脸检测: 识别图像或视频中的人脸,并标记出人脸的位置和边界框。 人脸识别: 识别人脸的身份或特征,通常使用已知的人脸数据库进行训练,然后在新的图像或视频中识别出人脸并匹配到相应的身份。 表情识别: 识别人脸的表情,如高兴、悲伤、愤怒等,并给出相应…...
Linux中账号登陆报错access denied
“Access denied” 是一个权限拒绝的错误提示,意味着用户无法获得所请求资源的访问权限。出现 “Access denied” 错误的原因可以有多种可能性,包括以下几种常见原因: 错误的用户名或密码:输入的用户名或密码不正确,导…...
python语言之round(num, n)小数四舍五入
文章目录 python round(num, n)小数四舍五入python round(num, n)基础银行家舍入(Bankers Rounding)利息被银行四舍五入后,你到底是赚了还是亏了? python小数位的使用decimal模块四舍五入(解决round 遇5不进) python round(num, n…...
安全风险攻击面管理如何提升企业网络弹性?
从研究人员近些年的调查结果来看,威胁攻击者目前非常善于识别和利用最具有成本效益的网络入侵方法,这就凸显出了企业实施资产识别并了解其资产与整个资产相关的安全态势的迫切需要。 目前来看,为了在如此复杂的网络环境中受到最小程度上的网络…...
常用的几款性能测试软件
Apache JMeter是一款免费、开源的性能测试工具,广泛应用于Web应用程序和服务的性能测试。它支持模拟多种不同类型的负载,可以测试应用程序在不同压力下的性能表现,并提供丰富的图表和报告来分析测试结果。 优点: 免费且开源&…...
谷歌google浏览器无法更新Chrome至最新版本怎么办?浏览器Chrome无法更新至最新版本
打开谷歌google浏览器提示:无法更新Chrome,Chrome无法更新至最新版本,因此您未能获得最新的功能和安全修复程序。点击「重新安装Chrome」后无法访问此网站,造成谷歌浏览器每天提示却无法更新Chrome至最新版本。 谷歌google浏览器无…...
认识异常(1)
❤️❤️前言~🥳🎉🎉🎉 hellohello~,大家好💕💕,这里是E绵绵呀✋✋ ,如果觉得这篇文章还不错的话还请点赞❤️❤️收藏💞 💞 关注💥&a…...
C++矩阵
C矩阵【基本】(will循环) #include<iostream> #include<string.h> using namespace std; int main() {int a[100][100]{0};int k 1;int i 0;int j 0;while(k<100){if(j>10){j0;i;}a[i][j]k;j;k;}i 0;j 0;while(true){if(i 9&am…...
解锁智能未来:用Ollama开启你的本地AI之旅
Ollama是一个用于在本地运行大型语言模型(LLM)的开源框架。它旨在简化在Docker容器中部署LLM的过程,使得管理和运行这些模型变得更加容易。Ollama提供了类似OpenAI的API接口和聊天界面,可以非常方便地部署最新版本的GPT模型并通过…...
苏州工业园区两学一做教育网站/百度推广入口官网
题目:随便输入一个数 n 作为猴子总数,当数到7的猴子就会被淘汰。 个人感觉写起来有点复杂,但还算比较好理解。。。 首先要明确这样一个道理,从1数到7,每数一轮就会淘汰一只猴子,所以要选出猴王一共要数n-…...
网站制作多少钱400/企业培训体系搭建
众包测试 什么是众包测试? 众包测试使用的是众多专家测试人员进行手动测试。测试人员旨在发现错误、记录可重现的步骤并提供错误报告。 众包测试有什么好处? 与内部 QA 相比,众包测试是一种具有成本效益的选择。 由于可用的测试人员数量众…...
营销型高端网站建设价格/百度竞价冷门产品
欢迎关注”生信修炼手册”!对于任意的表达量数据,定量加差异分析都是一套经典的组合拳。当我们想要展示特定基因的组间差异结果时,下面这种图表就派上了用场横坐标为基因,纵坐标是基因表达量,每一组的表达量采用了箱体图的形式来展…...
网站制作过程/北京如何优化搜索引擎
现在支持GLSL和OpenGL跟步调试的只有Nvidia的Nsight,只支持Nvidia的显卡;其他的基本都是track,不支持GLSL的跟步调试,比如AMD的GPUPerfClient以及gDEBugger。还有AMD的GPU ShaderAnalyzer也非常的不错,能看到相应的GLS…...
汕头免费建设网站制作/百度网盘app怎么打开链接
👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇转自 | 募格学术参考资料 | 抖音陈见夏夏(求关注版、秒闻视频、募格学术读者投稿、留言、微博、知乎等。试问谁没有为导师的论文批注心跳加速过呢…...
h5制作方法/seo网站优化方案摘要
文章目录第一题、01-Number-sequence第二题.02-Number Sequence - Part 2第三题:NUMBER SEQUENCE – PART 3第四题:NUMBER SEQUENCE – PART 4第五题:第六题:17-templer-03-en1.lab2的内容为上次课堂练习的内容,把做过…...