一穆壕、原生websocket
首先介紹一下實(shí)現(xiàn)即時通信方式,有如下三種:
1. 輪詢Ajax——不停詢問其屏,浪費(fèi)性能喇勋;
2.服務(wù)器響應(yīng)流:服務(wù)器不關(guān)閉連接,一次響應(yīng)偎行,一直保持連接——資源有限川背;
3. 使用websocket——有兼容問題
websocket是HTML5中新推出的通信協(xié)議贰拿,是在原來http協(xié)議基礎(chǔ)上進(jìn)行升級的,屬于長連接通信渗常。
原來http協(xié)議為一問一答形式壮不,先有請求才有響應(yīng)的機(jī)制。而websocket其底層利用了TCP協(xié)議(客戶端與服務(wù)器建立連接之后皱碘,便可以自由通信)询一,服務(wù)器也可以主動發(fā)請求給客戶端。
注:websocket只存在于前臺癌椿,是html5帶的一種東西健蕊。后臺本就有socket
原生的websocket中:
- 客戶端可用直接通過
new Websocket('ws://xxxx/')
建立通訊,通過原生的一系列事件與方法來實(shí)現(xiàn)與服務(wù)端的溝通踢俄; - 服務(wù)端基于
net
模塊(實(shí)現(xiàn)TCP協(xié)議)+crypto
模塊(保證安全)來創(chuàng)建連接和監(jiān)聽通信數(shù)據(jù)缩功; - 服務(wù)端原生的socket實(shí)現(xiàn)十分復(fù)雜,包括:
- 響應(yīng)客戶端的請求報文都办,從請求報文中拆分出連接的版本等基本信息判斷是否需要響應(yīng)嫡锌;
- 從請求報文中解析出掩碼并根據(jù)掩碼回寫響應(yīng)報文;
- 最復(fù)雜的在于數(shù)據(jù)幀的解析琳钉,涉及到幀結(jié)構(gòu)的拆分势木,數(shù)據(jù)的加密,二進(jìn)制數(shù)據(jù)的讀寫等歌懒。
總之啦桌,原生的websocket使用起來十分不便,在實(shí)際應(yīng)用中及皂,多使用第三方庫甫男。
二、中間件與第三方庫
核心思想socket.io——交互方式可能通過websocket/輪詢Ajax/服務(wù)器響應(yīng)流實(shí)現(xiàn):1.服務(wù)器可主動發(fā)數(shù)據(jù)到客戶端验烧;2.客戶端可通過服務(wù)器向客戶端發(fā)數(shù)據(jù)板驳。
客戶端:依賴socket.io-client
引入js文件
<script src = "/socket.io.js"></script>
建立連接
const socket = io('[http://localhost:8888');](http://localhost:8888');/)
監(jiān)聽事件
socket.on(eventName, data => {})
;向服務(wù)器傳遞消息
socket.emit(name, value)
;
服務(wù)端:依賴koa-socket
引入IO并實(shí)例化對象
const IO = require("koa-socket"); const io = new IO()
;io監(jiān)管app
io.attach(app)
;聲明事件
io.on(eventName, sock=> {})
;接收客戶端消息
io.on(name, ctx => {
//ctx.data就是前臺發(fā)送的數(shù)據(jù)
ctx.socket.emit(name, value); //回應(yīng)消息,name需要與前臺對應(yīng)
})
- 廣播事件
- 廣播事件
io.broadcast(name, value)
- 私聊推送
app._io.to(usersId).emit(name, value)
- 加入群組
ctx.socket.socket.join(id)
- 群組推送
ctx.socket.socket.to(groupId).emit(name, value)
三碍拆、案例——簡易聊天室
服務(wù)端:
const Koa = require("koa");
const static = require("koa-static");
const IO = require("koa-socket");
const app = new Koa();
//實(shí)例化socket.io對象
const io = new IO();
//io監(jiān)管app
io.attach(app);
//模擬用戶信息
let user = {};
//記錄用戶信息
io.on('user', ctx => {
//存儲用戶通信id
user[ctx.data.userName] = ctx.socket.id;
//添加分組信息
ctx.socket.socket.join(ctx.data.groupId);
})
io.on('msg', ctx => {
//獲取客戶端數(shù)據(jù)并廣播
let {to, from, content, groupId} = ctx.data;
if(groupId == '0') { //公共消息則廣播給所有人
io.broadcast('msg', {from, content});
} else if(to == 'all') { //群組消息則發(fā)送給整個群組
//向監(jiān)管部分推送消息
ctx.socket.socket.to('0').emit('msg', {from: from, content});
//向群組成員推送消息
ctx.socket.socket.to(groupId).emit('msg', {from: from, content});
//向發(fā)起人推送消息
app._io.to(user[from]).emit('msg', {from: from, content});
} else { //私聊信息則只發(fā)給特定的人
let fromStr = from + '(私聊)';
//向發(fā)起人推送消息
app._io.to(user[from]).emit('msg', {from: fromStr, content});
//私人消息則僅僅發(fā)送給對應(yīng)的人
app._io.to(user[to]).emit('msg', {from: fromStr, content});
}
})
//配置靜態(tài)資源文件
app.use(static('./'));
//開啟服務(wù)器
app.listen(8888, ()=>{
console.log('The server is running...');
})
客戶端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./socket.io.js" charset="utf-8"></script>
<style media="screen">
.chatroom {
display: none;
}
.myOwnMessage {
color: #205da1;
}
</style>
</head>
<body>
<!-- 用戶信息設(shè)置窗口 -->
<div class="setting">
輸入你的昵稱:<input type="text" name="username">
選擇加入的群聊組: <select class="groups" name="">
<option value="0">江湖</option>
<option value="1">魔法</option>
<option value="2">物理</option>
</select>
<button type="button" name="button" id="setName">提交</button>
</div>
<!-- 聊天區(qū)域 -->
<div class="chatroom">
<div class="info">
<span id="user">xxx</span>,您好倔监!歡迎來到聊天室~
</div>
<!-- 信息列表 -->
<ul id="chatList">
</ul>
<!-- 信息輸入?yún)^(qū)域 -->
<textarea id="content" name="content" rows="8" cols="80"></textarea>
<button type="button" id="submit" name="button">發(fā)送</button>
<button type="button" id="toBoss" name="button">私聊給老板</button>
</div>
</body>
<script type="text/javascript">
//建立連接
const socket = io('[http://localhost:8888');](http://localhost:8888');/)
//監(jiān)聽事件
socket.on('connect', data => {
console.log('連接上了!')
});
socket.on('disconnect', data => {
console.log('斷開連接了!')
});
//獲取元素
let oSubmit = document.getElementById('submit');
let oSubmitToBoss = document.getElementById('toBoss');
let oChat = document.getElementsByClassName('chatroom')[0];
let oSettingBox= document.getElementsByClassName('setting')[0];
let oSetName = document.getElementById('setName');
let oText = document.getElementById('content');
//記錄用戶名稱與分組信息
let username, groupId;
//綁定設(shè)定昵稱事件
oSetName.onclick = function () {
//獲取昵稱
userName = document.querySelectorAll('input[name="username"]')[0].value;
groupId = document.querySelectorAll('.groups')[0].value;
//隱藏昵稱設(shè)置區(qū)域
this.parentNode.style.display = 'none';
//顯示聊天區(qū)域并發(fā)送用戶信息
socket.emit('user', {
userName, groupId
});
document.getElementById('user').innerHTML = userName;
oChat.style.display = 'block';
}
function sendMsg(toWho) {
//獲取內(nèi)容
let sContent = oText.value;
oText.value = "";
//發(fā)送通信數(shù)據(jù)
socket.emit('msg', {
to: toWho,
groupId: groupId,
from: userName,
content: sContent
});
}
//綁定事件浩习,發(fā)送聊天信息
oSubmit.onclick = function () {
sendMsg('all');
}
//發(fā)送私聊信息
oSubmitToBoss.onclick = function () {
sendMsg('boss');
}
//監(jiān)聽服務(wù)器的廣播
socket.on('msg', sock => {
let oUl = document.getElementById('chatList');
let oLi = document.createElement('li');
//判斷是否為自己發(fā)送的消息济丘,是則設(shè)置樣式
if(sock.from.indexOf(userName) != -1) {
oLi.className = 'myOwnMessage';
}
oLi.innerHTML = `${sock.from}:${sock.content}`;
oUl.appendChild(oLi);
})
</script>
</html>
-
實(shí)現(xiàn)效果
聊天室效果