NodeJS

双向通讯

Socket

代码示例:

// index.js

const express = require('express')
const app = express()

app.use(express.static('./public'))

app.get('/', (req, res) => {
    res.send({
        ok: 1
    })
})
app.listen(3000)


const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({port: 8080});

wss.on('connection', function connection(ws) {
    ws.on('message', function message(data) {  // 监听来自客户端发送的数据
        console.log('received:%s', data);
        wss.clients.forEach(function each(client) {
            if (client !== ws && client.readyState === WebSocket.OPEN) {  // 判断客户端是否保持连接状态(排除掉自己)
                client.send(data, {binary: false});  // 广播信息
            }
        })
    });
    ws.send('HQSY');  // 返回给客户端的数据
});
<!-- public/chat.html -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Chat</title>
</head>
<body>
    <script>
        const ws = new WebSocket('ws://localhost:8080')
        ws.onopen = ()=> {
            console.log('连接成功!')
        }
        ws.onmessage = (msgObj) => {
            console.log(msgObj.data)
        }
        ws.onerror = () => {
            console.log('error')
        }
    </script>
</body>
</html>

鉴权

代码示例:

// routes/chat.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
    res.render('chat');
});

module.exports = router;
// bin/websocketServer.js

const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({port: 8080});
const JWT = require('../util/JWT')

const WebSocketType = {
    Error: 0,
    GroupList: 1,
    GroupChat: 2,
    SingleChat: 3
}
function createMessage(type, user, data) {
    return JSON.stringify({
        type,
        user,
        data
    })
}

wss.on('connection', function connection(ws, req) {
    // console.log(req.url)
    const myURL = new URL(req.url, 'http://127.0.0.1:3000')
    // console.log(myURL.searchParams.get('token'))
    const payload = JWT.verify(myURL.searchParams.get('token'))
    if (payload) {
        ws.send(createMessage(WebSocketType.GroupChat, null, '欢迎'));
    } else {
        ws.send(createMessage(WebSocketType.Error, null, 'token无效'))
    }
    ws.on('message', function message(data) {
        console.log('received:%s', data);
        wss.clients.forEach(function each(client) {
            if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(data, {binary: false});
            }
        })
    });
});
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');

// socket服务器
require('./websocketServer')

require('../config/db.config')

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
var uploadRouter = require('./routes/upload')
var chatRouter = require('./routes/chat')

var session = require('express-session')
var MongoStore = require('connect-mongo')

var app = express();

var JWT = require('./util/JWT')

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(session({
    name: 'cookieName',
    secret: 'abcdefg',
    cookie: {
        maxAge: 1000 * 60 * 60,
        secure: false
    },
    resave: true,
    saveUninitialized: true,
    store: MongoStore.create({
        mongoUrl: 'mongodb://127.0.0.1:27017/test',
        ttl: 1000 * 60 * 10
    })
}))

app.use((req, res, next) => {
    if (req.url.includes('login')) {
        next()
        return
    }

    const token = req.headers['authorization']?.split(' ')[1]
    if (token) {
        const payload = JWT.verify(token)
        if (payload) {
            const newToken = JWT.generate({
                _id: payload._id,
                username: payload.username
            }, '1h')
            res.header('Authorization', newToken)
            next()
        } else {
            res.status(401).send({errCode: -1, errInfo: 'token失效了'})
        }
    } else {
        next()
    }
})

app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
app.use('/upload', uploadRouter);
app.use('/chat', chatRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};
    // render the error page
    res.status(err.status || 500);
    res.render('error');
});

module.exports = app;
// bin/websocketServer.js

const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({port: 8080});
const JWT = require('../util/JWT')

const WebSocketType = {
    Error: 0,
    GroupList: 1,
    GroupChat: 2,
    SingleChat: 3
}
function createMessage(type, user, data) {
    return JSON.stringify({
        type,
        user,
        data
    })
}

function sendAll() {
    wss.clients.forEach(function each(client) {
        if (client.readyState === WebSocket.OPEN) {
            client.send(createMessage(WebSocketType.GroupList, null, JSON.stringify(Array.from(wss.clients).map(item =>
                item.user
            ))))
        }
    })
}

