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

iOS中宿主APP与录屏扩展进程数据传递方式

背景

在iOS生态系统中,应用程序的功能不再局限于单一的宿主应用,而是可以通过扩展进程实现更丰富的用户体验和功能。其中一种引人注目的扩展是录屏功能,它使用户能够捕捉设备屏幕上的活动,无论是游戏过程、教育演示还是其他应用场景。

然而,实现这一功能并非只涉及宿主应用的单一努力,更需要与扩展进程之间的高效数据传递。这种数据传递是确保录屏功能顺利运作的关键,也是开发者需要深入了解和灵活应用的技术之一。

在本篇博客中,我们将深入探讨iOS宿主APP与扩展进程之间的数据传递方式,了解它们如何协同工作,以实现无缝的录屏体验。

实现方案

方案一:通知

关于Fondation框架中的NSNotificationCenter应该都不陌生,但这里要使用的是Core Fondation框架中的CFNotificationCenterRef,是NSNotificationCenter更底层的实现。使用CFNotificationCenterRef进行数据传递的前提是,主应用和扩展应用需要设置相同的APP Group.

另外需要注意的是,通知只适合传递少量数据,比如一些重连,断开,或者其它少量信息的情况,大量数据的传递不适用,和我们使用NSNotificationCenter一样,应该也没有人使用它来连续不断地传递数据。

以主应用监听扩展应用为例,主应用中需要有下面三个需要实现方法:

  • 声明回调block
static NSString * const notificationName = @"notificationName";
void notificationCallback(CFNotificationCenterRef center,void * observer,CFStringRef name,void const * object,CFDictionaryRef userInfo) {NSString *identifier = (__bridge NSString *)name;NSObject *sender = (__bridge NSObject *)observer;NSDictionary *notiUserInfo = @{@"identifier":identifier};//为了简化处理过程,使用常规通知承接一下。[[NSNotificationCenter defaultCenter] postNotificationName:notificationNameobject:senderuserInfo:notiUserInfo];
}
  • 注册通知
//参数1:通知中心
//参数2:接收监听的对象。
//参数3:监听的回调
//参数4:通知给观察者的名称
//参数5:对于非Darwin通知中心,要观察的对象。
//参数6:决定应用程序在后台时如何处理通知。- (void)registerNotificationsWithIdentifier:(nullable NSString *)identifier {[self unregisterNotificationsWithIdentifier:identifier];CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();CFStringRef str = (__bridge CFStringRef)identifier;CFNotificationCenterAddObserver(center,(__bridge const void *)(self),notificationCallback,str,NULL,CFNotificationSuspensionBehaviorDeliverImmediately);
}
  • 注销通知
- (void)unregisterForNotificationsWithIdentifier:(nullable NSString *)identifier {CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();CFStringRef str = (__bridge CFStringRef)identifier;CFNotificationCenterRemoveObserver(center,(__bridge const void *)(self),str,NULL);
}

而在使用过程中和NSNotificationCenter十分相似:

  • 开始注册监听
//MARK: 注册监听插件消息通知
- (void)addUploaderEventMonitor {[self registerForNotificationsWithIdentifier:@"setupSocket"];//这里同时注册了纷发消息的通知,在宿主App中使用[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(broadcastInfo:) name:notificationName object:nil];
}
  • 实现监听到通知的回调方法
- (void)broadcastInfo:(NSNotification *)noti {NSDictionary *userInfo = noti.userInfo;NSString *identifier = userInfo[@"identifier"];if ([identifier isEqualToString:@"setupSocket"]) {}
}
  • dealloc方法中移除监听
- (void)removeUploaderEventMonitor {[self unregisterForNotificationsWithIdentifier:@"setupSocket"];[[NSNotificationCenter defaultCenter] removeObserver:self name:ScreenHoleNotificationName object:nil];
}- (void)dealloc{[self removeUploaderEventMonitor];
}

而扩展进程只需要负责在合适的时候发送通知:

//MARK: 发送通知
- (void)sendNotificationForMessageWithIdentifier:(nullable NSString *)identifier userInfo:(NSDictionary *)info {CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();CFDictionaryRef userInfo = (__bridge CFDictionaryRef)info;BOOL const deliverImmediately = YES;CFStringRef identifierRef = (__bridge CFStringRef)identifier;CFNotificationCenterPostNotification(center, identifierRef, NULL, userInfo, deliverImmediately);
}- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.[self sendNotificationForMessageWithIdentifier:@"setupSocket" userInfo:@{}];
}

我们可以把数据放到userInfo中传递给主应用。

方案二:KVO和KVC

