iOS面试知识点汇总
什么是面向对象编程?
面向对象编程把面向对象的思想应用于软件开发。对象是由数据和容许的操作组成的封装体,与客观实体有直接对应关系,类定义了具有相似性质的一组对象。继承性是对具有层次关系的类的属性和操作进行共享的一种方式。所谓面向对象就是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、 刻画客观世界,并设计、构建相应的软件系统。面向对象编程有以下三大特征:
- 封装,把客观事物封装成抽象的类,并且类可以将自己的数据和方法只对可信的类或对象公开,对不可信的类或者对象隐藏。
- 继承,子类继承父类,子类可以使用父类的所有功能,并在无需改动父类的情况下对这些功能进行扩展。
- 多态,继承自同一个父类的不同子类对象以它们自己的方式响应相同的消息。
atomic和non-atomic修饰的属性有什么区别?
- atomic:原子性,atomic修饰的属性是线程安全的(只是保证了属性的setter和getter的线程安全),但是效率低。
- non-atomic:非原子性,non-atomic修饰的属性是线程不安全的,但效率高。
如何使用atomic才能保证线程安全?
atomic
修饰的属性,只是保证了setter和getter的线程安全,并不是所有操作都是线程安全的。例如,当在不同的线程对一个原子性的可变数组分别进行插入和删除数据的操作时,就会出现问题。可以使用锁来保证线程安全。有以下几种加锁方式:
- @synchronized指令,使用最简单,性能也最差。
- 信号量。
- 互斥锁。
- 递归锁。
- OSSpinLock自旋锁(有bug)。
Objective-C的内存管理
Objective-C采用引用计数机制来管理内存。当使用alloc
或者new
方法创建一个实例对象时,该对象的引用计数值为1。对该对象执行retain
、copy
操作时,其引用计数会加1。对该对象执行release
、autorelease
操作时,对象的引用计数会减1。当对象的引用计数为0时,系统会向该对象发送一个dealloc
消息来释放它。
Objective-C提供了三种内存管理方式:MRC(手动引用计数),ARC(自动引用计数),Autorelease pool(自动释放池)。
什么是ARC?
ARC即自动引用计数。在MRC模式下,我们需要手动添加retain
,release
等操作来管理内存引用计数。而在ARC模式下,编译器会在编译代码时自动帮我们在代码中添加这些操作。
Autorelease Pool(自动释放池)的原理
线程刚进入 run loop 时,系统会自动创建一个自动释放池,该自动释放池以栈的形式实现。给某个对象发送autorelease
消息,run loop 接收到该事件后,会将该对象添加到自动释放池中。在线程退出run loop时,系统会回收自动释放池。当自动释放池被回收时,自动释放池中的所有对象会被移出自动释放池堆栈,并对所有这些对象执行一次release
操作。
readwrite,readonly,assign,retain,copy属性关键字的作用。
- readwrite表示属性可读可写,也就是说外部可以访问该属性并且可以修改该属性的值。
- readonly表示属性只读,也就是说外部只能访问该属性,但不能修改该属性的值。
- assign表示直接给属性赋值,给该属性分配值时,不会增加属性值的引用计数。
- retain表示持有,给该属性分配值时,会使属性值的引用计数加1。
- copy表示内容拷贝,分为浅拷贝和深拷贝:
- 浅拷贝:只是拷贝了指向了对象的内存地址的指针,对象的引用计数会加1。
- 深拷贝:会拷贝对象的具体内容,拷贝得到的对象和原对象的值是相同的,但是指针指向的内存地址不同。
什么是循环引用?
对象A和对象B互相引用对方作为自己的属性值。在ARC模式下,当对象A被释放时,才会给其属性值对象B发送一个release
消息来减少对象B的引用计数。但是对象B又引用对象A作为自己的属性值,也就是说只有当对象B被释放时,才会给其属性值对象A发送一个release
消息来减少对象A的引用计数。对象A和对象B就会互相等待对方释放,使它们的引用计数始终不为0,导致系统永远无法释放它们。
weak的实现原理
系统维护有一个全局的weak表,该表用于存储指向某个对象的所有weak指针。weak表是一个哈希表,Key是所指对象的地址,Value是weak指针数组。
当对weak
修饰或者__weak
修饰的实例变量执行赋值操作时,不会增加值对象的引用计数。系统只是将该实例变量的指针指向值对象的内存地址,并将该指针存储到weak表中。
当值对象被释放时,会在其dealloc
方法中根据值对象的地址在weak表中检索对应的weak指针数组,然后遍历该指针数组,将所有weak指针指向nil
。最后,从weak表中删除该值对象的记录。
assign和weak的区别,__block和__weak的区别。
为使用assign
修饰的属性分配值时会直接赋值,而不会更改属性值的引用计数,其通常被用来修饰基本数据类型。weak
修饰符只能在ARC模式下使用且只能修饰对象类型,其修饰的对象属性在赋值时也不会更改属性值的引用计数。但是当该对象属性被释放时,指向该对象属性地址的指针会被赋值为nil
。这样做能够防止出现野指针。
__block
类型修饰符在ARC和MRC模式下都能使用,其不仅可以修饰对象,还可以修饰基本数据类型。使用__block
修饰的变量会在block对象从栈区复制到堆区时,也被从栈区复制到堆区,这样就能在block内部修改变量的值。__weak
只能在ARC模式下使用,并且只能修饰对象。__weak
来弱引用对象,不会引起对象引用计数的更改,防止出现循环引用而导致内存泄漏。
#import、#include和@class的区别。
import是Objective-C导入头文件的关键字,include是C/C++导入头文件的关键字。使用include导入头文件时,相当于拷贝导入的头文件中的声明内容。所以如果使用include重复导入某个头文件的话,编译器会报重复定义的错误。而使用import导入头文件,编译器会在编译时做一次判断,如果已经导入了该头文件就不会再二次导入了。如果不是C/C++文件,应尽量使用import。并且如果能在实现文件中import,就不要在头文件中import。
@class用于声明某个类,其告诉编译器它后面的名字是一个类的名称,而这个类的具体实现暂时是不知道的。而在一个类的头文件中使用import导入某个类的时候,这个类的所有信息都会被导入。如果在许多个类的头文件中都使用import导入了该类,会降低编译器编译的效率。另外,当两个类使用import互相引用时,编译器会报错,而使用@class能够避免出现错误。
delegate,block和notification的区别。
- delegate:对于同一个协议,一个对象只能设置一个代理,也就是说只能一对一地通信。所以单例最好不要使用代理。代理注重信息传输的过程。例如,发起一个网络请求后,我们想要知道此时请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败,此时就应该在同一个协议中声明对应的委托方法,然后使用一个代理对象调用对应的委托方法来传输这些信息。
- block:block也只能一对一地通信,其注重信息传输的结果。例如,对于某个操作,我们只想知道其操作结果,这时应该使用block。相对于代理,其写法简单,不需要声明协议和委托方法。但使用block时,容易导致循环引用。
- notification:能够一对多地通信。当多个对象都需要对某个操作作出响应时,应该使用通知。
int,unsigned int,NSInteger和NSUInteger有什么区别?
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
int类型在32位系统中只能是int
类型,但在64位系统中却有可能为long
类型。而NSInteger
和NSUInteger
是动态定义的类型,在不同的设备和不同的架构中,它们可能是int
类型,也可能是long
类型。应该尽可能使用NSInteger
,这样就不用考虑设备是32位还是64位系统了。NSUInteger
是无符号的,即没有负数,而NSInteger
是有符号的。
应用程序的生命周期,其在启动时做了什么?如何优化应用程序的启动时间?
应用程序的生命周期
iOS应用程序有五种状态:
- 未运行状态(Not running):应用程序尚未启动或者正在运行但已被系统终止。
- 未激活状态(Inactive):应用程序在前台运行,但是目前还未开始接收事件。(它可能正在执行其他代码)应用程序通常只在切换到不同状态的过程中,短暂保持此状态。
- 激活状态(Active):应用程序在前台运行并且正在接收事件,这是应用程序在前台运行时所处的正常状态。
- 后台状态(Background):应用程序在后台并执行代码。大多数应用程序进入后台后,会在这个状态停留一会儿。之后,应用程序会被系统挂起。
- 挂起状态(Suspended):应用程序在后台处于睡眠状态,不会执行代码。当应用程序被挂起时,应用程序依然保留在内存中。当出现内存不足的情况时,系统可能会清除被挂起的应用程序,为其他前台应用程序提供更多的内存。
启动尚未运行的应用程序时,应用程序会先切换到未激活状态,在执行didFinishLaunchingWithOptions:
代理方法后,切换为激活状态。锁屏或者按下Home键后,应用程序会先切换到后台状态,一段时间后,大多数应用程序会被系统挂起,应用程序切换到挂起状态。内存吃紧时,系统可能会清除被挂起的应用程序,应用程序切换到未运行状态。
其在启动时做了什么?
启动未运行的应用程序,系统首先加载应用程序自身的可执行文件,接着加载动态链接库(所引用的iOS系统提供的framework),并向runtime注册Objective-C类以及把category中定义的方法插入到类的方法列表中,然后会调用main
函数将控制权移交给 UIKit 框架。在main
函数的实现中,会调用UIApplicationMain
函数。该函数会创建并初始化一个UIApplication
对象和一个AppDelegate
对象,并将AppDelegate
对象设为UIApplication
对象的代理,接着从可用的storyboard文件中加载应用程序的用户界面,并调用AppDelegate
对象的willFinishLaunchingWithOptions
和didFinishLaunchingWithOptions:
方法来执行自定义代码进行初始化设置,然后启动应用程序主线程的 run loop 来开始接收事件。
如何优化应用程序的启动时间?
main
函数调用之前的优化:
- 删除不必要的framework。
- 删除项目中不再使用的Objective-C类以及类中声明了但却没有使用过的方法。
main
函数调用之后的优化:
- 删除
willFinishLaunchingWithOptions:
和didFinishLaunchingWithOptions:
方法中在调试时用到的NSLog
语句。每次NSLog
打印会与苹果系统日志(ASL)进行一次连接,然后在打印结束后断开这次连接,效率较差。 - 将必须第一时间启动的日志库、统计库等,可以仍然放在
didFinishLaunchingWithOptions:
方法里面启动。需要在进入应用程序主页面之前完成的配置(项目配置,推送配置等)可以在进入广告页面后再设置。
与UIViewController对象关联的UIView对象的生命周期
- 在屏幕上呈现
UIViewController
时,会调用其loadView
方法来加载其关联的视图。 - 当
loadView
方法加载视图完毕后,会调用其viewDidLoad
方法。可以在该方法中配置视图的一些属性。 - 在视图将要显示在屏幕上之前,会调用其
viewWillAppear
方法。 - 当视图已经在屏幕上显示出来之后,会调用其
viewDidAppear
方法。 - 当视图将要从屏幕上移除时,会调用其
viewWillDisappear
方法。 - 当视图已经从屏幕上移除时,会调用其
viewDidDisappear
方法。
自 iOS6 起,viewWillUnload
和viewDidUnload
这两个方法被废除了。当系统发出内存警告的时候,会自动把视图控制器的 view
给清除掉。 同时,系统还会调用视图控制器的didReceiveMemoryWarning
方法,可以在该方法实现中释放一些额外的资源。
响应者、响应者链和事件处理
应用程序使用响应者对象来接收和处理事件,属于UIResponder
类的实例对象都是响应者。当应用程序接收到一个事件时,UIKit会自动将该事件指向最合适的响应者对象,此响应者称为第一响应者。响应者接收到原始事件后,必须处理该事件或者将此事件转发给另一个响应者。UIkit定义了如何将事件从一个响应者传递到下一个响应者的默认规则,我们可以随时通过覆盖响应者对象中的nextResponder
属性来更改这些规则。
对于每种类型的事件,UIKit都会指定一个第一响应者,并首先将事件发送给该对象,第一响应者根据事件的类型而有所不同:
- 触摸事件:第一响应者是触摸点所在的视图。
- 按压事件:第一响应者是有焦点的响应者。
- 摇晃运动事件:第一响应者是由我们自己(或者UIKit)指定为第一响应者的对象。
- 远程控制事件:第一响应者是由我们自己(或者UIKit)指定为第一响应者的对象。
- 编辑菜单消息:第一响应者是由我们自己(或者UIKit)指定为第一响应者的对象。
可以通过覆写响应者对象的nextResponder
属性来更改响应者链,许多UIKit类已经覆盖此属性并返回了特定的对象:
-
UIView
对象:如果这个视图是视图控制器的根视图,那么下一个响应者就是这个视图控制器;否则,下一个响应者就是它的父视图。 -
UIViewController
对象:如果视图控制器的视图是window的根视图,则下一个响应者就是window;如果视图控制器是被另一个视图控制器呈现的,则下一个响应者是这个呈现视图控制器。 -
UIWindow
对象:window的下一个响应者是UIApplication
对象。 -
UIApplication
对象:当应用程序的委托对象是UIResponder
类的实例,而不是视图、视图控制器或者应用程序对象本身时,其下一个响应者就是应用程序的委托对象。
触摸事件的第一响应者是触摸位置所在的视图,UIKit使用命中测试来确定触摸位置所在的视图。具体来说,UIKit将触摸位置与视图层中的视图对象的边界进行比较。UIView
的hitTest:withEvent:
方法会遍历视图层,寻找包含指定触摸位置的最深的子视图,这个视图就会成为触摸事件的第一响应者。
hitTest:withEvent:
方法会遍历当前视图层,并调用每个子视图的pointInside:withEvent:
方法来判断子视图的边界是否包含触摸点。如果pointInside:withEvent:
方法返回YES,则会同样遍历子视图的视图层,直到找到包含触摸点的最上层视图。如果视图不包含该触摸点,那么此视图层分支会被忽略掉。因此,我们可以通过覆写hitTest:withEvent:
方法来隐藏子视图中的触摸事件。此方法会忽略掉被隐藏、已禁用用户交互或者alpha
小于0.01的视图对象。在确定命中视图时,此方法不会考虑视图的内容。因此,即使触摸点位于该视图内容的透明部分,仍然可以确定命中此视图。当视图的clipsToBounds
的属性为NO
时,如果触摸点在视图的边界之外,即使它的子视图恰好包含该触摸点,但子视图超出了视图的边界,hitTest:withEvent:
方法也不会返回命中了此视图。
UIKit确定触摸事件的第一响应者之后,如果这个响应者类覆写实现了touchesBegan:withEvent:
、touchesMoved:withEvent:
和touchesEnded:withEvent:
方法中的一个或者多个,那么当触摸开始发生时,系统会调用响应者对象的touchesBegan:withEvent:
方法去回应触摸事件。当触摸位置移动时,会调用响应者对象的touchesMoved:withEvent:
方法去回应,当触摸结束时,会调用touchesEnded:withEvent:
方法去回应。如果这几个方法一个都没有被实现,那么UIKit会沿着默认的响应者链去传递触摸事件。如果响应者链中有响应者实现了前述方法,那么该响应者对象就会去处理传递来的触摸事件。否则,该触摸事件就不会被处理。
UIView和CALayer的区别和联系是什么?
UIView
是iOS系统中界面元素的基础,所有的界面元素都继承自它。每个UIView
对象都包含一个CALayer
对象,该CALayer
对象是UIView
对象的RootLayer,并且UIView
对象是CALayer
对象的委托。UIView
继承自UIResponder
,能够接收并响应事件,其只是负责管理要显示的内容。而CALayer
继承自NSObject
,不能响应事件,其负责绘制要显示的内容。UIView
更像是CALayer
的管理器,对其与绘图和坐标相关的属性进行操作时,如frame
,bounds
等,实际上是在对其所包含的CALayer
对象的的相关属性进行操作。
UIView
对象和CALayer
对象都包含树状层级结构,UIView
对象有subViews,CALayer
对象有subLayers。CALayer
对象内部维护着三个树状层级结构,分别是PresentLayer Tree(动画树),Mode Layer Tree(模型树),Render Tree(渲染树)。在对某个属性进行动画修改的时候,在动画的其实是Layer的presentLayer
的属性值,而最终显示在界面上的其实是modeLayer。
对不是UIView
对象的RootLayer的CALayer
对象的属性进行修改时,默认会触发隐式动画。而对UIView
对象的属性修改不会触发动画。
高性能地给UIImageView设置圆角的方法有哪些?
使用Core Graphics框架配合UIBezierPath
贝塞尔曲线重新绘制带圆角的图片:
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:50] addClip];
[self drawInRect:self.bounds];
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
在要设置圆角的UIImageView上在覆盖一个中间透明的只对圆角遮挡的图片,来达到设置圆角的效果。
使用drawRect方法有什么影响?
此方法的默认实现不做任何事情。使用Core Graphics和UIKit等技术绘制其视图内容的子类应该覆盖此方法并在其中实现其绘制代码。如果视图以其他方式设置其内容,则不需要重写此方法。
第一次调用该方法时,通常是绘制整个视图的内容。后续调用视图的drawRect
方法时,可能只重新绘制视图中的指定rect部分的内容。
当第一次显示视图时或者当发生使视图的可见部分无效的事件时,将调用此方法。我们不应该直接调用drawRect
方法,这样做是没有效果的。要使视图的全部或部分内容无效并因此导致全部或该部分内容被重绘,请调用setNeedsDisplay
或者setNeedsDisplayInRect:
方法。这些方法会将视图的内容标记为无效,并在下一次渲染周期时重新绘制图片。
界面滑动卡顿优化技巧
- Cell重用。
- 使用frame布局替换约束布局。
- 在获取Cell高度的代理方法中计算并缓存Cell高度。
- 在界面滚动停止后才开始加载图片。
- 尽量使用不带alpha通道的图片以及不要将视图的背景颜色设置为透明色,以提高内容渲染速度。
UICollectionView自定义布局过程
实现自定义布局有两个关键任务:
- 指定可滚动内容区域的大小。
- 为每个单元格和补充视图提供布局属性对象以便集合视图进行定位。
在布局过程中,集合视图首先会调用布局对象的prepareLayout
方法,我们需要在该方法中提前计算确定布局属性信息时所需的数据并缓存,从计算出来的数据中要能够得知集合视图整个内容区域的大小。
随后,集合视图调用布局对象的collectionViewContentSize
方法获取内容大小来配置其滚动视图。在该方法中我们需要根据提前计算的数据返回整个内容区域的大小。
接下来,集合视图基于当前的滚动位置调用布局对象的layoutAttributesForElementsInRect:
方法查找在特定区域中的单元格和视图的布局属性信息,然后使用它们来定位单元格和补充视图。我们需要在该方法中遍历提前生成的所有的布局属性信息,检查每个布局信息的frame,返回所有frame和给定rect相交的布局属性信息。
在集合视图滚动过程中,集合视图会在适当的时间重复调用布局对象的layoutAttributesForElementsInRect
方法来获取要显示的单元格的布局信息。
URL是什么?
统一资源定位符。通过一个URL,能找到互联网上唯一的一个资源。
URL的基本格式 = 协议://主机地址/路径。
- 协议:不同的协议代表着不同的资源查找方式、资源传输方式。
- 主机地址:存放资源的主机(服务器)的IP地址(域名)。
- 路径:资源在主机(服务器)中的具体位置。
URL中常见的协议有以下几种:
- HTTP:超文本传输协议,访问的是远程的网络资源。
- file:访问的是本地计算机上的资源。
- mailto:访问的是电子邮件地址。
- FTP:访问的共享主机的文件资源。
HTTP 协议和 HTTPS 协议
HTTP 协议
中文译为超文本传输协议,是一个应用层协议。其规定了客户端和服务器之间的数据传输格式,能够让客户端和服务器有效地进行数据沟通。
完整的HTTP通信分为两大步骤:
- 请求:客户端向服务器索要数据。
- 响应:服务器返回客户端索要的数据。
一个完整的由客户端发给服务器的请求中包含以下内容:
- 请求行:包含了请求方法、请求资源路径、HTTP协议版本。(
GET
,/Server/resources/images/1.jpg
,HTTP/1.1
。) - 请求头:包含了客户端想访问的服务器主机地址以及客户端的类型、软件环境、语言环境、所能接收的数据类型和支持数据压缩格式信息。(客户端想访问的服务器主机地址:
Host : 192.168.1.105:8080
,客户端的类型和软件环境:User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9) Firefox/30.0
,客户端所能接收的数据类型:Accept : text/html, */*
,客户端的语言环境:Accept-Language : zh-cn
,客户端支持的数据压缩格式:Accept-Encoding : gzip
。) - 请求体:客户端发送给服务器的具体数据。
一个完整的服务器响应包含以下内容:
- 状态行:包含了HTTP协议版本、状态码、状态英文名称。(
HTTP/1.1
,200
,OK
。) - 响应头:包含了对服务器的描述、对返回数据的描述。(服务器的类型:
Server: Apache-Coyote/1.1
,返回数据的类型:Content-Type: image/jpeg
,返回数据的长度:Content-Length: 56811
,响应的时间:Date: Mon, 23 Jun 2014 12:54:52 GMT
。) - 实体内容:服务器返回给客户端的具体数据。
HTTP协议定义了一些请求方法,不同的方法对资源有不同的操作方式。PUT
:增,DELETE
:删,POST
:改,GET
:查。最常用的是GET
和POST
,实际上GET
和POST
都能做到增删改查。
HTTP协议具有以下优点:
- HTTP协议比较简单,所以HTTP服务器的程序规模小,因而通信速度很快。
- 允许传输任意类型的数据,非常灵活。
- HTTP/0.9 和 HTTP/1.0 使用非持续连接,限制每次连接只处理一个请求。服务器对客户端的请求做出响应后,马上断开连接,这种方式可以节省传输时间。自 HTTP/1.1 起,默认保持连接。简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。但是也不会永久保持连接,它有一个保持时间。
缺点:
- HTTP协议是一种无状态协议。无状态是指协议对于事物处理没有记忆能力,服务器不知道客户端是什么状态。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。
- 明文传输数据,缺乏安全性。
解决HTTP协议无状态的问题:
- 通过Cookies保存状态信息:Cookie是一种在客户端保存状态信息的机制。客服端发送请求到服务器时,服务器会生成一个标识客户端的Cookie,并将其与数据一起返回给客户端,由客户端保存在缓存中。客户端再次发送请求时,会将该Cookie发送给服务器,服务器通过Cookie来确认客户端的状态信息。如果Cookie不设定过期时间的话,关闭浏览器时,Cookie就失效了。如果设置了Cookie的过期时间,那么浏览器会把Cookie保存到硬盘中,再次打开浏览器时,会依然有效,直到超过设置的有效期。Cookie机制将数据保存在客户端,缺乏安全性,且数据大小有限制。
- 通过Session保存状态信息:Session是一种在服务器端保存状态信息的机制。客户端向服务器发送请求时,服务器会生成一个session并保存,同时返回一个sessionId给客户端。客户端在后续的请求中将sessionId发给服务器,服务器通过该sessionId来确认客户端的状态信息。服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效了。Session机制将数据保存在服务器,会占用服务器的性能。
常见的响应状态码及其含义:
- 200:请求成功。
- 301:请求的数据已更改位置,并且是永久性的更改。
- 400:客户端请求的语法错误,服务器无法解析。
- 404:服务器无法根据客户端的请求找到资源。
- 500:服务器内部错误,无法完成请求。
HTTPS 协议
HTTP 协议以明文方式发送数据到服务器,因此其不适合传输一些敏感信息,如账号、密码等。
HTTPS 协议中文译为安全套接字层超文本传输协议。为了数据传输的安全,HTTPS 协议在 HTTP 协议的基础上加入了 SSL 协议, SSL 协议依靠证书来验证服务器的身份,并为客户端和服务器之间的通信加密。
单向认证:
- 客户端向服务器发送请求。
- 服务器响应请求,把服务器的证书发送给客户端。
- 客户端接收到证书,然后和客户端本地的证书对比。如果证书不一致或者无效,那么断开连接。如果通过,继续下一步。
- 客户端产生一个随机密钥,然后经服务器证书中的公钥进行加密,传给服务端。
- 服务端拿到加密密钥,用服务器的私钥解开密钥,得到对称密钥。
- 服务端和客户端指定这个密钥为加密密钥,握手结束。
- 客户端和服务端开始通讯,通讯数据由对称密钥加密。
双向认证:
- 双向认证比单向认证多了一步,就是服务器也要对客户端进行认证。双向认证不仅要在服务器和客户端都安装服务器证书,还需要在服务器放一个根证书和在客户端本地放一个由服务器的根证书签名的 p12 证书。
- 在单向认证的第4步中,客户端使用 p12 证书对数据进行签名,把 p12 证书和签名以及加密的对称密钥传给服务器,服务器根据根证书对 p12 证书和签名数据进行验证。
HTTPS 协议和 HTTP 协议的区别
- HTTPS 协议需要到 CA 申请证书,一般免费证书很少,需要交费。
- HTTP 协议明文传输信息,HTTPS 协议使用SSL协议加密传输信息。
- HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- HTTP的连接很简单,是无状态的。HTTPS协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
HTTP的POST请求和GET请求有什么区别?
POST请求和GET请求的主要区别表现在参数传递上。
GET请求会在URL后面以?
的形式拼接上发送给服务器的参数,多个参数之间用&
隔开,例如:http://ww.test.com/login?username=123&pwd=234&type=JSON。由于浏览器和服务器对URL的长度有限制,因此在URL后面附带的参数是有限制的,通常不能超过1KB。
POST请求将发送给服务器的参数全部放在请求体(Body Data)中,理论上POST请求传递的参数是没有限制的(具体要看服务器的处理能力)。
GET请求的安全性比POST请求的安全性要差些,如果包含敏感信息,建议使用POST请求。如果仅仅是查询数据,建议使用GET请求。如果是增加、修改、删除数据,建议使用POST请求。
POST请求的 body 使用 form-urlencoded 和使用 multipart/from-data 有什么区别?
发送纯文本数据时,使用 form-urlencoded 格式对数据进行编码。发送的数据包含图片、音频或其他二进制数据时,使用 multipart/from-data 格式对数据进行编码。
UDP协议、TCP协议和 Socket
ISO制定的OSI七层模型由下至上分别为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
UDP协议
UDP协议的全称是用户数据报协议,它是一种传输层协议。使用UDP协议传输数据时,服务器在发出数据报文之后,不会确认对方是否已接收到数据,也就不需要在客户端和服务器之间建立连接。因此,UDP协议是不可靠的。UDP协议发送的每个数据报文的大小限制在64KB之内,所以其传输速度非常快。其应用场景包括多媒体教室、网络视频会议系统等。
TCP协议
TCP协议的全称是传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层协议。其主要解决数据如何在网络中传输,而应用层的HTTP协议主要解决如何包装数据。
建立起一个TCP连接需要经过三次握手:
- 第一次握手:客户端发送SYN消息(握手信号)到服务器,等待服务器确认。
- 第二次握手:服务器收到SYN消息后,向客户端发送SYN+ACK消息,告知客服端服务器收到了SYN消息.
- 第三次握手:客户端收到服务器的SYN+ACK消息后,向服务器发送ACK(确认)消息,告知服务器客户端收到了SYN+ACK消息。
三次握手完毕后,正式开始在客户端和服务器之间传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP连接将被一直保持下去。
需要断开TCP连接时,服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过四次挥手:
- 第一次挥手:客户端发送FIN消息到服务器,等待服务器确认。
- 第二次挥手:服务器收到FIN消息后,发送ACK消息给客户端,告知客户端服务器收到了FIN消息。
- 第三次挥手:服务器关闭与客户端的连接,并发送FIN消息给客户端。
- 第四次挥手:客户端收到FIN消息后,发送ACK消息给服务器,告知其客户端收到了FIN消息。然后,客户端关闭与服务器的连接。
断开TCP连接需要四次挥手而不是三次是因为关闭连接时,当接收方收到对方的FIN消息后,仅仅表示对方没有数据需要发送了。但是接收方可能还有数据需要发送给对方,所以可能不会马上断开TCP连接。
Socket
套接字(socket)是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。
应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与 TCP/IP 协议交互提供了套接字接口。通过套接字接口区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
建立 Socket 连接至少需要一对套接字,其中一个运行于客户端,另一个运行于服务器。
创建 Socket 连接时,可以指定使用的传输层协议(TCP或UDP)。当使用TCP协议进行连接时,该 Socket 连接就是一个 TCP 连接。
JSON和XML两种数据结构的区别,JSON解析和XML解析的底层原理。
- XML的可读性比JSON要强一点;
- XML对数据描述性比较好。
- 两者都具有良好的扩展性;
- JSON的编码和解码更简单,而XML需要考虑子节点和父节点;
- JSON的数据体积小,传输速度更快;
JSON解析的底层原理:
遍历字符串中的字符,并根据特殊字符{}
、[]
和:
进行区分。{}
代表字典,[]
代表数组,:
是字典的键和值的分隔符。最终结果是将JSON文本转换为一个字典或者数组。
XML解析的底层原理:
- DOM解析:根据节点将XML文本转换为一个包含其所有内容的树,并对树进行遍历。使用DOM解析时,需要处理整个XML文本,对内存和性能的要求比较高。
- SAX解析:遍历XML文本,当发现给定的节点时,会触发回调来告知指定的标签已经找到。当只需要处理文档中所包含部分数据时,使用SAX解析可以在找到指定的标签后就停止解析。