當今時代做一個好前端需要具備的能力實在太多太多,之前通過JAVA構建了一套接收mq數據並通過websocket實現前後端實時數據廣播的springboot服務,中間出現了無數的深坑,解決的焦頭爛額。雖然完成了驗收,但想找尋一種更加簡單的方式進行數據的實時推送。

通過一個五一假期把nodejs過了一遍。拆分了下基本功能,分類記錄下如何將websocket添加到開發應用中(接入mq等方法會抽時間記錄)。進入正題

顯而易見,想要將Websocket加入到react與nodejs應用中,並非簡單的把websocket實現就大功告成了,通過簡易封裝並實現了幾個小例子,成功實現了功能封裝,藉助一個簡易聊天室的搭建進行開發流程的記錄。

基於nodejs+socket.io+react+react-router-dom實現簡單聊天室

為什麼選擇nodejs,主要是因為socket.io具備足夠優異的功能和強大的兼容性,socket.io最強大的特性就是消息的傳遞是基於傳輸的,而非全部依賴於websocket。因此socket.io可以在絕大部分的瀏覽器和設備上運行,支持IE6~ios各種環境。

打個比方,在很多情況下(伺服器無數據傳遞給客戶端),連接依然不會斷開,socket.io會自動使用long polling這種複雜的技術,但API簡潔如websocket。同時在極端情況下(websocket被防火牆禁止),socket.io依然能通過其他技術來處理這類問題

選擇node的原因也很顯而易見,本人鍾情於前端,但卻一直在進行全棧內容開發,隨著業務的逐步複雜,對技術棧理解的逐步深入,越來越覺得後端使用java讓自己變得不夠專精(技術太差嘻嘻嘻),同時解決後端bug也很喫力。人的精力是有限的,專精於一項技術是十分必要的。同時node作為前端必備技能,也是我選擇的最重要理由。舉個栗子:node強大到幾行代碼便可開啟一個伺服器(代碼如下)

var http = require(http);
//創建伺服器
var serv = http.createServer(function(req,res){
res.writeHead(200,{context-Type:text-html});
res.end(<marquee>Node</marquee>);
});
//佔用3800埠
serv.listen(3800);

進入正題,使用多項技術構建一個簡單聊天室。聊天室功能的依賴:

"express": "4.16.4",
"socket.io": "2.2.0",
"body-parser": "^1.19.0",

需要在package.json的dependencies中添加以上三項依賴,再通過

npm install

命令下載所需依賴。

構建一個普通Express應用,並將其監聽在3900埠上

服務端server.js

var express = require(express)
,sio = require(socket.io),
bodyParser = require(body-parser);
// var http = require(http); express4 no more need http
/*
create express app中間件
*/
var app =express()
app.use(bodyParser());
app.use(bodyParser.json({ type: application/vnd.api+json }))
app.use(bodyParser.urlencoded({ extended: false }))
/*
don.t forget listen to port
*/
var server = app.listen(3900);
var io=sio.listen(server);
// server.listen(3900);express4 no more need http

實現方式是將socket.io綁定在app上,監聽完畢後我們需要設置監聽器進行連接:

io.socket.on(connection,(socket)=>{
console.log(someone connected);
})

至此我們已經成功建立了連接,一旦有連接進來(訪問3900埠),就會在控制檯輸出簡單的信息了。由於Socket.io是引入的強依賴API,因此在瀏覽器中也要載入相應的依賴Socket.io-client;

在瀏覽器的客戶端中的package.json中添加Socket.io-client依賴,只需在react的執行環境下運行

npm install Socket.io-client --save

進行客戶端依賴的下載。--save將依賴保存在package.json的dependencies中。導入引用後就可以在react中使用相應的內容

客戶端index.js

import sockjs from socket.io-client
class home extends React.Component{
componentDidMount(){
this.testconnect();
}
testconnect=()=>{
let socket = sockjs("ws://localhost:3900", {
transports: [websocket]
})

// /不間斷嘗試重連接
socket.on(reconnect_attempt,()=> {
console.log("reconnect")
socket.transports = [websocket,polling, flashsocket];
});

// 重連接時出錯
socket.on(reconnect_error,(attemptNumber)=> {
console.log(attemptNumber)
});

//連接成功走這個方法
socket.on(connect,()=>{
console.log(socket.connected)
})

//報錯時走這個方法
socket.on(connect_error, (error) => {
console.log(error)
});

//連接存活驗證
socket.on(ping, (error) => {
console.log(ping_include)
});

在react中我們使用生命週期來進行方法的掛載,類似於源生JS中的window.onload,客戶端完成至此就實現了與伺服器的數據互通,此時可以看到伺服器輸出someone connected。

在本例中假如我們使用主流瀏覽器來連接socket.io伺服器,那麼socket.io均會使用WebSocket來進行通信。socket.io會選擇使用對用戶來說速度最快、對伺服器性能來說最好的方法進行連接,如果性能達不到要求,將首先保證連接正常。

客戶端與服務端連接成功後,將進一步實現服務端與客戶端的基礎代碼進行聊天信息處理

服務端server.js

io.sockets.on(connection,function(socket){
socket.on(join,(name)=>{
socket.nickname=name;
console.log("joinkname:"+name)
// console.log(socket.broadcast.RequestHeaders);
socket.broadcast.emit(announcement,name+join the chat);
// socket.emit(announcement,name+join the chat);
})
console.log("someone connected")
})

服務端監聽一個join事件,並需要將收到的信息通知給其他用戶,告訴當前用戶有新用戶連接進來了

客戶端index.js

socket.on("connect",function(){
// console.log(socket.broadcast)
socket.emit(join,prompt(what is your nickname));
$(#chat).css(display,block);

socket.on(announcement,(msg)=>{
if(msg){
console.log("announcement did right")
let li = `<li class="announcement">${msg}</li>`
$("#messages").append(li);
}
})
})

我們在客戶端中進行邏輯的處理,創建join事件與後端監聽的事件名稱匹配,當用戶連接成功時監聽已創建的connect事件。connect事件創建成功後確保msg存在的情況下我們再進行相應的dom操作。 到這裡我們就完成了客戶端與服務端的數據互通。但是目前只是做到單頁面內單用戶自身的數據推送,想要實現將數據發送給其他用戶還要通過以下操作

服務端server.js

socket.on(text,function(msg){
console.log("text message:"+msg)
console.log("nickname:"+socket.nickname)
socket.broadcast.emit(text,socket.nickname, msg);//注意比較broadcast與不加broadcast的區別
// socket.emit(text,socket.nickname, msg);
})

在服務端我們廣播接收到的text』事件。

客戶端index.js

$("#form").submit(()=>{
let inputan =$(#input);
console.log("submit success")
this.addmessage(me,inputan.val()) ;
socket.emit(text,inputan.val());

inputan.attr(value);
inputan.focus();

return false
} )
//不要自執行,要在調用後再執行
socket.on(text,this.addmessage)
}
// 將聊天記錄添加到首頁的每一個欄位上
addmessage=(from,text)=>{
if(text){
let li = `<li class="message"><b>${from}</b>:${text}</li>`
$("#messages").append(li);
}

在客戶端中,要實現將輸入數據發出,我們要分發一個text事件。實現的具體流程:當前用戶使用text事件將input內的value值傳輸到伺服器,再由伺服器將客戶端發送過來的內容分發給其他用戶並在其頁面進行展示。至此多用戶聊天室的整體內容就完成了。全部代碼如下

客戶端

import React from react;
import sockjs from socket.io-client
import ./home.less
import $ from jquery
class home extends React.Component{
componentDidMount(){
this.testconnect();
}
testconnect=()=>{
let socket = sockjs("ws://localhost:3900", {
transports: [websocket]
})
// /不間斷嘗試重連接
socket.on(reconnect_attempt,()=> {
console.log("reconnect")
socket.transports = [websocket,polling, flashsocket];
});

// 重連接時出錯
socket.on(reconnect_error,(attemptNumber)=> {
console.log(attemptNumber)
});

//連接成功走這個方法
socket.on(connect,()=>{
console.log(socket.connected)
})

//報錯時走這個方法
socket.on(connect_error, (error) => {
console.log(error)
});

//連接存活驗證
socket.on(ping, (error) => {
console.log(ping_include)
});
// 連接成功的connect方法
socket.on("connect",function(){
// console.log(socket.broadcast)
socket.emit(join,prompt(what is your nickname));
$(#chat).css(display,block);

socket.on(announcement,(msg)=>{
if(msg){
console.log("announcement did right")
let li = `<li class="announcement">${msg}</li>`
$("#messages").append(li);
}
})
})
// 添加獲取信息的方法

$("#form").submit(()=>{
let inputan =$(#input);
console.log("submit success")
this.addmessage(me,inputan.val()) ;
socket.emit(text,inputan.val());

inputan.attr(value);
inputan.focus();

return false
} )
//不要自執行,要在調用後再執行
socket.on(text,this.addmessage)
}
// 將聊天記錄添加到首頁的每一個欄位上
addmessage=(from,text)=>{
if(text){
let li = `<li class="message"><b>${from}</b>:${text}</li>`
$("#messages").append(li);
}
}

render(){
return(
<div id="chat">
<ul id="messages">
<form id="form">
<input type="text" id="input"/>
<button>send</button>
</form>
</ul>
</div>
)
}
}

export default home;

服務端

var express = require(express)
,sio = require(socket.io),
bodyParser = require(body-parser);
// var http = require(http); express4 no more need http
/*
create express app
*/
var app =express()
app.use(bodyParser());
app.use(bodyParser.json({ type: application/vnd.api+json }))
app.use(bodyParser.urlencoded({ extended: false }))
/*
don.t forget listen to port
*/
var server = app.listen(3900);
var io=sio.listen(server);
// server.listen(3900);express4 no more need http

io.sockets.on(connection,function(socket){
socket.on(join,(name)=>{
socket.nickname=name;
console.log("joinkname:"+name)
// console.log(socket.broadcast.RequestHeaders);
socket.broadcast.emit(announcement,name+join the chat);
// socket.emit(announcement,name+join the chat);
})
console.log("someone connected")
socket.on(text,function(msg){
console.log("text message:"+msg)
console.log("nickname:"+socket.nickname)
socket.broadcast.emit(text,socket.nickname, msg);//注意比較broadcast與不加broadcast的區別
// socket.emit(text,socket.nickname, msg);
})
})

實現後的效果:

用戶anzp

用戶william

node+socket.io+react+react-router-dom真心好用,近期會將react-router-dom的使用記錄及開發過程中的問題push上來。祝編碼愉快

Happy Hacking!!


推薦閱讀:
相關文章