最近公司非常忙,又是很长一段时间没有写博客了,虽然公司的项目使用不到ReactNative和nodejs相关的知识,但我仍然看好跨平台技术,毕竟以后是大前端的发展趋势。所以依旧不能停下学习的脚步,多做技术储备,提升核心竞争力,继续挖全栈的坑,拓展自己的技术栈。毕竟有的时候站在一个不同的角度,可能有不同的看法。之前做了一个获取网易腾讯免费漫画的app,后台用nodejs+mongodb+koa搭建,在展示漫画弹幕的时候,需要用到长链接实时展示当前最新的弹幕内容。之前对socket.io在react-native上的使用不太了解,借此机会学习一下相关的知识并做一个总结和记录。
效果如下:
服务端
添加依赖
1 | npm install socket.io |
引入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
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
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
2var Users = mongoose.model('users')
var Messages = mongoose.model('messages')初始化
1
2var users = {}; // 客户端用户id和socket id映射表
var chatId = 1; // 聊天室id使用socket.io通信
监听connection消息,与客户端建立连接
1
2
3
4
5
6var 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
28async 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
12module.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 | AsyncStorage.getItem(USER_ID) |
监听服务端发送的message消息并刷新
1 | this.socket.on('message', this.storeMessages); //监听message消息 |
点击发送按钮,向服务端发送消息
1 | onSend(message) { |
客户端的ui用了一个封装的库GiftedChat来简单展示一下。demo已打包上传,最后贴一个传送门