Android性能優(yōu)化之弱網(wǎng)優(yōu)化詳解
弱網(wǎng)優(yōu)化
匯總了一下眾多大佬的性能優(yōu)化文章,知識(shí)點(diǎn),主要包含:
UI優(yōu)化/啟動(dòng)優(yōu)化/崩潰優(yōu)化/卡頓優(yōu)化/安全性優(yōu)化/弱網(wǎng)優(yōu)化/APP深度優(yōu)化等等等~
本篇是第五篇:弱網(wǎng)優(yōu)化 [非商業(yè)用途,如有侵權(quán),請(qǐng)告知我,我會(huì)刪除]
強(qiáng)調(diào)一下: 性能優(yōu)化的開發(fā)文檔跟之前的面試文檔一樣,需要的跟作者直接要。
1、Serializable原理
通常我們使用Java的序列化與反序列化時(shí),只需要將類實(shí)現(xiàn)Serializable
接口即可,剩下的事情就交給了jdk。今天我們就來(lái)探究一下,Java序列化是怎么實(shí)現(xiàn)的,然后探討一下幾個(gè)常見的集合類,他們是如何處理序列化帶來(lái)的問(wèn)題的。
1.1 分析過(guò)程
幾個(gè)待思考的問(wèn)題
- 為什么序列化一個(gè)對(duì)象時(shí),僅需要實(shí)現(xiàn)
Serializable
接口就可以了。 - 通常我們序列化一個(gè)類時(shí),為什么推薦的做法是要實(shí)現(xiàn)一個(gè)靜態(tài)
final
成員變量serialVersionUID
。 - 序列化機(jī)制是怎么忽略
transient
關(guān)鍵字的, static變量也不會(huì)被序列化。
接下來(lái)我們就帶著問(wèn)題,在源碼中找尋答案吧。
1.2 Serializable接口
先看Serializable
接口,源碼很簡(jiǎn)單,一個(gè)空的接口,沒有方法也沒有成員變量。但是注釋非常詳細(xì),很清楚的描述了Serializable
怎么用、能做什么,很值得一看,我們撿幾個(gè)重點(diǎn)的翻譯一下,
/** * Serializability of a class is enabled by the class implementing the * java.io.Serializable interface. Classes that do not implement this * interface will not have any of their state serialized or * deserialized. All subtypes of a serializable class are themselves * serializable. The serialization interface has no methods or fields * and serves only to identify the semantics of being serializable. */
類的可序列化性通過(guò)實(shí)現(xiàn)java.io.Serializable
接口開啟。未實(shí)現(xiàn)序列化接口的類不能序列化,所有實(shí)現(xiàn)了序列化的子類都可以被序列化。Serializable
接口沒有方法和屬性,只是一個(gè)識(shí)別類可被序列化的標(biāo)志。
/** * Classes that require special handling during the serialization and * deserialization process must implement special methods with these exact * signatures: * * <PRE> * private void writeObject(java.io.ObjectOutputStream out) * throws IOException * private void readObject(java.io.ObjectInputStream in) * throws IOException, ClassNotFoundException; * private void readObjectNoData() * throws ObjectStreamException; * </PRE> */
在序列化過(guò)程中,如果類想要做一些特殊處理,可以通過(guò)實(shí)現(xiàn)以下方法writeObject()
, readObject()
, readObjectNoData()
,其中,
- writeObject方法負(fù)責(zé)為其特定類寫入對(duì)象的狀態(tài),以便相應(yīng)的
readObject()
方法可以還原它。 readObject()
方法負(fù)責(zé)從流中讀取并恢復(fù)類字段。- 如果某個(gè)超類不支持序列化,但又不希望使用默認(rèn)值怎么辦?
writeReplace()
方法可以使對(duì)象被寫入流之前,用一個(gè)對(duì)象來(lái)替換自己。 readResolve()
通常在單例模式中使用,對(duì)象從流中被讀出時(shí),可以用一個(gè)對(duì)象替換另一個(gè)對(duì)象。
1.3 ObjectOutputStream
//我們要序列化對(duì)象的方法實(shí)現(xiàn)一般都是在這個(gè)函數(shù)中 public final void writeObject(Object obj) throws IOException { ... try { //寫入的具體實(shí)現(xiàn)方法 writeObject0(obj, false); } catch (IOException ex) { ... throw ex; } } private void writeObject0(Object obj, boolean unshared) throws IOException { ...省略 Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays? Class<?> repCl; //獲取到ObjectStreamClass,這個(gè)類很重要 //在它的構(gòu)造函數(shù)初始化時(shí)會(huì)調(diào)用獲取類屬性的函數(shù) //最終會(huì)調(diào)用getDefaultSerialFields這個(gè)方法 //在其中通過(guò)flag過(guò)濾掉類的某一個(gè)為transient或static的屬性(解釋了問(wèn)題3) desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } //其中主要的寫入邏輯如下 //String, Array, Enum本身處理了序列化 if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); //重點(diǎn)在這里,通過(guò)`instanceof`判斷對(duì)象是否為`Serializable` //這也就是普通自己定義的類如果沒有實(shí)現(xiàn)`Serializable` //在序列化的時(shí)候會(huì)拋出異常的原因(解釋了問(wèn)題1) } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } ... } private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException { ... try { desc.checkSerialize(); //寫入二進(jìn)制文件,普通對(duì)象開頭的魔數(shù)0x73 bout.writeByte(TC_OBJECT); //寫入對(duì)應(yīng)的類的描述符,見底下源碼 writeClassDesc(desc, false); handles.assign(unshared ? null : obj); if (desc.isExternalizable() && !desc.isProxy()) { writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); } } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException { //句柄 int handle; //null描述 if (desc == null) { writeNull(); //類對(duì)象引用句柄 //如果流中已經(jīng)存在句柄,則直接拿來(lái)用,提高序列化效率 } else if (!unshared && (handle = handles.lookup(desc)) != -1) { writeHandle(handle); //動(dòng)態(tài)代理類描述符 } else if (desc.isProxy()) { writeProxyDesc(desc, unshared); //普通類描述符 } else { //該方法會(huì)調(diào)用desc.writeNonProxy(this)如下 writeNonProxyDesc(desc, unshared); } } void writeNonProxy(ObjectOutputStream out) throws IOException { out.writeUTF(name); //寫入serialVersionUID out.writeLong(getSerialVersionUID()); ... } public long getSerialVersionUID() { // 如果沒有定義serialVersionUID // 序列化機(jī)制就會(huì)調(diào)用一個(gè)函數(shù)根據(jù)類內(nèi)部的屬性等計(jì)算出一個(gè)hash值 // 這也是為什么不推薦序列化的時(shí)候不自己定義serialVersionUID的原因 // 因?yàn)檫@個(gè)hash值是根據(jù)類的變化而變化的 // 如果你新增了一個(gè)屬性,那么之前那些被序列化后的二進(jìn)制文件將不能反序列化回來(lái),Java會(huì)拋出異常 // (解釋了問(wèn)題2) if (suid == null) { suid = AccessController.doPrivileged( new PrivilegedAction<Long>() { public Long run() { return computeDefaultSUID(cl); } } ); } //已經(jīng)定義了SerialVersionUID,直接獲取 return suid.longValue(); } //分析到這里,要插一個(gè)我對(duì)序列化后二進(jìn)制文件的一點(diǎn)個(gè)人見解,見下面
1.4 序列化后二進(jìn)制文件的一點(diǎn)解讀
如果我們要序列化一個(gè)List<PhoneItem>
, 其中PhoneItem
如下,
class PhoneItem implements Serializable { String phoneNumber; }
構(gòu)造List的代碼省略,假設(shè)我們序列化了一個(gè)size
為5的List
,查看二進(jìn)制文件大概如下所示,
7372 xxxx xxxx 7371 xxxx xxxx 7371 xxxx xxxx 7371 xxxx xxxx 7371 xxxx xxxx
通過(guò)剛才的源碼解讀,開頭的魔數(shù)0x73表示普通對(duì)象,72表示類的描述符號(hào),71表示類描述符為引用類型。管中窺豹可知一點(diǎn)薄見,在解析二進(jìn)制文件的時(shí)候,就是通過(guò)匹配魔數(shù) (magic number) 開頭方式,從而轉(zhuǎn)換成Java對(duì)象的。當(dāng)在序列化過(guò)程中,如果流中已經(jīng)有同樣的對(duì)象,那么之后的序列化可以直接獲取該類對(duì)象句柄,變?yōu)橐妙愋?,從而提高序列化效率?/p>
//通過(guò)writeSerialData調(diào)用走到真正解析類的方法中,有沒有復(fù)寫writeObject處理的邏輯不太一樣 //這里以默認(rèn)沒有復(fù)寫writeObject為例,最后會(huì)調(diào)用defaultWriteFields方法 private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { ... int primDataSize = desc.getPrimDataSize(); if (primVals == null || primVals.length < primDataSize) { primVals = new byte[primDataSize]; } desc.getPrimFieldValues(obj, primVals); //寫入屬性大小 bout.write(primVals, 0, primDataSize, false); ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; i++) { ... try { //遍歷寫入屬性類型和屬性大小 writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } }
由于反序列化過(guò)程和序列化過(guò)程類似,這里不再贅述。
1.5 常見的集合類的序列化問(wèn)題
1.5.1 HashMap
Java要求被反序列化后的對(duì)象要與被序列化之前的對(duì)象保持一致,但因?yàn)閔ashmap的key是通過(guò)hash計(jì)算的。反序列化后計(jì)算得到的值可能不一致(反序列化在不同的jvm環(huán)境下執(zhí)行)。所以HashMap需要重寫序列化實(shí)現(xiàn)的過(guò)程,避免出現(xiàn)這種不一致的情況。
具體操作是將要自定義處理的屬性定義為transient
,然后復(fù)寫writeObject
,在其中做特殊處理
private void writeObject(java.io.ObjectOutputStream s) throws IOException { int buckets = capacity(); // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); //寫入hash桶的容量 s.writeInt(buckets); //寫入k-v的大小 s.writeInt(size); //遍歷寫入不為空的k-v internalWriteEntries(s); }
1.5.2 ArrayList
因?yàn)樵贏rrayList中的數(shù)組容量基本上都會(huì)比實(shí)際的元素的數(shù)大, 為了避免序列化沒有元素的數(shù)組而重寫writeObject
和readObject
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ ... s.defaultWriteObject(); // 寫入arraylist當(dāng)前的大小 s.writeInt(size); // 按照相同順序?qū)懭朐? for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } ... }
2、Parcelable
Parcelable是Android為我們提供的序列化的接口,Parcelable相對(duì)于Serializable的使用相對(duì)復(fù)雜一些,但Parcelable的效率相對(duì)Serializable也高很多,這一直是Google工程師引以為傲的,有時(shí)間的可以看一下Parcelable和Serializable的效率對(duì)比號(hào)稱快10倍的效率
Parcelable接口的實(shí)現(xiàn)類是可以通過(guò)Parcel寫入和恢復(fù)數(shù)據(jù)的,并且必須要有一個(gè)非空的靜態(tài)變量 CREATOR, 而且還給了一個(gè)例子,這樣我們寫起來(lái)就比較簡(jiǎn)單了,但是簡(jiǎn)單的使用并不是我們的最終目的
通過(guò)查看Android源碼中Parcelable可以看出,Parcelable實(shí)現(xiàn)過(guò)程主要分為序列化,反序列化,描述三個(gè)過(guò)程,下面分別介紹下這三個(gè)過(guò)程
2.1 Parcel的簡(jiǎn)介
在介紹之前我們需要先了解Parcel是什么?Parcel翻譯過(guò)來(lái)是打包的意思,其實(shí)就是包裝了我們需要傳輸?shù)臄?shù)據(jù),然后在Binder中傳輸,也就是用于跨進(jìn)程傳輸數(shù)據(jù)
簡(jiǎn)單來(lái)說(shuō),Parcel提供了一套機(jī)制,可以將序列化之后的數(shù)據(jù)寫入到一個(gè)共享內(nèi)存中,其他進(jìn)程通過(guò)Parcel可以從這塊共享內(nèi)存中讀出字節(jié)流,并反序列化成對(duì)象,下圖是這個(gè)過(guò)程的模型。
Parcel可以包含原始數(shù)據(jù)類型(用各種對(duì)應(yīng)的方法寫入,比如writeInt(),writeFloat()等),可以包含Parcelable對(duì)象,它還包含了一個(gè)活動(dòng)的IBinder對(duì)象的引用,這個(gè)引用導(dǎo)致另一端接收到一個(gè)指向這個(gè)IBinder的代理IBinder。
Parcelable通過(guò)Parcel實(shí)現(xiàn)了read和write的方法,從而實(shí)現(xiàn)序列化和反序列化??梢钥闯霭烁鞣N各樣的read和write方法,最終都是通過(guò)native方法實(shí)現(xiàn)
2.2 Parcelable的三大過(guò)程介紹(序列化、反序列化、描述)
到這里,基本上關(guān)系都理清了,也明白簡(jiǎn)單的介紹和原理了,接下來(lái)在實(shí)現(xiàn)Parcelable之前,介紹下實(shí)現(xiàn)Parcelable的三大流程
首先寫一個(gè)類實(shí)現(xiàn)Parcelable接口,會(huì)讓我們實(shí)現(xiàn)兩個(gè)方法
2.2.1 描述
其中describeContents就是負(fù)責(zé)文件描述,首先看一下源碼的解讀
通過(guò)上面的描述可以看出,只針對(duì)一些特殊的需要描述信息的對(duì)象,需要返回1,其他情況返回0就可以
2.2.2 序列化
我們通過(guò)writeToParcel方法實(shí)現(xiàn)序列化,writeToParcel返回了Parcel,所以我們可以直接調(diào)用Parcel中的write方法,基本的write方法都有,對(duì)象和集合比較特殊下面單獨(dú)講,基本的數(shù)據(jù)類型除了boolean其他都有,Boolean可以使用int或byte存儲(chǔ)
2.2.3 反序列化
反序列化需要定義一個(gè)CREATOR的變量,上面也說(shuō)了具體的做法,這里可以直接復(fù)制Android給的例子中的,也可以自己定義一個(gè)(名字千萬(wàn)不能改),通過(guò)匿名內(nèi)部類實(shí)現(xiàn)Parcelable中的Creator的接口
2.3 Parcelable的實(shí)現(xiàn)和使用
根據(jù)上面三個(gè)過(guò)程的介紹,Parcelable就寫完了,就可以直接在Intent中傳輸了,可以自己寫兩個(gè)Activity傳輸一下數(shù)據(jù)試一下,其中一個(gè)putExtra另一個(gè)getParcelableExtra即可
這里實(shí)現(xiàn)Parcelable也很簡(jiǎn)單:
1.寫一個(gè)類實(shí)現(xiàn)Parcelable然后alt+enter 添加Parcelable所需的代碼塊,AndroidStudio會(huì)自動(dòng)幫我們實(shí)現(xiàn)(這里需要注意如果其中包含對(duì)象或集合需要把對(duì)象也實(shí)現(xiàn)Parcelable)
2.4 Parcelable中對(duì)象和集合的處理
如果實(shí)現(xiàn)Parcelable接口的對(duì)象中包含對(duì)象或者集合,那么其中的對(duì)象也要實(shí)現(xiàn)Parcelable接口
寫入和讀取集合有兩種方式:
一種是寫入類的相關(guān)信息,然后通過(guò)類加載器去讀取, –> writeList | readList
二是不用類相關(guān)信息,創(chuàng)建時(shí)傳入相關(guān)類的CREATOR來(lái)創(chuàng)建 –> writeTypeList | readTypeList | createTypedArrayList
第二種效率高一些,但一定要注意如果有集合定義的時(shí)候一定要初始化
like this –>
public ArrayList authors = new ArrayList<>();
2.5 Parcelable和Serializable的區(qū)別和比較
Parcelable和Serializable都是實(shí)現(xiàn)序列化并且都可以用于Intent間傳遞數(shù)據(jù),Serializable是Java的實(shí)現(xiàn)方式,可能會(huì)頻繁的IO操作,所以消耗比較大,但是實(shí)現(xiàn)方式簡(jiǎn)單 Parcelable是Android提供的方式,效率比較高,但是實(shí)現(xiàn)起來(lái)復(fù)雜一些 , 二者的選取規(guī)則是:內(nèi)存序列化上選擇Parcelable, 存儲(chǔ)到設(shè)備或者網(wǎng)絡(luò)傳輸上選擇Serializable(當(dāng)然Parcelable也可以但是稍顯復(fù)雜)
3、http與https原理詳解
3.1 概述
早期以信息發(fā)布為主的Web 1.0時(shí)代,HTTP已可以滿足絕大部分需要。證書費(fèi)用、服務(wù)器的計(jì)算資源都比較昂貴,作為HTTP安全擴(kuò)展的HTTPS,通常只應(yīng)用在登錄、交易等少數(shù)環(huán)境中。但隨著越來(lái)越多的重要業(yè)務(wù)往線上轉(zhuǎn)移,網(wǎng)站對(duì)用戶隱私和安全性也越來(lái)越重視。對(duì)于防止惡意監(jiān)聽、中間人攻擊、惡意劫持篡改,HTTPS是目前較為可行的方案,全站HTTPS逐漸成為主流網(wǎng)站的選擇。
3.2 HTTP簡(jiǎn)介
HTTP(HyperText Transfer Protocol,超文本傳輸協(xié)議),是一種無(wú)狀態(tài) (stateless)
協(xié)議,提供了一組規(guī)則和標(biāo)準(zhǔn),從而讓信息能夠在互聯(lián)網(wǎng)上進(jìn)行傳播。在HTTP中,客戶端通過(guò)Socket創(chuàng)建一個(gè)TCP/IP連接,并連接到服務(wù)器,完成信息交換后,就會(huì)關(guān)閉TCP連接。(后來(lái)通過(guò)Connection: Keep-Alive
實(shí)現(xiàn)長(zhǎng)連接)
3.2.1 HTTP消息組成
- 請(qǐng)求行或響應(yīng)行
- HTTP頭部
- HTML實(shí)體,包括請(qǐng)求實(shí)體和響應(yīng)實(shí)體 HTTP請(qǐng)求結(jié)構(gòu),響應(yīng)結(jié)構(gòu)如圖所示:
HTTP頭部
HTTP頭部由一系列的鍵值對(duì)組成,允許客戶端向服務(wù)器端發(fā)送一些附加信息或者客戶端自身的信息,如:accept、accept-encoding、cookie
等。http頭后面必須有一個(gè)空行
請(qǐng)求行
請(qǐng)求行由方法、URL、HTTP版本組成。
響應(yīng)行
響應(yīng)行由HTTP版本、狀態(tài)碼、信息提示符組成。
3.3 HTTP2.0和HTTP1.X相比的新特性
- 新的二進(jìn)制格式,HTTP1.x的解析是基于文本?;谖谋緟f(xié)議的格式解析存在天然缺陷,文本的表現(xiàn)形式有多樣性,要做到健壯性考慮的場(chǎng)景必然很多,二進(jìn)制則不同,只認(rèn)0和1的組合?;谶@種考慮HTTP2.0的協(xié)議解析決定采用二進(jìn)制格式,實(shí)現(xiàn)方便且健壯。
- 多路復(fù)用,即每一個(gè)請(qǐng)求都是是用作連接共享機(jī)制的。一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)id,這樣一個(gè)連接上可以有多個(gè)請(qǐng)求,每個(gè)連接的請(qǐng)求可以隨機(jī)的混雜在一起,接收方可以根據(jù)請(qǐng)求的
id
將請(qǐng)求再歸屬到各自不同的服務(wù)端請(qǐng)求里面。 - header壓縮,HTTP1.x的
header
帶有大量信息,而且每次都要重復(fù)發(fā)送,HTTP2.0使用encoder來(lái)減少需要傳輸?shù)膆eader大小,通訊雙方各自cache一份header fields
表,既避免了重復(fù)header的傳輸,又減小了需要傳輸?shù)拇笮 ?/li> - 服務(wù)端推送,同
SPDY
一樣,HTTP2.0也具有server push
功能。
3.4 HTTP安全問(wèn)題
- 通信使用明文(不加密),內(nèi)容可能會(huì)被竊聽
- 不驗(yàn)證通信方的身份,因此有可能遭遇偽裝
- 無(wú)法證明報(bào)文的完整性,所以有可能已遭篡改
3.5 HTTPS協(xié)議
HTTPS 是最流行的 HTTP 安全形式。使用 HTTPS 時(shí),所有的 HTTP 請(qǐng)求和響應(yīng)數(shù)據(jù)在發(fā)送到網(wǎng)絡(luò)之前,都要進(jìn)行加密。 HTTPS 在 HTTP 下面提供了一個(gè)傳輸級(jí)的密碼安全層——可以使用 SSL,也可以使用其后繼者——傳輸層安全(TLS)。
相關(guān)術(shù)語(yǔ)
- 密鑰:改變密碼行為的數(shù)字化參數(shù)。
- 對(duì)稱密鑰加密系統(tǒng):編、解碼使用相同密鑰的算法。
- 不對(duì)稱密鑰加密系統(tǒng):編、解碼使用不同密鑰的算法。
- 公開密鑰加密系統(tǒng):一種能夠使數(shù)百萬(wàn)計(jì)算機(jī)便捷地發(fā)送機(jī)密報(bào)文的系統(tǒng)。
- 數(shù)字簽名:用來(lái)驗(yàn)證報(bào)文未被偽造或篡改的校驗(yàn)和。
- 數(shù)字證書:由一個(gè)可信的組織驗(yàn)證和簽發(fā)的識(shí)別信息。
- 密鑰協(xié)商算法:解決動(dòng)態(tài)密鑰分配、存儲(chǔ)、傳輸?shù)葐?wèn)題
3.5.1 TLS/SSL協(xié)議
TLS/SSL協(xié)議包含以下一些關(guān)鍵步驟:
- 傳輸?shù)臄?shù)據(jù)必須具有
機(jī)密性
和完整性
,一般采用對(duì)稱加密算法
和HMAC算法
,這兩個(gè)算法需要一系列的密鑰塊(key_block)
,比如對(duì)稱加密算法的密鑰、HMAC算法的密鑰,如果是AES-128-CBC-PKCS#7加密標(biāo)準(zhǔn),還需要初始化向量。 - 所有的加密塊都由
主密鑰(Master Secret)
生成,主密鑰就是第1章中講解的會(huì)話密鑰,使用密碼衍生算法將主密鑰轉(zhuǎn)換為多個(gè)密碼快。 - 主密鑰來(lái)自
預(yù)備主密鑰(Premaster Secret)
,預(yù)備主密鑰采用同樣的密碼衍生算法轉(zhuǎn)換為主密鑰,預(yù)備主密鑰采用RSA或者DH(ECDH)算法協(xié)商而來(lái)。不管采用哪種密鑰協(xié)商算法,服務(wù)器必須有一對(duì)密鑰(可以是RSA或者ECDSA密鑰),公鑰發(fā)給客戶端,私鑰自己保留。不同的密鑰協(xié)商算法,服務(wù)器密鑰對(duì)的作用也是不同的。 - 通過(guò)這些關(guān)鍵步驟,好像TLS/SSL協(xié)議的任務(wù)已經(jīng)結(jié)束,但這種方案會(huì)遇到中間人攻擊,這是TLS/SSL協(xié)議無(wú)法解決的問(wèn)題,必須結(jié)合
PKI
的技術(shù)進(jìn)行解決,PKI的核心是證書,證書背后的密碼學(xué)算法是數(shù)字簽名
技術(shù)。對(duì)于客戶端來(lái)說(shuō),需要校驗(yàn)證書,確保接收到的服務(wù)器公鑰是經(jīng)過(guò)認(rèn)證的,不存在偽造,也就是客戶端需要對(duì)服務(wù)器的身份進(jìn)行驗(yàn)證。
TLS/SSL協(xié)議核心就三大步驟:認(rèn)證、密鑰協(xié)商、數(shù)據(jù)加密
。
3.5.2 RSA握手
握手階段分成五步:
- 客戶端給出協(xié)議版本號(hào)、生成的
隨機(jī)數(shù)(Client random)
,以及客戶端支持的加密方法。 - 服務(wù)端確認(rèn)雙方使用的加密方法,并給出數(shù)字證書、以及一個(gè)服務(wù)器生成的隨機(jī)數(shù)。
- 客戶端確認(rèn)數(shù)字證書有效,然后生成一個(gè)新的
隨機(jī)數(shù)(Premaster secret)
,并使用數(shù)字證書中的公鑰,加密這個(gè)隨機(jī)數(shù),發(fā)給服務(wù)器。 - 服務(wù)器使用自己的私鑰,獲取客戶端發(fā)來(lái)的
隨機(jī)數(shù)(Premaster secret)
。 - 雙方根據(jù)約定的加密方法,使用前面的三個(gè)隨機(jī)數(shù),生成
會(huì)話密鑰(session key)
,用來(lái)加密接下來(lái)的對(duì)話過(guò)程。
握手階段有三點(diǎn)需要注意:
- 生成對(duì)話密鑰一共需要
三個(gè)隨機(jī)數(shù)
。 - 握手之后的對(duì)話使用
對(duì)話密鑰(session key)
加密(對(duì)稱加密),服務(wù)器的公鑰
和私鑰
只用于加密和解密對(duì)話密鑰(session key)
(非對(duì)稱加密),無(wú)其他作用。 - 服務(wù)器
公鑰
放在服務(wù)器的數(shù)字證書
之中。
3.5.3 DH握手
RSA整個(gè)握手階段都不加密,都是明文的。因此,如果有人竊聽通信,他可以知道雙方選擇的加密方法,以及三個(gè)隨機(jī)數(shù)中的兩個(gè)。整個(gè)通話的安全,只取決于第三個(gè)隨機(jī)數(shù)(Premaster secret)
能不能被破解。為了足夠安全,我們可以考慮把握手階段的算法從默認(rèn)的RSA
算法,改為 Diffie-Hellman
算法(簡(jiǎn)稱DH算法)。采用DH算法后,Premaster secret
不需要傳遞,雙方只要交換各自的參數(shù),就可以算出這個(gè)隨機(jī)數(shù)。
4、protobuffer
protobuffer是一種語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)的數(shù)據(jù)協(xié)議,優(yōu)點(diǎn)在于壓縮性好,可擴(kuò)展,標(biāo)準(zhǔn)化,常用于數(shù)據(jù)傳輸、持久化存儲(chǔ)等。
4.1 實(shí)現(xiàn)原理
4.1.1 protobuffer協(xié)議
1.壓縮性好(相比于同樣跨平臺(tái)、跨語(yǔ)言的json)
- 去除字段定義,分隔符(引號(hào),冒號(hào),逗號(hào))
- 壓縮數(shù)字,因?yàn)槿粘=?jīng)常使用到的比較小的數(shù)字,實(shí)際有效的字節(jié)數(shù)沒有4個(gè)字節(jié)
- 采用TLV的數(shù)據(jù)存儲(chǔ)方式:減少了分隔符的使用 & 數(shù)據(jù)存儲(chǔ)得緊湊
varint和zigzag算法:對(duì)數(shù)字進(jìn)行壓縮
protobuffer協(xié)議去除字段定義,分隔符
2.可拓展
protobuffer并不是一個(gè)自解析的協(xié)議(json自解析key),需要pb的meta數(shù)據(jù)解析,犧牲了可讀性,但在大規(guī)模的數(shù)據(jù)處理是可以接受的。可拓展性在于protobuffer中追加定義,新舊版本是可以兼容的,但是定義是嚴(yán)格有序的。
3.標(biāo)準(zhǔn)化
protobuffer生態(tài)提供了完整的工具鏈,protobuffer提供官方的生成golang/java等實(shí)現(xiàn)插件,這樣protobuffer字節(jié)碼的各語(yǔ)言版本的序列化、反序列化是標(biāo)準(zhǔn)化的,也包括插件生成代碼封裝。以及標(biāo)準(zhǔn)化的常用工具,比如doc-gen、grpc-gen、grpc-gateway-gen等工具。
4.1.2 典型應(yīng)用
gRPC
protobuffer封裝
需要注意:
1.pb結(jié)構(gòu)封裝的字段標(biāo)序是不能更改的,否則解析錯(cuò)亂;
2.pb結(jié)構(gòu)盡量把必選類型,比如int、bool放在filedNum<=16;可得到更好的varint壓縮效果;
3.grpc-client使用時(shí),req是指針類型,務(wù)必不要重復(fù)復(fù)制,盡量new request,否則編碼時(shí)會(huì)錯(cuò)亂;
4.2 protobuffer 編碼原理
protobuffer(以下簡(jiǎn)稱為PB)兩個(gè)重要的優(yōu)勢(shì)在于高效的序列化/反序列化和低空間占用,而這兩大優(yōu)勢(shì)是以其高效的編碼方式為基礎(chǔ)的。PB底層以二進(jìn)制形式存儲(chǔ),比binary struct方式更加緊湊,一般來(lái)講空間利用率也比binary struct更高,是以Key-Value的形式存儲(chǔ)【圖1】。
PB示例結(jié)構(gòu)如下:
//示例protobuf結(jié)構(gòu) message Layer1 { optional uint32 layer1_varint = 1; optional Layer2 layer1_message = 2; } message Layer2 { optional uint32 layer2_varint = 1; optional Layer3 layer2_message = 2; } message Layer3 { optional uint32 layer3_varint = 1; optional bytes layer3_bytes = 2; optional float layer3_float = 3; optional sint32 layer3_sint32 = 4; }
PB將常用類型按存儲(chǔ)特性分為數(shù)種Wire Type【圖 2】, Wire Type不同,編碼方式不同。根據(jù)Wire Type(占3bits)和field number生成Key。
Key計(jì)算方式如下,并以Varint編碼方式序列化【參考下面Varint編碼】,所以理論上[1, 15]范圍內(nèi)的field, Key編碼后占用一個(gè)字節(jié), [16,)的Key編碼后會(huì)占用一個(gè)字節(jié)以上,所以盡量避免在一個(gè)結(jié)構(gòu)里面定義過(guò)多的field。如果碰到需要定義這么多field的情況,可以采用嵌套方式定義。
//Key的計(jì)算方式 Key = field_number << 3 | Wire_Type
Type | Value | Meaning | Contain |
---|---|---|---|
Varint | 0 | varint | int32,int64,sint32,sint64,uint32,uint64,bool,enum |
Fixed64 | 1 | 64-bit | fixed64,sfixed64,double,float |
LengthDelimited | 2 | length-delimi | string,message,bytes,repeated |
StartGroup | 3 | start group | groups(deprecated) |
EndGroup | 4 | end group | groups(deprecated) |
Fixed32 | 5 | 32-bit | fixed32,float |
4.2.1 Varint編碼
inline uint8* WriteVarint32ToArray(uint32 value, uint8* target) { while (value >= 0x80) { *target = static_cast<uint8>(value | 0x80); value >>= 7; ++target; } *target = static_cast<uint8>(value); return target + 1; }
Layer1 obj; obj.set_layer1_varint(12345);
Varint編碼只用每個(gè)字節(jié)的低7bit存儲(chǔ)數(shù)據(jù),最高位0x80用做標(biāo)識(shí),清空:最后一個(gè)字節(jié),置位:還有數(shù)據(jù)。以上述操作為例,設(shè)uint32類型為12345,其序列化過(guò)程如下:
該字段的field_number = 1
, wire_type = 0
, 則key = 1 << 3 | 0 = 0x20
。那么在內(nèi)存中, 其序列為應(yīng)該為0x20B960,占3Bytes。對(duì)比直接用struct存儲(chǔ)會(huì)占4Bytes;
如果struct是用 uint64呢,將占8Bytes,而PB占用內(nèi)存仍是3Bytes。
下表是PB數(shù)值范圍與其字節(jié)數(shù)對(duì)應(yīng)關(guān)系。實(shí)際應(yīng)用中,我們用到的數(shù)大概率是比較小的,而且可能 動(dòng)態(tài)范圍比較大(有時(shí)需要用64位存儲(chǔ)),對(duì)比struct的內(nèi)存占用,PB優(yōu)勢(shì)很明顯。
數(shù)值范圍 | 占用字節(jié)數(shù) |
---|---|
0-127 | 2 |
128-16383 | 3 |
16384-2097151 | 4 |
2097152-268435455 | 5 |
4.2.2 ZigZag編碼
ZigZag編碼是對(duì)Varint編碼的補(bǔ)充與優(yōu)化。負(fù)數(shù)在內(nèi)存中以前補(bǔ)碼形式存在,但不管是負(fù)數(shù)的原碼還是補(bǔ)碼,最高位都是1;那么問(wèn)題來(lái)了,如果以上述Varint編碼方式,所有負(fù)數(shù)序列化以后都會(huì)以最大化占用內(nèi)存(32位占用6Bytes, 64位占用11Btyes)。所以,細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),對(duì)于有符號(hào)數(shù)的表示有兩種類型,int32和sint32。對(duì),sint32就是對(duì)這種負(fù)數(shù)序列化優(yōu)化的變種。
inline uint32 WireFormatLite::ZigZagEncode32(int32 n) { // Note: the right-shift must be arithmetic return (static_cast<uint32>(n) << 1) ^ (n >> 31); }
sint32 | uint32 |
---|---|
0 | 0 |
-1 | 1 |
1 | 2 |
-2 | 3 |
… | … |
2147483647 | 4294967294 |
-2147483648 | 4294967295 |
對(duì)于sint32類型的數(shù)據(jù),在varint編碼之前,會(huì)先進(jìn)行zigzag編碼,上圖是其映射關(guān)系。編碼后,較小的負(fù)數(shù),可以映射為較小的正數(shù),從而實(shí)現(xiàn)根據(jù)其信息量決定其序列化后占用的內(nèi)存大小。
所以聰明的同學(xué)們已經(jīng)知道該如何選擇了,對(duì)于有符合數(shù)盡量選擇sint32,而不是int32,不管從空間和時(shí)間上,都是更優(yōu)的選擇
4.2.3 length-delimi編碼
length-delimi編碼方式比較簡(jiǎn)單,是建立在varint編碼基礎(chǔ)上的,主要是針對(duì)message、bytes、repeated等類型,與TLV格式類似。先以varint編碼寫入tag即Key,再以varint編碼寫入長(zhǎng)度,最后把內(nèi)容memcpy到內(nèi)存中。
inline uint8* WriteBytesToArray(int field_number, const string& value, uint8* target) { target = WriteTagToArray(field_number, WIRETYPE_LENGTH_DELIMITED, target); return io::CodedOutputStream::WriteStringWithSizeToArray(value, target); } uint8* WriteStringWithSizeToArray(const string& str, uint8* target) { GOOGLE_DCHECK_LE(str.size(), kuint32max); target = WriteVarint32ToArray(str.size(), target); return WriteStringToArray(str, target); }
4.2.4 fixed編碼
fixed編碼很簡(jiǎn)單,主要針對(duì)類型有fixed32、fixed64、double、float。由于長(zhǎng)度固定,只需要Key + Value即可。對(duì)于浮點(diǎn)型會(huì)先強(qiáng)制轉(zhuǎn)換成相對(duì)應(yīng)的整形,反序列化時(shí)則反之。
inline uint32 EncodeFloat(float value) { union {float f; uint32 i;}; f = value; return i; } inline uint64 EncodeDouble(double value) { union {double f; uint64 i;}; f = value; return i; } inline void WireFormatLite::WriteFloatNoTag(float value, io::CodedOutputStream* output) { output->WriteLittleEndian32(EncodeFloat(value)); } inline void WireFormatLite::WriteDoubleNoTag(double value, io::CodedOutputStream* output) { output->WriteLittleEndian64(EncodeDouble(value)); }
4.2.5 整個(gè)編碼流程
4.3 protobuf解碼過(guò)程
上面已經(jīng)介紹了編碼原理,那么解碼的流程也就很簡(jiǎn)單了。解碼是一個(gè)遞歸的過(guò)程,先通過(guò)Varint解碼過(guò)程讀出Key, 取出field_number字段,如果不存在于message中,就放到UnKnownField中。如果是認(rèn)識(shí)的field_number,則根據(jù)wire_type做具體的解析。對(duì)于普通類型(如整形,bytes, fixed類型等)就直接寫入Field中,如果是嵌套類型(一般特指嵌套的Message),則遞歸調(diào)用整個(gè)解析過(guò)程。解析完一個(gè)繼續(xù)解析下一個(gè),直到buffer結(jié)束。
5、gzip壓縮方案
5.1 gzip介紹
gzip是GNU zip的縮寫,它是一個(gè)GNU自由軟件的文件壓縮程序,也經(jīng)常用來(lái)表示gzip這種文件格式。軟件的作者是Jean-loup Gailly和Mark Adler。1992年10月31日第一次公開發(fā)布,版本號(hào)是0.1,目前的穩(wěn)定版本是1.2.4。
Gzip主要用于Unix系統(tǒng)的文件壓縮。我們?cè)贚inux中經(jīng)常會(huì)用到后綴為.gz的文件,它們就是GZIP格式的?,F(xiàn)今已經(jīng)成為Internet 上使用非常普遍的一種數(shù)據(jù)壓縮格式,或者說(shuō)一種文件格式。 當(dāng)應(yīng)用Gzip壓縮到一個(gè)純文本文件時(shí),效果是非常明顯的,經(jīng)過(guò)GZIP壓縮后頁(yè)面大小可以變?yōu)樵瓉?lái)的40%甚至更小,這取決于文件中的內(nèi)容。
HTTP協(xié)議上的GZIP編碼是一種用來(lái)改進(jìn)WEB應(yīng)用程序性能的技術(shù)。web開發(fā)中可以通過(guò)gzip壓縮頁(yè)面來(lái)降低網(wǎng)站的流量,而gzip并不會(huì)對(duì)cpu造成大量的占用,略微上升,也是幾個(gè)百分點(diǎn)而已,但是對(duì)于頁(yè)面卻能壓縮30%以上,非常劃算。
利用Apache中的Gzip模塊,我們可以使用Gzip壓縮算法來(lái)對(duì)Apache服務(wù)器發(fā)布的網(wǎng)頁(yè)內(nèi)容進(jìn)行壓縮后再傳輸?shù)娇蛻舳藶g覽器。這樣經(jīng)過(guò)壓縮后實(shí)際上降低了網(wǎng)絡(luò)傳輸?shù)淖止?jié)數(shù)(節(jié)約傳輸?shù)木W(wǎng)絡(luò)I/o),最明顯的好處就是可以加快網(wǎng)頁(yè)加載的速度。
網(wǎng)頁(yè)加載速度加快的好處不言而喻,除了節(jié)省流量,改善用戶的瀏覽體驗(yàn)外,另一個(gè)潛在的好處是Gzip與搜索引擎的抓取工具有著更好的關(guān)系。例如 Google就可以通過(guò)直接讀取gzip文件來(lái)比普通手工抓取更快地檢索網(wǎng)頁(yè)。在Google網(wǎng)站管理員工具(Google Webmaster Tools)中你可以看到,sitemap.xml.gz 是直接作為Sitemap被提交的。
而這些好處并不僅僅限于靜態(tài)內(nèi)容,PHP動(dòng)態(tài)頁(yè)面和其他動(dòng)態(tài)生成的內(nèi)容均可以通過(guò)使用Apache壓縮模塊壓縮,加上其他的性能調(diào)整機(jī)制和相應(yīng)的服務(wù)器端緩存規(guī)則,這可以大大提高網(wǎng)站的性能。因此,對(duì)于部署在Linux服務(wù)器上的PHP程序,在服務(wù)器支持的情況下,我們建議你開啟使用Gzip Web壓縮。
5.2 Web服務(wù)器處理HTTP壓縮的過(guò)程如下:
- Web服務(wù)器接收到瀏覽器的HTTP請(qǐng)求后,檢查瀏覽器是否支持HTTP壓縮(Accept-Encoding 信息);
- 如果瀏覽器支持HTTP壓縮,Web服務(wù)器檢查請(qǐng)求文件的后綴名;
- 如果請(qǐng)求文件是HTML、CSS等靜態(tài)文件,Web服務(wù)器到壓縮緩沖目錄中檢查是否已經(jīng)存在請(qǐng)求文件的最新壓縮文件;
- 如果請(qǐng)求文件的壓縮文件不存在,Web服務(wù)器向?yàn)g覽器返回未壓縮的請(qǐng)求文件,并在壓縮緩沖目錄中存放請(qǐng)求文件的壓縮文件;
- 如果請(qǐng)求文件的最新壓縮文件已經(jīng)存在,則直接返回請(qǐng)求文件的壓縮文件;
- 如果請(qǐng)求文件是動(dòng)態(tài)文件,Web服務(wù)器動(dòng)態(tài)壓縮內(nèi)容并返回瀏覽器,壓縮內(nèi)容不存放到壓縮緩存目錄中。
下面是兩個(gè)演示圖:
未使用Gzip:
開啟使用Gzip后:
5.3 啟用apache的gzip功能
Apache上利用Gzip壓縮算法進(jìn)行壓縮的模塊有兩種:mod_gzip 和mod_deflate。要使用Gzip Web壓縮,請(qǐng)首先確定你的服務(wù)器開啟了對(duì)這兩個(gè)組件之一的支持。
雖然使用Gzip同時(shí)也需要客戶端瀏覽器的支持,不過(guò)不用擔(dān)心,目前大部分瀏覽器都已經(jīng)支持Gzip了,如IE、Mozilla Firefox、Opera、Chrome等。
通過(guò)查看HTTP頭,我們可以快速判斷使用的客戶端瀏覽器是否支持接受gzip壓縮。若發(fā)送的HTTP頭中出現(xiàn)以下信息,則表明你的瀏覽器支持接受相應(yīng)的gzip壓縮:
Accept-Encoding: gzip 支持mod_gzip Accept-Encoding: deflate 支持mod_deflate Accept-Encoding: gzip,deflate 同時(shí)支持mod_gzip 和mod_deflate
如firebug查看:
Accept-Encoding: gzip,deflate 是同時(shí)支持mod_gzip 和mod_deflate
如果服務(wù)器開啟了對(duì)Gzip組件的支持,那么我們就可以在http.conf或.htaccess里面進(jìn)行定制,下面是一個(gè).htaccess配置的簡(jiǎn)單實(shí)例:
mod_gzip 的配置:
# mod_gzip: <ifModule mod_gzip.c> mod_gzip_on Yes mod_gzip_dechunk Yes mod_gzip_item_include file .(html?|txt|css|js|php|pl)$ mod_gzip_item_include handler ^cgi-script$ mod_gzip_item_include mime ^text/.* mod_gzip_item_include mime ^application/x-javascript.* mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.* <ifModule>
mod_deflate的配置實(shí)例:
打開打開apache 配置文件httpd.conf
將#LoadModule deflate_module modules/mod_deflate.so去除開頭的#號(hào)
# mod_deflate: <ifmodule mod_deflate.c> DeflateCompressionLevel 6 #壓縮率, 6是建議值. AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/xml AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE text/javascript AddOutputFilterByType DEFLATE application/xhtml+xml AddOutputFilterByType DEFLATE application/xml AddOutputFilterByType DEFLATE application/rss+xml AddOutputFilterByType DEFLATE application/atom_xml AddOutputFilterByType DEFLATE application/x-javascript AddOutputFilterByType DEFLATE application/x-httpd-php AddOutputFilterByType DEFLATE image/svg+xml </ifmodule>
里面的文件MIME類型可以根據(jù)自己情況添加,至于PDF 、圖片、音樂(lè)文檔之類的這些本身都已經(jīng)高度壓縮格式,重復(fù)壓縮的作用不大,反而可能會(huì)因?yàn)樵黾覥PU的處理時(shí)間及瀏覽器的渲染問(wèn)題而降低性能。所以就沒必要再通過(guò)Gzip壓縮。通過(guò)以上設(shè)置后再查看返回的HTTP頭,出現(xiàn)以下信息則表明返回的數(shù)據(jù)已經(jīng)過(guò)壓縮。即網(wǎng)站程序所配置的Gzip壓縮已生效。
Content-Encoding: gzip
firebug查看:
注意:
1)不管使用mod_gzip 還是mod_deflate,此處返回的信息都一樣。因?yàn)樗鼈兌际菍?shí)現(xiàn)的gzip壓縮方式。
2)CompressionLevel 9是指壓縮程度的等級(jí)(設(shè)置壓縮比率),取值范圍在從1到9,9是最高等級(jí)。據(jù)了解,這樣做最高可以減少8成大小的傳輸量(看檔案內(nèi)容而定),最少也能夠節(jié)省一半。 CompressionLevel 預(yù)設(shè)可以采用 6 這個(gè)數(shù)值,以維持耗用處理器效能與網(wǎng)頁(yè)壓縮質(zhì)量的平衡. 不建議設(shè)置太高,如果設(shè)置很高,雖然有很高的壓縮率,但是占用更多的CPU資源. 3) 對(duì)已經(jīng)是壓縮過(guò)的圖片格式如jpg,音樂(lè)檔案如mp3、壓縮文件如zip之類的,就沒必要再壓縮了。
5.4 mod_gzip 和mod_deflate的主要區(qū)別是什么?使用哪個(gè)更好呢?
第一個(gè)區(qū)別是安裝它們的Apache Web服務(wù)器版本的差異:
Apache 1.x系列沒有內(nèi)建網(wǎng)頁(yè)壓縮技術(shù),所以才去用額外的第三方mod_gzip 模塊來(lái)執(zhí)行壓縮。
Apache 2.x官方在開發(fā)的時(shí)候,就把網(wǎng)頁(yè)壓縮考慮進(jìn)去,內(nèi)建了mod_deflate 這個(gè)模塊,用以取代mod_gzip。雖然兩者都是使用的Gzip壓縮算法,它們的運(yùn)作原理是類似的。
第二個(gè)區(qū)別是壓縮質(zhì)量:
mod_deflate 壓縮速度略快而mod_gzip 的壓縮比略高。一般默認(rèn)情況下,mod_gzip 會(huì)比mod_deflate 多出4%~6%的壓縮量。
那么,為什么使用mod_deflate?
第三個(gè)區(qū)別是對(duì)服務(wù)器資源的占用:
一般來(lái)說(shuō)mod_gzip 對(duì)服務(wù)器CPU的占用要高一些。mod_deflate 是專門為確保服務(wù)器的性能而使用的一個(gè)壓縮模塊,mod_deflate 需要較少的資源來(lái)壓縮文件。這意味著在高流量的服務(wù)器,使用mod_deflate 可能會(huì)比mod_gzip 加載速度更快。
不太明白?簡(jiǎn)而言之,如果你的網(wǎng)站,每天不到1000獨(dú)立訪客,想要加快網(wǎng)頁(yè)的加載速度,就使用mod_gzip。雖然會(huì)額外耗費(fèi)一些服務(wù)器資源, 但也是值得的。如果你的網(wǎng)站每天超過(guò)1000獨(dú)立訪客,并且使用的是共享的虛擬主機(jī),所分配系統(tǒng)資源有限的話,使用mod_deflate 將會(huì)是更好的選擇。
另外,從Apache 2.0.45開始,mod_deflate 可使用DeflateCompressionLevel 指令來(lái)設(shè)置壓縮級(jí)別。該指令的值可為1(壓縮速度最快,最低的壓縮質(zhì)量)至9(最慢的壓縮速度,壓縮率最高)之間的整數(shù),其默認(rèn)值為6(壓縮速度和壓縮質(zhì) 量較為平衡的值)。這個(gè)簡(jiǎn)單的變化更是使得mod_deflate 可以輕松媲美m(xù)od_gzip 的壓縮。
P.S. 對(duì)于沒有啟用以上兩種Gzip模塊的虛擬空間,還可以退而求其次使用php的zlib函數(shù)庫(kù)(同樣需要查看服務(wù)器是否支持)來(lái)壓縮文件,只是這種方法使用起來(lái)比較麻煩,而且一般會(huì)比較耗費(fèi)服務(wù)器資源,請(qǐng)根據(jù)情況慎重使用。
5.5 zlib.output_compression和ob_gzhandler編碼方式壓縮
服務(wù)器不支持mod_gzip、mod_deflate模塊,若想通過(guò)GZIP壓縮網(wǎng)頁(yè)內(nèi)容,可以考慮兩種方式,開啟zlib.output_compression或者通過(guò)ob_gzhandler編碼的方式。
1)zlib.output_compression是在對(duì)網(wǎng)頁(yè)內(nèi)容壓縮的同時(shí)發(fā)送數(shù)據(jù)至客戶端。
2)ob_gzhandler是等待網(wǎng)頁(yè)內(nèi)容壓縮完畢后才進(jìn)行發(fā)送,相比之下前者效率更高,但需要注意的是,兩者不能同時(shí)使用,只能選其一,否則將出現(xiàn)錯(cuò)誤。
兩者的實(shí)現(xiàn)方式做簡(jiǎn)單描述:
5.5.1 zlib.output_compression實(shí)現(xiàn)方式
在默認(rèn)情況下,zlib.output_compression是關(guān)閉:
; Transparent output compression using the zlib library ; Valid values for this option are 'off', 'on', or a specific buffer size ; to be used for compression (default is 4KB) ; Note: Resulting chunk size may vary due to nature of compression. PHP ; outputs chunks that are few hundreds bytes each as a result of ; compression. If you prefer a larger chunk size for better ; performance, enable output_buffering in addition. ; Note: You need to use zlib.output_handler instead of the standard ; output_handler, or otherwise the output will be corrupted. ; http://php.net/zlib.output-compression zlib.output_compression = Off ; http://php.net/zlib.output-compression-level ;zlib.output_compression_level = -1
如需開啟需編輯php.ini文件,加入以下內(nèi)容:
zlib.output_compression = On zlib.output_compression_level = 6
可以通過(guò)phpinfo()函數(shù)檢測(cè)結(jié)果。
當(dāng)zlib.output_compression的Local Value和MasterValue的值同為On時(shí),表示已經(jīng)生效,這時(shí)候訪問(wèn)的PHP頁(yè)面(包括偽靜態(tài)頁(yè)面)已經(jīng)GZIP壓縮了,通過(guò)Firebug或者在線網(wǎng)頁(yè)GZIP壓縮檢測(cè)工具可檢測(cè)到壓縮的效果。
5.5.2 ob_gzhandler的實(shí)現(xiàn)方式
如果需要使用ob_gzhandler,則需關(guān)閉zlib.output_compression,把php.ini文件內(nèi)容更改為:
zlib.output_compression = Off zlib.output_compression_level = -1
通過(guò)在PHP文件中插入相關(guān)代碼實(shí)現(xiàn)GZIP壓縮P壓縮:
if (extension_loaded('zlib')) { if ( !headers_sent() AND isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) //頁(yè)面沒有輸出且瀏覽器可以接受GZIP的頁(yè)面 { ob_start('ob_gzhandler'); } } //待壓縮的內(nèi)容 echo $context; ob_end_flush();
如何瀏覽器提示:內(nèi)容編碼錯(cuò)誤,應(yīng)該是:
使用ob_start('ob_gzhandler')時(shí)候前面已經(jīng)有內(nèi)容輸出,檢查前面內(nèi)容以及require include調(diào)用文件的內(nèi)容。若無(wú)法找到可以在調(diào)用其它文件前使用ob_start(),調(diào)用之后使用 ob_end_clean () 來(lái)清除輸出的內(nèi)容:
if (extension_loaded('zlib')) { if ( !headers_sent() AND isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) //頁(yè)面沒有輸出且瀏覽器可以接受GZIP的頁(yè)面 { ob_end_clean (); ob_start('ob_gzhandler'); } }
或者我們使用gzencode來(lái)壓縮:
<?php $encoding = 'gzip'; $content = '123456789'; ob_end_clean (); header('Content-Encoding: '.$encoding); $result = gzencode($content); echo $result; exit;
不管是zlib.output_compression還是ob_gzhandler,都僅能對(duì)PHP文件進(jìn)行GZIP壓縮,對(duì)于HTML、CSS、JS等靜態(tài)文件只能通過(guò)調(diào)用PHP的方式實(shí)現(xiàn)。
最后想說(shuō)的是,現(xiàn)在主流的瀏覽器默認(rèn)使用的是HTTP1.1協(xié)議,基本都支持GZIP壓縮,對(duì)于IE而言,假如你沒有選中其菜單欄工具-》Internet 選項(xiàng)-》高級(jí)-》HTTP 1.1 設(shè)置-》使用 HTTP 1.1,那么,你將感受不到網(wǎng)頁(yè)壓縮后的速度提升所帶來(lái)的快感!
以上就是Android性能優(yōu)化之弱網(wǎng)優(yōu)化詳解的詳細(xì)內(nèi)容,更多關(guān)于Android 性能弱網(wǎng)優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android性能優(yōu)化系列篇UI優(yōu)化
- Android性能優(yōu)化之JVMTI與內(nèi)存分配
- Android性能優(yōu)化之捕獲java crash示例解析
- Android性能優(yōu)化之線程監(jiān)控與線程統(tǒng)一詳解
- Android性能優(yōu)化之plt?hook與native線程監(jiān)控詳解
- Android?性能優(yōu)化實(shí)現(xiàn)全量編譯提速的黑科技
- Android性能優(yōu)化之RecyclerView分頁(yè)加載組件功能詳解
- Android性能優(yōu)化死鎖監(jiān)控知識(shí)點(diǎn)詳解
相關(guān)文章
AndroidManifest.xml <uses-feature>和<uses-permisstio
這篇文章主要介紹了AndroidManifest.xml <uses-feature>和<uses-permisstion>分析及比較的相關(guān)資料,需要的朋友可以參考下2017-06-06詳解Android開發(fā)之MP4文件轉(zhuǎn)GIF文件
這篇文章介紹的是將錄下來(lái)的視頻選取一小段轉(zhuǎn)為 GIF 文件,不僅時(shí)間段可以手動(dòng)選取,而且還需要支持截取視頻的局部區(qū)域轉(zhuǎn)為 GIF,網(wǎng)上調(diào)研了一下技術(shù)方案,覺得還是有必要把實(shí)現(xiàn)過(guò)程拿出來(lái)分享下,有需要的可以直接拿過(guò)去用。下面來(lái)一起看看。2016-08-08Android仿微信、qq點(diǎn)擊右上角加號(hào)彈出操作框
這篇文章主要為大家詳細(xì)介紹了Android仿微信、qq點(diǎn)擊右上角加號(hào)彈出操作框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04Android 游戲引擎libgdx 資源加載進(jìn)度百分比顯示案例分析
因?yàn)榘咐容^簡(jiǎn)單,所以簡(jiǎn)單用AndroidApplication -> Game -> Stage 搭建框架感興趣的朋友可以參考下2013-01-01Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼60秒后重新獲取功能
這篇文章主要為大家詳細(xì)介紹了Android點(diǎn)擊獲取驗(yàn)證碼60秒后重新獲取驗(yàn)證碼的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06Android開發(fā)筆記SQLite優(yōu)化記住密碼功能
這篇文章主要為大家詳細(xì)介紹了Android開發(fā)筆記SQLite優(yōu)化記住密碼功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android實(shí)現(xiàn)將已發(fā)送的短信寫入短信數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)將已發(fā)送的短信寫入短信數(shù)據(jù)庫(kù)的方法,是Android手機(jī)開發(fā)常見的技巧,需要的朋友可以參考下2014-09-09