工程师如何“神还原”用户问题?闲鱼回放技术揭秘

我们透过系统底层来捕获ui事件流和业务数据的流动,并利用捕获到的这些数据通过事件回放机制来复现线上的问题。本文先介绍录制和回放的整体框架,接着介绍里面涉及到的3个关键技术点,也是这里最复杂的技术(模拟触摸事件,统一拦截器实现,统一hook block)。

背景

现在的app基本都会提供用户反馈问题的入口,然而提供给用户反馈问题一般有两种方式:

 ●  直接用文字输入表达,或者截图
 ●  直接录制视频反馈

这两种反馈方式常常带来以下抱怨:

 ●  用户:输入文字好费时费力
 ●  开发1:看不懂用户反馈说的是什么意思?
 ●  开发2:大概看懂用户说的是什么意思了,但是我线下没办法复现哈
 ●  开发3:看了用户录制的视频,但是我线下没办法重现,也定位不到问题

所以,为了解决以上问题,我们用一套全新的思路来设计线上问题回放体系。

线上问题回放体系的意义

 ●  用户不需要输入文字反馈问题,只需要重新操作一下app重现问题步骤即可。
 ●  开发者拿到用户反馈的问题脚本后,通过线下回放对问题一目了然,跟录制视频效果一样,是的,你没看错,就是跟看视频一样。
 ●  通过脚本的回放实时获取到app运行时相关数据(本地数据,网络数据,堆栈等等), 以便排查问题。
 ●  为后续自动测试提供想象空间——你懂的。

效果视频

c46a95e4e2bf1f5a377b4b651d91e52322005e49

技术原理

1.app与外部环境的关系

 

从上面的关系图可以看出,整个app的运行无非是用户ui操作,然后触发app从外界获取数据,包括网络数据,gps数据等等,也包括从手机本地获取数据,比如相册数据,机器数据,系统等数据。 所以我们要实现问题回放只需要记录用户的UI操作和外界数据,app自身数据即可。

app录制 = 用户的UI操作 + 外界数据(手机内和手机外) + app自身数据

2.线上问题回放架构由两部分组成:录制和回放

录制是为回放服务,录制的信息越详细,回放成功率就越高,定位问题就越容易

录制其实就是把ui和数据记录下来,回放其实就是app自动驱动UI操作并把录制时的数据塞回相应的地方。

3.录制架构图

 

录制流程:

 

4.回放架构图

 

回放跟录制框架图基本一样,实际上录制和回放的代码是在一起,逻辑也是统一的,为了便于表达,我人为划分成两个架构图出来。

回放的流程: 

1.启动app,点击回放按钮。

2.引擎加载回放脚本。

3.从脚本中解析出需要注册的运行时事件并注册,在回放里不需要业务上层来注册事件,这里跟录制是不一样的。

4.从脚本中解析出需要注册的静态数据事件并注册。

5.从脚本中解析出需要播放的事件数据,并组成消费队列。

6.启动播放器,从消费队列里读取一个个事件来播放,如果是ui事件则直接播放,如果是静态数据事件则直接按照指令要求替换数据值,如果是非ui运行时事件则通过事件指令规则来确定是主动播放还是等待拦截对应的事件,如果需要等待拦截对应的事件,则播放器会一直等待此事件直到此事件被app消费掉为止。只有此事件被消费了,播放器才能播放下一个事件。

7.当拦截到被注册的事件后,根据此事件指令要求把相应的数据塞到相应的字段里。

8.跳回6继续运行,直到消费队列里的事件被消费完。

注意:回放每个事件时会实时自动打印出相应的堆栈信息和事件数据,有利于排查问题

关键技术介绍

1.模拟触摸事件

从ui事件数据解中析出被触摸的view,以及此view所在的视图树中的层级关系,并在当前回放界面上查找到对应的view,然后往该view上发送ui操作事件(点击,双击等等),并带上触摸事件的坐标信息,其实这里是模拟触摸事件。我们先来介绍触摸事件的处理流程:

等待触摸阶段

 ●  手机屏幕处于待机状态,等待触摸事件发生
 ●  手指开始触摸屏幕

系统反应阶段

 ●  屏幕感应器接收到触摸,并将触摸数据传给系统IOKit(IOKit是苹果的硬件驱动框架)
 ●  系统IOKit封装该触摸事件为IOHIDEvent对象
 ●  接着系统IOKit把IOHIDEvent对象转发给SpringBoard进程

SpringBoard进程就是iOS的系统桌面,它存在于iDevice的进程中,不可清除,它的运行原理与Windows中的explorer.exe系统进程相类似。它主要负责界面管理,所以只有它才知道当前触摸到底有谁来响应。

SpringBoard接收阶段

 ●  SpringBoard收到IOHIDEvent消息后,触发runloop中的Source1回调__IOHIDEventSystemClientQueueCallback()方法。
 ●  SpringBoard开始查询前台是否存在正在运行的app,如果存在,则SpringBoard通过进程通信方式把此触摸事件转发给前台当前app,如果不存在,则SpringBoard进入其自己内部响应过程。

app处理阶段

 ●  前台app主线程Runloop收到SpringBoard转发来的消息,并触发对应runloop 中的Source1回调_UIApplicationHandleEventQueue()。
 ●  _UIApplicationHandleEventQueue()把IOHIDEvent处理包装成UIEvent进行处理分发。
 ●  Soucre0回调内部UIApplication的sendEvent:方法,将UIEvent传给UIWindow。
 ●  在UIWindow为根节点的整棵视图树上通过hitTest(_:with:)和point(inside:with:)这两个方法递归查找到合适响应这个触摸事件的视图。
 ●  找到最终的叶子节点视图后,就开始触发此视图绑定的相应事件,比如跳转页面等等。

从上面触摸事件处理过程中我们可以看出要录制ui事件只需要在app处理阶段中的UIApplication sendEvent方法处截获触摸数据,回放时也是在这里把触摸模拟回去。

下面是触摸事件录制的代码,就是把UITouch相应的数据保存下来即可 这里有一个关键点,需要把touch.timestamp的时间戳记录下来,以及把当前touch事件距离上一个touch事件的时间间隔记录下来,因为这个涉及到触摸引起惯性加速度问题。比如我们平时滑动列表视图时,手指离开屏幕后,列表视图还要惯性地滑动一小段时间。


- (void)handleUIEvent:(UIEvent *)event{if (!self.isEnabled) return;if (event.type != UIEventTypeTouches) return;NSSet *allTouches = [event allTouches];UITouch *touch = (UITouch *)[allTouches anyObject];if (touch.view) {if (self.filter && !self.filter(touch.view)) {return;}}switch (touch.phase) {case UITouchPhaseBegan:{self.machAbsoluteTime = mach_absolute_time();self.systemStartUptime = touch.timestamp;self.tuochArray = [NSMutableArray array];[self recordTouch:touch click:self.machAbsoluteTime];break;}case UITouchPhaseStationary:{[self recordTouch:touch click:mach_absolute_time()];break;}case UITouchPhaseCancelled:{[self recordTouch:touch click:mach_absolute_time()];[[NSNotificationCenter defaultCenter] postNotificationName:@"notice_ui_test" object:self.tuochArray];break;}case UITouchPhaseEnded:{[self recordTouch:touch click:mach_absolute_time()];[[NSNotificationCenter defaultCenter] postNotificationName:@"notice_ui_test" object:self.tuochArray];break;}case UITouchPhaseMoved:{[self recordTouch:touch click:mach_absolute_time()];}default:break;}}

我们来看一下代码怎么模拟单击触摸事件(为了容易理解,我把有些不是关键,复杂的代码已经去掉),接着我们来看一下模拟触摸事件代码 一个基本的触摸事件一般由三部分组成:

 ●  UITouch对象 - 将用于触摸
 ●  第一个UIEvent Began触摸
 ●  第二个UIEvent Ended触摸

实现步骤:

1.代码的前面部分都是一些UITouch和UIEvent私有接口,私有变量字段,由于苹果并不公开它们,为了让其编译不报错,所以我们需要把这些字段包含进来,回放是在线下,所以不必担心私有接口被拒的事情。

2.构造触摸对象:UITouch和UIEvent,把记录对应的字段值塞回相应的字段。塞回去就是用私有接口和私有字段。

