进程间通信( IPC )
| 名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Bundle | 简单易用 | 只能传输 Bundle 支持的数据类型 | 四大组件间的进程间通信 |
| 文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
| AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有 RPC 需求 |
| Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很处理高并发清醒,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求 |
| ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 | 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 | 一对多的进程间数据共享 |
| Socket | 可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点烦琐,不支持直接的RPC | 网络数据交换 |
IPC(Intent Process Communication) :泛指进程之间任何形式的通信行为
RPC(Reomote Procedure Call) :负责与远程服务端进行过程间通信的行为
Bundle
实现了Parcelable对象,达到序列化目的,内部使用一个ArrayMap进行相关数据的存储
Messenger
Messenger是基于消息Message传递的一种轻量级IPC进程间通信方式 (通过在一个进程中创建一个指向Handler的Messenger,并将该Messenger传递给另一个进程),当然本质就是对Binder的封装(也是通过AIDL实现的 )
通过Messenger可以让我们可以简单地在进程间直接使用Handler进行Message传递,跨进程是通过Binder(AIDL实现),而消息发送是通过Handler#sendMessage方法,而处理则是 Handler#handleMessage处理的;当然除了Handler之外还可以是自定义的相关的某些IBinder接口,Messenger的跨进程能力是由构造时关联的对象提供的
//Messenger send
public void send(Message message) throws RemoteException {
//mTarger = IMessenger
mTarget.send(message);
}
//实现类 MessengerImpl *在Handler中
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
msg.sendingUid = Binder.getCallingUid();
Handler.this.sendMessage(msg);
}
}
总结
-
使用Messager来传递Message,Message中能使用的字段只有what、arg1、arg2、Bundle和replyTo, 自定义的Parcelable对象无法通过object字段来传输
如果是自定义的 Parcelable,在Message反序列化
readFromParcel()时getClassLoader()会报异常(ClassNotFoundException);但是Bundle对Parcelable进行了兼容(setClassLoader),可以直接将序列化对象在外部接受线程进行反序列化因为Message在Binder进行无法提前进行干预,Bundle在我们自己的接收进程可以进行干预
-
Message中的Bundle支持多种数据类型,replyTo字段用于传输Messager对象,以便进程间相互通信
-
Messager以串行的方式处理客户端发来的消息,不适合有大量并发的请求
-
Messager方法只能传递消息,不能跨进程调用方法
AIDL Android Interface Definition Language
Android接口定义语言,用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
其内部仅支持六种数据类型:
- 基本数据类型(
short并不支持) String和CharSequence- List 接口(会自动将List接口转为
ArrayList),且集合的每个元素都必须能够被 AIDL 支持 - Map 接口(会自动将 Map 接口转为
HashMap),且每个元素的 key 和 value 都必须被 AIDL 支持 Parcelable的实现类- AIDL 接口本身
如果 AIDL 文件中使用了自定义的 parcelable 对象,那么必须新建一个和它同名的 AIDL 文件
- 除了基本类型数据,其它类型的参数必须标上方向:in、out、inout。in 表示输入;out 表示输出;inout 表示输入输出型的参数,注意按需使用,因为 out 以及 inout 在底层实现是需要一定开销的。
- AIDL 接口仅支持方法,不支持静态变量,也不支持普通的接口
- AIDL 所有相关的类在另一个应用使用的时候需要保证所有文件的路径完全一致,因为跨进程涉及到序列化和反序列化
Binder
为什么是Binder
Android系统基于Linux内核,Linux提供了管道、消息队列、共享内存和socket等IPC机制,在性能、稳定性、安全性几个方面下,还是选择了Binder
-
性能
Socket传输效率低,开销大主要用在跨网络的进程和本级上进程间低速通信,并且Socket需要拷贝2次数据(A -> 内存缓存区 -> 内核缓存区 -> 内存缓存区 -> B),Binder只需要拷贝1次,性能上仅次于共享内存
IPC方式 数据拷贝次数 共享内存 0 Binder 1 Socket/管道/消息队列 2 -
稳定性
Binder基于C/S架构,客户端(Client)有什么需求丢给服务器(Service)去完成处理,架构清晰、职责明确又相互独立,自然稳定性更好。
内存共享虽然无需拷贝,但是控制负责,难以使用 -
安全性
Android为每个App都分配独立的UID,传统的IPC只能由用户在数据包中填入UID,且访问接入点是开放,容易被恶意篡改和利用。可靠的身份标识只有由IPC机制在内核中添加,同时Binder即支持实名Binder,又支持匿名Binder,安全性更高一些
Binder原理
-
动态内核可加载模块 && 内存映射
动态内核可加载模块(Loadable Kernel Module,LKM)的机制 :模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行
Android可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信
运行在这个内核空间,各个进程通过Binder实现通信的内核模块叫Binder驱动(Binder Dirver)
内存映射:将用户空间的一块内存区域映射到内核空间,映射关系建立后,用户对这块内存区域的修改直接反映到内核空间;反之内核空间对这段区域的修改也直接反映到用户空间
内存映射减少数据拷贝次数,实现用户和内核的高效互动,两个空间各自修改都能直接反映在映射的内存区域,从而被对方空间及时感知,基于此,内存映射能够提供对进程间通信的支持
-
Binder IPC实现原理
Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。
传统如果需要把磁盘数据读取到进程用户区域,需要两次拷贝(磁盘 -> 内核空间 -> 用户空间),在这种场景下mmap可以通过物理介质和用户空间之间建立映射,减少数据拷贝次数,用内存读写代替I/O,提高文件读去效率
Binder并不存在物理介质,Binder利用用户空间和内核空间创建数据接收缓存空间建立映射
一次完整的Binder IPC通信过程:
- 首先Binder驱动在内核创建一个数据接收缓存区
- 接着在内内核中开辟一块内核缓存区,建立内核缓存区和内核数据接收区之间的映射关系,以及内核中数据接收缓存区和接受进程用户空间地址的映射关系
- 发送方通过系统调用copy_from_user()将数据copy到内核中的内核缓存区,由于内核缓存区与接受进程的用户空间存在内存映射,因此就相当于吧数据发送到了接收进程的用户空间,这样变完成了一次进程通信
知识点:为什么需要开辟两个缓存区,这两个缓存区互相映射的目的是什么,直接使用一个缓存区不行吗?
【内存缓存区】是属于内核空间的,而【数据接收缓存区】是属于Binder驱动的,Binder驱动没办法让【内核缓存区】跟接受进程的用户空间直接映射,所以必须要在自己内部多加一块【数据接受缓存区】做映射中转。
用户空间只会信任内核空间,binder驱动属于第三方,无法直接让用户空间和内核空间通过binder进行接收,所以必须使用一道中转区域实现
-
Binder通信过程
-
首先,一个进程使用
BINDER_SET_CONTEXT_MGR命令通过 Binder 驱动将自己注册成为ServiceManager -
Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表
-
Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。
-
-
Binder通信中的代理模式
当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这个方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的
当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了
Binder的完整定义
-
从进程间通信的角度看,Binder 是一种进程间通信的机制
-
从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象
-
从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理
-
从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程的对象特殊处理,自动完成代理对象和本地对象之间的转换
Android Service
-
介绍:
Service不是一个线程,Service通常运行在当成宿主的主线程中,如果Service中需要执行耗时操作需要额外开辟子线程,避免ANR异常。除非在AndroidManifest文件中声明进程名,否则Service所在进程就是默认的Application进程
-
目的:
执行一些需要在后台运行的事务和通过Service对外开放某些操作
startService
-
调用startServer:首次启动,先调用
onCreate()在调用onStartCommand();当Service已经启动后直接调用onStartCommand() -
调用
stopService()/stopSelf(),会执行onDestroy()结束Service的生命周期 -
startService方式启动Service不会调用
onBind(),startSerivce()可多次调用,每次都会执行onStartCommand()不管调用多少次,只需要一次
stopService()就可以结束Service,如果没有主动结束,Service会一直存活在后台 -
onStartCommand()返回值-
START_STICKY = 1:Service所在进程被kill之后,系统会保留Service状态为开始状态
系统会尝试重启Service,当服务被再次启动,传递过来的Intent可能为null,需要注意
-
START_NOT_STICKY = 2:Service所在进程被kill之后,系统不再重启服务
-
START_REDELIVER_INTENT:系统自动重启Service,并传递之前的Intent
-
bindService
- bindService是异步执行,需要额外构建一个ServiceConnection对象用于接收bindService状态,同时还要指定bindService的类型
- bindService启动方式也可以执行多次,与startService方式 不同,当发起对象与Service已经成功绑定后,不会多次返回SetviceConnection中的回调方法
- 通过
bindService与Service绑定后,当没有对象与Service绑定时,Service生命周期结束,包括绑定对象被销毁或者主动调用unbindService
startService和bindService同时开启
当同时调用startServer和bindService后,需要分别调用stopService和unbindService,Service才会走onDestroy()
一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁
HTTP
Http在网络中的分层
OSI模型:
由于最上面三层(应用层、表示层、会话层)在TCP/IP组中是一个应用层,就合并到应用层
TCP/IP模型:
Http在网络分层中属于应用层协议
Http报文
-
请求报文:客户端向服务器发送的报文
由 请求行,请求头,空行,请求数据 这四部分组成
-
响应报文:服务端向客户端发送的报文
由 状态行,请求头,空行,请求数据 这四部分组成
RequestMethod请求方法
-
GET:使用GET的请求用于从服务器获取数据;
-
HEAD:和GET请求相似,但是没有响应体;
-
POST:用于将内容提交到服务器,通常用于修改或者删除服务器上的资源;
-
PUT:通常用于修改服务器上的数据;
-
DELETE:删除服务器指定的资源;
-
CONNECT:建立一个到由目标资源标识的服务器的隧道,用于代理服务器;
-
OPTIONS:用于描述目标资源的通信选项,通常用于跨域请求;
-
TRACE:沿着到目标资源的路径执行一个消息环回测试,用于追踪请求;
Get和Post的区别
| Get | Post | |
|---|---|---|
| 缓存 | 能被缓存 | 不能缓存 |
| 编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded或multipart/form-data。为二进制数据使用多重编码 |
| 对数据长度的限制 | URL最大长度是2048个字符 | 无限制 |
| 对数据类型的限制 | 只允许 ASCLL 字符 | 没有限制,也允许二进制数据 |
| 可见性 | 数据在URL中是可见的 | 数据不会显示在URL中 |
扩展疑问点:
-
安全性
get和post都不存在所谓的安全性,即便是post在url中不可见,但是通过抓包一样的能正常看到具体的请求参数,想要安全需要使用https方式对数据进行加密请求
-
get可以带body
GET方法是可以带上Body请求的,但是可能会发生拒绝请求的错误,或者一些其他的错误出现
Header头部
Header是一个键值对,属于元数据
四大类型
- 通用头:是客户端和服务器都可以使用的头部,可以在客户端、服务器和其他应用程序之间提供一些非常有用的通用功能,如Date头部;
- 请求头:是请求报文特有的,它们为服务器提供了一些额外信息,比如客户端希望接收什么类型的数据,如Accept头部;
- 响应头:便于客户端提供信息,比如,客服端在与哪种类型的服务器进行交互,如Server头部;
- 实体头:指的是用于应对实体主体部分的头部,比如,可以用实体头部来说明实体主体部分的数据类型,如Content-Type头部;
常用Header类型
- Host:客户端指定自己想访问的WEB服务器的域名/IP地址和端口号
- Content-Type
- text/html:文本数据类型
- x-www-form-urlencoded:表单类型,常用与提交数据到服务器,如注册界面的数据提交
- multitype/form-data:二进制文件表单提交类型,如上传图片
- application/json,image/jpeg,application/zip ...:提交单项内容到服务器,如提交json,提交image,提交zip包到服务器
- Content-Length:指定响应体的长度,表示我这次请求需要返回多少直接内容,多用于分块传输
- User-Agent:用于向服务器表明身份信息,标识我是来自客户端/Web短的请求
- Range:表示想从服务器取哪部分的内容,比如byte:start-end;用于多线程下载或者断点续传
- Accept:告诉服务器客户端能接受什么类型的数据,比如text/html
- Accept-Charset:告诉服务器能接收的字符集,比如utf-8
- Accept-Encoding:告诉客户端能接受的编码压缩类型,如zip
- Content-Encoding:服务器告诉客户端自己用什么压缩方法,如gzip,deflate等
Cache缓存
http为什么需要
- 通过网络获取内容,会受速度影响并且开销很大,需要在客户端和服务器之间建立通讯,然后通过传包的方式来进行通讯,受网络速度影响,有可能会响应比较慢,导致前端的展示体验不好;
- 减少开销,如果每一次请求都从服务器取的话,那么服务器将会面临巨大的压力,有可能会挂掉,导致访问不了;
- 使用缓存还可以减少网络带宽的占用,过多的请求会导致网络阻塞,从而响应速度也变慢;
- 减少无意义的重复请求,比如某个页面的数据,运营一天才会去修改一次,但是我每次进来这个页面都去从服务器请求数据,这样是没有意义的,只会导致资源的浪费;
总结:使用缓存可以大大的提升响应速度,降低服务器压力,减少带宽的占用
http缓存机制
-
通过ETag验证缓存
本质上是一个header,称为验证令牌,由服务器根据文件生成hash值或者其他的某个值
旨在验证本地缓存的时效性,服务器返回相同数据时,服务器验证对比令牌后,返回
304 Not Modified进行回传,通知客户端可以使用本地的缓存并更新本地缓存的时效 -
Cache-Control 缓存控制
用于定义缓存策略,定义在什么场景下,缓存多长时间,本质上是一个header
缓存指令:
- no-cache:会先通过ETag验证令牌和服务器进行通讯,判断服务器的数据是否有修改过,如果有修改过,则使用服务器返回的新的数据,否则的话就取缓存,使用这个指令会和服务器进行一次通讯
- no-store:指令为
no-store的情况下,一律不进行缓存,都是从服务器取数据,一般用于需要安全的场景或者需要实时刷新的场景 - max-age:表示当前请求的响应体的有效时间为多长,超过了这个时效则从服务器取数据
- public:表示可以被任何中间者(代理服务器,cdn等)或者浏览器缓存数据,通常情况下,public并不是必须的指令,有其他指令(比如max-age)表示了该请求可以被缓存
- private:表示不可以被任何中间者(代理服务器,cdn等)缓存数据,但是浏览器可以缓存该指令的数据
- must-revalidate:如果缓存不过期就可以继续使用,过期了就必须去服务器验证
-
Expirse缓存头
包含日期和时间,下次请求会将本地时间跟这个时间进行比对,如果缓存有效,则取缓存
由于本地时间和服务器时间不同步,会存在时效不准的问题,在HTTP1.1之后更多的是使用Cache-Control指令
HTTP 1.1
当下使用最多的最流行的版本
-
优化点
- 缓存:在 HTTP 1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP 1.1则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since等更多可供选择的缓存头来控制缓存策略。
- 带宽优化:新增range头字段,用于分块传输
- 长链接:头字段新增了Connection:keep-alive,表示可以复用一部分链接,在tcp连接上可以传达多个请求,以此来减少建立和连接带来的消耗
- 增加管线化技术(pipelining),在当前请求还没有返回时,可以发送下一个请求到服务器,以此来降低请求时间
缺点:
- 长链接:虽然加入长链接可以减少建立和连接带来的消耗,但是不同的域名的链接不能复用,只能重新创建长链接,会耗费资源,并且给服务器带来巨大的压力
- 在header中携带请求的数据量过大,造成流量的浪费,如果每次请求header的内容不变,但是header携带的数据量又很大的情况下,就会造成资源的浪费
- 管线化技术(pipelining),是服务器的处理是等处理完当前请求的响应之后,才会去处理下一个请求的响应,即使当前很多请求都已经处理完了,服务器还是得根据请求的顺序来进行响应
HTTP 2.0
HTTP 2.0基于谷歌开发的SPDY协议
- 优化点
- 多路复用:相较于HTTP 1.1中,请求串行,2.0多个请求可以并行请求
- Header压缩:使用首部表来跟踪和存储之前发送的键值对,对于相同的内容,不会再每次请求和响应时发送
- 数据优先级:可以设置数据优先级,服务器会优先处理和返回较高优先级的请求
- 服务端推送:不需要客户端在对服务器进行请求,服务器可以直接推送相关文件给客户端
断点续传功能
通过使用Http的Range头字段来实现,支持从服务器获取指定内容范围大小的文件
-
优点:传输效率高,传输过程受到网络性影响无需重新开始
可以同步开多个线程进行并行请求,每个线程负责一个区间段,如百度云、迅雷等下载流程
Cookie
-
概念:通过在请求和响应报文中写入Cookie信息来控制客户端的状态,解决HTTP无状态的问题,本质就是存储在浏览器上一个很小的文本文件(也可能存在本地文件里)
HTTP是一种无状态协议,没法保存之前请求响应的上下文信息,但是对于一些场景来说需要用到之前的状态,所以为了解决这个问题产生了Cookie。但是它并不是为了解决通讯协议无状态的问题,而是为了解决客户端与服务端会话状态的问题,这个状态是指后段服务的状态而不是通讯协议的状态。
-
流程:
- 客户端请求服务器
- 服务器生成Cookie信息使用Set-Cookie添加到响应报文头部上
- 客户端在拿到之后保存Cookie
- 在下次请求的时候通过把信息写入请求报文头部Cookie中传给服务端
HTTP和HTTPS的区别
HTTP
基础
Http超文本传输协议,应用层协议。主要用于Web上传输超媒体文本的底层协议,经常在浏览器和服务器之间传递数据。通信就是以纯文本的形式进行。
Http是无状态
无状态是HTTP协议对客户端请求状态没有进行存储,比如:每次请求都需要重新登录
Http是无连接
无连接主要是限制每次连接只处理一个请求。每次请求都是客户端发起请求,服务端响应请求,然后就断开。这期间通过三次握手建立连接,四次挥手断开连接。每次请求即便是多次请求并请求同一个资源,服务端都无法判断是否是相同请求,都需要重新响应请求。
为了解决客户端和服务端保持会话连接,通过cookie和session来记录http状态。
Http的其他特点是简单快速,只需传送方法和路径就可以向服务端进行请求;还有支持传输任意类型的数据对象。
HTTPS
基础
https是http的“升级”版本 :HTTPS = HTTP + SSL / TLS
SSL是安全层,TLS是传输安全层,是SSL的继承。使用SSL或TLS可确保传输数据的安全性。
Https传输的不再是文本,而是二进制流,使得传输更高效,且加密处理更加安全
Https工作流程
-
客户端请求Https请求并链接到服务器的443端口,此过程和请求Http请求一样,进行三次握手
-
服务端向客户端发送数字证书,其中包含公钥、证书颁发者、到期日期
现比较流行的加解密码对,即公钥和私钥。公钥用于加密,私钥用于解密。所以服务端会保留私钥,然后发送公钥给客户端。
-
客户端收到证书,会验证证书的有效性,验证通过后会生成一个随机的
pre-master key。再将密钥通过接收到的公钥加密然后发送给服务端 -
服务端接收后使用私钥进行解密得到
pre-master key -
获得
pre-master key后,服务器和客户端可以使用主密钥进行通信
Http与Https的区别
所以在回答 HTTP 与 HTTPS 的区别的问题,可以从下面几个方面进行回答:
- 加密: HTTPS 是 HTTP 协议的更加安全的版本,通过使用SSL/TLS进行加密传输的数据;
- 连接方式: HTTP(三次握手)和 HTTPS (三次握手+数字证书)连接方式不一样;
- 端口: HTTP 默认的端口是 80 和 HTTPS 默认端口是 443
TCP/IP
概念
-
IP协议时TCP/IP协议族的动力,为商城协议提供无状态、无连接、不可靠的服务
-
TCP是面向连接的传输层协议,UDP协议是面向无连接传输层协议(区别)
扩展:IP4和IP6的区别:IPv6具有比IPv4大得多的地址空间;IPv6使用了128位的地址,而IPv4只用32位
TCP三次握手和四次挥手
三次握手 客户端和服务器总共发送3个包
建立一个 TCP 连接时,需要客户端和服务器总共发送3个包 目的:链接服务器指定端口,建立TCP链接,并同步双方序列号和确认号,交换TCP窗口大小信息
socket 编程中,客户端执行 connect() 时。将触发三次握手
-
第一次握手
SYN(bit) = 1, seq = x(客户端随机生成的序列号)客户端发送一个 TCP 的
SYN bit标志位置为1的包,指明客户端打算连接的服务器的端口,以及初始序号x,保存在包头的序列号seq(Sequence Number)字段里,发送完成后,客户端进入SYN_SEND状态 -
第二次握手
SYN(bit) = 1, seq = y(服务端随机生成的序列号), ACK(bit) = 1, ACKnum = x + 1服务器返回确认应答,
SYN bit标志位和ACK bit标志位均为1。服务器端选择自己ISN序列号,放到Seq域里,同时将确认序号ACKnum(Acknowledgement Number)设置为客户端的ISN加1,即x + 1。返回完毕后,服务器端进入SYN_RCVD状态 -
第三次握手
ACK(bit) = 1, ACKnum = y + 1客户端再次发送确认包(ACK),
SYN bit标志位为0,ACK bit = 1,并把服务器发来的ACKnum序列号字段+1,放在确定字段中发给服务端。发送完毕后,客户端进入ESTABLISHED,当服务端接收到后也进入ESTABLISHED状态,TCP握手结束
四次挥手 拆除TCP链接发送3个包
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
-
第一次挥手
FIN = 1, seq = x任意一端(客户端、服务端),发送一个FIN位置为1的包,标识自己没有数据了,然后该端进入
FIN_WAIT_1(等待1)状态 -
第二次挥手
ACK = 1,ACKnum = x + 1第一次挥手接受端返回一个确认包,发完后,进入
CLOSE_WAIT状态,接受到第二次挥手端进入FIN_WAIT_2(等待2)状态 -
第三次挥手
FIN = 1, seq = y第二次挥手接受端准备好关闭连接时,向另一端发送结束连接请求,FIN = 1,发送完成后进入
LAST_ACK状态,等待另一端最后一个ACK -
第四次挥手
ACK = 1, ACKnum = y + 1第三次挥手接收端收到关闭请求,发送确认包,进入
TIME_WAIT状态,等待可能出现的重传ACK包,在等待某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到ACK,会认为另一端已经正常关闭,预置自己也关闭,进入CLOSED状态
- TCP三次握手的必要性:防止服务器端因接收了早已失效的连接请求报文,从而一直等待客户端请求,最终导致形成死锁、浪费资源
- TCP四次挥手的必要性:为了保证通信双方都能通知对方,需释放、断开连接
- 为什么客户端关闭连接前要等待2MSL时间:MSL: 最大报文段生存时间
整体进度流程:
TCP粘包问题
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾
-
粘包发生在那些情况下
-
由TCP连接复用造成的粘包问题
多个进程使用一个TCP连接,此时多种不同结构的数据进到TCP的流式传输
-
因为TCP默认会使用Nagle算法,此算法会导致粘包问题
而Nagle算法主要做两件事:
- 只有上一个分组得到确认,才会发送下一个分组;
- 收集多个小分组,在一个确认到来时一起发送。
多个分组拼装为一个数据段发送出去,如果没有好的边界处理,在解包的时候会发生粘包问题。
-
数据包过大造成的粘包问题
应用进程缓冲区的一条消息的字节的大小超过了发送缓冲区的大小
因为消息已经被分割了,有可能一部分已经被发送出去了,对方已经接受了,但是另外一部分可能刚放入套接口发送缓冲区里准备进一步发送,就直接导致接受的后一部分,直接导致了粘包问题的出现 -
接收方不及时接收缓冲区的包,造成多个包接收
-
流量控制,拥塞控制也可能导致粘包
-
-
粘包问题如何处理
- Nagle算法问题导致的,需要结合应用场景适当关闭该算法。
- 其他几种情况的处理方法主要分两种:
- 尾部标记序列。通过特殊标识符表示数据包的边界,例如\n\r\t,或者一些隐藏字符。
- 头部标记分步接收。在TCP报文的头部加上表示数据长度。
- 应用层发送数据时定长发送。
TCP与UDP区别
| 区别点 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 有序性 | 有序 | 无序 |
| 面向 | 字节流 | 报文(保留报文的边界) |
| 有界性 | 有界 | 无界 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有(慢开始、拥塞避免、快重传、快恢复) | 无 |
| 传输速度 | 慢 | 快 |
| 量级 | 重量级 | 轻量级 |
| 双工性 | 全双工 | 一对一、一对多、多对一、多对多 |
| 头部 | 大(20-60 字节) | 小(8 字节) |
| 应用 | 文件传输、邮件传输、浏览器等 | 即时通讯、视频通话等 |
| 协议 | 6 | 17 |
Socket
基本概念
-
定义:应用层 与
TCP/IP协议族通信的中间软件抽象层,是一个封装了 TCP / IP协议接口(API)从设计模式角度来看,Socket就是一个门面模式,把复杂的TCP/IP协议族隐藏在Socket接口后面,对应用层来说,让Socket去组织数据
1、Socket不是一种协议,而是一个编程调用接口(API),属于传输层(主要解决数据如何在网络中传输) 2、通过Socket,我们才能在Andorid平台上通过 TCP/IP协议进行开发 3、对用户来说,只需调用Socket去组织数据,以符合指定的协议,即可通信
IP地址和端口号组成了Socket,都是成对出现:
Socket = {(IP地址1 : PORT端口号),(IP地址2 : ROPT)}
原理&应用
Socket工作流程:
-
服务端先初始化Socket,然后与端口绑定(
bind),对端口进行监听(listen),调用Accept进行等待客户端连接会使用一个while死循环对
Accep()方法进行调用,Accep()方法在没有数据进行接收的处于堵塞状态扩展:使用while的目的,是service端可能对应多个客户端的连接,所以会在此使用一个死循环进行调用
-
客户端初始化Socket,然后连接服务器(
connect),如果连接成功,这时客户端与服务端的连接就建立了connect()方法负责连接,具体的实现连接取决于实现方案,如:TCP三次握手过程
-
客户端发送数据请求(
客户端send),服务端接收请求(服务端Receive)并处理请求,然后把回应数据发给客户端(服务端send),客户端读取数据(客户端Receive),最后关闭数据(close),一次交互结束。close方法TCP中就是四次挥手过程
Socket的使用主要类型:
- 流套接字(streamsocket):基于 TCP协议,采用
流的方式提供可靠的字节流服务 - 数据报套接字(datagramsocket):基于 UDP协议,采用
数据报文提供数据打包发送的服务
原理图:
Socket与Http对比
Socket属于传输层,因为TCP/IP协议属于传输层,解决的是数据如何在网络中传输的问题HTTP协议属于应用层,解决的是如何包装数据
由于二者不属于同一层面,所以本来是没有可比性的。但随着发展,默认的Http里封装了下面几层的使用,所以才会出现Socket & HTTP协议的对比(主要是工作方式的不同):
Http:采用 请求—响应 方式。
1、即建立网络连接后,当 客户端 向 服务器 发送请求后,服务器端才能向客户端返回数据。
2、可理解为:是客户端有需要才进行通信
Socket:采用 服务器主动发送数据 的方式
1、即建立网络连接后,服务器可主动发送消息给客户端,而不需要由客户端向服务器发送请求
2、可理解为:是服务器端有需要才进行通信