使用 socket.io 响应 Native 一对一对话

Posted

技术标签:

【中文标题】使用 socket.io 响应 Native 一对一对话【英文标题】:React Native one to one conversation using socket.io 【发布时间】:2021-03-24 09:33:33 【问题描述】:

我目前有一个 react native 应用程序,其中 nodejs express Sequelize 作为我的后端,postgres 作为我的数据库。

因此,在每个帖子旁边的帖子屏幕上,我有一个文本输入和一个按钮,当前用户可以在其中向帖子的用户发送初始消息。一旦按下按钮,这两个用户之间关于这篇文章的对话就会在我的数据库中创建并存储在我的对话表中,并且发送的消息条目也存储在我的消息表中。

我已经在这两个用户之间实现了双向通信。但我的问题是我需要刷新应用程序才能向用户显示当前用户发送的消息并向接收用户显示收到的消息。

我已经研究了一段时间,并试图了解如何使用 socket.io 实现此功能,但无处可去。

客户端

这是我的聊天屏幕

 function ChatScreen(route,navigation) 

 const message = route.params.message;
 const [messages, setMessages] = useState(message.Messages);
 const [text, setText] = useState('');
 const  user  = useAuth();
 const [socket, setSocket] = useState(null);


 useEffect(() => 
 const newsocket =io.connect(socketurl)
setMessages(messages);
newsocket.on('connection', msg => 
    console.log('i have joined')
    setMessages(messages=>messages.concat(msg))
    setSocket(newsocket)
 )
 return()=>newsocket.close;
 , []);

const updateText=(text)=>
setText(text);


  const onSend = (ConversationId,senderId,receiverId,message) => 
  console.log("sent")
messagesApi.sendMessage(ConversationId,senderId,receiverId,message);
setText("")

socket.emit('message',  to: (user.id===route.params.message.user1 ? 
route.params.message.user2 : route.params.message.user1), from: 
user.id, message,ConversationId );
;

return(

<Text>user.id === message.Recipient.id ? 
message.Creator.name:message.Recipient.name</Text>

    <KeyboardAvoidingView 
    style=
        display: "flex",
        flex: 1,
      
      behavior=Platform.OS === "ios" ? "padding" : null
      keyboardVerticalOffset=Platform.OS === "ios" ? 25 : 0
    >

<FlatList
    inverted
    data=message.Messages
    keyExtractor=(message) => message.id.toString()
    renderItem=(item,index)=>(
        <MessageBubble
        text=item.message
        mine=item.senderId !== user.id
        />
    )/>

    <View style=styles.messageBoxContainer>
        <TextInput 
        style=styles.messageBox 
        placeholder="Message..." 
        multiline
        clearButtonMode="while-editing"
        onChangeText=updateText 
        value=text 
        autoCorrect=false
        />
        <TouchableOpacity onPress=onSend>
            <Text style=styles.send>Send</Text>
        </TouchableOpacity>
    </View>
    </KeyboardAvoidingView>
)

服务器端

index.js

const express = require("express");
const app = express();
const http = require("http");
const socketio = require("socket.io")
const server=http.createServer(app);
const io =socketio(server)

io.on("connection", socket => 
 socket.on('message', (data) => 
  socket.join(data.ConversationId);
  io.sockets.in(data.to).emit('send_message',  message: data.message, 
  to: data.to );
);
);

const port = process.env.PORT || config.get("port");
server.listen(port, function () 
console.log(`Server started on port $port...`);
);

目前,当我发送消息时,消息会存储在我的数据库中,但我的聊天不会立即更新(即不实时),我需要刷新我的应用程序并显示消息。

有人可以帮我检查一下我目前拥有的套接字的实现是否正确,如果正确,我如何立即渲染我的平面列表?

更新

我认为我的 useEffect 有问题,因为当我打开聊天时,我没有在控制台中看到“我已加入”:

useEffect(() => 
setMessages(messages);
socket.on('connect', msg => 
    console.log('i have joined')
    setMessages(messages=>messages.concat(msg))
 )
 , []);

【问题讨论】:

两个用户必须向同一个端口发送/发送事件。在前端定义服务器套接字端口和用户登录时,他们将事件发送到该套接字以获取新条目。现在,当用户开始对话时,它会再次创建一个包含另一个用户 ID 的新条目。我使用套接字作为通知。 @VinitBhavsar 你有一个如何使用套接字的例子,因为这是我第一次使用它们吗? 我没有任何服务器端设置套接​​字的经验,我们在前端所做的只是 const socket = io("socket port address");像这样发出事件 socket.emit("send_request",data);套接字事件监听器 socket.on("get_request", () => ) 所以当 send_request 被调用时,get_request 会得到更新 那么,我应该在哪里创建我的套接字? 【参考方案1】:

您当前在每次状态更改时创建一个新连接..const socket =io.connect(socketurl)

您有一个 useEffect 回调,这将是放置连接逻辑的合乎逻辑的地方,目前您只侦听一次连接,但创建多个连接,因此永远不会在这些新连接上调用您的“连接”事件。但是无论如何你只想连接一次,所以我们只需要将连接逻辑也放在 useEffect 中,而不仅仅是连接事件。

因为连接到套接字是异步的,所以您需要在渲染之前等待连接。所以我们可以做的是将套接字存储在状态中,当我们获得连接设置套接字状态时,这将使用现在有效的套接字触发重新渲染。

例如。

const [socket, setSocket] = useState(null);
...
useEffect(() => 
   const socket = io.connect(socketurl)
   setMessages(messages);
   newsocket.on('connect', msg =>  //connect not connection
      console.log('i have joined')
      setMessages(messages=>messages.concat(msg));
      setSocket(newSocket);
   );
   //might make sense to close the socket too, 
   //otherwise a memory leak. 
   return () => newSocket.close();
, [route, navigation]); 


if (!socket) 
  //we don't have a socket yet,
  return "loading..";
 else 
  // we have a socket, 
  const onSend = (ConversationId,senderId,receiverId,message) => ...
  ....
  // now render..
  return (
    <Text>.........

【讨论】:

感谢您回复我。我遵循了您提供的完全相同的代码,但它不起作用。当我按下聊天时,我仍然没有在控制台中看到“我已加入”。另外,我在服务器端的套接字逻辑是否正确? 我不断收到 TypeError: null is not an object (evalating socket.emit) @kd12345 确保在我放置//we have a socket 的位之后使用您需要套接字的任何东西,因为这样套接字就不能为空。 是的,我将整个 onSend 函数放在那里,但这样做我无法在返回函数底部的 TouchableOpacity onPress 中访问它 @kd12345 如果您已经完成了我所展示的操作,那么 TouchableOpacity 将在范围内,但您没有正确完成某些操作。也许更新您的原始问题代码,显示您当前拥有的内容..【参考方案2】:

套接字索引.js

    /** Socket.io server listens to our app **/
    var io = require('socket.io').listen(app);
    
    
    io.on('connection', function (socket) 
    /** On User Log In **/
    socket.on('login', function (data) 
            console.log('user joined >>', data)
            userList.addUser(data, socket);
        );

    // Example Event //
    socket.on('get_online_friends', userId => 
    //Get List Data And Then //
    let data = [UserList];
    socket.emit('send_online_friend', data);
    
    )
    // On Logout //
     socket.on('disconnect', function (reason) 
            var offlineId = userList.removeUser(socket)
    );
    
    

user_list.js

    var userList = ;
    module.exports = userList;
    
    var userData = [];
    var userSocData = ;
    
    userList.user = userData;
    userList.userSoc = userSocData;
    
    userList.getUserList = function () 
        return userSocData;
    ;
    
    userList.addUser = function (user, client) 
        userSocData[user] = 
            socketId: client.id
        
    ;
    
    userList.setReceiverId = function (user, client) 
        var index = userData.findIndex(x => x.user_id == user['user_id']);
        if (index !== -1) 
            userData[index]['receiver_id'] = user['receiver_id'];
        
        userSocData[user['user_id']] = 
            socket: client.id
        ;
    ;
    
    
    userList.removeUser = function (client) 
        for (const property in userSocData) 
            if (client.id === userSocData[property].socketId) 
                var userID = property;
                delete userSocData[property]
            
        
        return userID;
    ;

前端

    ***
    socket.emit("get_request", userData.user_id);
            socket.on("get_request_data", function (data) 
              if (data.status) 
                self.setState( onlineFriends: data.data );
              
            );
    ***

【讨论】:

这就是我在服务器端设置套接​​字的方式 嗨,我已经用我的新代码更新了我的问题,如果你可以看看。 是否需要检查两个用户是否都在套接字中? 对不起 Vinit,我还是新的插座,所以请和我一起裸露。我在我的服务器端 socket.on('message', (data) => socket.join(data.ConversationId); io.sockets.in(data.to).emit('send_message', message: data.message, to: data.to ); ); 好的,谢谢,目前当当前用户向另一个用户发送消息时,消息会在正确的对话中发送给正确的用户。我的问题是它没有实时呈现我需要刷新我的应用程序才能让两个用户都能看到消息

以上是关于使用 socket.io 响应 Native 一对一对话的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将 React Native 与 socket.io 结合使用

使用 Socket.io 在 React Native 移动应用程序中不起作用

使用 node.js、socket.io 和 redis 的一对一聊天应用程序

Native Socket.IO and Android

如何使用 Node JS 服务器在 Socket.io 中进行一对一聊天?

Node 和 Socket.IO - 私人聊天(一对一)