【问题标题】:Unable to properly establish a connection between React Native client and Node.js server with redux-saga and socket.io无法使用 redux-saga 和 socket.io 在 React Native 客户端和 Node.js 服务器之间正确建立连接
【发布时间】:2020-06-25 21:59:22
【问题描述】:

快速上下文:我正在尝试构建评论页面的反应原生原型,用户可以在其中接收实时更新(cmets、用户进入评论屏幕、用户离开等)。为此,我使用了 react-redux、redux-saga、socket.io 和 node.js(服务器)。我是 redux-saga 的新手,所以我可能在这里遗漏了一些明显的东西,所以请稍等...罪魁祸首肯定在于 watchCommentActions 函数/saga...

问题: 安装完成后,评论屏幕会调度以下操作 { type:comment.room.join, value },然后 rootSaga 会正确确认,但是,当尝试通过 const socket = yield call(connect); 使用 promise-resolve 结构连接到套接字时,promise 永远不会解析,这会阻塞生成器(它不会继续进行下一个 yield)。奇怪的是,另一方面,服务器确实记录了与套接字的连接,所以连接客户端 --> 服务器似乎没问题。此外,通过热重新加载应用程序,我可以设法解决承诺(就像生成器需要运行两次才能解决套接字连接),但是 socket.emit("join-room")永远不会到达服务器,并且生成器再次卡住。 同样,当我尝试通过发布评论来触发 write 生成器并因此调度 {type: comment.post.start, value } 时 *socket.emit("comment", {text: value. text}) 也没有到达服务器。 简而言之,没有什么真正起作用,也没有抛出任何错误......太棒了。