3.触摸的view位置转换为Window坐标,然后往app里发送事件 [[UIApplication sharedApplication] sendEvent:event];

4.要回放这些触摸事件,我们需要把他丢到CADisplayLink里面来执行。

//// SimulationTouch.m//// Created by 诗壮殷 on 2018/5/15.//#import "SimulationTouch.h"#import <objc/runtime.h>#include <mach/mach_time.h>@implementation UITouch (replay)- (id)initPoint:(CGPoint)point window:(UIWindow *)window{NSParameterAssert(window);self = [super init];if (self) {[self setTapCount:1];[self setIsTap:YES];[self setPhase:UITouchPhaseBegan];[self setWindow:window];[self _setLocationInWindow:point resetPrevious:YES];[self setView:[window hitTest:point withEvent:nil]];[self _setIsFirstTouchForView:YES];[self setTimestamp:[[NSProcessInfo processInfo] systemUptime]];}return self;}@end@interface UIInternalEvent : UIEvent- (void)_setHIDEvent:(IOHIDEventRef)event;@end@interface UITouchesEvent : UIInternalEvent- (void)_addTouch:(UITouch *)touch forDelayedDelivery:(BOOL)delayedDelivery;- (void)_clearTouches;@endtypedef enum {kIOHIDDigitizerEventRange = 0x00000001,kIOHIDDigitizerEventTouch = 0x00000002,kIOHIDDigitizerEventPosition = 0x00000004,} IOHIDDigitizerEventMask;IOHIDEventRef IOHIDEventCreateDigitizerFingerEvent(CFAllocatorRef allocator,AbsoluteTime timeStamp,uint32_t index,uint32_t identity,IOHIDDigitizerEventMask eventMask,IOHIDFloat x,IOHIDFloat y,IOHIDFloat z,IOHIDFloat tipPressure,IOHIDFloat twist,Boolean range,Boolean touch,IOOptionBits options);@implementation SimulationTouch- (void)performTouchInView:(UIView *)view start:(bool)start{UIWindow *_window = view.window;CGRect fInWindow;if ([view isKindOfClass:[UIWindow class]]){fInWindow = view.frame;}else{fInWindow = [_window convertRect:view.frame fromView:view.superview];}CGPoint point =CGPointMake(fInWindow.origin.x + fInWindow.size.width/2,fInWindow.origin.y + fInWindow.size.height/2);if(start){self.touch = [[UITouch alloc] initPoint:point window:_window];[self.touch setPhase:UITouchPhaseBegan];}else{[self.touch _setLocationInWindow:point resetPrevious:NO];[self.touch setPhase:UITouchPhaseEnded];}CGPoint currentTouchLocation = point;UITouchesEvent *event = [[UIApplication sharedApplication] _touchesEvent];[event _clearTouches];uint64_t machAbsoluteTime = mach_absolute_time();AbsoluteTime timeStamp;timeStamp.hi = (UInt32)(machAbsoluteTime >> 32);timeStamp.lo = (UInt32)(machAbsoluteTime);[self.touch setTimestamp:[[NSProcessInfo processInfo] systemUptime]];IOHIDDigitizerEventMask eventMask = (self.touch.phase == UITouchPhaseMoved)? kIOHIDDigitizerEventPosition: (kIOHIDDigitizerEventRange | kIOHIDDigitizerEventTouch);Boolean isRangeAndTouch = (self.touch.phase != UITouchPhaseEnded);IOHIDEventRef hidEvent = IOHIDEventCreateDigitizerFingerEvent(kCFAllocatorDefault,timeStamp,0,2,eventMask,currentTouchLocation.x,currentTouchLocation.y,0,0,0,isRangeAndTouch,isRangeAndTouch,0);if ([self.touch respondsToSelector:@selector(_setHidEvent:)]) {[self.touch _setHidEvent:hidEvent];}[event _setHIDEvent:hidEvent];[event _addTouch:self.touch forDelayedDelivery:NO];[[UIApplication sharedApplication] sendEvent:event];}@end

总的来说就下载苹果提供触摸事件的源码库,分析源码,然后设置断掉调试,甚至反汇编来理解触摸事件的原理。

2.统一拦截器

