2017-08-10 116 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(),儘管它每次都會創建連接,但它不會傳播事件。