欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android性能優(yōu)化之弱網(wǎng)優(yōu)化詳解

 更新時(shí)間:2022年10月19日 10:14:17   作者:塞爾維亞大漢  
這篇文章主要為大家介紹了Android性能優(yōu)化之弱網(wǎng)優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

弱網(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ù)組而重寫writeObjectreadObject

    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
TypeValueMeaningContain
Varint0varintint32,int64,sint32,sint64,uint32,uint64,bool,enum
Fixed64164-bitfixed64,sfixed64,double,float
LengthDelimited2length-delimistring,message,bytes,repeated
StartGroup3start groupgroups(deprecated)
EndGroup4end groupgroups(deprecated)
Fixed32532-bitfixed32,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-1272
128-163833
16384-20971514
2097152-2684354555

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);
}
sint32uint32
00
-11
12
-23
21474836474294967294
-21474836484294967295

對(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)文章!

相關(guān)文章

最新評(píng)論