[轉]struct和class的區別 觀察者模式 https鏈接 點擊button收到點

問題:

4道過濾菜鳥的iOS面試題 

網上已經有不少針對各類知識點的面試題,面試時有些人未必真正理解也能經過背題看上去很懂。我本身總結了4道面試題,好快速的判斷這我的是不是一個合格的工程師,歡迎你們點評。html

1.struct和class的區別ios

在面試以前你以爲全部的計算機專業的學生都應該能答的上來,事實是我面的人裏有超過三分一沒有答上來。git

有時我還會順便問下swfit裏的array是什麼類型,在大量複製時會不會有性能問題。程序員

2.介紹一下觀察者模式github

也許有些人已經以爲設計模式有些過期,沒有整本讀過。就算如此iOS裏經常使用的幾個設計模式我以爲總要了解吧。面試

這裏若是說NSNotificationCenter怎麼使用的就直接pass。算法

這個回答應該包括三個部分:首先這個設計模式爲了解決什麼問題,其次經過什麼方案來解決,最後纔是當前體系下的具體實現方案。編程

3.在一個https鏈接的網站裏,輸入帳號密碼點擊登陸後,到服務器返回這個請求前,中間經歷了什麼設計模式

這題是在其餘看到的,原本題目是登陸gmail的時候,可是國內也許有些人不知道Google很早就全站https了,因此這裏特別指出是https的鏈接。數組

這裏面能夠談的東西就不少了,TCP/IP下有很是多的協議。不須要什麼都能說的清楚,可是對於整個網絡鏈接模型的理解能夠看出基本功。

4.在一個app中間有一個button,在你手觸摸屏幕點擊後,到這個button收到點擊事件,中間發生了什麼

runloop和響應鏈須要說的清楚。

有時還會順便問問UIResponder、UIControl、UIView的關係。


這4個問題只是爲了一上來能夠快速篩選掉不合適的程序員,畢竟有的人只須要幾分鐘就知道他不合適了,好節省時間。

 


一、Struct和Class的區別 

 

C++中的struct對C中的struct進行了擴充,它已經再也不只是一個包含不一樣數據類型的數據結構了,它已經獲取了太多的功能。
struct能包含成員函數嗎? 能!
struct能繼承嗎? 能!!
struct能實現多態嗎? 能!!!
 

既然這些它都能實現,那它和class還能有什麼區別?

最本質的一個區別就是默認的訪問控制: 

默認的繼承訪問權限

struct是public的,class是private的。
你能夠寫以下的代碼:
struct A
{
  char a;
};
struct B : A
{
  char b;
};

這個時候B是public繼承A的。

若是都將上面的struct改爲class,那麼B是private繼承A的。這就是默認的繼承訪問權限。 

因此咱們在平時寫類繼承的時候,一般會這樣寫:
class B : public A

就是爲了指明是public繼承,而不是用默認的private繼承。

 

固然,到底默認是public繼承仍是private繼承取決於子類而不是基類

個人意思是,struct能夠繼承class,一樣class也能夠繼承struct,那麼默認的繼承訪問權限是看子類究竟是用的struct仍是class。以下:

 

struct A{};class B : A{}; //private繼承
struct C : B{}; //public繼承

 

struct做爲數據結構的實現體,它默認的數據訪問控制是public的,而class做爲對象的實現體,它默認的成員變量訪問控制是private的

 

我依舊強調struct是一種數據結構的實現體,雖然它是能夠像class同樣的用。我依舊將struct裏的變量叫數據,class內的變量叫成員,雖然它們並沒有區別。


究竟是用struct仍是class,徹底看我的的喜愛,你能夠將程序裏全部的class所有替換成struct,它依舊能夠很正常的運行。但我給出的最好建議,仍是:當你以爲你要作的更像是一種數據結構的話,那麼用struct,若是你要作的更像是一種對象的話,那麼用class。 

固然,我在這裏還要強調一點的就是,對於訪問控制,應該在程序裏明確的指出,而不是依靠默認,這是一個良好的習慣,也讓你的代碼更具可讀性。 

說到這裏,不少了解的人或許都認爲這個話題能夠結束了,由於他們知道struct和class的「惟一」區別就是訪問控制。不少文獻上也確實只提到這一個區別。 

但我上面卻沒有用「惟一」,而是說的「最本質」,那是由於,它們確實還有另外一個區別,雖然那個區別咱們平時可能不多涉及。

那就是:「class」這個關鍵字還用於定義模板參數,就像「typename」。但關鍵字「struct」不用於定義模板參數。這一點在Stanley B.Lippman寫的Inside the C++ Object Model有過說明。 

問題討論到這裏,基本上應該能夠結束了。但有人曾說過,他還發現過其餘的「區別」,那麼,讓咱們來看看,這究竟是不是又一個區別。仍是上面所說的,C++中的struct是對C中的struct的擴充,既然是擴充,那麼它就要兼容過去C中struct應有的全部特性。例如你能夠這樣寫: 

struct A //定義一個struct
{
   char c1;
   int n2;
   double db3;
};
A a={'p', 7, 3.1415926}; //定義時直接賦值 

也就是說struct能夠在定義的時候用{}賦初值。那麼問題來了,class行不行呢?將上面的struct改爲class,試試看。報錯!噢~因而那人跳出來講,他又找到了一個區別。咱們仔細看看,這真的又是一個區別嗎? 

你試着向上面的struct中加入一個構造函數(或虛函數),你會發現什麼?
對,struct也不能用{}賦初值了
的確,以{}的方式來賦初值,只是用一個初始化列表來對數據進行按順序的初始化,如上面若是寫成A a={'p',7};則c1,n2被初始化,而db3沒有。這樣簡單的copy操做,只能發生在簡單的數據結構上,而不該該放在對象上。加入一個構造函數或是一個虛函數會使struct更體現出一種對象的特性,而使此{}操做再也不有效。 

事實上,是由於加入這樣的函數,使得類的內部結構發生了變化。而加入一個普通的成員函數呢?你會發現{}依舊可用。其實你能夠將普通的函數理解成對數據結構的一種算法,這並不打破它數據結構的特性。 

那麼,看到這裏,咱們發現即便是struct想用{}來賦初值,它也必須知足不少的約束條件,這些條件實際上就是讓struct更體現出一種數據機構而不是類的特性。 

那爲何咱們在上面僅僅將struct改爲class,{}就不能用了呢?

其實問題恰巧是咱們以前所講的——訪問控制!你看看,咱們忘記了什麼?對,將struct改爲class的時候,訪問控制由public變爲private了,那固然就不能用{}來賦初值了。加上一個public,你會發現,class也是能用{}的,和struct毫無區別!!! 

作個總結,從上面的區別,咱們能夠看出,struct更適合當作是一個數據結構的實現體,class更適合當作是一個對象的實現體。

 

 

 

二、觀察者模式

 

 

什麼是觀察者模式?咱們先打個比方,這就像你訂報紙。好比你想知道美國最近放生了些新聞,你可能會訂閱一份美國週刊,而後一旦美國有了新的故事,美國週刊就發一刊,並郵寄給你,當你收到這份報刊,而後你就可以瞭解美國最新的動態。其實這就是觀察者模式,A對B的變化感興趣,就註冊爲B的觀察者,當B發生變化時通知A,告知B發生了變化。這是一種很是典型的觀察者的用法,我把這種使用方法叫作經典觀察者模式。固然與之相對的還有另一種觀察者模式——廣義觀察者模式。

從經典的角度看,觀察者模式是一種通知變化的模式,通常認爲只在對象發生變化感興趣的場合有用。主題對象知道有觀察者存在,設置會維護觀察者的一個隊列;而從廣義的角度看,觀察者模式是中傳遞變化數據的模式,須要查看對象屬性時就會使用的一種模式,主題對象不知道觀察者的存在,更像是圍觀者。須要知道主題對象的狀態,因此即便在主題對象沒有發生改變的時候,觀察者也可能會去訪問主題對象。換句話說廣義觀察者模式,是在不一樣的對象之間傳遞數據的一種模式。

觀察者模式應當是在面向對象編程中被大規模使用的設計模式之一。從方法論的角度出發,傳統的認知論認爲,世界是由對象組成的,咱們經過不停的觀察和了解就可以瞭解對象的本質。整我的類的認知模型就是創建在「觀察」這種行爲之上的。咱們經過不停與世界中的其餘對象交互,並觀察之來了解這個世界。一樣,在程序的世界中,咱們構建的每個實例,也是經過不不停的與其餘對象交互(查看其餘對象的狀態,或者改變其餘對象的狀態),並經過觀察其餘實例的變化並做出響應,以來完成功能。這也就是,爲何會把觀察模式單獨提出來,作一個專門的剖析的緣由——在我看來他是不少其餘設計模式的基礎模式,而且是編程中極其重要的一種設計模式。


經典觀察者模式

經典觀察者模式被認爲是對象的行爲模式,又叫發佈-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。經典觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知全部觀察者對象,使它們可以自動更新本身或者作出相應的一些動做。在文章一開始舉的例子就是典型觀察者模式的應用。

而在IOS開發中咱們可能會接觸到的經典觀察者模式的實現方式,有這麼幾種:NSNotificationCenter、KVO、Delegate等


感知通知方式

在經典觀察者模式中,由於觀察者感知到主題對象變化方式的不一樣,又分爲推模型和拉模型兩種方式。

推模型

ios desing pattern observer 1

主題對象向觀察者推送主題的詳細信息,無論觀察者是否須要,推送的信息一般是主題對象的所有或者部分數據。推模型實現了觀察者和主題對象的解耦,二者之間沒有過分的依賴關係。可是推模型每次都會以廣播的方式,向全部觀察者發送通知。全部觀察者被動的接受通知。當通知的內容過多時,多個觀察者同時接收,可能會對網絡、內存(有些時候還會涉及IO)有較大影響。

在IOS中典型的推模型實現方式爲NSNotificationCenter和KVO。

NSNotificationCenter

NSnotificationCenter是一種典型的有調度中心的觀察者模式實現方式。以NSNotificationCenter爲中心,觀察者往Center中註冊對某個主題對象的變化感興趣,主題對象經過NSNotificationCenter進行變化廣播。這種模型就是文章開始發佈訂閱報紙在OC中的一種相似實現。全部的觀察和監聽行爲都向同一個中心註冊,全部對象的變化也都經過同一個中心向外廣播。

SNotificationCenter就像一個樞紐同樣,處在整個觀察者模式的核心位置,調度着消息在觀察者和監聽者之間傳遞。

ios desing pattern observer 2

一次完整的觀察過程如上圖所示。整個過程當中,關鍵的類有這麼幾個(介紹順序按照完成順序):

  1. 觀察者Observer,通常繼承自NSObject,經過NSNotificationCenter的addObserver:selector:name:object接口來註冊對某一類型通知感興趣.在註冊時候必定要注意,NSNotificationCenter不會對觀察者進行引用計數+1的操做,咱們在程序中釋放觀察者的時候,必定要去報從center中將其註銷了。
  2. 通知中心NSNotificationCenter,通知的樞紐。
  3. 主題對象,被觀察的對象,經過postNotificationName:object:userInfo:發送某一類型通知,廣播改變。
  4. 通知對象NSNotification,當有通知來的時候,Center會調用觀察者註冊的接口來廣播通知,同時傳遞存儲着更改內容的NSNotification對象。

apple版實現的NotificationCenter讓我用起來不太爽的幾個小問題

在使用NSNotificationCenter的時候,從編程的角度來說咱們每每不止是但願可以作到功能實現,還能但願編碼效率和整個工程的可維護性良好。而Apple提供的以NSNotificationCenter爲中心的觀察者模式實現,在可維護性和效率上存在如下缺點:

  1. 每一個註冊的地方須要同時註冊一個函數,這將會帶來大量的編碼工做。仔細分析可以發現,其實咱們每一個觀察者每次註冊的函數幾乎都是雷同的。這就是種變相的CtrlCV,是典型的醜陋和難維護的代碼。
  2. 每一個觀察者的回調函數,都須要對主題對象發送來的消息進行解包的操做。從UserInfo中經過KeyValue的方式,將消息解析出來,然後進行操做。試想一下,工程中有100個地方,同時對前面中在響應變化的函數中進行了解包的操做。然後期需求變化須要多傳一個內容的時候,將會是一場維護上的災難。
  3. 當大規模使用觀察者模式的時候,咱們每每在dealloc處加上一句:
    [[NSNotificationCenter defaultCenter] removeObserver:self]
    而在實際使用過程當中,會發現該函數的性能是比較低下的。在整個啓動過程當中,進行了10000次RemoveObserver操做,


    經過下圖能夠看出這一過程消耗了23.4%的CPU,說明這一函數的效率仍是很低的。
    ios desing pattern observer 6
    這仍是隻有一種消息類型的存在下有這樣的結果,若是整個NotificationCenter中混雜着多種消息類型,那麼恐怕對於性能來講將會是災難性的。

    增長了多種消息類型以後,RemoveObserver佔用了啓動過程當中63.9%的CPU消耗。
    ios desing pattern observer 7
    而因爲Apple沒有提供Center的源碼,因此修改這個Center幾乎不可能了。
改進版的有中心觀察者模式(DZNotificationCenter)

GitHub地址 在設計的時候考慮到以上用起來不爽的地方,進行了優化:

  1. 將解包到執行函數的操做進行了封裝,只須要提供某消息類型的解包block和消息類型對應的protocol,當有消息到達的時候,消息中心會進行統一解包,並直接調用觀察者相應的函數。
  2. 對觀察者的維護機制進行優化(還未作完),提高查找和刪除觀察者的效率。