iOS 7引入了应用群组功能允许创建一个共享沙盒,应用和应用扩展都可以访问它。还可以支持多个应用之间共享数据,但前提是应用必须使用相同的证书进行签名。

该方案同样也离不开应用群组。同时需要借助NSUserDefauls及KVO和KVC。

仍然是以扩展进程向主应用传递数据为例,主应用需要实现以下几个步骤:

  • 创建NSUserDefaults对象,并进行监听(注意该对象需要显式的强引用)。
self.userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.m10v.hperf"];
[self.userDefaulst addObserver:self forKeyPath:@"didReceiveVideo" options:NSKeyValueObservingOptionNew context:nil];
  • 实现监听的回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"didReceiveVideo"]){NSData *data = [userDefaulst objectForKey:@"didReceiveVideo”];} 
}

而对于扩展进程同样也只需要负责发送数据:

  • 创建NSUserDefaults对象,并使用KVC给didReceiveVideo进行赋值。
NSUserDefaults * userDefaulst = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.m10v.hperf"]; 
NSData *videoData = [NSData new]; 
[userDefaulst setObject:videoData forKey:@"didReceiveVideo"];

方案三:使用NSFileManager

该方案也需要使用到应用群组,核心思想还是通过应用群组创建共享容器来帮我祝我们在两个进程间共享数据。

以扩展进程向主APP传递数据为例,与上面方案的不同之处在于,此方案的侧重点在扩展进程。

扩展进程:

  • 创建NSFileManager将数据写入文件
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"your.app.group.identifier"];
NSURL *dataURL = [containerURL URLByAppendingPathComponent:@"recordedData.bin"];// 将 CMSampleBuffer 数据写入文件
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
NSData *data = [NSData dataWithContentsOfURL:dataURL];
[data writeToURL:dataURL atomically:YES];

主应用:

  • 读取共享文件中的内容
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"your.app.group.identifier"];
NSURL *dataURL = [containerURL URLByAppendingPathComponent:@"recordedData.bin"];// 从文件读取 CMSampleBuffer 数据
NSData *data = [NSData dataWithContentsOfURL:dataURL];

方案四:使用Socket

使用这种方式进行数据传递比较灵活,但实现起来也比较繁琐,首先需要两个进程之间建立好Socket链接,之后才能进行数据传递的操作。以GCDAysncSocket为例。

以扩展进程发送数据到主应用为例

主应用:

  • 属性声明
#import "GCDAsyncSocket.h"
@interface ViewController ()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket * socket;
@property (nonatomic, strong) NSMutableArray * socketArrayM;
//消息接口队列(串行)
@property (nonatomic, strong) dispatch_queue_t queue;
@end
  • 数据初始化,创建队列,初始化socket
- (void)viewDidLoad {[super viewDidLoad];[self initData];[self setupSocket];
}- (void)initData{self.socketArrayM = [NSMutableArray array];self.queue = dispatch_queue_create("com.joyme.panghu.socket", DISPATCH_QUEUE_SERIAL);
}//MARK: 初始化socket
- (void)setupSocket{self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.queue];self.socket.IPv6Enabled = NO;NSError * error;//设置端口号[self.socket acceptOnPort:8999 error:&error];//读取数据[self.socket readDataWithTimeout:-1 tag:0];}
  • 实现Socket的代理方法
//MARK: GCDAsyncSocketDelegate//链接断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{[self.socketArrayM removeObject:sock];
}//读取流通道关闭
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock{[self.socketArrayM removeObject:sock];
}//接收到新的链接
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{[self.socketArrayM addObject:newSocket];[newSocket readDataWithTimeout:-1 tag:0];
}//读取到数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{NSLog(@"收到:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}

扩展进程:

扩展进程同样也需要进行类似的步骤,来创建Socket以进行连接。

  • 属性声明
#import "SampleHandler.h"
#import "GCDAsyncSocket.h"@interface SampleHandler ()<GCDAsyncSocketDelegate>@property (nonatomic, strong) GCDAsyncSocket * socket;
///消息接收队列
@property (nonatomic, strong) dispatch_queue_t queue;@end
  • 初始化数据
(void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {[self initData];[self setupSocket];
}- (void)initData{self.queue = dispatch_queue_create("com.joyme.panghu.socket_2", DISPATCH_QUEUE_SERIAL);
}- (void)setupSocket{self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.queue];NSError * error;[self.socket connectToHost:@"127.0.0.1" onPort:8999 error:&error];[self.socket readDataWithTimeout:-1 tag:0];
}
  • Socket代理方法
