美文网首页
react-native集成Websocket,protobuf

react-native集成Websocket,protobuf

作者: sun_xiao_fan | 来源:发表于2019-02-14 16:58 被阅读0次

    一、 开始前的说明

    本文从最开始一步一步搭建一个react-native + redux-saga + websocket + protobuf的项目。项目完全是按照规范的应用开发目录构建。

    1. 你将学到的知识
      1.1 如何创建一个 react-native项目,react-native项目工程目录搭建。
      1.2 如何集成 redux 。
      1.3 如何集成 redux-saga。
      1.4 如何使用 websocket。
      1.5 如何使用protobufjs。

    1.1 创建一个react-native项目

    (创建项目非常简单,如果你不会或者还没有搭建环境 请参考 点击前往
    在cmd控制台运行 react-native init (项目名)sagaandprotobuf
    创建成功后会看到如图1

    图1
    看到这个图说明已经创建成功了!

    1.2 集成redux-saga

    关于 redux 和 redux-saga 的介绍和功能就不详细说明了,网上很多

    1.2.1 添加 redux 和 redux-saga

    (1) 集成redux-saga 前我们先要集成redux。首先看一下当前生成的项目目录。如下图2


    图2.png

    (2)添加在package.json中添加rudux 相关包 , 运行 npm install 安装

    "dependencies": {
        "react-redux": "^6.0.0",
        "redux": "^4.0.1",
        "redux-logger": "^3.0.6",
        "redux-saga": "^1.0.0"
      },
    

    (3) 安装完成后 创建redux 和 redux-saga 的相关目录文件夹和文件:
       根目录下创建app文件夹,用来存放我们要写的代码。
       [1] 在app文件夹下新建三个文件夹:actions(用来存放以后所有的action),pages(用来存放页面),reducers(用来存放reducer),sagas(用来存放各模块的saga文件)。因为在项目开发中需要将各个模块的action 和 raducer分开开发,所以或有很多这类文件。
       [2] 在app文件夹下新建actionsTypes.js,rootReducers.js,rootsagas.js,store.js 文件文件。 在pages文件夹下新建 home.js。
       [3] 在action 文件夹里面新建 WebsocketAction.js,reducers 文件夹下新建 WebsocketReducer.js,sagas文件夹下新建 WebsocketSaga.js。
    至此我们还没有写一行代码。只是将框架搭建起来。目录结构如下图3。


    图3.png

    (4)按照我上面的步骤建立好文件后,只需要跟着下面粘贴代码就可以了,开始复制粘贴:
    [1] actionsTypes.js 代码如下

    /**
     * create by sxf on 2019/2/14.
     * 功能: 事件类型统一分配
     */
    export const CONNECTSUCCESS = 'CONNECTSUCCESS';
    export const CONNECT = 'CONNECT';
    export const CONNECTFALL = 'CONNECTFALL';
    export const SENDMSG = 'SENDMSG';
    export const CONNECTCLOSE = 'CONNECTCLOSE';
    export const RETURNMSG = 'RETURNMSG';
    

    [2] rootReducers.js 代码如下

    import {combineReducers} from  'redux'
    import WebsocketReducer from './reducers/WebsocketReducer'
    
    //这里面必须要有初始数据 - 否则报错
    const rootReducer = combineReducers({
        WebsocketReducer
    });
    
    export default rootReducer;
    

    [3] store.js 代码如下:将redux-saga和redux结合起来

    
    import {createStore, applyMiddleware, compose} from 'redux'
    import createSagaMiddleware , { END } from 'redux-saga'
    import {createLogger}  from 'redux-logger'
    import rootReducer from './rootReducers'
    import sagas from './rootsagas'
    
    const configureStore = preloadedState => {
        const sagaMiddleware = createSagaMiddleware();
        const store = createStore(
            rootReducer,
            preloadedState,
            compose (
                applyMiddleware(sagaMiddleware, createLogger())
            )
        )
        sagaMiddleware.run(sagas);
        store.close = () => store.dispatch(END)
        return store;
    }
    
    const store = configureStore();
    export default store;
    

    [4] WebsocketAction.js 代码如下

    import {CONNECT, CONNECTCLOSE, CONNECTFALL, CONNECTSUCCESS,SENDMSG,RETURNMSG } from './../actionsTypes'
    
    const wsconnect =  (connectobj)  => ({ type : CONNECT,connectobj:connectobj});
    const wsconnectclose =  ()  => ({ type : CONNECTCLOSE});
    const connectsuccess =  ()  => ({ type : CONNECTSUCCESS});
    const connectfall =  ()  => ({ type : CONNECTFALL});
    const sendmsg =  (sendmsg)  => ({ type : SENDMSG,sendmsg:sendmsg});
    const wsmsgres =  (msgstr)  => ({ type : RETURNMSG,msgstr:msgstr});
    
    
    export {
        connectsuccess,
        connectfall,
        wsconnect,
        wsconnectclose,
        sendmsg,
        wsmsgres
    }
    

    [5] WebsocketReducer.js 代码如下

    import * as types from './../actionsTypes'
    
    const initwsState ={
        status:'未连接',
        isSuccess:false,
        ws:null,
        msg:""
    }
    
    export default function webSockerfun(state=initwsState,action) {
        switch (action.type){
            case types.CONNECTSUCCESS:
                return{
                    ...state,
                    status:"连接成功",
                }
                break;
            case types.CONNECTFALL:
                return{
                    ...state,
                    status:"未连接",
                    msg:""
                }
                break;
            case types.RETURNMSG:
                return{
                    ...state,
                    msg:action.msgstr
                }
                break;
            default:
                return state;
        }
    }
    

    [6] 修改App.js 内容为下代码。使得默认页面指向 home.js,并将redux 和react-native结合起来。

    import React, {Component} from 'react';
    import { Provider } from 'react-redux'
    import store from './app/store'
    import Home from './app/pages/home'
    
    type Props = {};
    export default class App extends Component<Props> {
      render() {
        return (
            <Provider store={store}>
                <Home/>
            </Provider>
        );
      }
    }
    

    [7] 你可能发现怎么没写rootsagas 和 WebsocketSaga的代码呢?因为集成protobufjs是在WebsocketSaga当中。所以WebsocketSaga这部分要特别注意(重点和坑点 后面会讲)。现在的WebsocketSaga代码是一个无法运行的空的方法。
    WebsocketSaga.js 代码如下

    import { END} from 'redux-saga'
    import { put , take, fork ,cancel ,cancelled,delay,call} from 'redux-saga/effects'
    import {CONNECT,CONNECTCLOSE,SENDMSG}  from './../actionsTypes'
    import {connectsuccess,connectfall,wsmsgres} from './../actions/WebsocketAction'
    export function* watchWebsocket() {
         // 这里面未来会逻辑代码 现在增加这个方法主要是为了 rootsaga.js 不报错
    }
    

    rootsagas.js 代码如下

    import {fork} from "redux-saga/effects";
    import {watchWebsocket} from './sagas/WebsocketSaga'
    
    
    export default function* rootSaga() {
        yield fork(watchWebsocket);
    }
    

    [7] home.js 页面的代码如下

    
    import React, { Component } from 'react';
    import {
        StyleSheet,
        Text,
        View,
        TouchableOpacity
    } from 'react-native';
    import { connect } from 'react-redux';
    import {wsconnect,wsconnectclose,sendmsg} from './../actions/WebsocketAction'
    import WebsocketReducer from "../reducers/WebsocketReducer";
    class Home extends Component {
        _onConnect(){
            this.props.dispatch(wsconnect({mydispatch: this.props.dispatch}));
        }
        _onConnectclose(){
            this.props.dispatch(wsconnectclose());
        }
        _onsendmsg(){
            // 随便发送点数据
            this.props.dispatch(sendmsg({msgtext:"protoBuf发送数:"+ Math.round(Math.random()*100)}));
        }
        render() {
            return (
                <View style={styles.container}>
                    <Text style={styles.counter}>{this.props.WebsocketReducer.status}</Text>
                    <TouchableOpacity style={styles.reset} onPress={()=>this._onConnect()}>
                        <Text>连接websocket</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={styles.start} onPress={()=>this._onConnectclose()}>
                        <Text>断开连接</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={styles.stop} onPress={()=>this._onsendmsg()}>
                        <Text>发送消息</Text>
                    </TouchableOpacity>
                    <Text style={styles.counter}>{this.props.WebsocketReducer.msg}</Text>
                </View>
            );
        }
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center',
            flexDirection: 'column'
        },
        counter: {
            fontSize: 50,
            marginBottom: 70
        },
        reset: {
            margin: 10,
            backgroundColor: 'yellow'
        },
        start: {
            margin: 10,
            backgroundColor: 'yellow'
        },
        stop: {
            margin: 10,
            backgroundColor: 'yellow'
        }
    })
    
    const mapStateToProps = state => ({
        WebsocketReducer:state.WebsocketReducer
    })
    
    export default connect(mapStateToProps)(Home);
    

    好了 截止到目前 redux-saga 和 redux 都集成成功了。

    1.3 集成protobufjs

    说明: 关于protobuf 的知识网上有很多 推荐看 点击前往这片文章。这个讲的比较好。不过这个里面有一个坑和官方git是一样的后面会说。

    (1) 首先将 protobufjs 包下载下来, 添加包到package.json中 运行 npm install 安装

    "dependencies": {
        ...
        "protobufjs": "^6.8.8"
      },
    

    (2) 一般情况会和后端定义一个.proto文件,用来定义proto格式。这里我们在sagas文件夹下新建一个文件awesome.proto 这个文件的内容如下(这个文件的内容规范 请看官网或者是啊上面的推荐网站)

    // awesome.proto
    package awesomepackage;
    syntax = "proto3";
    message AwesomeMessage {
        string awesome_field = 1; // becomes awesomeField
    }
    

    (3) 将这个文件用pbjs 命令转成 json文件。(这个地方很坑,官网和其他地方都没有提到,如果不转的话在rudux-saga中是无法使用的。因为protobuf.load()方法采用的是回调函数的异步机制,违背了saga的书写规范。)
    如何使用这个命令?
    [1] 在\node_modules\protobufjs\bin目录下找到 pbjs 文件 这个就是命令文件。
    [2] 控制台命令进入这个目录 cd node_modules\protobufjs\bin
    [3] 运行 node ,这里面可能会运行不成功 卡在installing espree@^3.5.4 这个地方。我试了下 好像是npm 下载这个包会卡住, 这里手动 yarn add espree这个包吧(没有yarn?那你需要好好补补课了)

    node  pbjs -t json E:\2019stude\react-native-reduxsaga-protobuf-websocket\mystudy\app\sagas\awesome.proto > E:\2019stude\react-native-reduxsaga-protobuf-websocket\mystudy\app\sagas\awesome.json
    

    运行完以后再项目的sagas文件夹下可以看到awsome.json文件
    [4] 回头将WebsocketSaga代码改成下面的

    import { END} from 'redux-saga'
    import { put , take, fork ,cancel ,cancelled,delay,call} from 'redux-saga/effects'
    import {CONNECT,CONNECTCLOSE,SENDMSG}  from './../actionsTypes'
    import {connectsuccess,connectfall,wsmsgres} from './../actions/WebsocketAction'
    var protobuf = require("protobufjs");
    
    var ws = null; // 缓存 websocket连接
    var _mydispatch = null; // 这个变量是因为saga无法支持callback 只能变通处理(这也是个坑点)
    var protpfile = null; // 缓存proto文件
    export function* watchWebsocket() {
        while (true){
            const action = yield take(CONNECT);
            if(_mydispatch == null){
                _mydispatch = action.connectobj.mydispatch;
            }
            yield fork(connectWebsocket,_mydispatch);
            var sendmsgTask = yield fork(sendmsg);
            yield take(CONNECTCLOSE);
            yield fork(connectcolseWebsocket);
            yield cancel(sendmsgTask);
        }
    }
    
    function* sendmsg(){
        try{
            while (true){
                const sendaction = yield take(SENDMSG);
                yield fork(decodeencodewithproto,sendaction.sendmsg.msgtext);
    
            }
        }finally {
            if(yield cancelled()){
                console.log("取消了监听发送任务");
            }
        }
    }
    
    function* decodeencodewithproto(sendstr) {
        let restroot ;
        if(protpfile == null){
            // 缓存proto 对象
            restroot = yield call(protobuffun);
            protpfile = restroot;
        }else{
            restroot = protpfile;
        }
        var AwesomeMessage = restroot.lookupType("awesomepackage.AwesomeMessage");
        var payload = { awesomeField: sendstr };
        var errMsg = AwesomeMessage.verify(payload);
        if (errMsg)
            throw Error(errMsg);
        var message = AwesomeMessage.create(payload); // or use .fromObject if conversion is necessary
        var buffer = AwesomeMessage.encode(message).finish();
        ws.send(buffer);
    }
    
    
    function protobuffun() {
        return new Promise(resolve => {
            //之所以要转成json 就是因为这个地方无法使用reload方法 只能用require方法
            var jsonDescriptor = require("./awesome.json"); // exemplary for node
            var root = protobuf.Root.fromJSON(jsonDescriptor);
            resolve(root);
        })
    }
    
    
    function* connectcolseWebsocket() {
        ws.close();
    }
    
    function* connectWebsocket(mydispatch) {
        ws = new WebSocket("ws://echo.websocket.org");
    
        ws.onopen = () => {
            mydispatch(connectsuccess())
        };
        ws.onerror = e => {
            mydispatch(connectfall())
        };
        ws.onmessage = e => {
            console.log(e.data)
            var buf = new Uint8Array(e.data);
            var _AwesomeMessage = protpfile.lookupType("awesomepackage.AwesomeMessage");
            var message = _AwesomeMessage.decode(buf).awesomeField;
            console.log(message);
            mydispatch(wsmsgres(message))
        };
        ws.onclose = e => {
            // connection closed
            mydispatch(connectfall())
        };
    }
    

    OK 大工搞成了。跑起来看下效果吧!!


    图4.png

    项目的源代码在我的git上面 地址是 https://github.com/chen735623058/react-native-reduxsaga-protobuf-websocket。 如果帮到您了辛苦给颗星呗。

    相关文章

      网友评论

          本文标题:react-native集成Websocket,protobuf

          本文链接:https://www.haomeiwen.com/subject/qqmdeqtx.html