录制和回放都居于事件流来处理的,而数据的事件流其实就是对一些关键方法的hook,由于我们为了保证对业务代码无侵入和扩展性(随便注册事件),我们需要对所有方法统一hook,所有的方法由同一个钩子来响应处理。如下图所示

 

这个钩子是用用汇编编写,由于汇编代码比较多,而且比较难读懂,所以这里暂时不附上源码,汇编层主要把硬件里面的一些数据统一读取出来,比如通用寄存器数据和浮点寄存器数据,堆栈信息等等,甚至前面的前面的方法参数都可以读取出来,最后转发给c语言层处理。

汇编层把硬件相关信息组装好后调用c层统一拦截接口,汇编层是为c层服务。c层无法读取硬件相关信息,所以这里只能用汇编来读取。c层接口通过硬件相关信息定位到当前的方法是属于哪个事件,知道了事件,也意味着知道了事件指令,知道了事件指令,也知道了哪些字段需要塞回去,也知道了被hook的原始方法。

c层代码介绍如下: 由于是统一调用这个拦截器,所以拦截器并不知道当前是哪个业务代码执行过来的,也不知道当前这个业务方法有多少个参数,每个参数类型是什么等等,这个接口代码处理过程大概如下:

 ●  通过寄存器获取对象self
 ●  通过寄存器获取方法sel
 ●  通过self和sel获取对应的事件指令
 ●  通过事件指令回调上层来决定是否往下执行
 ●  获取需要回放该事件的数据
 ●  把数据塞回去,比如塞到某个寄存器里,或者塞到某个寄存器所指向的对象的某个字段等等
 ●  如果需要立即回放则调用原来被hook的原始方法,如果不是立即回放,则需要把现场信息保存起来,并等待合适的时机由播放队列来播放(调用)

//xRegs 表示统一汇编器传入当前所有的通用寄存器数据,它们地址存在一个数组指针里//dRegs 表示统一汇编器传入当前所有的浮点寄存器数据,它们地址也存在一个数组指针里//dRegs 表示统一汇编器传入当前堆栈指针//fp 表示调用栈帧指针void replay_entry_start(void* xRegs, void* dRegs, void* spReg, CallBackRetIns *retIns,StackFrame *fp, void *con_stub_lp){void *objAdr = (((void **)xRegs)[0]);//获取对象本身self或者block对象本身EngineManager *manager = [EngineManager sharedInstance];ReplayEventIns *node = [manager getEventInsWithBlock:objAdr];id obj = (__bridge id)objAdr;void *xrArg = ((void **)xRegs)+2;if(nil == node){SEL selecter = (SEL)(((void **)xRegs)[1]); //对应的对象调用的方法Class tclass = [obj class];//object_getClass(obj);object_getClass方法只能通过对象获取它的类,不能传入class 返回class本身,do{node = [manager getEventIns:tclass sel:selecter];//通过对象和方法获取对应的事件指令节点}while(nil == node && (tclass = class_getSuperclass(tclass)));}else{xrArg = ((void **)xRegs)+1;}assert(node && "node is nil in replay_call_start");//回调通知上层当前回放是否打断if(node.BreakCurReplayExe && node.BreakCurReplayExe(obj,node,xrArg,dRegs)){retIns->nodeAddr = NULL;retIns->recordOrReplayData = NULL;retIns->return_address = NULL;return;}bool needReplay = true;//回调通知上层当前即将回放该事件if(node.willReplay){needReplay = (*(node.willReplay))(obj,node,xrArg,dRegs);}if(needReplay){ReplayEventData *replayData = nil;if(node.getReplayData){//获取回放该事件对应的数据replayData = (*(node.getReplayData))(obj,node,xrArg,dRegs);}else//默认获取方法{replayData = [manager getNextReplayEventData:node];}//以下就是真正的回放,即是把数据塞回去,并调用原来被hook的方法if(replayData){if(replay_type_intercept_call == node.replayType){sstuffArg(xRegs,dRegs,spReg,node,replayData.orgDic);NSArray *arglist = fetchAllArgInReplay(xRegs, dRegs, spReg, node);ReplayInvocation *funobj = [[ReplayInvocation alloc] initWithFunPtr:node.callBack ? node.callBack : [node getOrgFun]args:arglistargType:[node getFunTypeStr]retType:rf_return_type_v];if([[EngineManager sharedInstance] setRepalyEventReady:replayData funObj:funobj]){//放到播放队列里播放,返回没调用地址,让其不往下走retIns->return_address = NULL;return ;}}else{//塞数据sstuffArg(xRegs,dRegs,spReg,node,replayData.orgDic);}}retIns->nodeAddr = (__bridge void *)node;retIns->recordOrReplayData = (__bridge void *)replayData;retIns->return_address = node.callBack ? node.callBack : [node getOrgFun];replayData.runStatus = relay_event_run_status_runFinish;}else{retIns->nodeAddr = NULL;retIns->recordOrReplayData = NULL;retIns->return_address = [node getOrgFun];}}

3.怎样统一hook block

如果你只是想大概理解block的底层技术,你只需google一下即可。 如果你想全面深入的理解block底层技术,那网上的那些资料远远满足不了你的需求。 只能阅读苹果编译器clang源码和列出比较有代表性的block例子源码,然后转成c语言和汇编,通过c语言结合汇编研究底层细节。

何谓 oc block?

 ●  block就是闭包,跟回调函数callback很类似,闭包也是对象。
 ●  blcok的特点: 1.可有参数列表 2.可有返回值 3.有方法体 4.capture上下文变量 5.有对象引用计数的内存管理策略(block生命周期)。
 ●  block的一般存储在内存中形态有三种 _NSConcretStackBlock(栈)_NSConcretGlobalBlock(全局)_NSConcretMallocBlock(堆)。

系统底层怎样表达block?

我们先来看一下block的例子:

void test(){__block int var1 = 8; //上下文变量NSString *var2 = @"我是第二个变量”; //上下文变量void (^block)(int) = ^(int arg)//参数列表{var1 = 6;NSLog(@"arg = %d,var1 = %d, var2 = %@", arg, var1, var2);};block(1);//调用block语法dispatch_async(dispatch_get_global_queue(0, 0), ^{block(2); //异步调用block});}

这段代码首先定义两个变量,接着定义一个block,最后调用block。

 ●  两个变量:这两个变量都是被block引用,第一个变量有关键字__block,表示可以在block里对该变量赋值,第二个变量没有__block关键字,在block里只能读,不能写。
 ●  两个调用block的语句:第一个直接在当前方法test()里调用,此时的block内存数据在栈上,第二个是异步调用,就是说当执行block(2)时test()可能已经运行完了,test()调用栈可能已经被销毁。那这种情况block的数据肯定不能在栈上,只能在堆上或者在全局区。

系统底层表达block比较重要的几种数据结构如下:

注意:虽然底层是用这些结构体来表达block,但是它们并不是源码,是二进制代码

enum{BLOCK_REFCOUNT_MASK = (0xffff),BLOCK_NEEDS_FREE = (1 << 24),BLOCK_HAS_COPY_DISPOSE = (1 << 25),BLOCK_HAS_CTOR = (1 << 26),//todo == BLOCK_HAS_CXX_OBJ?BLOCK_IS_GC = (1 << 27),BLOCK_IS_GLOBAL = (1 << 28),BLOCK_HAS_DESCRIPTOR = (1 << 29),//todo == BLOCK_USE_STRET?BLOCK_HAS_SIGNATURE = (1 << 30),OBLOCK_HAS_EXTENDED_LAYOUT = (1 << 31)};enum{BLOCK_FIELD_IS_OBJECT = 3,BLOCK_FIELD_IS_BLOCK = 7,BLOCK_FIELD_IS_BYREF = 8,OBLOCK_FIELD_IS_WEAK = 16,OBLOCK_BYREF_CALLER = 128};typedef struct block_descriptor_head{unsigned long int reserved;unsigned long int size; //表示主体block结构体的内存大小}block_descriptor_head;typedef struct block_descriptor_has_help{unsigned long int reserved;unsigned long int size; //表示主体block结构体的内存大小void (*copy)(void *dst, void *src);//当block被retain时会执行此函数指针void (*dispose)(void *);//block被销毁时调用struct block_arg_var_descriptor *argVar;}block_descriptor_has_help;typedef struct block_descriptor_has_sig{unsigned long int reserved;unsigned long int size;const char *signature;//block的签名信息struct block_arg_var_descriptor *argVar;}block_descriptor_has_sig;typedef struct block_descriptor_has_all{unsigned long int reserved;unsigned long int size;void (*copy)(void *dst, void *src);void (*dispose)(void *);const char *signature;struct block_arg_var_descriptor *argVar;}block_descriptor_has_all;typedef struct block_info_1{void *isa;//表示当前blcok是在堆上还是在栈上,或在全局区_NSConcreteGlobalBlockint flags; //对应上面的enum值,这些枚举值是我从编译器源码拷贝过来的int reserved;void (*invoke)(void *, ...);//block对应的方法体(执行体,就是代码段)void *descriptor;//此处指向上面几个结构体中的一个,具体哪一个根据flags值来定,它用来进一步来描述block信息//从这个字段开始起,后面的字段表示的都是此block对外引用的变量。NSString *var2;byref_var1_1 var1;} block_info_1;

这个例子中的block在底层表达大概如下图:

 

首先用block_info_1来表达block本身,然后用block_desc_1来具体描述block相关信息(比如block_info_1结构体大小,在堆上还是在栈上?copy或dispose时调用哪个方法等等),然而block_desc_1具体是哪个结构体是由block_info_1中flags字段来决定的,block_info_1里的invoke字段是指向block方法体,即是代码段。block的调用就是执行这个函数指针。由于var1是可写的,所以需要设计一个结构体(byref_var1_1)来表达var1,为什么var2直接用他原有的类型表达,而var1要用结构体来表达。篇幅有限,这个自己想想吧?

block小结

 ●  为了表达block,底层设计三种结构体:block_info_1,block_desc_1,byref_var1_1,三种函数指针: block invoke方法体,copy方法,dispose方法
 ●  其实表达block是非常复杂的,还涉及到block的生命周期,内存管理问题等等,我在这里只是简单的贯穿主流程来介绍的,很多细节都没介绍。

怎样统一 hook block?

通过上面的分析,得知oc里的block就是一个结构体指针,所以我在源码里可以直接把它转成结构体指针来处理。 统一hook block源码如下:

VoidfunBlock createNewBlock(VoidfunBlock orgblock, ReplayEventIns *blockEvent,bool isRecord){if(orgblock && blockEvent){VoidfunBlock newBlock = ^(void){orgblock();if(nil == blockEvent){assert(0);}};trace_block_layout *blockLayout = (__bridge trace_block_layout *)newBlock;blockLayout->invoke = (void (*)(void *, ...))(isRecord?hook_var_block_callBack_record:hook_var_block_callBack_replay);return newBlock;}return nil;}

我们首先新建一个新的block newBlock,然后把原来的block orgblock 和 事件指令blockEvent包到新的blcok中,这样达到引用的效果。然后把

新的block转

成结构体指针,并把结构体指针中的字段invoke(方法体)指向统一回调方法。你可能诧异新的block是没有参数类型的,原来block是有参数类型,

外面调用原

来block传递参数时会不会引起crash?答案是否定的,因为这里构造新的block时 我们只用block数据结构,block的回调方法字段已经被阉割,回

调方法已经指

向统一方法了,这个统一方法可以接受任何类型的参数,包括没有参数类型。这个统一方法也是汇编实现,代码实现跟上面的汇编层代码类似,这

里就不附上源

码了。

那怎样在新的blcok里读取原来的block和事件指令对象呢? 代码如下:

void var_block_callback_start_record(trace_block_layout * blockLayout){VoidfunBlock orgBlock = (__bridge VoidfunBlock)(*((void **)((char *)blockLayout + sizeof(trace_block_layout))));ReplayEventIns *node = (__bridge ReplayEventIns *)(*((void **)((char *)blockLayout + 40)));}

总结

本文大概介绍了问题回放框架,接着介绍三个关键技术。这三个技术相对比较深入,欢迎在留言区评论,我们期待与大家交流,共同探讨。


阿里云双十一1折拼团活动:满6人,就是最低折扣了!
【满6人】1核2G云服务器99.5元一年298.5元三年 2核4G云服务器545元一年 1227元三年
【满6人】1核1G MySQL数据库 119.5元一年
【满6人】3000条国内短信包 60元每6月
参团地址:http://click.aliyun.com/m/1000020293/


原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

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

相关文章

英特尔推出世界最大 FPGA 芯片;任正非表示华为尚未直接和美国公司商谈5G技术授权;OpenTitan开源……...

戳蓝字“CSDN云计算”关注我们哦&#xff01; 嗨&#xff0c;大家好&#xff0c;重磅君带来的【云重磅】特别栏目&#xff0c;如期而至&#xff0c;每周五第一时间为大家带来重磅新闻。把握技术风向标&#xff0c;了解行业应用与实践&#xff0c;就交给我重磅君吧&#xff01;重…

前端传递json,后端应该怎样接收呢?

Content-Type: application/json#后端接收方式 RestController RequestMapping("/user") Slf4j public class UserController {/*** param userForm*/PostMapping("/register")public ResponseVo register(RequestBody UserForm userForm) {log.info("…

网络数据隐私保护,阿里工程师怎么做?

个人数据挖掘和个人隐私保护&#xff0c;并非鱼与熊掌&#xff0c;可视分析的技术手段能够帮助我们保护个人隐私数据&#xff0c;避免后续的数据挖掘暴露隐私的同时&#xff0c;平衡数据质量发生的变化&#xff0c;减少对后续数据挖掘的影响。针对网络数据中的隐私保护问题&…

刷爆了!这份被程序员疯传的Python神作牛在哪?

随着AI的兴起&#xff0c;Python彻底火了。除了谷歌爬虫、Google广告等项目在大量使用Python开发。包括豆瓣、知乎在内的很多互联网公司都将 Python 作为了主要编程语言开发。对于程序员来说&#xff0c;Python应用前景广&#xff0c;市场需求大&#xff0c;随之而来的是薪资非…

阿里云李刚:下一代低延时的直播CDN

在上周落幕帷幕的多媒体领域技术盛会——LiveVideoStackCon音视频技术大会上&#xff0c;阿里云的高级技术专家李刚进行了《下一代低延时的直播CDN》技术分享。主讲人李刚&#xff0c;多年关注在CDN这个领域&#xff0c;早期主要研究和cache服务器缓存以及流媒体相关的技术, 专…

教你用一条SQL搞定跨数据库查询难题

导读 日前&#xff0c;某电商用户由于业务发展迅猛&#xff0c;访问量极速增长&#xff0c;导致数据库容量及性能遭遇瓶颈。为降低数据库大小&#xff0c;提升性能&#xff0c;用户决定对架构进行垂直拆分。根据不同的表来进行拆分&#xff0c;对应用程序的影响也更小&#xf…

漫画:什么是公有云、私有云和混合云?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 漫话编程 责编 | 阿秃为了方便大家理解&#xff0c;我们尽量用通俗的语言和举例子的方式讲解&#xff0c;并且文中还配备了漫画供大家参考学习。随着最近几年的云计算技术的主键发展和普及&#xff0c;越来越多的企业通过采用…

PTS + ARMS打造性能和应用诊断利器

服务端的性能测试&#xff0c;尤其是业务性能测试&#xff0c;是用来评估性能容量、诊断性能瓶颈和应用错误&#xff0c;或是验证高可用的能力&#xff0c;以此达到降低成本、提升用户体验的目的。但是&#xff0c;当需要有进一步的定位和刨析时&#xff0c;这类性能测试就会显…

Envoy源码分析之Dispatcher

Dispatcher 在Envoy的代码中Dispatcher是随处可见的&#xff0c;可以说在Envoy中有着举足轻重的地位&#xff0c;一个Dispatcher就是一个EventLoop&#xff0c;其承担了任务队列、网络事件处理、定时器、信号处理等核心功能。在Envoy threading model这篇文章所提到的EventLoo…

这项技术厉害了!让旅行者 2 号从星际空间发首批数据!

限时8.3折&#xff0c;立即购票&#xff1a;https://dwz.cn/z1jHouwE物联网作为信息系统向物理世界的延伸&#xff0c;极大地拓展了人类认知和控制物理世界的能力&#xff0c;被称为继计算机和互联网之后的世界信息产业的第三次浪潮&#xff0c;正在深刻地改变着人类的生存环境…

修改文件 华为交换机_华为交换机系统文件管理配置命令大全(二)

11、解压文件&#xff08;unzip&#xff09;<Huawei>dirDirectory of flash:/Idx Attr Size(Byte) Date Time FileName0 drw- - Aug 07 2015 13:51:14 src1 drw- - Apr 02 2016 11:29:41 pmdata2 drw- - Apr 02 2016 11:29:52 dhcp3 -rw- 28 Apr 02 2016 11:29:53 privat…

从阿里云数据库入选Gartner谈数据库的演化

根据全球权威的IT咨询公司Gartner的最新研究报告&#xff0c;在2018年度数据库系统的魔力象限中&#xff0c;阿里云数据库被列入“远见者”象限&#xff0c;这是国产数据库首次进入Gartner魔力象限。Gartner的魔力四象限&#xff0c;描述了数据库厂商的产品能力和市场规模。四个…

申请美国计算机科学,美国计算机科学的申请特点

计算机科学官方定义&#xff1a;计算机科学是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。它通常被形容为对那些创造、描述以及转换信息的算法处理的系统研究&#xff0c;计算机科学专业的申请特点如下&#xff1a;申请难度中等学校…

云计算软件生态圈:摸到一把大牌

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 老姜责编 | 阿秃出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;“我觉得我摸着了一把大牌。”软件领域的新锐企业——有赞公司创始人兼CEO白鸦在转向SaaS领域的一个细分市场时&#xff0c;曾对天使投资人…

一体台式计算机名称,【一体台式电脑】一体台式电脑品牌推荐,台式一体机电脑哪款好_什么值得买...

1. Apple 苹果 MXWT2CH/A iMac(2019)27英寸一体机 8GB 2666MHZ DDR4 内存商品简介&#xff1a;Apple 苹果 iMac(2019)27英寸一体机保留了前代产品的27英寸5K屏幕(分辨率5120 x 2880&#xff0c;DCI-P3色域)对比上代Retina视网膜屏幕电脑&#xff0c;外观没有变化&#xff0c;主…

SLS机器学习介绍(02):时序聚类建模

文章系列链接 SLS机器学习介绍&#xff08;01&#xff09;&#xff1a;时序统计建模SLS机器学习介绍&#xff08;02&#xff09;&#xff1a;时序聚类建模SLS机器学习介绍&#xff08;03&#xff09;&#xff1a;时序异常检测建模SLS机器学习介绍&#xff08;04&#xff09;&a…

蚂蚁金服金融级容器引擎实践之路

小蚂蚁说&#xff1a; 在金融级分布式架构中使用容器&#xff0c;许多企业的开发者都面临许多挑战。在2018年ATEC蚂蚁金服技术探索大会上&#xff0c;蚂蚁金服高级技术专家盛延敏在演讲中分析了容器与云原生技术的本质&#xff0c;为容器在分布式架构上的使用带来了实用高效的…

【耿老师公开课】反转!物联网火爆,开发者却很难入门?

戳蓝字“CSDN云计算”关注我们哦&#xff01;在 2019 北京网络安全大会上&#xff0c;工信部负责人表示&#xff0c;我国面向 5G 和车联网将建设网安防护体系&#xff0c;提升监测预警和应急响应能力。其中物联网设备已成为网安防护新重点。为什么工信部会这么重视物联网&#…

哈工大未来计算机院士,2017年中国高校新增工程院院士名单出炉,哈工大依然很强!...

原标题&#xff1a;2017年中国高校新增工程院院士名单出炉&#xff0c;哈工大依然很强&#xff01;院士是一个国家在科学技术方面的最高称号&#xff0c;不光在我们国家有&#xff0c;其他很多国家也有院士称号。而我国的院士一般都是指中国工程院院士和中国科学院院士&#xf…

老司机开车,教会女朋友什么是「马拉车算法」

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 李威责编 | 阿秃马拉车算法&#xff08; Manacher‘s Algorithm &#xff09;是小吴最喜欢的算法之一&#xff0c;因为&#xff0c;它真的很牛逼&#xff01;马拉车算法是用来 查找一个字符串的最长回文子串的线性方法 &…