我使用API构建了一个服务器。它使用Axios进行未记录的调用,使用Socket.io进行记录的调用。 然后我有一个网站连接到它。而且效果很好。 但我还有一个内置于react-native的应用程序,它有一个奇怪的行为:它在每个发出时打开连接而不关闭它们以前的连接。正如您在下面看到的,我在服务器上安装了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);
}
});
};
我做错了什么?
答案 0 :(得分:1)
尝试使用socket.once()
代替socket.on()
,但每次仍会创建连接,但不会传播事件。
答案 1 :(得分:0)
所以我在这里得到一个答案。我会试着留下一个我想找的答案。关于如何在React-native中包含Socket.io的一种教程。如果您知道更好的解决方案,请写下来。 正如我写的那样,问题是React-Native中的socket是全局的,这样我错误的实现就更明显了。 首先,我在错误的地方初始化socket。我找到的正确位置是路由器所在的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');
在contructor和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'});
};
};
我希望它会对某人有所帮助。
答案 2 :(得分:0)
你不应该将你的socket.io实例置于本机组件中。而是将它们放在索引文件中。因此,它不会在反应生命周期事件中被触发
答案 3 :(得分:0)
import socketIOClient from 'socket.io-client'
async componentDidMount(){
this.socket = socketIOClient('http://192.168.8.118:8000', {
transports: ['websocket']
})
}