//MARK: GCDAsyncSocketDelegate
//链接成功
- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url{}- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{[self.socket readDataWithTimeout:-1 tag:0];[self sendReadData];
}//写入数据成功
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{}//读取到数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{}- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{}
  • 发送数据
//MARK: 发送数据
- (void)sendReadData{NSString * ready = @"ready";NSData * data = [ready dataUsingEncoding:NSUTF8StringEncoding];[self.socket writeData:data withTimeout:-1 tag:0];
}

建立连接成功之后,我们会在APP接收到扩展进程传递来的数据

2023-02-10 11:49:50.143508+0800 RecordDemo[7575:1395544] 收到:ready

使用Socket与上面的方法不同之处,首先不需要创建应用群组;数据传递是双向的,建立好链接后,主APP可以向扩展进程发送数据,扩展进程也可以向宿主APP发送数据。

但上面的只是一个基础的数据传递方案,想要相对完整的建立链接流程,我们还需要考虑到端口冲突的问题。实现大量数据的传递还需要其它工具来实现比如(NTESSocketPacket,数据打包,NTESTPCircularBuffer数据解包)。

结语

当涉及主APP与扩展进程之间的数据传递方案时,我们面临着多种选择,并没有一种绝对优越的方案,只有最适合特定场景的方案。在实践中,我们可能需要结合多个方案以满足不同需求。这种灵活性使得我们能够根据具体情境选择最合适的方式,从而更好地满足项目的要求。在选择方案时,务必考虑到实际需求和性能优化,以确保系统的稳定性和效率。综上所述,灵活运用不同的数据传递方案,是构建强大应用的重要一环。

相关文章:

iOS中宿主APP与录屏扩展进程数据传递方式

背景 在iOS生态系统中&#xff0c;应用程序的功能不再局限于单一的宿主应用&#xff0c;而是可以通过扩展进程实现更丰富的用户体验和功能。其中一种引人注目的扩展是录屏功能&#xff0c;它使用户能够捕捉设备屏幕上的活动&#xff0c;无论是游戏过程、教育演示还是其他应用场…...

Windows系统下的可用RADIUS软件-[资源]

RADIUS协议相关原理介绍&#xff0c;可参考博客RADIUS协议原理介绍报文分析配置指导-RFC2865/RFC2866。 本文用于提供和介绍Window系统下几种可用的RADIUS软件。主要涉及软件有radius_ping&#xff08;绿色免安装版&#xff09;和WinRadius&#xff08;绿色免安装版&#xff09…...

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十五:基础数据模块相关功能实现

一、本章内容 本章使用已实现的公共组件实现系统管理中的基础数据中的验证码管理、消息管理等功能。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址: 基于VUE3+Layui从头搭建通用后台管理系统合集-验证码功能实现 3.2 西瓜…...

MAC苹果笔记本电脑如何彻底清理垃圾文件软件?

苹果电脑以其流畅的操作系统和卓越的性能而备受用户喜爱。然而&#xff0c;随着时间的推移&#xff0c;系统可能会积累大量垃圾文件&#xff0c;影响性能。本文将介绍苹果电脑怎么清理垃圾文件的各种方法&#xff0c;以提升系统运行效率。 CleanMyMac X是一款专业的Mac清理软件…...

【Linux C | 文件I/O】文件的打开关闭 | open、creat、colse 函数

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

【BEV感知】BEVFormer 融合多视角图形的空间特征和时序特征 ECCV 2022

前言 本文分享BEV感知方案中&#xff0c;具有代表性的方法&#xff1a;BEVFormer。 它基于Deformable Attention&#xff0c;实现了一种融合多视角相机空间特征和时序特征的端到端框架&#xff0c;适用于多种自动驾驶感知任务。 主要由3个关键模块组成&#xff1a; BEV Que…...

Amazon Toolkit — CodeWhisperer 使用

tFragment--> 官网&#xff1a;https://aws.amazon.com/cn/codewhisperer/?trkcndc-detail 最近学习了亚马逊云科技的 代码工具&#xff0c;感慨颇多。下面是安装 和使用的分享。 CodeWhisperer&#xff0c;亚马逊推出的实时 AI 编程助手&#xff0c;是一项基于机器学习…...

Flink SQL填坑记2:Flink和MySQL的Bigdata类型不同导致ClassCastException报错

最近在开发Flink SQL的时候,需要关联Kafka事实表和MySQL维表,得到的数据写入Phoenix表中,但是其中有个字段,Kafka表、MySQL表和Phoenix表都是BigData类型,但是在实现的时候却报“java.math.BigInteger cannot be cast to java.lang.Long”异常,从报错信息来看,是由于Big…...

