> 文章列表 > 网络请求实战-实战websocket聊天程序

网络请求实战-实战websocket聊天程序

网络请求实战-实战websocket聊天程序

目录

WebSocket协议初探

Socket连接的建立过程

聊天室:node.js端

 聊天室:web端

小结


WebSocket协议初探

一个基于TCP的通信协议

  • 复用HTTP的握手
  • 基于TCP传输协议

 

101切换协议

WebSocket连接之后,传输的都是二进制数据了

Socket连接的建立过程

  • 观察浏览器/node.js端WebSocket连接的建立过程
  • 观察对握手的处理和协议转换的过程
<!DOCTYPE html>
<html>
<body>
<h2>Chatroom</h2>
<div></div>
<label>say</label>
<input />
<button>send</button>
<script>
const client = new WebSocket('ws://chat.svc:8080')
// wss -> WebSocket Secure
// https - tls/ssl - tcp/ip
// wss - tls - tcp/ip
client.onopen = () => {console.log('connection open')
}
</script>
</body>
</html>
const express = require('express')
const app = express()
const path = require('path')
const parseHeader = require('parse-headers') // 引用一个解析headers的库
const crypto = require('crypto') // node加密
app.get('/', (req, res) => {// 运行网页res.sendFile(path.resolve(__dirname, 'handshake.html'))
})
app.listen(3000) // 前端服务占用端口
/* -- websocket server -- */
const net = require('net')
const server = net.createServer()
server.on('connection', socket => { // 有请求进来
// 网络插槽socket.on('data', (buffer) => {// 监听数据来了没const str = buffer.toString() // 第一次握手过来的字符串console.log('---message ---') // 看一下客户端发送了几次消息,建立连接发送了2次console.log(str)const headers = parseHeader(str)const sha1 = crypto.createHash('sha1') // 生成一个hash// 重新计算sha1 + 一个公开的串 '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'跟浏览器对一下sha1.update(headers['sec-websocket-key'] + '')const acceptKey = sha1.digest('base64') // 验签const response = `HTTP/1.1 101 Switching Protocols // 同意你Switching Protocols
Upgrade: websocket // 帮你转换协议websocket
Connection: Upgrade // 升级
Sec-Websocket-Accept: ${acceptKey}// http协议换行前面不能有空格,不然会误以为也是头部字符串// http协议这里需要空行,才会结束body`// 返回值socket.write(response) // 返回给客户端})
})
server.listen(8080)// 服务端占用端口

解析封包,位运算找到用户发送的消息payload,处理payload

聊天室:node.js端

观察用node.js端实现过程

思考:下载全部聊天记录应该如何实现?用另一个TCP/IP连接,socket或者http都可以,不用登录的那个连接,避免全部聊天记录过大,造成阻塞

const express = require('express')
const app = express()
const path = require('path')
app.use(express.static('static'))
app.get('/', (req, res) => {res.sendFile(path.resolve(__dirname, "chatroom.html"))
})
app.listen(3000)
// 聊天室
const io = require('socket.io')(8080)// 服务端socket.io提供自己的方式
// 创建socket成本并不高,只是一个对象;计算,存储,日志,这些成本会很高
const users = new Map() // 全局对象 以socket方式建立一个Map是不可行 的,只适用于demo
// 聊天室全体消息
function broadcast(type, message, sender) { // sender发送者,系统消息为空for(let socket of users.keys()) {// 聊天室内每个连接都发一个socket.send({type, message, sender})}
}
io.on('connect', socket => { // 底层都处理好了,不用握手什么的了
// {type, message}
// linux文件的i/o模型 epoll 宏位数
// i/o wait 也比较耗性能socket.on('message', data => { // 等消息进来console.log('here123123123', data) // jsonswitch(data.type) {case 'LOGIN': // 登录users.set(socket, {name : data.name})// set给Map加值key=socket;value={name : data.name}// 实现一个方法,聊天室全体消息broadcast('LOGIN', `${data.name}加入了聊天`)breakcase 'CHAT': // 聊天// 通过socket可以拿到用户登录信息,socket区分用户仅限于demo,socket一段时间后是会换连接的// 实际上:用户校验,用户传tokenconst user = users.get(socket)broadcast('CHAT', data.message, user.name)break}})
})

 聊天室:web端

<!DOCTYPE html>
<html><head><style>#content {padding :10px;border : 1px solid #343434;}</style>
</head>
<body>
<h2>Chatroom</h2>
<div id='content'></div>
<label>say</label>
<input id='ipt' />
<button onclick="send()">send</button>
<script src="/socket.io.js"></script>
<script>
// 客户端socket.ioconst socket = io('ws://chat.svc:8080')// 连接socketconst name = "user" + new Date().getTime() // 用户昵称socket.send({ // 一进聊天室type : 'LOGIN',name})const contentDiv = document.getElementById('content')socket.on('message', data => { // 服务端传过来的广播消息const {message, sender} = datalet senderName = senderif(!sender) {senderName = "系统"}else if(sender === name) {senderName = "我"}const div = document.createElement('div')div.className = data.type.toLowerCase() // class名div.innerHTML = `${senderName}: ${message}` // 将展示的消息contentDiv.append(div)})const ipt = document.getElementById('ipt')ipt.addEventListener('keyup', e => { // 监听按键if(e.key === 'Enter') { // 回车键发送send()}})function send(){const val = ipt.valueif(val === '') {return}console.log(val)socket.send({ // 发送消息type : "CHAT",message : val})ipt.value = "" // 清空输入框ipt.focus() // 重新聚焦}</script>
</body>
</html>

小结

  • WebSocket握手和协议转换的过程很【自然】
  • socket(网络插座)为客户端/服务端提供通信机制