項(xiàng)目總結(jié):近期的兩個(gè)小項(xiàng)目

本文同時(shí)發(fā)布至我的個(gè)人博客,點(diǎn)擊進(jìn)入我的個(gè)人博客閱讀。本博客供技術(shù)交流與經(jīng)驗(yàn)分享,可自由轉(zhuǎn)載。轉(zhuǎn)載請(qǐng)?jiān)谠u(píng)論區(qū)或私信簡單通知,感謝!
近期為了應(yīng)對(duì)校招面試,比較有針對(duì)性地做了兩個(gè)技術(shù)項(xiàng)目:一個(gè)是基于 Java NIO 實(shí)現(xiàn)的簡易版非阻塞 Http 服務(wù)器,另一個(gè)是基于 Spring Boot + Websocket 實(shí)現(xiàn)的網(wǎng)絡(luò)聊天室。與以往的項(xiàng)目總結(jié)不同,這次我會(huì)忽略一些代碼實(shí)現(xiàn)細(xì)節(jié),把更多的篇幅用來總結(jié)開發(fā)過程中我遇到的難題以及在面試中面試官提出的相關(guān)問題。

基于 Java NIO 實(shí)現(xiàn)的簡易版非阻塞 Http 服務(wù)器

首先需要強(qiáng)調(diào)的是,這種 Http 服務(wù)器的實(shí)現(xiàn)是非常樸素直接的,沒有考慮很多具體實(shí)踐中的問題。最近在看《Netty In Action》才越來越感覺之前做項(xiàng)目時(shí)想法上的過于單純。但項(xiàng)目本身其實(shí)十分適合初學(xué)者進(jìn)階的,因?yàn)槠渲猩婕暗降闹R(shí):Java NIO、網(wǎng)絡(luò)多路復(fù)用、Http 報(bào)文解析、緩沖區(qū)設(shè)計(jì),都是十分重要的基礎(chǔ)問題,也是在校招技術(shù)面試中面試官喜歡深入考察的問題。在開發(fā)過程中,我借鑒了 Java NIO: Non-blocking Server非阻塞服務(wù)器這篇文章的思路,上面提及的幾個(gè)關(guān)鍵問題文章中也有較為詳細(xì)的解析。

項(xiàng)目基本架構(gòu)

image

Java BIO/NIO/AIO

關(guān)于 Java BIO/NIO/AIO 這三者的區(qū)別,個(gè)人覺得 Java BIO, NIO, AIO understanding 這篇文章總結(jié)得十分簡潔到位,作者分別從編程、原理、底層三方面進(jìn)行總結(jié)。編程方面作者講解的十分到位,而要理解原理部分可能需要一些 Unix 網(wǎng)絡(luò)編程的相關(guān)知識(shí),以下就做一些網(wǎng)絡(luò)編程相關(guān)的補(bǔ)充:

  • Unix 網(wǎng)絡(luò)編程中將 IO 模型分為五類:阻塞 IO、非阻塞 IO(輪詢)、IO 復(fù)用、信號(hào)驅(qū)動(dòng)式 IO、異步 IO。其中非阻塞 IO(輪詢)、IO 復(fù)用、信號(hào)驅(qū)動(dòng)式 IO 都可以理解為非阻塞 IO 的不同形式實(shí)現(xiàn)。(圖解UNIX的I/O模型一文可以讓你快速理解 Unix 中的 IO 模型)

  • 無論是屬于哪種 IO 模型,其 IO 過程都可以分為兩個(gè)階段: IO request(數(shù)據(jù)請(qǐng)求)和 IO operation(數(shù)據(jù)復(fù)制)兩個(gè)階段。

  • 阻塞 IO 與非阻塞 IO 的區(qū)別在于:使用阻塞 IO 時(shí),線程在 IO request 和 IO operation 階段都會(huì)進(jìn)入阻塞;而使用非阻塞 IO 時(shí),線程只在 IO operation 階段進(jìn)入阻塞,IO request 無需阻塞。

  • 同步 IO 與異步 IO 的區(qū)別在于:同步 IO 在 IO operation 階段會(huì)進(jìn)入阻塞,而異步 IO 在 IO request 和 IO operation 階段都不會(huì)進(jìn)入阻塞。

  • 按照 Unix 網(wǎng)絡(luò)編程中對(duì) IO 模型的分類,Java BIO/NIO/AIO 的實(shí)現(xiàn)分別對(duì)應(yīng)了阻塞 IO/IO 多路復(fù)用/異步 IO 這三種模型。

BIO vs NIO

Java AIO(在 JDK 1.7 中也稱為 NIO.2),在性能上相較于 NIO 并沒有帶來提升,所以在 Netty 4.0.0 中也移除了對(duì) AIO 的支持。無論是在實(shí)際應(yīng)用中還是在技術(shù)面試中都較少提及 AIO,這里我們重點(diǎn)比較一下 BIO 與 NIO 的區(qū)別:

  1. 處理的對(duì)象:BIO 直接面向 Socket 的字節(jié)流,每次從流中讀一個(gè)或多個(gè)字節(jié),直到讀取完所有字節(jié);NIO 面向緩沖塊(Block),需要時(shí)可以在緩沖區(qū)中前后移動(dòng)處理,這增加了處理過程的靈活性。

  2. 阻塞:BIO 必須要對(duì)線程進(jìn)行阻塞,NIO 無需阻塞,一個(gè)單獨(dú)的線程可以管理多個(gè)輸入和輸出通道。

  3. 選擇器:Java NIO的選擇器允許一個(gè)單獨(dú)的線程同時(shí)監(jiān)視多個(gè)通道,可以注冊(cè)多個(gè)通道到同一個(gè)選擇器上,然后使用一個(gè)單獨(dú)的線程來“選擇”已經(jīng)就緒的通道。

  4. 零拷貝:Java NIO中提供的 FileChannel 擁有 transferTotransferFrom 兩個(gè)方法,可直接把 FileChannel 中的數(shù)據(jù)拷貝到另外一個(gè) Channel,或者直接把另外一個(gè) Channel 中的數(shù)據(jù)拷貝到 FileChannel 。通過該方法傳輸數(shù)據(jù)并不需要將源數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài),再從用戶態(tài)拷貝到目標(biāo)通道的內(nèi)核態(tài),同時(shí)也避免了兩次用戶態(tài)和內(nèi)核態(tài)間的上下文切換,也即使用了“零拷貝”。

