ストリーミングプロトコル:インターネット映像配信プロトコル記述(プログレッシブ、HLS、DASH、HDS、RTMPプロトコル)

はじめに:

以下、各単純な比較のためのプロトコルであり、各プロトコルの詳細については後述します。

ここに画像を挿入説明

A. プログレッシブのHttp:

まず、送信モードを分けることができ、ファイルのダウンロード、HTTPプログレッシブダウンロード、HTTPストリーミング、リアルタイムストリーミング 4つのカテゴリ。

プログレッシブダウンロード:ダウンロードがまだ動作している間、ダウンロードされたメディアファイルの再生を通過することである(演奏ダウンロード中)。支持体は、ビデオドラッグを開いて、高速再生の必要性をダウンロードされ、一般的なMP4やFLVファイル形式のサポートが良いですシーク、ターミナルプレイヤーが任意ので、ファイル全体を満たすために待っていない、再生を開始する時点を選択し、ダウンロードセクションを完了ことはできません再生する途中、短いクッションに、一時停止をクリックしてダウンロードしたファイルを持続される典型的なプログレッシブダウンロードです。

プログレッシブダウンロードファイルは、ダウンロードファイル間の形態であり、介在ストリーミングメディアを再生します徐々に再生中のファイルが起動し、コンピュータに保存されたファイル全体を取ることなく、ダウンロードする前に、ストリーミングメディアと同じ。違いは、コンテンツが完成し再生を持っていた後、ストリーミングメディアは、全体のプログレッシブダウンロードファイルがコンピュータに保存されていることです。

トランスミッション:ファイルのダウンロードは、httpプログレッシブは、httpストリーミングリアルタイムストリーミング

転送モード

  • あなたが使用する前にまず、ダウンロード、ファイル全体のダウンロードが完了します。
  • ダウンロード、使用側間は、ダウンロードしたファイルの断片化のエッジを使用していますが。
  • ファイルが断片化された、増分アップグレードは、接続、リアルタイム・トランスポート・ストリームを確立することです。

トランスミッション結果

  • あなたは、ファイル全体をダウンロードし、ファイルをローカルに保存する必要があります。
  • 違いは、ファイルのダウンロードを追求するだけでなく、ローカルファイル、大きなスペースを断片化したファイルの保存からダウンロードすることができるということです。
  • 投げ、無またはキャッシュ・スペースのわずかな量で放映されました。

送信内容

  • ファイルまたはファイル全体の一部。
  • 断片化されたファイルとインデックスファイル
  • 放送信号は、ファイルまたはリアルタイムストリーミング形式にエンコードされています。

配信方式:長いリンク短い接続

トランスポートプロトコル
HTTP / FTP HTTPのような

  • HLS / HDS / DASHなど:のHttpに基づいて
  • RTMP / RSTP、などです:リアルタイムフロー制御プロトコル。

需要-インデックスファイルは、ファイルのスライスをダウンロードするために、すべてのスライスのダウンロードアドレスが含まれています。接続、下端放送のリアルタイム伝送確立
ライブ-リアルタイムのストリーミング、放送のスライス接続、側吐出側を作成するためのファイルをダウンロードし、ライブアップデートインデックスファイルは、インデックスファイルを更新します。

ルックバックの:(フラッシュライブテストは、オンデマンドの一部)でなければなりません

テスト環境:win10 64魏の
テストプラットフォーム:chorm
テストチャンネル:cctv1の
プロトコル:HTTPのプログレッシブプロトコルの
テストアカウント:
テスト結果:(右の情報に目を向けるのアイデア)

ポート番号に関係なくプレイまたは一時停止状態の状態の、応答サイズは常にシーク後の再ダウンロード、増加して、8000です
ここに画像を挿入説明
ここに画像を挿入説明

II。HLSプロトコル:

HTTPライブストリーミング(頭字語HLS)は、アップル社によって提案されたことをHTTPベースのストリーミングメディアのネットワーク伝送プロトコルです。
HLSアーキテクチャ

