2017-08-10 78 views
0

我使用API​​构建了一个服务器。它使用Axios进行未记录的呼叫,使用Socket.io进行记录的呼叫。 然后我有一个网站连接到它。这完美地工作。 但我也有一个内置的react-native应用程序,它有一个奇怪的行为:它打开每个发射的连接而不关闭它们以前的连接。正如你在下面看到的,我console.log websocket.engine.clientsCount在服务器上。每当我从电话应用程序发出时,它都会打开一个新的连接,找到服务器的数量不断增加。Socket.io与React-Native打开多个连接

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" 

这里是反应本地应用程序的innitialisation。

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

根据我的理解,问题在于React-Native中的套接字是全局的。每次我发射时,它都会打开一个新的连接而不关闭前一个连接。 我正在尝试初始化套接字并使其成为全局的,以便能够在动作创建器中发出。如果我使用'context',我可以在其他组件中检索已初始化的套接字,但它不会再发出(我错过了我猜测的圆形对象的概念)。另外,授权连接的钥匙串标记是异步的,并且它不适用于生命周期组件功能。 有没有人有工作实施? – Pibo

回答

-1

所以,我在这里给出答案。我会尽量留下一个我想找到的答案。一种关于如何在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。他们只是为了验证。以后这个权利,remeber到卸载时断开连接:

componentWillUnmount() { 
    this.state.websocket.disconnect() 
    } 

并在此之后的权利,保存插座的背景下:

getChildContext() { 
    return {websocket: this.state.websocket}; 
    } 

Remeber在组件的底部申报方面:

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

我希望它能帮助别人。

0

你不应该把你的socket.io实例放在原始组件里面。而是将它们放入索引文件中。所以它不会在反应生命周期事件中触发

+0

这是我的原始实施。但之后,我没有找到通过应用程序共享初始化套接字的方式,以便从动作创建者处发出。我发现的唯一方法是使用上下文,为此我需要初始化组件中的套接字(为我所知)。我们在网上找到的很多实现都会带来我必须解决的错误。只是他们没有注意到问题 - > console.log('客户端连接:',websocket.engine.clientsCount)在服务器中。 – Pibo

+0

但您可以从索引文件导出套接字对象。 – Developer

1

尝试socket.once()而不是socket.on(),尽管它每次都会创建连接,但它不会传播事件。