總結(jié)自:Java進(jìn)階(五)Java I/O模型從 BIO到 NIO和 Reactor模式

HTTP 報(bào)文及解析

項(xiàng)目中對(duì) HTTP 協(xié)議的支持也做了簡化處理:只判別了 GET、POST、HEAD、PUT、DELETE 五種方法,主要是基于 HTTP 的 Content-Length 字段來實(shí)現(xiàn) HTTP 報(bào)文切割(處理 TCP 粘包問題)。但是在面試中,HTTP 協(xié)議幾乎是所有面試官會(huì)深究的一部分內(nèi)容。大概涉及的問題如下:

Message 緩沖區(qū)設(shè)計(jì)

  • 讀取不完整的 message:每次 Reader 從 Channel 讀取一個(gè)數(shù)據(jù)塊后,先通過一個(gè) Http 報(bào)文 parser 來確定是否有一個(gè)完整的 message。若有,則讀取一個(gè)完整的 message 后將剩余部分緩存起來;若無,則直接將整個(gè)數(shù)據(jù)塊緩存起來。緩存的數(shù)據(jù)塊將在下次讀取時(shí)與下次讀取的數(shù)據(jù)塊合并,再進(jìn)行重新的 parsing。

  • message buffer 的大?。篗essageBuffer 實(shí)現(xiàn)了一個(gè)容量可伸縮的 message buffer。它提供三種大小的 buffer,4KB/128KB/1MB。初始時(shí) buffer 默認(rèn)為 4KB,若空間不足時(shí),MessageBuffer 內(nèi)部的方法會(huì)自動(dòng)將 buffer 擴(kuò)容。

  • message buffer 的讀寫:仿寫了 nio.Buffer 中的 flip() 函數(shù)(readPoswritePos),在 message buffer 的讀寫操作中調(diào)用 flip() 來避免錯(cuò)讀、漏讀。

生產(chǎn)者-消費(fèi)者隊(duì)列

用一個(gè) ArrayBlockingQueue 來存放 socket,創(chuàng)建兩個(gè)線程,acceptor 和 processor 分別使用其 put / take 操作來進(jìn)行生產(chǎn)和消費(fèi)。

關(guān)于 ArrayBlockingQueue:1. 基于數(shù)組,直接將對(duì)象放入和取出隊(duì)列。(LinkedBlockingQueue 基于單向鏈表,放入與取出時(shí)操作 Node);2. 基于一個(gè) ReentrantLock 和兩個(gè) Condition(notEmpty 和 notFull) 實(shí)現(xiàn)。(LinkedBlockingQueue 基于兩個(gè) ReentrantLock :putLock 和 takeLock 和兩個(gè) Condition:notEmpty 和 notFull 實(shí)現(xiàn))

基于 Spring Boot + Websocket 實(shí)現(xiàn)的網(wǎng)絡(luò)聊天室

項(xiàng)目實(shí)現(xiàn)了一個(gè)簡單的網(wǎng)絡(luò)聊天室,基于 Spring Boot 搭建后臺(tái),基于 Websocket 與 STOMP 協(xié)議實(shí)現(xiàn)即時(shí)通訊,使用 Spring Security 與 Spring Oauth 實(shí)現(xiàn)用戶登錄,基于 JWT 實(shí)現(xiàn)對(duì)單點(diǎn)登錄的支持。

Oauth 驗(yàn)證流程

image

Websocket 協(xié)議

參考WebSocket協(xié)議深入探究

JWT

  • JWT(Json Web Token) 一個(gè)非常輕巧的規(guī)范。這個(gè)規(guī)范允許我們使用JWT在用戶和服務(wù)器之間傳遞安全可靠的信息。

  • 字符串形式,三部分組成:頭部(Header)、載荷(Payload)、簽名(Signature)

  • 頭部:用于描述 JWT

    
    {
    
      "typ": "JWT",
    
      "alg": "HS256" // 指定簽名的加密算法
    
    }
    
    
  • 載荷:真正需要傳遞的內(nèi)容 + 部分其他信息

    
    {
    
        "iss": "User JWT",
    
        "iat": 1441593502,
    
        "exp": 1441594722,
    
        "aud": "www.example.com",
    
        "sub": "sub@example.com",
    
        "from_user": "B",
    
        "target_user": "A"
    
    }
    
    
  • 簽名:用于在服務(wù)端判斷 JWT 的內(nèi)容是否經(jīng)過篡改,使用 JWT 頭部指定的加密算法。

  • 格式:header.payload.signature(先通過 base64 將 JSON 轉(zhuǎn)化為字符串)

  • 載荷內(nèi)容可以通過 base64 反編碼獲得,所以不應(yīng)用 JWT 傳輸敏感數(shù)據(jù)。

  • 使用場(chǎng)景:添加好友、創(chuàng)建訂單、實(shí)現(xiàn)單點(diǎn)登錄

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容