関連モジュール

  1. メディアエンコーダ:トランスコーディング、およびMPEG-2トランスポートストリームにカプセル化するための装置によってストリーム出力。
  2. ストリーム分割器は:ブロードキャストのために、MPEG-2ストリームを出力(M3U8スライスとTS)の後に小さな複数の断片に分割されています。
  3. ファイルセグメンタ:需要、MPEG-2ストリームを出力(M3U8スライスとTS)の後に小さな複数の断片に分割されています。

原理:

简单讲就是把整个流分成一个个小段,基于 HTTP 的文件来下载,每次只下载一些,传输内容包括两部分:一是m3u8纯文本索引文件,二是TS媒体文件。简单的传输方式就是在一个m3u8中包含ts切片的url列表,依次下载播放。如下图所示:
ここに画像を挿入説明
还有就是有多级索引,如下图所示,客户端先下载一级Index file,它里面记录了二级索引文件(Alternate-A、Alternate-B、Alternate-C)的地址,然后客户端再去下载二级索引文件,该文件是按照带宽不同划分了不同分辨率的切片文件,然后客户端就可以根据实际的贷款按顺序下载TS视频文件并连续播放以实现码率自适应。

HLSプロトコル輸送性構造

一般为了加快速度,.m3u8 放在 web 服务器上,ts 文件放在 cdn 上。.m3u8 文件,其实就是以 UTF-8 编码的m3u 文件,这个文件本身不能播放,只是存放了播放信息的文本文件:
ここに画像を挿入説明
HLSプロトコルM3U8ファイルの解析

标签含义:

A: #EXTINF指示出下面TS片的时间长度,单位是秒,可以是整数也可以浮点数,浮点数一般精确到小数点后面3位。在示例中,第一个ts的时长为8秒。

同时,EXTINF也影响了播放器刷新M3U8文件的间隔,正常情况下,播放器会把当前下载的TS片的EXTINF的值作为每次刷新M3U8文件的间隔;
如果播放器发现本次取到的M3U8文件内容没有更新,会在1-2秒内再次刷新。

B:ts切片的时长不能大于#EXT-X-TARGETDURATION的值

C:#EXT-X-ENDLIST这个表示视频结束,有这个标志同时也说明当前的流是一个非直播流(有结束的意思)。

D:#EXT-X-PLAYLIST-TYPE:VOD的意思是当前的视频流并不是一个直播流,而是点播流。

直播:

  1. http 请求 m3u8 的 url(包含部分播放列表,没有结束标识)。
  2. 服务端返回一个 m3u8 的播放列表,这个播放列表是实时更新的(类似于滑动窗口机制),一般一次给出5段数据的 url。
  3. 客户端解析 m3u8 的播放列表,再按序请求每一段的 url,获取 ts 数据流。

点播:

  1. http 请求 m3u8 的 url。(包含所有播放列表,有结束标识)。
  2. 解析 m3u8 的播放列表,再按序请求每一段的 url,获取 ts 数据流。

备注:hls 协议是将直播流分成一段一段的小段视频去下载播放的,所以假设列表里面的包含5个 ts 文件,每个 TS 文件包含5秒的视频内容,那么整体的延迟就是25秒。因为当你看到这些视频时,已经将视频录制好上传上去了,所以时这样产生的延迟。当然可以缩短列表的长度和单个 ts 文件的大小来降低延迟,极致来说可以缩减列表长度为1,并且 ts 的时长为1s,但是这样会造成请求次数增加,增大服务器压力,当网速慢时回造成更多的缓冲,所以苹果官方推荐的ts时长时10s,所以这样就会大概有30s的延迟。

弊端:

  1. 采用HLS协议直播的视频延迟时间无法下到10秒以下,所以说对直播延迟比较敏感的服务请慎用HLS。(伪直播)。
  2. 对于点播服务来说, 由于 TS 切片通常较小, 海量碎片在文件分发, 一致性缓存, 存储等方面都有较大挑战。
测试:(某视网H5)

测试环境:win10 64位
测试平台:chorm
播放器类型:H5
协议:HLS协议
测试结果:
测试类型:直播