遗言:在将我的套接字逻辑转移到 saga 之前,套接字连接是无缝工作的。我还尝试通过使用相同的 connect 函数而不是 createWebSocketConection (https://redux-saga.js.org/docs/advanced/Channels.html) 来重用文档的通道实现,但仍会出现 promise-resolve-socket 情况.另外,我注意到类似的问题来自我研究过的同一个 git repo 以了解 sagas 逻辑 (https://github.com/kuy/redux-saga-chat-example/blob/master/src/client/sagas.js),但是,它们都没有让我理解我的实现有什么问题。最后,如果有更好的方法来用 redux-saga 实现这个逻辑,我很感兴趣,我想要的只是一个健壮的、集中的和可重用的实现。

Sagas/index.js

import { all, takeEvery, takeLatest } from "redux-saga/effects";
import { comment } from "../Reducers/commentCacheReducer";
import { like } from "../Reducers/postsCacheReducer";
import { posts } from "../Reducers/postsReducer";
import flow from "./commentSagas";
import { likePost, unlikePosts } from "./likeSagas";
import { fetchPosts } from "./postsSagas";

function* watchLikeActions() {
    yield takeLatest(like.add.start, likePost);
    yield takeLatest(like.remove.start, unlikePost);
}

function* watchFetchActions() {
    yield takeEvery(posts.fetch.start, fetchPosts);
}

function* watchCommentsActions() {
    yield takeEvery(comment.room.join, flow);
}

export default function* rootSaga() {
    yield all([watchLikeActions(), watchFetchActions(), watchCommentsActions()]);
}

Sagas/commentSaga.js

import { eventChannel } from "redux-saga";
import { call, cancel, fork, put, take } from "redux-saga/effects";
import io from "socket.io-client";
import { endpoint } from "../../API/ServerAPI";
import { addUser, fetchComment, leaveRoom, removeUser } from "../Actions/commentActions";
import { comment } from "../Reducers/commentCacheReducer";

function connect() {
    const socket = io(endpoint);
    return new Promise((resolve) => {
        socket.on("connection", () => {
            resolve(socket);
        });
    });
}

function subscribe(socket) {
    return new eventChannel((emit) => {
        socket.on("users.join-room", ({ userId }) => {
            emit(addUser({ userId }));
        });
        socket.on("users.leave-room", ({ userId }) => {
            emit(removeUser({ userId }));
        });
        socket.on("comments.new", ({ comments }) => {
            emit(fetchComment({ comments }));
        });
        socket.on("users.join-room", ({ userId }) => {
            emit(addUser({ userId }));
        });

        return () => {};
    });
}

function* read(socket) {
    const channel = yield call(subscribe, socket);
    while (true) {
        let action = yield take(channel);
        yield put(action);
    }
}

function* write(socket) {
    while (true) {
        const { value } = yield take(comment.post.start);
        socket.emit("comment", { text: value.text });
    }
}

function* handleIO(socket) {
    yield fork(read, socket);
    yield fork(write, socket);
}

export default function* flow() {
    const socket = yield call(connect);
    socket.emit("join-room", (res) => {
        console.log(JSON.stringify(res));
    });

    const task = yield fork(handleIO, socket);

    let action = yield take(leaveRoom);
    yield cancel(task);
    yield put(action);
    socket.emit("leave-room");
}

server.js

const http = require("http");
const app = require("./app");
const socketIo = require("socket.io");
const mongoose = require("mongoose");

const normalizePort = (val) => {
    const port = parseInt(val, 10);

    if (isNaN(port)) {
        return val;
    }
    if (port >= 0) {
        return port;
    }
    return false;
};
const port = normalizePort(process.env.PORT || "3000");
app.set("port", port);

const errorHandler = (error) => {
    if (error.syscall !== "listen") {
        throw error;
    }
    const address = server.address();
    const bind = typeof address === "string" ? "pipe " + address : "port: " + port;
    switch (error.code) {
        case "EACCES":
            console.error(bind + " requires elevated privileges.");
            process.exit(1);
            break;
        case "EADDRINUSE":
            console.error(bind + " is already in use.");
            process.exit(1);
            break;
        default:
            throw error;
    }
};

const server = http.createServer(app);

const io = socketIo(server);

server.on("error", errorHandler);
server.on("listening", () => {
    const address = server.address();
    const bind = typeof address === "string" ? "pipe " + address : "port " + port;
    console.log("Listening on " + bind);
});

// comments room
// Storing in variable just for testing purposes, will
// connect to MongoDB once the socket problem gets solved.
let userIds = [];

io.on("connection", (socket) => {
    console.log("[server] connect");
});

io.on("join-room", (socket, {userId}) => {
    console.log(`[server] join-room: ${userId}`);
    userIds.push(userId);
    socket.socket.username = userId;
    socket.broadcast.emit("users.join-room", { userId });
});

io.on("leave-room", (socket) => {
    const { userId } = socket.socket;
    if (userId) {
        console.log(`[server] leaving-room: ${userId}`);
        userIds = userIds.filter((u) => u !== userId);
        delete socket.socket["userId"];

        socket.broadcast("users.leave-room", { userId });
    }
});

// Storing in variable just for testing purposes, will
// connect to MongoDB once the socket problem gets solved.
let messages = [];

io.on("comment", (socket, { text }) => {
    console.log(`[server] message: ${text}`);
    const message = {
        id: messages.length,
        text,
        userId: socket.socket.userId
    };
    messages.push(message);

    socket.broadcast("comments.new", { message });
});

编辑 1 快速浏览socket.io文档后,我意识到我的服务器快速实现有问题,我只是忘记在连接协议中注册事件处理程序......但是,仍然需要触发生成器两次才能启动套接字连接,允许承诺解决和用户加入套接字房间。

io.on("connect", (socket) => {
    console.log("[server] connect");
    socket.on("join-room", ({ userId }) => {
        console.log(`[server] join-room: ${userId}`);
        userIds.push(userId);
        socket.username = userId;
        socket.broadcast.emit("users.join-room", { userId });
    });

    socket.on("leave-room", ({ userId }) => {
        if (userId) {
            console.log(`[server] leaving-room: ${userId}`);
            userIds = userIds.filter((u) => u !== userId);
            delete socket["userId"];

            socket.broadcast.emit("users.leave-room", { userId });
        }
    });

    socket.on("comment", ({ text }) => {
        console.log(`[server] message: ${text}`);
        const message = {
            id: messages.length,
            text,
            userId: socket.userId
        };
        messages.push(message);

        socket.broadcast.emit("comments.new", { message });
    });
});

【问题讨论】:

    标签: node.js reactjs react-native socket.io redux-saga


    【解决方案1】:

    connect,不是connection

    https://github.com/socketio/socket.io-client

    (commentSagas.js > connect())

    【讨论】:

    • 谢谢 Mooshua,我都试过了,但你是对的“连接”确实改善了一点,但是,我仍然必须触发 joinRoom 操作至少两次(打开然后关闭然后打开我的评论screen) 让我的生成器到达 socket.emit("join-room") 似乎仍然没有到达服务器,然后无论我做什么,我的生成器最终都会卡在 socket.emit("加入房间”)。我也是生成器的新手,这显然没有帮助。
    • 参见“编辑 1” + 我发现了一个解决 promise 连接问题的 hacky 解决方案,结果使用 race() 避免了手动重新触发生成器(通过卸载和重新安装 cmets 屏幕):const timeout = yield race({ socket: call(connect), timeout: delay(100) });即使它可以无缝运行,我仍然想知道出了什么问题。
    猜你喜欢
    • 2020-02-12
    • 2020-06-20
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 2016-07-30
    • 2012-04-24
    • 2011-05-20
    • 2015-07-28
    相关资源
    最近更新 更多