wss.on('connection', function connection(ws, req) {
    const myURL = new URL(req.url, 'http://127.0.0.1:3000')
    const payload = JWT.verify(myURL.searchParams.get('token'))
    if (payload) {
        ws.send(createMessage(WebSocketType.GroupChat, null, '欢迎'));
        ws.user = payload
        // 必须群发
        sendAll()
    } else {
        ws.send(createMessage(WebSocketType.Error, null, 'token无效'))
    }
    ws.on('message', function message(data) {
        // console.log('received: %s', data);
        // wss.clients.forEach(function each(client) {
        //     if (client !== ws && client.readyState === WebSocket.OPEN) {
        //         client.send(data, {binary: false});
        //     }
        // })
        const msgObj = JSON.parse(data)
        switch (msgObj.type) {
            case WebSocketType.GroupList:
                console.log(Array.from(wss.clients).map(item => {
                    item.user
                }))
                ws.send(createMessage(WebSocketType.GroupList, null, JSON.stringify(Array.from(wss.clients).map(item => {
                    item.user
                }))))
                break;
            case WebSocketType.GroupChat:
                // console.log(msgObj.data)
                console.log('received: %s', data);
                wss.clients.forEach(function each(client) {
                    if (client.readyState === WebSocket.OPEN) {
                        client.send(createMessage(WebSocketType.GroupChat, ws.user, msgObj.data), {binary: false});
                    }
                })
                break;
            case WebSocketType.SingleChat:
                wss.clients.forEach(function each(client) {
                    if (client.user.username === msgObj.to && client.readyState === WebSocket.OPEN) {
                        client.send(createMessage(WebSocketType.SingleChat, ws.user, msgObj.data), {binary: false});
                    }
                })
                break;
        }
    });
    ws.on('close', () => {  // 当有客户端离线时
        wss.clients.delete(ws.user)
        sendAll()
    })
});
<!-- views/chat.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Chat</title>
</head>
<body>
    <script>
        const WebSocketType = {
            Error: 0,
            GroupList: 1,
            GroupChat: 2,
            SingleChat: 3
        }
        const ws = new WebSocket(`ws://localhost:8080?token=${localStorage.getItem('token')}`)
        ws.onopen = () => {
            console.log('open')
        }
        ws.onmessage = (msgObj) => {
            console.log(msgObj.data)
            msgObj = JSON.parse(msgObj.data)
            switch (msgObj.type) {
                case WebSocketType.Error:
                    localStorage.removeItem('token')
                    location.href = '/login'
                    break;
                case WebSocketType.GroupChat:
                    console.log(msgObj)
                    break;
            }
        }
    </script>
</body>
</html>
<!-- views/chat.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Chat</title>
</head>
<body>
    <input type="text" id="text"><button id="send">send</button>
    <select id="select">

    </select>
    <script>
        function createMessage(type, data, to = null) {
            return JSON.stringify({
                type,
                data,
                to
            })
        }
        const select = document.querySelector('#select')
        const send = document.querySelector('#send')
        const text = document.querySelector('#text')
        const WebSocketType = {
            Error: 0,
            GroupList: 1,
            GroupChat: 2,
            SingleChat: 3
        }
        const ws = new WebSocket(`ws://localhost:8080?token=${localStorage.getItem('token')}`)
        ws.onopen = () => {
            console.log('open')
        }
        ws.onmessage = (msgObj) => {
            console.log(msgObj.data)
            msgObj = JSON.parse(msgObj.data)
            switch (msgObj.type) {
                case WebSocketType.Error:
                    localStorage.removeItem('token')
                    location.href = '/login'
                    break;
                case WebSocketType.GroupList:
                    console.log(JSON.parse(msgObj.data))
                    const onlineList = JSON.parse(msgObj.data)
                    select.innerHTML = ''
                    select.innerHTML = `<option value="all">all</option>` + onlineList.map(item => `
                        <option value="${item.username}">${item.username}</option>
                    `).join('')
                    break;
                case WebSocketType.GroupChat:
                    const title = msgObj.user?msgObj.user.username:'广播'
                    console.log(title + ':' + msgObj.data)
                    break;
                case WebSocketType.SingleChat:
                    console.log(msgObj.user.username + ':' + msgObj.data)
                    break;
            }
        }
        send.onclick = () => {
            if(select.value === 'all') {
                ws.send(createMessage(WebSocketType.GroupChat, text.value))
            } else {
                ws.send(createMessage(WebSocketType.SingleChat, text.value, select.value))
            }
        }
    </script>
