0%

socket.io+nodejs+mongodb+reactnative 实现简易聊天室


最近公司非常忙,又是很长一段时间没有写博客了,虽然公司的项目使用不到ReactNative和nodejs相关的知识,但我仍然看好跨平台技术,毕竟以后是大前端的发展趋势。所以依旧不能停下学习的脚步,多做技术储备,提升核心竞争力,继续挖全栈的坑,拓展自己的技术栈。毕竟有的时候站在一个不同的角度,可能有不同的看法。之前做了一个获取网易腾讯免费漫画的app,后台用nodejs+mongodb+koa搭建,在展示漫画弹幕的时候,需要用到长链接实时展示当前最新的弹幕内容。之前对socket.io在react-native上的使用不太了解,借此机会学习一下相关的知识并做一个总结和记录。


效果如下:
效果gif

服务端

添加依赖

1
2
npm install socket.io
npm install mongoose

引入socket.io对象

1
var Socketio = require('socket.io')

引入mongoose对象

1
var mongoose = require('mongoose')

创建和引入mongoose的数据库表对象

  • 创建users表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    'use strict'
    var mongoose = require('mongoose')
    var UsersSchema = new mongoose.Schema({
    name: String,
    createdAt: {
    type: Date,
    default: Date.now()
    },
    updateAt: {
    type: Date,
    default: Date.now()
    }
    })
    UsersSchema.pre('save', function (next) {
    if (this.isNew) { // 如果这是个新的数据
    this.createdAt = this.updateAt = Date.now() // 设置当前时间
    } else {
    this.updateAt = Date.now() // 否则刷新更新时间
    }
    next()
    })

    module.export = mongoose.model('users', UsersSchema)
  • 创建messages表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    'use strict'
    var mongoose = require('mongoose')

    var MessagesSchema = new mongoose.Schema({
    text: String,
    user: Object,
    chatId: String,
    createdAt:{
    type:Date,
    default:Date.now()
    },
    updateAt:{
    type:Date,
    default:Date.now()
    }
    })
    MessagesSchema.pre('save', function(next) {
    if(this.isNew){ // 如果这是个新的数据
    this.createAt = this.updateAt = Date.now() // 设置当前时间
    } else {
    this.updateAt = Date.now() // 否则刷新更新时间
    }
    next()
    })

    var MessageModel = mongoose.model('messages', MessagesSchema)
    module.export = MessageModel
  • 引入表对象

    1
    2
    var Users = mongoose.model('users')
    var Messages = mongoose.model('messages')
  • 初始化

    1
    2
    var users = {}; // 客户端用户id和socket id映射表
    var chatId = 1; // 聊天室id

    使用socket.io通信

  • 监听connection消息,与客户端建立连接

    1
    2
    3
    4
    5
    6
    var websocket = Socketio(server)
    websocket.on('connection',(socket) => {
    console.log('连上了')
    socket.on('userJoined', (userId) => onUserJoined(userId, socket));
    socket.on('message', (message) => onMessageReceived(message, socket));
    });
  • 监听userJoined消息,用户进入聊天室触发,客户端向服务端发送userJoined消息,第一次进入的用户,客户端发送的用户id为空,服务端在回调中获得用户id和客户端的socket对象,如果用户id为空,插入数据库,取得用户id,并向客户端发送用户id,客户端将这个用户id保存下来,下一次发送这个id。服务端刷新用户数组中该客户端对应的用户id,如果用户id不为空直接执行这一步,最后发送数据库中保存的该聊天室发送过的消息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    async function onUserJoined(userId, socket) {
    console.log('有用户进来了' + userId)
    // 新用户userId为空,向数据库users表插入id
    if (!userId) {
    var userData = new Users({})
    let user = await userData.save()
    socket.emit('userJoined', user._id);
    users[socket.id] = user._id;
    } else { //否则刷新用户id
    users[socket.id] = userId;
    }
    // 发送之前的消息
    await sendExistingMessages(socket);
    }
    // 发送之前的消息
    async function sendExistingMessages(socket) {
    // 查数据
    await Messages.find({
    chatId: chatId
    }).sort({createdAt:1}).exec((err, messages) => {
    // 如果没有任何消息,直接返回
    if (!messages.length){
    return;
    }
    socket.emit('message', messages.reverse());
    })

    }
  • 监听message消息,在接收到客户端发送的消息时触发,服务端在回调中获得消息和发送的socket对象,从用户数组中取得该socket对象对应的客户端的用户id,用户id为空时可能因服务端重启造成用户数组的数据丢失,直接返回不处理,最后保存这条消息到数据库,发送这条消息给除了当前客户端用户的所有用户

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 用户接收到消息
    async function onMessageReceived(message, socket) {
    console.log(message.text)
    var userId = users[socket.id];
    // 用户id为空返回
    if (!userId) {
    console.log('没有这个用户啦')
    return;
    }
       // 保存消息并发送
    await sendAndSaveMessage(message, socket);
    }
    // 保存消息到数据,发送消息给除了当前用户的所有的用户
    async function sendAndSaveMessage(message, socket) {
    // 创建消息数据
    var messageData = new Messages({
    text: message.text,
    user: message.user,
    createdAt: new Date(message.createdAt),
    chatId: chatId
    });
    var message = await messageData.save()
    // 发送消息给除了当前用户的所有的用户
    socket.broadcast.emit('message', message);
    }

    koa中集成socket.io

    如果服务端已经集成了koa需要再集成socket.io
    传入koa的server对象,外面包一层Socketio对象导出,再注册监听端口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = function (server) {
    let websocket = Socketio(server)
    websocket.on('connection', (socket) => {
    clients[socket.id] = socket;//保存客户端对象到列表
    console.log('连上了')
    socket.on('userJoined', (userId) => onUserJoined(userId, socket));
    socket.on('message', (message) => onMessageReceived(message, socket));
    });
    }
    // xxx为socketio的module路径
    server.listen(1234)
    require('xxx')(server)

    客户端

    这里以react-native举例

引入socket.io

1
import SocketIOClient from 'socket.io-client';

初始化socket.io

1
this.socket = SocketIOClient('http://localhost:3000'); // 设置服务端的地址和端口号

使用socket.io通信

第一次进入时发送空的用户id给服务端,监听userJoined消息,保存服务端给的用户id,第二次发送本地保存的用户id给服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AsyncStorage.getItem(USER_ID)
.then((userId) => {
// 本地没有用户id
if (!userId) {
// 发送空的用户id
this.socket.emit('userJoined', null);
// 监听userJoined消息,保存服务端给的用户id
this.socket.on('userJoined', (userId) => {
AsyncStorage.setItem(USER_ID, userId);
this.setState({ userId });
});
} else {
// 发送本地保存的用户id
this.socket.emit('userJoined', userId);
this.setState({ userId });
}
})
.catch((e) => alert(e));

监听服务端发送的message消息并刷新

1
2
3
4
5
6
7
8
this.socket.on('message', this.storeMessages); //监听message消息
storeMessages(messages) {
this.setState((previousState) => {
return {
messages: this.msgs.append(previousState.messages, messages),
};
});
}

点击发送按钮,向服务端发送消息

1
2
3
4
onSend(message) {
this.socket.emit('message', message);
this.storeMessages(messages);
}

客户端的ui用了一个封装的库GiftedChat来简单展示一下。demo已打包上传,最后贴一个传送门