本地MinIO存储服务如何创建Buckets并实现公网访问上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统&#xff0c;它可以100%的运行在标准硬件上&#xff0c;即X86等…...

通过https协议访问Tomcat部署并使用Shiro认证的应用跳转登到录页时协议变为http的问题

问题描述&#xff1a; 在最近的一个项目中&#xff0c;有一个存在较久&#xff0c;并且只在内部城域网可访问的一个使用Shiro框架进行安全管理的Java应用&#xff0c;该应用部署在Tomcat服务器上。起初&#xff0c;应用程序可以通过HTTP协议访问&#xff0c;一切运行都没…...

Backend - Django 项目创建 运行

目录 一、配置环境 二、创建 Django 项目 &#xff08;一&#xff09;新建文件夹 &#xff08;二&#xff09;打开文件夹 &#xff08;三&#xff09;打开运行终端 &#xff08;四&#xff09;创建基础项目 &#xff08;五&#xff09;创建app 1. 安装Django &#xf…...

C# .Net学习笔记—— Expression 表达式目录树

一、什么是表达式目录树 &#xff08;1&#xff09;Expression我们称为是表达式树&#xff0c;是一种数据结构体&#xff0c;用于存储需要计算&#xff0c;运算的一种结构&#xff0c;这种结构可以只是存储&#xff0c;而不进行运算。通常表达式目录树是配合Lambda一起来使用的…...

《论文阅读28》Unsupervised 3D Shape Completion through GAN Inversion

GAN&#xff0c;全称GenerativeAdversarialNetworks&#xff0c;中文叫生成式对抗网络。顾名思义GAN分为两个模块&#xff0c;生成网络以及判别网络&#xff0c;其中 生成网络负责根据随机向量产生图片、语音等内容&#xff0c;产生的内容是数据集中没有见过的&#xff0c;也可…...

一个正则快速找到在ES中使用profile的时产生慢查询的分片

在es中使用profile分析慢查询的时候&#xff0c;往往因为分片过多&#xff0c;或者因为查询条件太复杂&#xff0c;分析的结果几十万行。在kibana上点半天&#xff0c;也找不到一个耗时长的分片。 kibana上可以通过正则来匹配。其实我们只需要匹配到耗时大于10秒的请求。 检索语…...

链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)

在上一篇文章中&#xff0c;我们探索了顺序表这一基础的数据结构&#xff0c;它提供了一种有序存储数据的方法&#xff0c;使得数据的访 问和操作变得更加高效。想要进一步了解&#xff0c;大家可以移步于上一篇文章&#xff1a;探索顺序表&#xff1a;数据结构中的秩序之美 今…...

Python tkinter控件全集之组合选择框 ttk.ComboBox

Tkinter标准库 Tkinter是Python的标准GUI库&#xff0c;也是最常用的Python GUI库之一&#xff0c;提供了丰富的组件和功能&#xff0c;包括窗口、按钮、标签、文本框、列表框、滚动条、画布、菜单等&#xff0c;方便开发者进行图形界面的开发。Tkinter库基于Tk for Unix/Wind…...

Axure之中继器的使用(交互动作reperter属性Item属性)

目录 一.中继器的基本使用 二.中继器的动作&#xff08;增删改查&#xff09; 2.1 新增 2.2 删除 2.3 更新行 2.4 效果展示 2.5 模糊查询 三.reperter属性 在Axure中&#xff0c;中继器&#xff08;Repeater&#xff09;是一种功能强大的组件&#xff0c;用于创建重复…...

数字化医疗新篇章:构建智能医保支付购药系统

在迎接数字化医疗时代的挑战和机遇中&#xff0c;智能医保支付购药系统的建设显得尤为重要。本文将深入介绍如何通过先进的技术实现&#xff0c;构建一套智能、高效的医保支付购药系统&#xff0c;为全面建设健康中国贡献力量。 1. 引言 随着医疗科技的飞速发展&#xff0c;…...

11_12-Golang中的运算符

**Golang **中的运算符 主讲教师&#xff1a;&#xff08;大地&#xff09; 合作网站&#xff1a;www.itying.com** **&#xff08;IT 营&#xff09; 我的专栏&#xff1a;https://www.itying.com/category-79-b0.html 1、Golang 内置的运算符 算术运算符关系运算符逻辑运…...

k8s-ingress特性 9

TLS加密 创建证书 测试访问 auth认证 创建认证文件 rewrite重定向 进入域名时&#xff0c;会自动重定向到hostname.html 示例&#xff1a; 测试 版本的升级迭代&#xff0c;之前利用控制器进行滚动更新&#xff0c;在升级过程中无法做到快速回滚 更加平滑的升级&#xff1…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...