M3u8每次更新一个,类似于滑动窗口机制,第一次是列表12345,第二次是23456,第三次事34567,然后按列表地址下载ts切片,直播暂停,m3u8文件继续更新,ts文件停止下载。直播继续播放,继续下载ts切片。下图为央视网直播时连续更新三次m3U8文件的截图:

ここに画像を挿入説明
ここに画像を挿入説明
HLSプロトコルM3U8ファイルの連続ライブアップデート

(测试类型:点播)

M3u8一次性包含所有ts文件播放列表,依次进行下载播放,暂停的时候ts切片不下载,播放继续下载,seek进度条的时候,ts切片会从选择位置开始下载。下图为某视网点播seek的ts片段。如下图:
ファイルがロードされたマップされた後HLSプロトコルは、オンデマンドでTSファイルを追求M3U8

三.RTMP协议:

Real Time Message Protocol(RTMP)实时信息传输协议,它是由Adobe公司提出的一种应用层的协议。与HLS等基于Http的其他协议相比,Http协议是本地播放,而RTMP协议是服务器实时播放。

原理:

RTMP传输媒体数据的过程中,服务器端端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送给客户端。客户端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据。

RTMP协议规定,播放一个流媒体有几个前提步骤:握手,建立连接,建立流,播放。其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。

过程:
1. 握手(HandShake)

  • 一个RTMP连接以握手开始,双方分别发送大小固定的三个数据块

a)握手开始于客户端发送C0、C1块。服务器收到C0或C1后发送S0和S1。
b)当客户端收齐S0和S1后,开始发送C2。当服务器收齐C0和C1后,开始发送S2。
c)当客户端和服务器分别收到S2和C2后,握手完成。
握手
ここに画像を挿入説明

2.建立网络连接(NetConnection)

a)客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。
b)服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。
c)服务器发送设置带宽()协议消息到客户端。
d)客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到服务器端。
e)服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端。
f)服务器发送命令消息中的“结果”(_result),通知客户端连接的状态。
接続を確立します

3建立网络流(NetStream)

a)客户端发送命令消息中的“创建流”(createStream)命令到服务器端。
b)服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态。
建立流
フローの確立
4 播放(Play)

a)客户端发送命令消息中的“播放”(play)命令到服务器。
b)接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息。
c)服务器发送用户控制消息中的“streambegin”,告知客户端流ID。
d)播放命令成功的话,服务器发送命令消息中的“响应状态” NetStream.Play.Start & NetStream.Play.reset,告知客户端“播放”命令执行成功。
e)在此之后服务器发送客户端要播放的音频和视频数据。
播放流
プレイの流れ
结构:
RTMP协议中基本的数据单元称为消息(Message)。当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk)。

1 消息

消息是RTMP协议中基本的数据单元。不同种类的消息包含不同的Message Type ID,代表不同的功能。RTMP协议中一共规定了十多种消息类型,分别发挥着不同的作用。例如,Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需操作其中的数据。Message Type ID为8,9的消息分别用于传输音频和视频数据。Message Type ID为15-20的消息用于发送AMF编码的命令,负责用户与服务器之间的交互,比如播放,暂停等等。消息首部(Message Header)有四部分组成:标志消息类型的Message Type ID,标志消息长度的Payload Length,标识时间戳的Timestamp,标识消息所属媒体流的Stream ID。消息的报文结构如图3所示。

消息
ニュース
2 消息块

在网络上传输数据时,消息需要被拆分成较小的数据块,才适合在相应的网络环境上传输。RTMP协议中规定,消息在网络上传输时被拆分成消息块(Chunk)。消息块首部(Chunk Header)有三部分组成:用于标识本块的Chunk Basic Header,用于标识本块负载所属消息的Chunk Message Header,以及当时间戳溢出时才出现的Extended Timestamp。消息块的报文结构如图4所示。

消息块
メッセージブロック

3 消息分块

在消息被分割成几个消息块的过程中,消息负载部分(Message Body)被分割成大小固定的数据块(默认是128字节,最后一个数据块可以小于该固定长度),并在其首部加上消息块首部(Chunk Header),就组成了相应的消息块。消息分块过程如图5所示,一个大小为307字节的消息被分割成128字节的消息块(除了最后一个)。

消息分块
 メッセージブロック

RTMP传输媒体数据的过程中,发送端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送出去。接收端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据。

Audio data包:
ここに画像を挿入説明
RTMP Header:

  1. 基本头包括两个信息,Format + Chunk Stream ID。
  2. Timestamp:时间戳,3字节。
  3. Extended timestamp:当时间值很大 (>= 0x00FFFFFF)时, 上述的Timestamp字段不能传递这样的数值,
    因为Timestamp字段只有3个字节,此时需要使用扩展字段Extended Timestamp, 该字段是4个字节。
  4. Body size:消息长度,3字节。
  5. Type ID:消息类型id,1字节。
  6. Stream ID:消息流id,4字节。

Video包Header和audio

RTMP Body:

  1. 第一个字节af,a就是10,代表AAC编码格式。
  2. 第一个字节中的后四位f代表如下:
			前2个bit的含义采样频率,这里是二进制11,代表44kHZ 的采样频率。
			    0 = 5.5 kHz ,1 = 11 kHz ,2 = 22 kHz ,3 = 44 kHz
			
			第3个bit,代表 音频用16位的样本大小。
			    0 = 8-bit samples ,1 = 16-bit samples
			
			第4个bit代表声道
			    0 = Mono sound(单声道) ,1 = Stereo sound(立体声)
  1. 第2个字节,AACPacketType,这个字段来表示AACAUDIODATA的类型:0 = AAC sequence header,1
    = AAC raw。第一个音频包用0,后面的都用1。
测试:(中国经济网络电视Flash)

测试环境:win10 64位
测试平台:chorm
测试频道:中经电视(http://cen.ce.cn/);宣传生活频道()
协议:RTMP协议
测试账号:
测试结果:

网上搜了一堆RTMP协议测试地址,但是用charles抓包并没有看到明显的有用信息,于是做了很多尝试,为了确定该网站用的是RTMP协议,我先打开了网站视频,用“netstat -aon|findstr “1935””看了一下是否有端口号1935的ip,1935是RTMP协议的端口号,后来我又用WiresShark进行抓包,对RTMP协议请求流程做了验证,实际播放流程跟理论播放流程有一些出入,结果如下图:

ここに画像を挿入説明
以下是宣城经济生活频道的RTMP协议抓包:
ここに画像を挿入説明
对比两个节目,请求流程还是有点不同,所以猜测,两个平台都对RTMP协议做了一定处理。

四.HDS协议:

Http Dynamic Streaming(HDS)是一个由Adobe公司模仿HLS协议提出的另一个基于Http的流媒体传输协议。其模式跟HLS类似,但是又要比HLS协议复杂一些,也是索引文件和媒体切片文件结合的下载方式。

原理:

在服务器端降一个视频文件分割成segment节,segment节表示的是这个视频的几种不同的分辨率模式,针对某种分辨率的segment节,可以再划分成fragmen片段,每个片段都是视频的一小段时间,分段后每个片段会有segment+fragment的索引,客户端会根据索引请求视频片段。索引文件可以是.f4m的manifest文件,也可以是.bootstrap文件,在某视网的测试就发现它是采取的后者。

HDSのアーキテクチャ
相关模块:
Live Packager :该工具只针对HDS,同时集成在FMS(3.8以上)。它可以实时测量RTMP流(live),并将之转化成新的.f4f文件,满足实时性要求。内置的apache服务器使用HTTP ORIGIN MODULE对生成的文件进行解析,然后提供出HTTP流。

File Packager:一个命令行工具,它可以按照需求把多媒体文件形成流碎片并把碎片写进.f4f文件。

HTTP ORIGIN MODULE:HDS的重要组成部分,其为apache的一个modules,负责对(.f4f/.f4m/.f4x/.bootstrap/.drmmeta)等文件进行解析,然后转换成HTTP流输出。

OSMF Player:一个开源的播放器,建立在Open Source Media Framework(OSMF)的框架上,支持HTTP流,要求Flash player 10.1或以上。

文件类型:
.f4f:分段文件,包含视频数据,每个文件包含源文件的一个分段(segment)。每个segment可以包含更多的碎片(fragment)。每个片段或碎片都有自己的引导信息,提供缓存管理喝快速搜索。播放器可以根据这些信息去请求每个segment或fragment。

.f4m:媒体描述文件,包含媒体的编解码信息,多比特率等等。

.f4x:索引文件,包含指定分片在流中的位置。

.bootstrap:引导文件,引导信息可能来自于bootstrap,也可能来自于.f4m文件(写在bootstrapInfo标签中),取决于是否指定external-bootsrtap选项(Using the bootstrap information (run tables) contained in the file, the client translates the desired timecode to a segment number/fragment number pair. the client then constructs a URL based on this number pair, which requests a specific fragment from a specific F4F file.)

.drmmeta:用于保存加密信息,也需要使用external-bootstrap引用进来。

请求流程:
1.客户端向Web服务器发送一个HTTP请求,例如http://www.example.com/media/http_dynamic_Streaming.f4m。

2.服务器将收到的请求传递给HTTP Origin Module。

3.HTTP Origin Module将描述文件(F4M)返回到客户端。

4.客户端收到描述文件(F4M),根据 bootstrap中的信息中的传送时间,组成一个segment#/fragment#对。

5.然后客户端解析F4M的内容向服务器发送一个请求,比如http://www.example.com/media/http_dynamic_StreamingSeg1-Frag1(segment中的fragmen片段)或者http://www.example.com/media/http_dynamic_StreamingSeg1.f4f。(segment)

图. 碎片化内容(F4F)的结构
図フラグメンテーションコンテンツ(F4F)構造
6.服务器返回相应的视频片段。

7.客户端接收分片,处理之后播放。

标签含义
.bootstrap:
ここに画像を挿入説明
abst: 表示HDS内容的总体信息 adobe bootstrap Info box table
asrt:表示segment总体信息 adobe segment run table
afrt: 表示fragment总体信息 adobe fragment run table

manifest:

StraemType :流类型
BootstrapInfo:引导信息,可以写在标签内,也可以再指定一个url。(上图就是采用了后者)
Media:媒体文件的url或者url的一部分。码率不同,url地址不同。
Mediadata:编解码相关信息。

优势

1.不需要防火墙开普通web浏览器所需端口以外的任何端口
2.允许视频切片在浏览器、网关和CDN的缓存,从而显著降低源服务器的负载。
3.HDS 的文件格式为 FLV/F4V/MP4,索引文件为 f4m,同时支持直播和时移。

测试:(某视网Flash)

测试环境:win10 64位
测试平台:chorm
测试频道:
协议:HDS协议
测试类型:直播
测试结果:

以下是chales抓到的截图:
ここに画像を挿入説明

测试发现,客户端先请求服务器返回一个时间,然后根据这个时间请求manifest文件,根据manifest文件下载index.bootstrap文件,根据index。Bootstrap文件中的信息,请求分片文件。媒体分片每下载5次,更新一次index.bootstrape文件,不同于HLS协议的是,直播暂停的时候,分片文件停止下载,index.bootstrap文件也停止更新,在视觉上跟点播一样,重新播放也还是能接上上次播放位置,但是暂停时间越长,index.bootstrap文件的更新时间就越久(bootstrap中的分片索引表就越长)。以下是暂停前和播放后更新index.bootstrap中数据的截图:

ここに画像を挿入説明
ここに画像を挿入説明

虽然看不出数据内容,但可以看出数据长度的变化。这样不停的暂停,与实际直播内容的延时就越来越大,测试发现,当长度到达一定数量,也就是播放内容和实际直播内容的延时达到一定程度,服务器缓存中不存在请求的url,报404错误,客户端会重新请求manfist文件,然后下载index.bootstrap文件,再下载相应切片,跟一开始流程一致。暂时没有找到点播的测试案例。

五.MPEG-DASH协议

Dynamic Adaptive Streaming over HTTP(缩写DASH),是国际标准组MPEG制定的技术标准。

原理:

DASH协议原理跟HLS协议差不多,都是将文件分成一片片小段进行分发,也是分为MPD索引文件和媒体分片文件。客户端首先请求服务器下载解析MPD文件,获取码率等信息,然后根据实际带宽情况去请求某种码率的媒体分片文件以实现自适应切换。MPD 可以以不同的方式,例如SegmentList,SegmentTemplate,SegmentBase和SegmentTimeline,根据使用情况下进行组织。

DASH协议架构:
DASHのプロトコルアーキテクチャ
DASH协议传输结构:
DASH伝送プロトコル構造
标签含义:
Period:一条完整的mpeg dash码流可能由一个或多个Peroid组成,每个Period代表一个时间段。比如一个码流是60s,Peroid1是0-10s,Peroid2是11-30,Peroid3是31-60。

Representation:表示不同码率的码流,实际播放的时候,视频会在一个AdaptationSet中的不同Representaiton 之间切换码率,会依次请求该Representaiton下不同Segment序列。

Segment:每一个具体的片段。(1,2,4,6,10s …) 。MPD中描述segment URL的形式有多种,如Segment list,Segment template,Single segment。

AdaptationSet:包含了媒体呈现的形式(视频/音频/字幕),AdaptationSet由一组可供切换的不同码率的码流(Representation)组成。

测试:(TVB)

测试环境:win10 64位
测试平台:chorm
测试频道:翡翠台(https://qa.mytvsuper.com/tc/live/81)
协议:MPEG DASH协议
测试账号:
测试结果:
(测试类型:点播)

下图是用charles抓取的.mpd数据截图:
ここに画像を挿入説明
视频列表
ここに画像を挿入説明

音频列表
以下是媒体分片的请求列表截图:
ここに画像を挿入説明
测试发现,点播暂停切片文件都不再下载,播放继续下载,广告和视频都是一个文件。Seek后,跳到相应列表位置下载。

观察上图,可以发现规律,比如音频第一个数字后缀是0,第二个是441353,第三个441353 +440294 = 881647正好是初始化后的第二个音频切片文件命的数字后缀部分,就是按照上述列表一次累加,视频的url也是这样。

(测试类型:直播)
ここに画像を挿入説明
ここに画像を挿入説明
测试发现,客户端首先发起一个请求,服务器返回一个session,然后用客户端用这个session请求.mpd文件,根据mpd文件再请求切片文件。

以下是切片的格式:
ここに画像を挿入説明
(字幕)
ここに画像を挿入説明
(音频)
ここに画像を挿入説明

(视频)
ここに画像を挿入説明
Mime type类型注释:

Application:用于传输应用程序数据或者二进制数据;

Audio:用于传输音频或者音声数据;

Video:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。

テキスト:正規化のためにテキスト情報を示し、テキスト・メッセージは、より以上の文字セットフォーマットであってもよいです。

マルチ:本体の接続部の複数のメッセージを構成し、これらの部分は、トランザクションの異なるタイプであってもよいです。

メッセージ:メッセージをパッケージ化するためのEメール。

画像:静止画像データを送信するために使用。

数多くのテストが明確な法SegmentTemplateは認められなかったが、累積値またはこのリストに従って、中間の初期推定値は、テストにオンデマンドベース、アルゴリズムのいくつかの種類を経ることが後のテストは、デジタルURLサフィックスの値を決定することはできません同じでなければならないリストの更新(HLSプロトコルM3U8と同様MPD更新、各更新)分析は、HLSモードの合意で​​す。

以下は、4回MPDスクリーンショットを更新されます。

ここに画像を挿入説明
ここに画像を挿入説明
ここに画像を挿入説明
ここに画像を挿入説明
https://www.jianshu.com/p/8b803ba0e526

リリース8元の記事 ウォン称賛18 ビュー10000 +

おすすめ

転載: blog.csdn.net/weixin_38054045/article/details/104454621