繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM