[英]how to manage multiple socket connections for a chat app with socket.io, react native, nodejs
[英]Socket.io opening multiple connections with React-Native
我用 API 构建了一个服务器。 它对未记录的调用使用 Axios,对记录的调用使用 Socket.io。 然后我有一个网站连接到它。 这非常有效。 但我也有一个内置于 react-native 的应用程序,它有一个奇怪的行为:它在每次发出时打开连接而不关闭它们以前的连接。 正如你在下面看到的,我在服务器上控制台.log websocket.engine.clientsCount。 每次我从电话应用程序发出时,它都会打开一个新连接,找到越来越多的服务器。
在服务器上,我使用以下版本:
"connect-mongo": "^1.3.2",
"express": "^4.14.1",
"express-session": "^1.12.1",
"jwt-simple": "^0.5.1",
"mongodb": "^2.2.30",
"mongoose": "^4.11.5",
"passport": "^0.3.2",
"passport-jwt": "^2.2.1",
"passport-local": "^1.0.0",
"socket.io": "^1.7.3",
"socketio-jwt": "^4.5.0"
这里是API的代码。 为了清楚起见,我删除了一些代码。
const passport = require('passport');
const express = require('express');
const session = require('express-session');
const http = require('http');
const morgan = require('morgan');
const mongoose = require('mongoose');
const socketio = require('socket.io');
const bodyParser = require('body-parser');
const socketioJwt = require("socketio-jwt"); // da commentare
const Users = require('../models/users');
const passportService = require('./services/passport');
const requireAuth = passport.authenticate('jwt', {session: false});
const requireLogin = passport.authenticate('local', {session: false});
const config = require('./config');
const app = express();
const socketRouter = require('./services/socketRouter');
const MongoStore = require('connect-mongo')(session);
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost/blablabla';
mongoose.connect(mongoUri);
...
const server = http.Server(app);
const websocket = socketio(server);
// add authorization for jwt-passport when first connection -> https://github.com/auth0/socketio-jwt
websocket.use(socketioJwt.authorize({
secret: config.secret,
handshake: true
}));
const sessionMiddleware = session({
store: new MongoStore({ // use MongoDb to store session (re-using previous connection)
mongooseConnection: mongoose.connection,
ttl: (1 * 60 * 60)
}),
secret: config.secretSession,
httpOnly: true,
resave: false,
saveUninitialized: false,
cookie: { maxAge: 86400000 }
});
app.use(sessionMiddleware);
...
websocket.on('connection', (socket) => {
Users.findById(socket.decoded_token.sub, function(err, user) {
if (err) { console.log('the user wasn\'t find in database', err); }
if (user) {
socket.join(user._id);
console.log('Clients connected: ', websocket.engine.clientsCount);
// ------ PROTECTED EVENTS ------ //
...
// ------------------------------ //
}
socket.on('disconnect', ()=> {
socket.leave(user._id);
onsole.log('user disconnected');
});
});
});
...
我不会把网站的初始化,因为它运作良好。
在移动应用程序上,我使用以下版本:
"react-native": "^0.41.0",
"react-native-keychain": "^1.1.0",
"socket.io-client": "^1.7.3",
"socketio-jwt": "^4.5.0"
这是本机应用程序的初始化。
import * as Keychain from 'react-native-keychain';
import { BASIC_WS_URL } from '../api';
const io = require('socket.io-client/dist/socket.io');
const socketEvents = require('./events');
exports = module.exports = (store) => {
Keychain.getGenericPassword().then((credentials) => {
if (credentials && credentials !== false) {
const { password } = credentials;
const websocket = io(BASIC_WS_URL, {
jsonp: false,
transports: ['websocket'], // you need to explicitly tell it to use websockets
query: {
token: password
}
});
websocket.connect();
websocket.on('connect', (socket) => {
console.log('Connected');
});
websocket.on('reconnect', (socket) => {
console.log('Re-connected');
});
websocket.on('disconnect', (socket) => {
console.log('Disconnected');
});
// all the events to listen
socketEvents(websocket, store);
}
});
};
我做错了什么?
所以我在这里给出一个答案。 我会尽量留下一个我想找到的答案。 一种关于如何在 React-native 中包含 Socket.io 的教程。 请,如果您知道更好的解决方案,请写下来。 正如我所写的,问题是 React-Native 中的套接字是全局的,这样我错误的实现就更加明显了。 首先,我在错误的地方初始化了套接字。 我找到的正确位置是在 App.js 中,路由器所在的位置。 为了清楚起见,我删除了一些代码。
// important to access the context of React
import PropTypes from 'prop-types';
// Library used to save encrypted token
import * as Keychain from 'react-native-keychain';
// url to address
import { BASIC_WS_URL } from '../../api';// library to encrypt and decrypt data
const io = require('socket.io-client/dist/socket.io');
在构造函数和 componentDidMount 中准备这个函数:
state = {}
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
keichain 是一个承诺,所以它在 componentDidMount 中不起作用。 要使其工作,您必须执行以下操作,因此每一步都将等待前一步完成:
async componentWillMount() {
const response = await Keychain.getGenericPassword();
const websocket = await io(BASIC_WS_URL, {
jsonp: false,
// forceNew:true,
transports: ['websocket'],
query: {
token: response.password
}
});
await websocket.connect();
await websocket.on('connect', (socket) => {
console.log('Sono -> connesso!');
});
await websocket.on('reconnect', (socket) => {
console.log('Sono riconnesso!');
});
await websocket.on('disconnect', (socket) => {
console.log('Sono disconnesso!');
});
await websocket.on('error', (error) => {
console.log(error);
});
// a function imported containing all the events (passing store retrieved from context declare at the bottom)
await socketEvents(websocket, this.context.store);
// then save the socket in the state, because at this point the component will be already rendered and this.socket would be not effective
await this.setStateAsync({websocket: websocket});
}
记得删除 console.logs 然后。 他们只是为了验证。 在此之后,请记住在卸载时断开连接:
componentWillUnmount() {
this.state.websocket.disconnect()
}
在此之后,将套接字保存在上下文中:
getChildContext() {
return {websocket: this.state.websocket};
}
记得在组件底部声明上下文:
App.childContextTypes = {
websocket: PropTypes.object
}
// access context.type to get the store to pass to socket.io initialization
App.contextTypes = {
store: PropTypes.object
}
所以,最后的结果是这样的:
...
// important to access the context of React
import PropTypes from 'prop-types';
// Library used to save encrypted token
import * as Keychain from 'react-native-keychain';
// url to address
import { BASIC_WS_URL } from '../../api';// library to encrypt and decrypt data
const io = require('socket.io-client/dist/socket.io');
...
class App extends Component {
constructor() {
super();
...
}
state = {}
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
// set the function as asynchronous
async componentWillMount() {
//retrieve the token to authorize the calls
const response = await Keychain.getGenericPassword();
// initialize the socket connection with the passwordToken (wait for it)
const websocket = await io(BASIC_WS_URL, {
jsonp: false,
// forceNew:true,
transports: ['websocket'], // you need to explicitly tell it to use websockets
query: {
token: response.password
}
});
// connect to socket (ask for waiting for the previous initialization)
await websocket.connect();
await websocket.on('connect', (socket) => {
console.log('Sono -> connesso!');
});
await websocket.on('reconnect', (socket) => {
console.log('Sono riconnesso!');
});
await websocket.on('disconnect', (socket) => {
console.log('Sono disconnesso!');
});
await websocket.on('error', (error) => {
console.log(error);
});
// a function imported containing all the events
await socketEvents(websocket, this.context.store);
await this.setStateAsync({websocket: websocket});
}
componentWillUnmount() {
this.state.websocket.disconnect()
}
getChildContext() {
return {websocket: this.state.websocket};
}
render() {
return (
... // here goes the router
);
}
}
App.childContextTypes = {
websocket: PropTypes.object
}
// access context.type to get the store to pass to socket.io initialization
App.contextTypes = {
store: PropTypes.object
}
export default App;
然后,在任何页面/容器中,您都可以这样做。 -> 在组件底部声明上下文:
Main.contextTypes = {
websocket: PropTypes.object
}
当你调度一个动作时,你将能够发出:
this.props.dispatch(loadNotif(this.context.websocket));
在动作创建器中,你会发出这样的信息:
exports.loadNotif = (websocket) => {
return function (dispatch) {
// send request to server
websocket.emit('action', {par: 'blablabla'});
};
};
我希望它会帮助某人。
你不应该把你的 socket.io 实例放在 react native 组件中。 而是将它们放在您的索引文件中。 所以它不会在 react 生命周期事件期间被触发
尝试socket.once()
而不是socket.on()
,虽然它每次仍然会创建连接但它不会传播事件。
import socketIOClient from 'socket.io-client'
async componentDidMount(){
this.socket = socketIOClient('http://192.168.8.118:8000', {
transports: ['websocket']
})
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.