</body>
</html>

socket.io

代码示例:

// bin/websocketServer.js

const io = require("socket.io");
const JWT = require('../util/JWT')

function start(server) {
    const io = require('socket.io')(server)
    io.on('connection', (socket) => {
        // console.log('aaa', socket.handshake.query.token)
        const payload = JWT.verify(socket.handshake.query.token)
        if (payload) {
            socket.user = payload
            socket.emit(WebSocketType.GroupChat, createMessage(null, '欢迎'))
            sendAll(io)
        } else {
            socket.emit(WebSocketType.Error, createMessage(null, 'token失效'))
        }
        socket.on(WebSocketType.GroupList, () => {
            // console.log(Array.from(io.sockets.sockets).map(item => item[1].user))  // 不同版本使用方法各不相同

            socket.emit()
        })
        socket.on(WebSocketType.GroupChat, (msg) => {
            // console.log(msg)
            io.sockets.emit(  // 群发(包括自己)
                WebSocketType.GroupChat,
                createMessage(socket.user, msg.data)
            )
            // socket.broadcast.emit(  // 群发(不包括自己)
            //     WebSocketType.GroupChat,
            //     createMessage(socket.user, msg.data)
            // )
        })
        socket.on(WebSocketType.SingleChat, (msgObj) => {
            Array.from(io.sockets.sockets).forEach(item => {
                if (item[1].user.username === msgObj.to) {
                    item[1].emit(
                        WebSocketType.SingleChat,
                        createMessage(item.user, msgObj.data)
                    )
                }
            })
        })
        io.on('disconnect', () => {  // 用户离线时
            sendAll(io)
        })
    })
}

const WebSocketType = {
    Error: 0,
    GroupList: 1,
    GroupChat: 2,
    SingleChat: 3
}

function createMessage(user, data) {
    return {
        user,
        data
    }
}

function sendAll(io) {
    io.sockets.emit(
        WebSocketType.GroupList,
        createMessage(null, Array.from(io.sockets.sockets)
            .map(item => item[1].user).filter(item => item)  // 防止undefined
        )
    )
}

module.exports = start
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');

var websocketServer = require('./websocketServer')

require('../config/db.config')

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);
// 启动websocket
websocketServer(server)

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}
<!-- views/chat.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Chat</title>
    <script src="/javascripts/socket.io.min.js"></script>
</head>
<body>
    <input type="text" id="text"><button id="send">send</button>
    <select id="select">

    </select>
    <script>
        function createMessage(data, to = null) {
            return {
                type,
                data,
                to
            }
        }
        const select = document.querySelector('#select')
        const send = document.querySelector('#send')
        const text = document.querySelector('#text')
        const WebSocketType = {
            Error: 0,
            GroupList: 1,
            GroupChat: 2,
            SingleChat: 3
        }
        const socket = io(`ws://localhost:3000?token=${localStorage.getItem('token')}`)  // 未填实参默认连接localhost
        socket.on(WebSocketType.GroupChat, (msg) => {
            // console.log(msg)
            const title = msg.user ? msg.user.username: '广播'
            console.log(title + ':' + msg.data)
        })
        socket.on(WebSocketType.SingleChat, (msg) => {
            // console.log(msg)
            const title = msg.user ? msg.user.username: '广播'
            console.log(title + ':' + msg.data)
        })
        socket.on(WebSocketType.Error, (msg) => {
            localStorage.removeItem('token')
            location.href = '/login'
        })
        socket.on(WebSocketType.GroupList, (msgObj) => {
            // console.log(msg)
            const onlineList = msgObj.data
            select.innerHTML = ''
            select.innerHTML = `<option value="all">all</option>` + onlineList.map(item => `
                        <option value="${item.username}">${item.username}</option>
                    `).join('')
        })
        send.onclick = () => {
            if (select.value === 'all') {
                socket.emit(WebSocketType.GroupList, createMessage(text.value))
            } else {
                socket.emit(WebSocketType.GroupList, createMessage(text.value, select.value))
            }
        }
    </script>
</body>
</html>