Socket.io用React-Native打开多个连接

时间:2017-08-10 09:57:59

标签: node.js react-native socket.io

我使用API​​构建了一个服务器。它使用Axios进行未记录的调用,使用Socket.io进行记录的调用。 然后我有一个网站连接到它。而且效果很好。 但我还有一个内置于react-native的应用程序,它有一个奇怪的行为:它在每个发出时打开连接而不关闭它们以前的连接。正如您在下面看到的,我在服务器上安装了websocket.engine.clientsCount。每当我从手机应用程序发出时,它都会打开一个新连接,找到服务器数量越来越多的服务器。

enter image description here

在服务器上,我使用以下版本:

"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);
    }
  });
};

我做错了什么?

4 个答案:

答案 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']
  })
}