Skip to content

WebSocket 通信 ​

HTTP 的局限 ​

HTTP 是无状态的,服务器无法主动向客户端推送数据。传统轮询方案效率低(每次请求包含完整头部,有效数据占比小)。

WebSocket 简介 ​

WebSocket 实现客户端与服务端的双向通信,适用于:

  • 社交订阅 / 即时聊天
  • 多玩家游戏
  • 协同编辑
  • 股票 / 基金实时报价
  • 在线教育

搭建 WebSocket 服务器 ​

使用 socket.io:

bash
npm install socket.io
npm install socket.io
js
const express = require('express')
const app = express()

const server = require('http').createServer(app)
const io = require('socket.io')(server)

io.on('connection', (socket) => {
  console.log('客户端已连接')
})

// 必须用 server 监听,不能用 app.listen
server.listen(3000)
const express = require('express')
const app = express()

const server = require('http').createServer(app)
const io = require('socket.io')(server)

io.on('connection', (socket) => {
  console.log('客户端已连接')
})

// 必须用 server 监听,不能用 app.listen
server.listen(3000)

客户端引入 /socket.io/socket.io.js(自动生成)。

消息发送 ​

js
// 只发送给自己
socket.emit('message', data)

// 发送给除自己外的所有人
socket.broadcast.emit('message', data)

// 发送给所有人(包括自己)
io.emit('message', data)
// 只发送给自己
socket.emit('message', data)

// 发送给除自己外的所有人
socket.broadcast.emit('message', data)

// 发送给所有人(包括自己)
io.emit('message', data)

示例 ​

js
// 服务端
io.on('connection', (socket) => {
  socket.on('chat', (data) => {
    io.emit('chat', data)  // 广播给所有人
  })
})

// 客户端
const socket = io()
socket.emit('chat', 'Hello')
socket.on('chat', (data) => {
  console.log('收到:', data)
})
// 服务端
io.on('connection', (socket) => {
  socket.on('chat', (data) => {
    io.emit('chat', data)  // 广播给所有人
  })
})

// 客户端
const socket = io()
socket.emit('chat', 'Hello')
socket.on('chat', (data) => {
  console.log('收到:', data)
})

房间管理 ​

js
io.on('connection', (socket) => {
  // 加入房间
  socket.on('subscribe', (data) => {
    socket.join(data.room)
    io.to(data.room).emit('some event')
  })

  // 加入多个房间
  socket.to('room1').to('room2').emit('hello')

  // 离开房间
  socket.on('unsubscribe', (data) => {
    socket.leave(data.room)
  })

  // 房间内广播(不包括发送者)
  socket.broadcast.to('roomA').emit('message', '大家好')

  // 房间内广播(包括发送者)
  io.sockets.in('roomA').emit('message', '大家好')
})
io.on('connection', (socket) => {
  // 加入房间
  socket.on('subscribe', (data) => {
    socket.join(data.room)
    io.to(data.room).emit('some event')
  })

  // 加入多个房间
  socket.to('room1').to('room2').emit('hello')

  // 离开房间
  socket.on('unsubscribe', (data) => {
    socket.leave(data.room)
  })

  // 房间内广播(不包括发送者)
  socket.broadcast.to('roomA').emit('message', '大家好')

  // 房间内广播(包括发送者)
  io.sockets.in('roomA').emit('message', '大家好')
})

断开连接 ​

js
io.on('connection', (socket) => {
  socket.on('disconnect', (reason) => {
    console.log('用户断开连接:', reason)
  })
})
io.on('connection', (socket) => {
  socket.on('disconnect', (reason) => {
    console.log('用户断开连接:', reason)
  })
})

在线人数统计 ​

js
let onlineUsers = {}
let i = 0

io.on('connection', (socket) => {
  socket.name = ++i
  onlineUsers[socket.name] = socket

  socket.on('disconnect', () => {
    delete onlineUsers[socket.name]
  })

  socket.on('/user/list', (callback) => {
    callback(Object.keys(onlineUsers).length)
  })
})
let onlineUsers = {}
let i = 0

io.on('connection', (socket) => {
  socket.name = ++i
  onlineUsers[socket.name] = socket

  socket.on('disconnect', () => {
    delete onlineUsers[socket.name]
  })

  socket.on('/user/list', (callback) => {
    callback(Object.keys(onlineUsers).length)
  })
})

React 中使用 ​

jsx
import IO from 'socket.io-client'

const socket = IO('ws://localhost:3006', {
  transports: ['websocket']
})

socket.on('connect', () => {
  console.log('已连接', socket.connected)
})

socket.on('connect_error', (error) => {
  console.log('连接错误', error)
})

// 页面关闭时断开连接
socket.close()
import IO from 'socket.io-client'

const socket = IO('ws://localhost:3006', {
  transports: ['websocket']
})

socket.on('connect', () => {
  console.log('已连接', socket.connected)
})

socket.on('connect_error', (error) => {
  console.log('连接错误', error)
})

// 页面关闭时断开连接
socket.close()

完整聊天室示例 ​

js
// 服务端
io.on('connection', (socket) => {
  socket.on('message', (msg) => {
    io.emit('message', msg)
  })

  socket.on('/user/list', (callback) => {
    callback('success')
  })

  socket.on('user/modify', (name, callback) => {
    user.name = name
    callback('修改成功')
  })
})

// 客户端
socket.emit('message', '您好')
socket.emit('/user/list', (list) => {})
socket.emit('user/modify', 'hny', (result) => {})
// 服务端
io.on('connection', (socket) => {
  socket.on('message', (msg) => {
    io.emit('message', msg)
  })

  socket.on('/user/list', (callback) => {
    callback('success')
  })

  socket.on('user/modify', (name, callback) => {
    user.name = name
    callback('修改成功')
  })
})

// 客户端
socket.emit('message', '您好')
socket.emit('/user/list', (list) => {})
socket.emit('user/modify', 'hny', (result) => {})