DZNotificationCenter的用法和NSNotificationCenter在註冊和註銷觀察者的地方是同樣的,不同的地方在於,你在使用的時候須要提供解析消息的block。你能夠經過兩種方式來提供。

  • 直接註冊的方式
  • 實現DZNotificationInitDelegaete協議,當整個工程中大規模使用觀察者的時候,建議使用該方式。這樣有利於統一管理全部的解析方式。

     

    在使用的過程當中爲了,可以保證在觀察者處可以回調相同的函數,能夠實現針對某一消息類型的protocol

    這樣就可以保證,在使用觀察者的地方不用反覆的拼函數名和解析消息內容了。

     

    KVO

    KVO的全稱是Key-Value Observer,即鍵值觀察。是一種沒有中心樞紐的觀察者模式的實現方式。一個主題對象管理全部依賴於它的觀察者對象,而且在自身狀態發生改變的時候主動通知觀察者對象。 讓咱們先看一個完整的示例:

    完成一次完整的改變通知過程,通過如下幾回過程:

    1. 註冊觀察者[message addObserver:self forKeyPath:kKVOPathKey options:NSKeyValueObservingOptionNew context:Nil];
    2. 更改主題對象屬性的值,即觸發發送更改的通知 _message.key = @"asdfasd";
    3. 在制定的回調函數中,處理收到的更改通知
    4. 註銷觀察者 [_message removeObserver:self forKeyPath:kKVOPathKey];

    KVO實現原理

    通常狀況下對於使用Property的屬性,objc會爲其自動添加鍵值觀察功能,你只須要寫一句@property (noatomic, assign) float age 就可以得到age的鍵值觀察功能。而爲了更深刻的探討一下,KVO的實現原理咱們先手動實現一下KVO:

    首先,須要手動實現屬性的 setter 方法,並在設置操做的先後分別調用 willChangeValueForKey: 和 didChangeValueForKey方法,這兩個方法用於通知系統該 key 的屬性值即將和已經變動了;

    其次,要實現類方法 automaticallyNotifiesObserversForKey,並在其中設置對該 key 不自動發送通知(返回 NO 便可)。這裏要注意,對其它非手動實現的 key,要轉交給 super 來處理。

    在這裏的手動實現,主要是手動實現了主題對象變動向外廣播的過程。後續如何廣播到觀察者和觀察者如何響應咱們沒有實現,其實這兩個過程apple已經封裝的很好了,猜想一下的話,應該是主題對象會維護一個觀察者的隊列,當自己屬性發生變更,接受到通知的時候,找到相關屬性的觀察者隊列,依次調用observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context來廣播更改。 還有一個疑問,就是在自動實現KVO的時候,系統是否和咱們手動實現作了一樣的事情呢?

    自動實現KVO及其原理

    咱們仔細來觀察一下在使用KVO的過程當中類DZMessage的一個實例發生了什麼變化: 在使用KVO以前:

    ios desing pattern observer 3

    當調用Setter方法,並打了斷點的時候:

    ios desing pattern observer 4

    神奇的發現類的isa指針發生了變化,咱們本來的類叫作DZMessage,而使用KVO後類名變成了NSKVONotifying_DZMessage。這說明objc在運行時對咱們的類作了些什麼。

    咱們從Apple的文檔Key-Value Observing Implementation Details找到了一些線索。

    Automatic key-value observing is implemented using a technique called isa-swizzling. The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table.This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

    當某一個類的實例第一次使用KVO的時候,系統就會在運行期間動態的建立該類的一個派生類,該類的命名規則通常是以NSKVONotifying爲前綴,以本來的類名爲後綴。而且將原型的對象的isa指針指向該派生類。同時在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實現真正的通知機制,正如前面咱們手動實現KVO同樣。這麼作是基於設置屬性會調用 setter 方法,而經過重寫就得到了 KVO 須要的通知機制。固然前提是要經過遵循 KVO 的屬性設置方式來變動屬性值,若是僅是直接修改屬性對應的成員變量,是沒法實現 KVO 的。

    同時派生類還重寫了 class 方法以「欺騙」外部調用者它就是起初的那個類。所以這個對象就成爲該派生類的對象了,於是在該對象上對 setter 的調用就會調用重寫的 setter,從而激活鍵值通知機制。此外,派生類還重寫了 dealloc 方法來釋放資源。


    拉模型

    ios desing pattern observer 5

    拉模型是指主題對象在通知觀察者的時候,只傳遞少許信息或者只是通知變化。若是觀察者需求要更具體的信息,由觀察者主動從主題對象中拉取數據。相比推模型來講,拉模型更加自由,觀察者只要知道有狀況發生就行了,至於何時獲取、獲取那些內容、甚至是否獲取均可以自主決定。可是,卻存在兩個問題:

    • 若是某個觀察者響應過慢,可能會漏掉以前通知的內容
    • 觀察者必須保存一個對目標對象的引用,並且還須要瞭解主題對象的結構,這就使觀察者產生了對主題對象的依賴。

    可能每種設計模式都會存在或多或少的一些弊端,可是他們的確可以解決問題,也有更多有用的地方。在使用的時候,就須要咱們權衡利弊,作出一個合適的選擇。而工程師的價值就體如今,可以在紛繁複雜的工具世界中找到最有效的那個。而若是核桃沒被砸開,不是你手力氣不大的問題,而是你選錯了工具,誰讓你非得用門縫夾,不用錘子呢!

    固然,上面那段屬於題外話。言歸正傳,在OBJC編程中,典型的一種拉模型的實現是delegate。可能不少人會不一樣意個人觀點,說delegate應當是委託模式。好吧,我不否定,delegate的確是委託模式的一種極度典型的實現方式。可是這並不妨礙,他也是一種觀察者模式。其實原本各類設計模式之間就不是涇渭分明的。在使用和解釋的時候,只要你可以說得通,並且可以解決問題就行了,不必糾纏他們的名字。而在通知變化這個事情上delegate的確是可以解決問題的。

    咱們來看一個使用delegate實現拉模型的觀察者的例子:

    • 先實現一個delegate方便註冊觀察者,和回調函數

     

     

    • 註冊觀察者

     

     

    • 當主題對象的屬性發生改變的時候,發送內容有變化的通知

     

     

    • 觀察者收到主題對象有變化的通知後,主動去拉取變化的內容。

     

     


    廣義觀察者模式

    在上面介紹了,觀察者被動的接受主題改變的經典意義上的觀察者模式以後,咱們再來看一下廣義觀察者模式。固然上面所講的經典觀察者模式,也是一種一種傳遞數據的方式。廣義觀察者涵蓋了經典觀察者模式。

    每每咱們會有須要在「觀察者」和「主題對象」之間傳遞變化的數據。而這種狀況下,主題對象可能不會像經典觀察者模式中的主題對象那樣勤勞,在發生改變的時候不停的廣播。在廣義觀察者模式中,主題對象多是懶惰的,而是由觀察者經過不停的查詢主題對象的狀態,來獲知改變的內容。

    咱們熟悉的服務器CS架構,始終比較典型的冠以觀察者模式,服務器是伺服的,等待着客戶端的訪問,客戶端經過訪問服務器來獲取最新的內容,而不是服務器主動的推送。

    之因此,要提出廣義觀察者模式這樣一個概念。是爲了探討一下觀察者模式的本質。方便咱們可以更深入的理解觀察者模式,而且合理的使用它。並且咱們平時更多的將注意力放在了通知變化上面,而觀察者根本的目的是在於,在觀察者和主題對象之間,傳遞變化的數據。這些數據多是變化這個事件自己,也多是變化的內容,甚至多是一些其餘的內容。

    從變化數據傳遞的角度來思考的話,可以實現這個的模式和策略實在是數不勝數,好比傳統的網絡CS模型,好比KVC等等。在這裏就先不詳細展開討論了。

     

    三、HTTPS鏈接的前幾毫秒發生了什麼

     

    提示:英文原文寫於2009年,當時的Firefox和最新版的Firefox,界面也有很大改動。如下是正文。

    花了數小時閱讀了如潮的好評,Bob最終火燒眉毛爲他購買的托斯卡納全脂牛奶點擊了「進行結算」,而後……

    哇!剛剛發生了什麼?

    在點擊按鈕事後的220毫秒時間內,發生了一系列有趣的事情,火狐瀏覽器(Firefox)不只改變了地址欄顏色,並且在瀏覽器的右下角出現了一個小鎖頭的標誌。在我最喜歡的互聯網工具Wireshark的幫助下,咱們能夠經過一個通過略微調整的用於debug的火狐瀏覽器來探究這一過程。

    根據RFC 2818標準(譯者注:RFC 2818爲HTTP Over TLS-網絡協議),火狐瀏覽器自動經過鏈接Amazon.com的443端口來響應HTTPS請求。

    不少人會把HTTPS和網景公司(Netscape)於上世紀九十年代中期建立的SSL(安全套接層)聯繫起來。事實上,隨着時間的推移,這二者之間的關係也慢慢淡化。隨着網景公司漸漸的失去市場份額,SSL的維護工做移交給了Internet工程任務組(IETF)。由網景公司發佈的第一個版本被從新命名爲TLS 1.0(安全傳輸層協議 1.0),並於1999年1月正式發佈。考慮到TLS已經發布了將近10年,現在已經很難再見到真正的SSL通訊了。

     

    客戶端問候(Client Hello)

    TLS將所有的通訊以不一樣方式包裹爲「記錄」(Records)。咱們能夠看到,從瀏覽器發出的第一個字節爲0x16(十進制的22),它表示了這是一個「握手」記錄。

    接下來的兩個字節是0x0301,它表示了這是一條版本爲3.1的記錄,同時也向咱們代表了TLS1.0其實是基於SSL3.1構建而來的。

    整個握手記錄被拆分爲數條信息,其中第一條就是咱們的客戶端問候(Client Hello),即0x01。在客戶端問候中,有幾個須要着重注意的地方:

    •  隨機數:

    在客戶端問候中,有四個字節以Unix時間格式記錄了客戶端的協調世界時間(UTC)。協調世界時間是從1970年1月1日開始到當前時刻所經歷的秒數。在這個例子中,0x4a2f07ca就是協調世界時間。在他後面有28字節的隨機數,在後面的過程當中咱們會用到這個隨機數。

    • SID(Session ID):

    在這裏,SID是一個空值(Null)。若是咱們在幾秒鐘以前就登錄過了Amazon.com,咱們有可能會恢復以前的會話,從而避免一個完整的握手過程。

    • 密文族(Cipher Suites):

    密文族是瀏覽器所支持的加密算法的清單。整個密文族是由推薦的加密算法「TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA」和33種其餘加密算法所組成。別擔憂其餘的加密算法會出現問題,咱們一下子就會發現Amazon也沒有使用推薦的加密算法。

    •  Server_name擴展:

    經過這種方式,咱們可以告訴Amazon.com:瀏覽器正在試圖訪問https://www.amazon.com。這確實方便了不少,由於咱們的TLS握手時間發生在HTTP通訊以前,而HTTP請求會包含一個「Host頭」,從而使那些爲了節約成本而將數百個網站域名解析到一個IP地址上的網絡託管商可以分辨出一個網絡請求對應的是哪一個網站。傳統意義上的SSL一樣要求一個網站請求對應一個IP地址,可是Server_name擴展則容許服務器對瀏覽器的請求授予相對應的證書。若是沒有其餘的請求,Server_name擴展應該容許瀏覽器訪問這個IPV4地址一週左右的時間。

     

    服務器問候(Server Hello)

    Amazon.com回覆的握手記錄由兩個比較大的包組成(2551字節)。記錄中包含了0x0301的版本信息,意味着Amazon贊成咱們使用TLS1.0訪問的請求。這條記錄包含了三條有趣的子信息:

    1.服務器問候信息(Server Hello)(2):

    1. 咱們獲得了服務器的以Unix時間格式記錄的UTC和28字節的隨機數。
    2. 32字節的SID,在咱們想要從新鏈接到Amazon.com的時候能夠避免一整套握手過程。
    3. 在咱們所提供的34個加密族中,Amazon挑選了「TLS_RSA_WITH_RC4_128_MD5」(0x0004)。這就意味着Amazon會使用RSA公鑰加密算法來區分證書籤名和交換密鑰,經過RC4加密算法來加密數據,利用Md5來校驗信息。咱們以後會深刻的研究這一部份內容。我我的認爲,Amazon選擇這一密碼組是有其自身的緣由的。在咱們所提供的密碼族中,這一加密組的加密方式是CPU佔用最低的,這就容許Amazon的每臺服務器接受更多的鏈接。固然了,也許還有一個緣由是,Amazon是在向這三種加密算法的發明者Ron Rivest(羅恩·李·維斯特)致敬。

    2.證書信息(11):

    這段巨大的信息共有2464字節,其證書容許客戶端在Amazon服務器上進行認證。這個證書其實並無什麼奇特之處,你能經過瀏覽器瀏覽它的大部份內容。

    3.服務器問候結束信息(14):

    這是一個零字節信息,用於告訴客戶端整個「問候」過程已經結束,而且代表服務器不會再向客戶端詢問證書。

     

    校驗證書

    此時,瀏覽器已經知道是否應該信任Amazon.com。在這個例子中,瀏覽器經過證書確認網站是否受信,它會檢查 Amazon.com 的證書,而且確認當前的時間是在「最先時間」2008年8月26日以後,在「最晚時間」2009年8月27日以前。瀏覽器還會確認證書所攜帶的公共密鑰已被受權用於交換密鑰。

    爲何咱們要信任這個證書?

    證書中所包含的簽名是一串很是長的大端格式的數字:

    任何人均可以向咱們發送這些字節,但咱們爲何要信任這個簽名?爲了解釋這個問題,咱們首先要回顧一些重要的數學知識:

    RSA加密算法的基礎介紹

    人人經常會問,編程和數學之間有什麼聯繫?證書就爲數學在編程領域的應用提供了一個實際的例子。Amazon的服務器告訴咱們須要使用RSA算法來校驗證書籤名。什麼又是RSA算法呢?RSA算法是由麻省理工(MIT)的Ron Rivest、Adi Shamirh和Len Adleman(RSA命名各取了三人名字中的首字母)三人於上世紀70年代建立的。三位天才的學者結合了2000多年數學史上的精華,發明了這種簡潔高效的算法:

    選取兩個較大的初值p和q,相乘得n;n = p*q  接下來選取一個較小的數做爲加密指數e,d做爲解密指數是e的倒數。在加密的過程當中,n和e是公開信息,解密密鑰d則是最高機密。至於p和q,你能夠將他們公開,也能夠做爲機密保管。可是必定要記住,e和d是互爲倒數的兩個數。

    假設你如今有一段信息M(轉換成數字),將其加密只須要進行運算:C ≡ Me (mod n)

    這個公式表示M的e次冪,mod n表示除以n取餘數。當這段密文的接受者知道解密指數d的時候就能夠將密文進行還原:Cd ≡ (Me)d ≡ Me*d ≡ M1 ≡ M (mod n)

    有趣的是,解密指數d的持有者還能夠將信息M進行用解密指數d進行加密:Md ≡ S (mod n)

    加密者將S、M、e、n公開以後,任何人均可以得到這段信息的原文:Se ≡ (Md)e ≡ Md*e ≡ Me*d ≡ M1 ≡ M (mod n)

    如同RSA的公共密鑰加密算法常常被稱之爲非對稱算法,由於加密密鑰(在咱們的例子中爲e)和解密密鑰(在咱們的例子中是d)並不對稱。取餘運算的過程也不像咱們日常接觸的運算(諸如對數運算)那樣簡單。RSA加密算法的神奇之處在於你能夠很是快速的進行數據的加密運算,即 ,可是若是沒有解密密碼d,你將很難破解出密碼,即運算 將不可能實現。正如咱們所看到的,經過對n進行因式分解而獲得p和q,再推斷出解密密鑰d的過程難於上青天。

     

    簽名驗證

    在使用RSA加密算法的時候,最重要的一條就是要確保任何涉及到的數字都要足夠複雜才能保證不被現有的計算方法所破解。這些數字要多複雜呢?Amazon.com的服務器是利用「VeriSign Class 3 Secure Server CA」來對證書進行簽名的。從證書中,咱們能夠看到這個VeriSign(電子簽名校驗器,也稱威瑞信公司)的係數n有2048位二進制數構成,換算成十進制足足有617位數字:

    1890572922 9464742433 9498401781 6528521078 8629616064 3051642608 4317020197 7241822595 6075980039 8371048211 4887504542 4200635317 0422636532 2091550579 0341204005 1169453804 7325464426 0479594122 4167270607 6731441028 3698615569 9947933786 3789783838 5829991518 1037601365 0218058341 7944190228 0926880299 3425241541 4300090021 1055372661 2125414429 9349272172 5333752665 6605550620 5558450610 3253786958 8361121949 2417723618 5199653627 5260212221 0847786057 9342235500 9443918198 9038906234 1550747726 8041766919 1500918876 1961879460 3091993360 6376719337 6644159792 1249204891 7079005527 7689341573 9395596650 5484628101 0469658502 1566385762 0175231997 6268718746 7514321

    (若是你想要對這一大串數字進行分解因式得到p和q,那就祝你好運!順便一提,若是你真的計算出了p和q,那你就破解了Amazon.com數字簽名證書了!)

    這個VeriSign的加密密鑰e是 。固然,他們將解密密鑰d保管得十分嚴密,一般是在擁有視網膜掃描和荷槍實彈的警衛守護的機房當中。在簽名以前,VeriSign會根據相關約定的技術文檔,對Amazon.com證書上所提供的信息進行校驗。一旦證書信息符合相關要求,VeriSign會利用SHA-1哈希算法獲取證書的哈希值(hash),並對其進行聲明。在Wireshark中,完整的證書信息會顯示在「signedCertificate」(已簽名證書)中:

    這裏應該是軟件的用詞不當,由於這一段其實是指那些即將被簽名的信息,而不是指那些已經包含了簽名的信息。

    實際上通過簽名的信息S,在Wireshark中被稱之爲「encrypted」(密文)。咱們將S的e次冪除以n取餘數(即公式: )就能計算出被加密的原文,其十六進制以下:

    0001FFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFF00302130 0906052B0E03021A05000414C19F8786 871775C60EFE0542 E4C2167C830539DB

    根據PKCS#1 v1.5標準(譯者注:The Public-Key Cryptography Standards (PKCS)是由美國RSA數據安全公司及其合做夥伴制定的一組公鑰密碼學標準)規定:「第一個字節是00,這樣就能夠保證加密塊在被轉換爲整數的時候比其加密參數要小。」第二個字節爲01,表示了這是一個私有密鑰操做(數字簽名就是私有密鑰操做的一種)。後面緊接着的一連串的FF字節是爲了填充數據,使得這一串數字變得足夠大(加大黑客惡意破解的難度)。填充數字以一個00字節結束。緊接着的30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14這些字節是PKCS#1 v2.1標準中用於說明這段哈希值是經過SHA-1算法計算而出的。最後的20字節是SHA-1算法所計算出來的哈希值,即對未加密信息的摘要描述。(譯者注:原文中這裏使用了帶引號的signedCertificate,根據做者前文描述,這應該是Wireshark軟件的bug,實際上應指的是未被加密的信息。)

    由於這段信息的格式正確,且最後的哈希值與咱們獨立計算出來的校驗一致,因此咱們能夠判定,這必定是知道「VeriSign Class 3 Secure Server CA」的解密密鑰d的人對它進行了簽名。而世界上只有VeriSign公司才知道這串密鑰。

    固然了,咱們也能夠重複驗證這個「VeriSign Class 3 Secure Server CA」的證書的確是經過VeriSign公司的「第三類公私證書認證(Class 3 Public Primary Certification Authority)」進行簽名的。

    可是,即使是這樣,咱們爲何要信任VeriSign公司?整個的信任鏈條就此斷掉了。

    由圖能夠看到,「VeriSign Class 3 Secure Server CA」對Amazon.com進行了簽名,而「VeriSign Class 3 Public Primary Certification Authority」對「VeriSign Class 3 Secure Server CA」進行了簽名,可是最頂部的「VeriSign Class 3 Public Primary Certification Authority」則對本身進行了簽名。這是由於,這個證書自從NSS(網絡安全服務)庫中的certdata.txt 升級到1.4版以後就做爲「受信任的根證書頒發機構」(譯者注:參照微軟官方翻譯)被編譯到了Mozilla產品中(火狐瀏覽器)。這段信息是由網景公司的Robert Relyea於2000年9月6日提交的,並隨附如下注釋:

    「由僅存的NSS編譯了框架。包含一個在線的certdata.txt文檔,其中包含了咱們受信的跟證書頒發機構(一旦咱們得到了其餘受信機構的許可會陸續將他們添加進去)」。

    這個舉動有着至關長遠的影響,由於這些證書的有效日期是從1996年1月28日到2028年1月1日。

    肯·湯普遜(Ken Thompson)在他的《對深信不疑的信任》(譯者注:Reflections on Trusting Trust是肯湯普遜1983年得到圖靈獎時的演說)的演說中解釋的很好:你最終仍是要絕對信任某一人,在這個問題上沒有第二條路可走。在本文的例子中,咱們就毫無保留的信任Robert Relyea作了一個正確的決定。咱們一樣但願Mozilla在本身軟件中加入「受信任根證書頒發機構」這種行爲也是合理的吧。

    這裏須要注意的是:這一系列的證書和簽名只是用來造成一個信任鏈。在公共互聯網上,VeriSign的根證書被火狐瀏覽器徹底信任的時間遠早於你接觸互聯網。在一個公司中,你能夠建立本身的受信任的根證書頒發機構並把它安裝到任何人的計算機中。

    相對的,你也能夠購買VeriSign公司的業務,下降整個證書信任鏈的信任風險。經過第三方的認證機構(在這個例子裏是VeriSign公司)咱們能利用證書創建起信任關係。若是你有相似於「悄悄話」的安全途徑來傳遞一個祕密的key,那你也能夠使用一個預共享密鑰(PSK)來創建起信任關係。諸如TLS-PSK、或者帶有安全遠程密碼(SRP)的TLS擴展包都能讓咱們使用預共享密鑰。不行的是,這些擴展包在應用和支持方面遠遠比不上TLS,因此他們有的時候並不實用。另外,這些替代選項須要額外德爾安全途徑進行保密信息的傳輸,這一部分的開銷遠比咱們如今正在應用的TLS龐大。換句話說,這也就是咱們爲何不該用那些其餘途徑構建信任關係的緣由。

    言歸正傳,咱們所須要的最後確認的信息就是在證書上的主機名跟咱們預想的是同樣的。Nelson Bolyard在SSL_AuthCertificate 函數中的註釋爲咱們解釋其中的緣由:

    「SSL鏈接的客戶端確認證書正確,並檢查證書中所對應的主機名是否正確,由於這是咱們應對中間人攻擊的惟一方式!」 (譯者注:中間人攻擊是一種「間接」的入侵攻擊,這種攻擊模式是經過各類技術手段將受入侵者控制的一臺計算機虛擬放置在網絡鏈接中的兩臺通訊計算機之間,這臺計算機就稱爲「中間人」。)

    這樣的檢查是爲了防止中間人攻擊:由於咱們對整個信任鏈條上的人都採起了徹底信任的態度,認爲他們並不會進行黑客行爲,就像咱們的證書中所聲稱它是來自Amazon.com,可是假如他的真實來源並不是Amazon.com,那咱們可能就有被攻擊的危險。若是攻擊者使用域名污染(DNS cache poisoning)等技術對你的DNS服務器進行篡改,那麼你也許會把黑客的網站誤認爲是一個安全的受信網站(諸如Amazon.com),由於地址欄顯示的信息一切正常。這最後一步對證書頒發機構的檢查就是爲了防止這樣的事情發生。

     

    隨機密碼串(Pre-Master Secret)

    如今咱們已經瞭解了Amazon.com的各項要求,而且知道了公共解密密鑰e和參數n。在通訊過程當中的任何一方也都知道了這些信息(佐證就是咱們經過Wireshark得到了這些信息)。如今咱們所須要作的事情就是生成一串竊密者/攻擊者都不能知道的隨機密碼。這並不像聽上去的那麼簡單。早在1996年,研究人員就發現了網景瀏覽器1.1的僞隨機數發生器僅僅利用了三個參數:當天的時間,進程ID和父進程ID。正如研究人員所指出的問題:這些用於生成隨機數的參數並不具備隨機性,並且他們相對來講比較容易被破解。

    由於一切都是來源於這三個隨機數參數,因此在1996,利用當時的機器僅須要25秒鐘的時間就能夠破解一個SSL通訊。找到一種生成真正隨機數的方法是很是困難的,若是你不相信這一點,那就去問問Debian OpenSSL的維護工程師吧。若是隨機數的生成方式遭到破解,那麼創建在這之上的一系列安全措施都是毫無心義的。

    在Windows操做系統中,用於加密目的隨機數都是利用一個叫作CryptGenRandom的函數生成的。這個函數的哈希表位對超過125個來源的數據進行抽樣!火狐瀏覽器利用CryptGenRandom函數和它自身的函數來構成它本身的僞隨機數發生器。(譯者注:之因此稱之爲僞隨機數是由於真正意義上的隨機數算法並不存在,這些函數仍是利用大量的時變、量變參數來經過複雜的運算生成相對意義上的隨機數,可是這些數之間仍是存在統計學規律的,只是想要找到生成隨機數的過程並不那麼容易)。

    咱們並不會直接利用生成的這48字節的隨機密碼串,可是因爲不少重要的信息都是由他計算而來的,因此對隨機密碼串的保密就顯得格外重要。正如我以前所預料到的,火狐瀏覽器對隨機密碼串的保密十分嚴格,因此我不得不編譯了一個用於debug的版本。爲了觀察隨機密碼串,我還特意設置了SSLDEBUGFILE和SSLTRACE兩個環境變量。

    其中,SSLDEBUGFILE顯示的就是隨機密碼串的值:

    須要注意的是,這串數字從各類意義上來講都不是真正的隨機數,就拿它的前兩位來講:這就是根據TLS協議約定的TLS版本號(0301)。

     

    密碼交換(Trading Secret)

    咱們如今須要作的就是計算出Amazon.com所要求的密碼。由於Amazon.com但願使用「TLS_RSA_WITH_RC4_128_MD5」加密組,因此咱們使用RSA加密算法進行這一過程。你能夠將這48字節的隨機密碼串做爲初始參數,可是根據公共密鑰密碼標準(PKCS)#1 v1.5中的註釋,咱們須要用隨機數據將隨機密碼串填充到實際要求的參數大小(1024位二進制/128字節)。這樣的話攻擊者想要破解咱們的隨機密碼串就難上加難了。這也是咱們保障本身安全的最後一道防線,以防咱們在前面的步驟中犯了諸如重複使用密碼這樣的低級錯誤。若是咱們重複使用了隨機密碼串,因爲使用了隨機數填充,竊密者在網絡中攔截的也會是兩個不一樣的值。

    一樣的,咱們很難直接觀察到火狐瀏覽器中的這一過程,因此我不得不在填充隨機數的函數中增長了debug的語句,使咱們可以觀察這一過程:

    在這個例子中,完整的填充後的隨機密碼串爲:

    00 02 12 A3 EA B1 65 D6 81 6C 13 14 13 62 10 53 23 B3 96 85 FF 24 FA CC 46 11 21 24 A4 81 EA 30 63 95 D4 DC BF 9C CC D0 2E DD 5A A6 41 6A 4E 82 65 7D 70 7D 50 09 17 CD 10 55 97 B9 C1 A1 84 F2 A9 AB EA 7D F4 CC 54 E4 64 6E 3A E5 91 A0 06 00 03 01 BB 7B 08 98 A7 49 DE E8 E9 B8 91 52 EC 81 4C C2 39 7B F6 BA 1C0A B1 95 50 29 BE 02 AD E6 AD 6E 11 3F20 C4 66 F0 64 22 57 7E E1 06 7A 3B

    火狐瀏覽器使用這個值計算出 ,咱們能夠看到它顯示在「客戶端交換密鑰」(Client Key Exchange)的記錄中:

    在這個過程的最後,火狐瀏覽器會發送一個不加密的信息:一條「Change Cipher Spec」記錄:

    經過這種方式:火狐瀏覽器要求Amazon.com在後面的通訊過程當中使用約定的加密方式傳輸信息。

     

    得到主密鑰(Master Secret)

    若是咱們正確完成了以前的過程,而且各方都得到了48字節(256二進制位)的隨機密碼串。從Amazon.com的角度來看,這裏還有一些信任問題:隨機密碼串是由客戶端生成的,並無將任何服務器信息或者以前約定的信息加入其中。這一點,咱們會經過生成主密鑰的方式加以完善。根據協議規範約定,這個的計算過程爲:

    pre_master_secret就是咱們以前傳送的隨機密碼串,」master secret」是一串ASCII碼(例如:6d 61 73 74 65 72……),再鏈接上在客戶端問候和服務器問候(來自Amazon的)的信息。

    PRF是在規範中約定的僞隨機函數,它將密鑰、ASCII碼標籤、哈希值整合在一塊兒。各有一半的參數分別使用MD5和SHA-1獲取哈希值。這是一種十分明智的作法,即便是想要單單破解相對簡單MD5和SHA-1也不是那麼容易的事情。並且這個函數會將返回值傳給自身直至迭代到咱們須要的位數。

    利用這個函數,咱們生成了48字節的主密鑰:

    4C AF 20 30 8F4C AA C5 66 4A 02 90 F2 AC 10 00 39 DB 1D E0 1F CB E0 E0 9D D7 E6 BE 62 A4 6C 18 06 AD 79 21 DB 82 1D 53 84 DB 35 A7 1F C1 01 19

     

    生成各類密鑰

    如今,各方面已經有了主密鑰,根據協議約定,咱們須要利用PRF生成這個會話中所須要的各類密鑰,稱之爲「密鑰塊」(key block):

    密鑰塊用於構成如下密鑰:

    由於咱們使用了相似於高級加密標準(AES)的密碼流代替了分組密碼咱們就不須要初始化向量(IVs)了。所以咱們只須要雙方的兩個16字節(128二進制位)的消息認證碼(Message Authentication Code,MAC),由於MD5的哈希值就是16字節的。此外,雙方也須要16字節(128二進制位)的RC4碼。因此咱們總共須要從密碼塊得到2*16 + 2*16 = 64字節的數據。

    運行PRF,咱們能獲得如下值:

     

    準備加密!

    客戶端最後一次送出的握手信息是「結束信息」。這條信息保證了沒有人篡改握手信息,而且咱們已經知曉所必須的密鑰。客戶端將整個握手過程的所有信息都放入一個名爲「handshake_messages」的緩衝區。咱們能經過僞隨機函數利用主密鑰、「client finished」標籤、MD5和SHA-1的哈希值生成12字節的「區別數據」(verify_data):

    咱們在這個結果前面加上0x14(用於表示結束信息)和00 00 0c(用於表示verify_data 有12字節)。就像之後全部的加密過程同樣,咱們要在加密以前確保原始數據沒有被篡改。由於咱們使用的是「TLS_RSA_WITH_RC4_128_MD5」密碼組,這就意味着咱們須要使用MD5哈希函數。

    有些人一聽到MD5函數就會嗤之以鼻,由於其自身的確存在一些缺陷。我本身固然也不會推薦這種算法。可是TLS的聰明之處就在於他並不直接使用MD5函數,只是利用哈希值的版原本校驗數據。只就意味着咱們並未直接應用到MD5(m):

    HMAC_MD5(Key, m) = MD5((Key ⊕ opad) ++ MD5((Key ⊕ ipad) ++ m)

    (其中,⊕表示的是異或運算)

    在實際中:

    HMAC_MD5(client_write_MAC_secret, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment));

    正如你所見,咱們在函數中使用了一個根據明文(在這裏明文叫作「TLSCompressed」)編號的序號(seq_num)。這個序號的做用就是爲了阻止攻擊者在數據流中間插入以前被其截獲的信息。若是發生了這樣的攻擊,序號就能清楚的警告咱們數據中的異常。一樣的,這個序號也能幫助咱們發現攻擊者從數據流中剔除的數據。

    剩下的工做只剩下啊加密這些數據了!

     

    RC4加密

    咱們以前協商過的密碼組是「TLS_RSA_WITH_RC4_128_MD5」。這就意味着咱們須要使用RC4(Ron`s code 4)加密規則進行數據流的加密。羅納德李威斯特(Ron Rivest)開發了這種基於一個256字節的Key產生隨機加密效果的算法。這個算法簡單到你幾分鐘就能記住。

    首先,RC4生成一個256字節的數組S,並用0-255填充。接下來的工做就是將須要將KEY混合插入進數組中並反覆迭代。你能夠編寫一個狀態機,利用它來闡釋隨機字節。爲了產生隨機字節,咱們須要將S數組打亂,如圖所示:

    爲了加密一個字節,咱們將其與隨機字節進行異或運算。記住:一位二進制數與1做異或運算會翻轉(譯者注:即1^1=0;1^0=1)。由於咱們利用的是隨機數,因此從統計學的角度來說,有一半的數被翻轉。這種隨機翻轉的現象就是咱們加密數據的有效方法。正如你所見,這並不複雜,並且計算速度十分快,我認爲這也是Amazon.com選擇它的緣由之一。

    記得咱們有「client_write_key」和「server_write_key」嗎?這就意味咱們須要兩個RC4實例:一個用來加密瀏覽器向服務器傳送的數據,一個用來解密服務器向瀏覽器傳送的數據。

    「client_write_key」最初的幾個字節是7E 20 7A 4D FE FB 78 A7 33 …若是咱們對這些字節和未加密的數據頭以及版本信息(「14 00 00 0C98 F0 AE CB C4 …」)進行異或運算,咱們就能獲得在Wireshark中看到的加密信息了:

    服務器端作的幾乎是相同的事情。它們發送了一個密鑰協議的說明和一個包含所有握手過程的結束信息,其中有結束信息的解密版本。所以,這種機制就保證了客戶端和服務器能成功的解密信息。

     

    歡迎來到應用層!

    如今,從咱們點擊了按鈕以後已通過去了220毫秒,咱們終於爲應用層作好了準備!如今,咱們發送的普通的HTTP數據流會經過TLS層的加密實例進行加密,在服務器的解密實例進行解密。並且TLS會對數據進行哈希校驗,以保證數據內容的準確性。

    在這個時候,整個的握手過程就結束了。咱們的TLS記錄內容如今有了23條(0x17)。加密數據以「17 03 01」開頭,表示了記錄類型和TLS版本,後面緊跟着加密數據的大小和哈希校驗值。

    加密的數據的明文以下:

    在Wireshark中顯示以下:

    惟一有趣的地方是序號是按照記錄來增加,這條記錄是1,下一條就是2。

    服務器端利用「server_write_key」作着一樣的事情。咱們能看到服務器的相應結果,包括程序開頭的指示位:

    解密後的信息以下:

    這就是一個來自Amazon負載平衡服務器的普通HTTP迴應:包含了非描述性的服務器信息「Server: Server」和一個拼錯了的「Cneonction: close」。

    TLS層在應用層的下面,因此軟件和服務器可以像正常的HTTP傳輸那樣進行工做,惟一的區別就是傳輸的數據會被TLS層進行加密。

    OpenSSL是一個應用很廣的TLS開源庫。

    整個鏈接會一直保持,除非有一方提出了「關閉警告(closure alert)」而且關閉了鏈接。若是咱們在鏈接斷開後的短期內再次提出鏈接請求,咱們能夠使用以前使用過的key來進行鏈接,從而避免一次新的握手過程。(這個要取決於服務器端key的有效時間。)

    須要注意的是:應用程序能夠發送任何數據,可是HTTPS的特殊之處在於WEB應用的普遍普及。要知道還有很是多的基於TCP/IP而且使用TLS進行數據加密的協議(如FTPS,sSMTP)。使用TLS要比你本身發明一種是數據加密方案便捷的多。何況,你所使用的安全協議必定要足夠安全。

     

    …完工!

    TLS RFC的文檔包含了更多的信息,有須要的朋友們能夠本身查閱,咱們在這裏只是簡單的介紹了其中的過程和原理,觀察了這220毫秒內發生在火狐瀏覽器和Amazon服務器之間發生的故事:由Amazon.com基於速度和安全的綜合考慮選擇的「TLS_RSA_WITH_RC4_128_MD5」密碼組在HTTPS鏈接創建過程當中的所有流程。

    正如咱們所看到的那樣,若是有人能對Amazon服務器的參數n進行因式分解獲得p和q的話,那他就能破解所有的基於亞馬遜證書的安全通訊。因此Amazon爲這個參數設置了有效期以防止這種事情的發生:

    在咱們提供的密碼族中,有一組密碼組「TLS_DHE_RSA_WITH_AES_256_CBC_SHA」使用了Diffie-Hellman密鑰交換,並所以能提供良好的前向安全特性。這就意味着若是有人破解了交換密鑰的數學運算方式,他們也不能利用這個來破解其餘的會話。可是他的一個劣勢在於其運算需求更大的數字和更高的運算能力。AES算法在不少密碼組中都出現了,它與RC4的不一樣之處在於它每次使用的是16字節的「塊」而RC4使用的是單字節。由於其key最高能到256位二進制位,因此通常認爲它比RC4的安全性更高。

    在短短的220毫秒的時間裏,兩個節點經過互聯網鏈接起來,而且利用一系列手段創建起了互信機制,構建了加密算法,進行加密數據的傳輸。

    正是由於如此,咱們故事的主人公才能在Amazon上買到他想要的牛奶!


     

     

    四、點擊一個Button究竟發生了什麼

     

    很簡單的一個問題:「在屏幕上有個按鈕,你點下去,以後究竟發生了什麼」,就這麼個問題若是你沒有留心的話可能還真不必定回答的出來。也許你會說,它的控制器會響應它的點擊方法。但是,而後呢,它的控制器怎麼就知道是去響應這個button的點擊方法,而不是另一個呢。要回答這個問題就涉及到響應者鏈的(The Responder Chain)的問題了,蘋果官網是這麼說的:

    大體呢是這麼個意思,當一個又用戶引發的事件產生時,UIKit這個框架會產生一個包含處理該事件信息的一個對象。而後它把這個對象放入活動的app事件隊列中,對於觸摸事件來講,那個對象就是一系列的被打包成UIEvent的觸摸事件。對於動做事件來講,那個對象隨着你使用的框架以及你感興趣的動做的不一樣而不一樣。

    說的有點繞,但起碼前兩句看懂了不是,你觸摸後就會產生一個對象,而後把他放入了一個隊列,而後怎麼處理的呢,請繼續看下文,我門知道那個因爲你觸摸而產生的對象被放入了一個隊列中,那放入隊列中幹什麼呢,固然是等待處理了,那誰去處理它呢。對了,我以爲應該是那個button去處理,我是點了它的,固然是它去處理了,你點的它你固然知道了,那系統是怎麼知道的呢。iOS系統使用觸碰測試(hit-testing)這個過程來檢查,首先系統會主動地檢查這個觸摸點是否發生着任何一個與之有關的視圖範圍內,一旦發現這個觸摸點在這個視圖範圍內,那麼就遞歸地依次檢查這個視圖對象的全部子視圖,這樣最終找到的那個視圖就是hit-testing過程找出來的視圖對象。在iOS系統肯定了那個視圖對象以後,它就把剛纔放入事件隊列的那個事件仍給這個視圖對象來處理。什麼?你還沒理解,那就請看圖:

    假如你點的是View E,尋找過程就下面幾步:

    • 首先無論你點的哪,它確定在View A裏面,屏幕就那麼大,能跑哪去
    • View A有兩個字視圖View B 和View C,因此係統就檢查是在他兩誰的裏面
    • 發現不在View B裏面,而是在View C裏面,因而開始檢查View C的字視圖View D和View E
    • 發現觸摸點不在View D的範圍,而在View E的範圍
    • View E已經沒有字視圖了,因而View E就這麼被找到了
      哈哈,到這裏,我終於明白了,這麼簡單啊。然而並沒什麼卵用!知道又怎麼樣。繼續,上面提到了,iOS爲了肯定應該由哪一個程序去響應事件,它會依次去查找視圖裏面的每個子View,那它是調用什麼方法去查找的呢,每一個View都有這麼一個hitTest:withEvent:方法,若是點擊的是這個View,那麼這個View就把本身返回(若是是子View就把子View返回),做爲第一響應者。知道這個後咱們就能夠搞出點事了,好比咱們能夠攔截這個方法去作本身的事情,好比產品經理提出這麼變態要求:「在屏幕的任何地方滑動要求裏面的表頁跟着滑動」,那麼咱們能夠這麼作,新建一個類繼承UITableView而後在裏面寫上一下內容: 
      - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return self; }
      這就搞定了

     

     

    參考:

    1.http://mp.weixin.qq.com/s?__biz=MzAxMzE2Mjc2Ng==&mid=2652154904&idx=1&sn=83a2073f1defe2a0d490bd851d81efef&scene=0#wechat_redirect

    2.http://blog.csdn.net/yuliu0552/article/details/6717915

    3.http://blog.jobbole.com/55505/

    4.http://blog.jobbole.com/48369/

    5.http://www.jianshu.com/p/3b6347cd01a4

相關文章
相關標籤/搜索