前言
前段時(shí)間修一個(gè)Bug的時(shí)候,涉及到了系統(tǒng)簡(jiǎn)歷上傳的功能,看了下代碼發(fā)現(xiàn)用到了Socket來(lái)和后端通信。
簡(jiǎn)要概況一下業(yè)務(wù)場(chǎng)景:先通過(guò)http請(qǐng)求來(lái)提交一份簡(jiǎn)歷文件,后端返回部分?jǐn)?shù)據(jù)。當(dāng)后端解析處理完簡(jiǎn)歷文件后,通過(guò)socket返回詳細(xì)的簡(jiǎn)歷信息。
這里涉及到了兩個(gè)值得記錄的地方,一個(gè)是socket通信的實(shí)現(xiàn),一個(gè)是服務(wù)端主動(dòng)向客戶(hù)端通信的方式,這篇文章主要介紹這兩點(diǎn)。
Socket通信
Socket的實(shí)現(xiàn)有基于TCP協(xié)議的,也有基于UDP協(xié)議的,具體底層原理和模型這里就不贅述了,相關(guān)的文章也有不少。這里簡(jiǎn)要概況一下Socket和我們常用的Http的區(qū)別。
Http是請(qǐng)求-響應(yīng)式的通信方式,客戶(hù)端請(qǐng)求,服務(wù)端響應(yīng),然后通信結(jié)束。第二次通信就又需要重新建立連接。
Socket是一種全雙工通信,當(dāng)客戶(hù)端和服務(wù)端建立起連接后,如果不主動(dòng)斷開(kāi),雙方可以一直互相發(fā)送消息,適合于雙方頻繁通信的場(chǎng)景,也是支持服務(wù)端主動(dòng)推送的一種通信方式。
WebSocket是Html5推出的前端可以直接使用的API,不過(guò)目前項(xiàng)目中用的還是Socket.io比較多。Socket.io在瀏覽器環(huán)境下封裝了WebSocket, 可以給開(kāi)發(fā)者帶來(lái)更好的體驗(yàn),在功能上也更完善。接下來(lái)我會(huì)使用Socket.io實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Demo。
Socket.io實(shí)現(xiàn)通信功能
首先是服務(wù)端代碼,先使用express來(lái)起一個(gè)服務(wù)。請(qǐng)求http://localhost:3000/upload接口返回對(duì)應(yīng)數(shù)據(jù)。
const express = require('express')
const app = express()
const port = 3000
app.get('/upload', (req, res) => {
res.send({
name: 'Harlan的簡(jiǎn)歷'
})
})
app.listen(3000)
接下來(lái)我們引入socket.io,實(shí)現(xiàn)一開(kāi)始提到的業(yè)務(wù)場(chǎng)景:客戶(hù)端上傳簡(jiǎn)歷文件之后服務(wù)端先返回基本信息,服務(wù)端解析完簡(jiǎn)歷文件后返回詳細(xì)信息。
const express = require('express')
const app = express()
const port = 3000
const server = require('http').createServer(app);
const io = require('socket.io')(server);
// 解決跨域問(wèn)題
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
app.get('/upload', (req, res) => {
res.send({
name: 'Harlan的簡(jiǎn)歷'
})
// 耗時(shí)操作,3秒后通過(guò)socket返回?cái)?shù)據(jù),不使用http防止阻塞前端操作
setTimeout(() => {
io.emit('upload-resume', 'Harlan的簡(jiǎn)歷詳情')
}, 3000)
})
server.listen(port, () => console.log(`Example app listening on port ${port}!`) );
然后是客戶(hù)端代碼,這里也不用啥vue、react前端框架了,直接使用CDN引入socket.io,跑在瀏覽器中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js"></script>
</head>
<body>
<button id="upload">上傳</button>
<script>
const socket = io("http://localhost:3000");
const uploadBtn = document.querySelector("#upload");
uploadBtn.addEventListener("click", () => {
fetch("http://localhost:3000/upload")
.then((res) => res.json())
.then((res) => console.log('上傳簡(jiǎn)歷成功,返回?cái)?shù)據(jù):', res));
});
socket.on("upload-resume", (res) => {
console.log('收到后端耗時(shí)處理的數(shù)據(jù):', res)
});
</script>
</body>
</html>
邏輯比較簡(jiǎn)單,先創(chuàng)建socket連接,然后點(diǎn)擊按鈕上傳簡(jiǎn)歷文件,接收到http返回的數(shù)據(jù),一段時(shí)間后通過(guò)socket接受到其他的信息然后進(jìn)行業(yè)務(wù)處理。

服務(wù)端推送技術(shù)
在這個(gè)業(yè)務(wù)場(chǎng)景中,socket通信其實(shí)是實(shí)現(xiàn)了一種服務(wù)端主動(dòng)推送的功能,下面介紹一下我了解的服務(wù)端推送的技術(shù) ,僅做簡(jiǎn)單介紹,有時(shí)間可能會(huì)再寫(xiě)幾篇文章詳細(xì)介紹一下。
- 客戶(hù)端輪詢(xún)
這是一開(kāi)始最早用到的一種方式,客戶(hù)端定期去請(qǐng)求服務(wù)端看看有沒(méi)有數(shù)據(jù)需要推送過(guò)來(lái),缺點(diǎn)顯而意見(jiàn),會(huì)進(jìn)行大量無(wú)意義的http請(qǐng)求,消耗性能,但是現(xiàn)在有些項(xiàng)目可能還會(huì)用這種技術(shù)。 - Socket通信
socket是這篇文章主要介紹的東西,也是服務(wù)端主動(dòng)推送的一種方式 - 消息隊(duì)列
消息隊(duì)列也是以前用過(guò)的一種技術(shù),比較常用的庫(kù)是Rabbitmq的js實(shí)現(xiàn)amqplib。曾經(jīng)做過(guò)PC端的Electron項(xiàng)目和IOS端應(yīng)用的通信,當(dāng)時(shí)使用了amqplib這個(gè)庫(kù)。 - RPC
RPC也是實(shí)現(xiàn)服務(wù)端推送的一種方式,以前調(diào)研過(guò)GRPC這個(gè)庫(kù),有興趣的可以關(guān)注一下,和其他的幾種技術(shù)還是不一樣的。 - Http2
現(xiàn)在Http2也已經(jīng)實(shí)現(xiàn)了服務(wù)端推送的功能,如果你的項(xiàng)目里已經(jīng)開(kāi)始使用Http2.0的話(huà),也可以考慮這種方式。
小結(jié)
socket最常用的場(chǎng)景還是進(jìn)行頻繁的客戶(hù)端/服務(wù)端交互,比如說(shuō)最經(jīng)典的聊天室功能,核心的技術(shù)就是socket通信。在有的業(yè)務(wù)功能中,服務(wù)端推送也是必不可少的一種技術(shù),但最終要如何選擇還得根據(jù)項(xiàng)目情況來(lái)做具體分析。