socket.io

1,270 阅读1分钟

Socket.io是基于事件的实时双向通信的类库,具有跨平台,安全可靠,速度快,实时性强,兼容性良好(支持的浏览器最低达IE6左右)等优点。

一、如何使用?

服务端(以express为例):

const express = require("express");
//建立express实例
const app = new express();
//建立http服务
const http = require("http").Server(app);
//对该http服务实施socket封装,即该服务下的接口满足socket协议
const io = require("socket.io")(http);

io.on("connection", (socket) => {
    console.log("connect");
    //全局通知
    io.emit("test1", socket.id);
    
    socket.on("chatMessageToSocketServer", (msg) => {
        console.log(msg);
        //私人通知
        socket.emit("test1", `Dear ${socket.id}, I have received your replay`);
    });
});

io.on("disconnect", () => {
    console.log("disconnect");
});

http.listen(8080, () =>{
    console.log("listening on *:8080");
});

客户端(以react为例):

import io from 'socket.io-client';
export default class SocketClient {
    //建立连接
    connect(socketPath) {
        this.socket = io.connect(socketPath);
        return new Promise((resolve, reject) => {
            this.socket.on("connect", () => reslove());
            this.socket.on("connect_error", (err) => reject(err));
        });
    }
    
    //断开连接
    disconnect() {
        return new Promise((resolve) => {
            this.socket.disconnect(() => {
                this.socket = null;
                resolve();
            })
        })
    }
    //向服务端发送事件
    emit(event, data) {
        return new Promise((resolve, reject) => {
            if (!this.socket)
                return reject("No socket connection.");
            return this.socket.emit(event, data, (res) => {
                if (res.error) 
                    return reject(res.error);
                return resolve();
            });
        });
    }
    //监听服务端发送的事件
    on(event, fn) {
        return new Promise((resolve, reject) => {
            if (!this.socket) 
                return reject("No socket connection.");
            this.socket.on(event, fn);
            resolve();
        });
    }
}

若运行以上代码,已足够满足了基本的需求了,实现了客户端与服务器之间的通信。但是如果想实现私信的话,即通过服务器这个平台,让一个用户向另一个用户进行通信,那又该如何实现呢?

既然已经知道了需求,那我们就开始分析,首先我们已经知道了客户端和服务器之间是可以通信的,那么我们就借助服务器这个平台进行识别和转发用户的信息到另一个用户那就行了,那服务器如何发送消息到指定的用户那呢?这就需要我们用到了socket.to(id).emit来指定私信目标。其中id指的便是私信目标的socket.id,因此如果我们需要进行私信的话,首先得将加入到该连接的所有用户的socket.id存储起来,代码如下:

let socketArr = [];

io.on("connection", (socket) => {
    socketArr.push(socket.id); //把连接的socket.id都存入数组
    socket.on("chatMessageToSocketServer", (msg) => {
        const index = socketArr.indexOf(socket.id); //获取index
        const id = index === scoketArr.length - 1 ? socketArr[0] : scketArr[index + 1]; //私信的目标id
        socket.to(id).emit("test1", `from ${socket.id} : ${msg} to ${id}`);
    });
});

当然这样也可能遇到其他的问题,比如一个用户可能重连很多次,而每次重连都会触发connection事件,这样会导致用户对不上,而且每次重连时socket.id是不一样的,因此,我们应该每次重连时给服务器发送一个消息,该消息带有用户的唯一标志的id,然后我们在该接收事件中进行用户添加和更新。

二、命名空间和房间的概念

同一服务端socket多路复用,减少TCP的连接,同时可分离应用程序的关注点,简而言之就是功能模块化

1、Namespaces

同一个连接不同的端口号进行处理,可以理解为同一个服务下的不同接口。

自定义命名空间:

const nsp = io.of("/english");
nsp.on("connection", (socket) => {
    console.log('someone connected!');
});

客户端

const client = new SocketClient();
client.connect("http://locahost:8080/english");

2、Rooms

同一个连接同一个端口号但在不同房间,类似Namespace的子集。

加入房间:

io.on("connection", (socket) => {
    socket.join("Jack's room");
});

向某个房间的每个人发送消息:

io.to("Jack's room").emit("some event");

有图有真相: