本文同時發(fā)布至我的個人博客空另,點擊進入我的個人博客閱讀。本博客供技術交流與經驗分享蹋砚,可自由轉載扼菠。轉載請在評論區(qū)或私信簡單通知摄杂,感謝!
近期為了應對校招面試循榆,比較有針對性地做了兩個技術項目:一個是基于 Java NIO 實現(xiàn)的簡易版非阻塞 Http 服務器匙姜,另一個是基于 Spring Boot + Websocket 實現(xiàn)的網絡聊天室。與以往的項目總結不同冯痢,這次我會忽略一些代碼實現(xiàn)細節(jié),把更多的篇幅用來總結開發(fā)過程中我遇到的難題以及在面試中面試官提出的相關問題框杜。
基于 Java NIO 實現(xiàn)的簡易版非阻塞 Http 服務器
首先需要強調的是浦楣,這種 Http 服務器的實現(xiàn)是非常樸素直接的,沒有考慮很多具體實踐中的問題咪辱。最近在看《Netty In Action》才越來越感覺之前做項目時想法上的過于單純振劳。但項目本身其實十分適合初學者進階的,因為其中涉及到的知識:Java NIO油狂、網絡多路復用历恐、Http 報文解析、緩沖區(qū)設計专筷,都是十分重要的基礎問題弱贼,也是在校招技術面試中面試官喜歡深入考察的問題。在開發(fā)過程中磷蛹,我借鑒了 Java NIO: Non-blocking Server非阻塞服務器這篇文章的思路吮旅,上面提及的幾個關鍵問題文章中也有較為詳細的解析。
項目基本架構
Java BIO/NIO/AIO
關于 Java BIO/NIO/AIO 這三者的區(qū)別味咳,個人覺得 Java BIO, NIO, AIO understanding 這篇文章總結得十分簡潔到位庇勃,作者分別從編程、原理槽驶、底層三方面進行總結责嚷。編程方面作者講解的十分到位,而要理解原理部分可能需要一些 Unix 網絡編程的相關知識掂铐,以下就做一些網絡編程相關的補充:
Unix 網絡編程中將 IO 模型分為五類:阻塞 IO罕拂、非阻塞 IO(輪詢)、IO 復用堡纬、信號驅動式 IO聂受、異步 IO。其中非阻塞 IO(輪詢)烤镐、IO 復用蛋济、信號驅動式 IO 都可以理解為非阻塞 IO 的不同形式實現(xiàn)。(圖解UNIX的I/O模型一文可以讓你快速理解 Unix 中的 IO 模型)
無論是屬于哪種 IO 模型炮叶,其 IO 過程都可以分為兩個階段: IO request(數(shù)據(jù)請求)和 IO operation(數(shù)據(jù)復制)兩個階段碗旅。
阻塞 IO 與非阻塞 IO 的區(qū)別在于:使用阻塞 IO 時渡处,線程在 IO request 和 IO operation 階段都會進入阻塞;而使用非阻塞 IO 時祟辟,線程只在 IO operation 階段進入阻塞医瘫,IO request 無需阻塞。
同步 IO 與異步 IO 的區(qū)別在于:同步 IO 在 IO operation 階段會進入阻塞旧困,而異步 IO 在 IO request 和 IO operation 階段都不會進入阻塞醇份。
按照 Unix 網絡編程中對 IO 模型的分類,Java BIO/NIO/AIO 的實現(xiàn)分別對應了阻塞 IO/IO 多路復用/異步 IO 這三種模型吼具。
BIO vs NIO
Java AIO(在 JDK 1.7 中也稱為 NIO.2)僚纷,在性能上相較于 NIO 并沒有帶來提升,所以在 Netty 4.0.0 中也移除了對 AIO 的支持拗盒。無論是在實際應用中還是在技術面試中都較少提及 AIO怖竭,這里我們重點比較一下 BIO 與 NIO 的區(qū)別:
處理的對象:BIO 直接面向 Socket 的字節(jié)流,每次從流中讀一個或多個字節(jié)陡蝇,直到讀取完所有字節(jié)痊臭;NIO 面向緩沖塊(Block),需要時可以在緩沖區(qū)中前后移動處理登夫,這增加了處理過程的靈活性广匙。
阻塞:BIO 必須要對線程進行阻塞,NIO 無需阻塞悼嫉,一個單獨的線程可以管理多個輸入和輸出通道艇潭。
選擇器:Java NIO的選擇器允許一個單獨的線程同時監(jiān)視多個通道,可以注冊多個通道到同一個選擇器上戏蔑,然后使用一個單獨的線程來“選擇”已經就緒的通道蹋凝。
零拷貝:Java NIO中提供的
FileChannel
擁有transferTo
和transferFrom
兩個方法,可直接把FileChannel
中的數(shù)據(jù)拷貝到另外一個 Channel总棵,或者直接把另外一個 Channel 中的數(shù)據(jù)拷貝到FileChannel
鳍寂。通過該方法傳輸數(shù)據(jù)并不需要將源數(shù)據(jù)從內核態(tài)拷貝到用戶態(tài),再從用戶態(tài)拷貝到目標通道的內核態(tài)情龄,同時也避免了兩次用戶態(tài)和內核態(tài)間的上下文切換迄汛,也即使用了“零拷貝”。
總結自:Java進階(五)Java I/O模型從 BIO到 NIO和 Reactor模式
HTTP 報文及解析
項目中對 HTTP 協(xié)議的支持也做了簡化處理:只判別了 GET骤视、POST鞍爱、HEAD、PUT专酗、DELETE 五種方法睹逃,主要是基于 HTTP 的 Content-Length 字段來實現(xiàn) HTTP 報文切割(處理 TCP 粘包問題)。但是在面試中,HTTP 協(xié)議幾乎是所有面試官會深究的一部分內容沉填。大概涉及的問題如下:
-
HTTP 報文格式:
image HTTP Method 有哪些疗隶?
HTTP 常見狀態(tài)碼有哪些?
HTTP 建立 TCP 長連接翼闹?(Connection:Keep-Alive)
Message 緩沖區(qū)設計
讀取不完整的 message:每次 Reader 從 Channel 讀取一個數(shù)據(jù)塊后,先通過一個 Http 報文 parser 來確定是否有一個完整的 message猎荠。若有坚弱,則讀取一個完整的 message 后將剩余部分緩存起來;若無关摇,則直接將整個數(shù)據(jù)塊緩存起來史汗。緩存的數(shù)據(jù)塊將在下次讀取時與下次讀取的數(shù)據(jù)塊合并,再進行重新的 parsing拒垃。
message buffer 的大小:MessageBuffer 實現(xiàn)了一個容量可伸縮的 message buffer瓷蛙。它提供三種大小的 buffer悼瓮,4KB/128KB/1MB。初始時 buffer 默認為 4KB艰猬,若空間不足時横堡,MessageBuffer 內部的方法會自動將 buffer 擴容。
message buffer 的讀寫:仿寫了
nio.Buffer
中的flip()
函數(shù)(readPos
和writePos
)冠桃,在 message buffer 的讀寫操作中調用flip()
來避免錯讀命贴、漏讀。
生產者-消費者隊列
用一個 ArrayBlockingQueue 來存放 socket食听,創(chuàng)建兩個線程胸蛛,acceptor 和 processor 分別使用其 put / take 操作來進行生產和消費。
關于 ArrayBlockingQueue:1. 基于數(shù)組樱报,直接將對象放入和取出隊列葬项。(LinkedBlockingQueue 基于單向鏈表,放入與取出時操作 Node)迹蛤;2. 基于一個 ReentrantLock 和兩個 Condition(notEmpty 和 notFull) 實現(xiàn)民珍。(LinkedBlockingQueue 基于兩個 ReentrantLock :putLock 和 takeLock 和兩個 Condition:notEmpty 和 notFull 實現(xiàn))
基于 Spring Boot + Websocket 實現(xiàn)的網絡聊天室
項目實現(xiàn)了一個簡單的網絡聊天室,基于 Spring Boot 搭建后臺盗飒,基于 Websocket 與 STOMP 協(xié)議實現(xiàn)即時通訊嚷量,使用 Spring Security 與 Spring Oauth 實現(xiàn)用戶登錄,基于 JWT 實現(xiàn)對單點登錄的支持逆趣。
Oauth 驗證流程
Websocket 協(xié)議
JWT
JWT(Json Web Token) 一個非常輕巧的規(guī)范蝶溶。這個規(guī)范允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。
字符串形式汗贫,三部分組成:頭部(Header)身坐、載荷(Payload)秸脱、簽名(Signature)
-
頭部:用于描述 JWT
{ "typ": "JWT", "alg": "HS256" // 指定簽名的加密算法 }
-
載荷:真正需要傳遞的內容 + 部分其他信息
{ "iss": "User JWT", "iat": 1441593502, "exp": 1441594722, "aud": "www.example.com", "sub": "sub@example.com", "from_user": "B", "target_user": "A" }
簽名:用于在服務端判斷 JWT 的內容是否經過篡改,使用 JWT 頭部指定的加密算法部蛇。
格式:header.payload.signature(先通過 base64 將 JSON 轉化為字符串)
載荷內容可以通過 base64 反編碼獲得摊唇,所以不應用 JWT 傳輸敏感數(shù)據(jù)。
使用場景:添加好友涯鲁、創(chuàng)建訂單巷查、實現(xiàn)單點登錄