likes
comments
collection
share

使用Socket.io实现一对一网络通信

作者站长头像
站长
· 阅读数 42

Socket.IO什么?

Socket.io是一个面向实时web 应用的 JavaScript 库。它使得服务器和客户端之间实时双向的通信成为可能。他有两个部分:在浏览器中运行的客户端库,和一个面向Node.js的服务端库。两者有着几乎一样的API。

使用Socket.io实现多人通信很方便,但是实现一对一通信,就需要添加部分逻辑。我下面整理了一对一通信的实现方法

实现过程

因为需要服务端,所以选择创建express作为服务端,客户端,我们用Vue3简单模拟一下。

首先我们先创建一个文件,cd到文件中,在终端输入

npm init -y

然后再执行以下命令

npm install express

npm install socket.io

npm install -g nodemon

下载express和socket.io的第三方依赖。

随后我们在当前文件夹下,创建app.js文件,然后修改packge.json文件。

"scripts": {
    "dev": "nodemon ./app.js"
  },

修改成这样,使用npm run dev命令启动时,会用nodemon启动app.js文件,方便我们后续修改app.js文件。

const express=require('express');
const app=express();
const {Server} =require('socket.io');
const io = new Server(3000,{
    cors:{
        origin:['http://localhost:8080']
    }
})

app.listen(8000,()=>{
    console.log("服务器启动咯~")
})

随后,我们在app.js文件中,先将express和socket.io初始化,我们将socket.io建立在3000端口上,然后配置跨域(以为后续的vue cli为我们创建的项目默认端口是8080,所以我们需要设置8080端口可以跨域),最后我们让app监听8000端口,让这个程序持续启动。

接下来,我们创建客户端项目,这里我用的是vue cli创建,使用vue create client

使用Socket.io实现一对一网络通信

我们选择第三个,按回车。

使用Socket.io实现一对一网络通信

进去之后,我们选择有Router配置,将其他的取消,然后按回车下一步,因为我们会用到路由模拟用户,所以这里会用到路由中的useRouter。

创建好项目后,我们只需要一个页面,删除多余的路由,保留App.vue中的,然后创建一个Chatting.vue组件,绑定在chatting路由下,随后我们打开三个页面。

使用Socket.io实现一对一网络通信

使用Socket.io实现一对一网络通信

使用Socket.io实现一对一网络通信

这三个页面都用到了query传参,所以我们模拟了三个用户。

打开终端,安装socket.io第三方依赖

npm install socket.io-client

随后我们需要初始化socket.io,然后创建一个响应式对象,通过useRouter拿到query,模拟用户。

import {io} from "socket.io-client";
import {useRouter} from "vue-router"
import { reactive } from "vue";


const router =useRouter();
const state =reactive({
    username:router.currentRoute.value.query.username,
})
const socket = io('http://localhost:3000',{
    query:{
        username:state.username
    }
})

在这里,我们拿到用户之后,作为query参数,传输到服务端。

我们在服务端通过io.on监听connection时间,可以拿到客户端传输过来的query参数。

io.on('connection',(socket)=>{
    const username=socket.handshake.query.username;
})

拿到参数之后,我们服务端肯定需要做一个用户列表,看那些用户在线,所以我们需要声明一个userList数组

这个数组中存储用户对象,用户对象中有用户名和用户id,这个用户id就是socket.id,每个用户都是唯一的(当用户重新登录时,这个id会和上一次的不一样,所以我们需要判断用户是否在userList中)

const userList=[]
io.on('connection',(socket)=>{
    const username=socket.handshake.query.username;
    if(!username) return;
    const userInfo=userList.find(user=>user.username===username)
    if(userInfo){
        userInfo.id=socket.id
    }
    else{
        userList.push({
            id:socket.id,
            username
        })
    }
})

如果用户之前登录过,就说明已经在userList中了,所以我们需要更新用户id,如果用户没登录过,我们直接将用户对象存储到userList中。

当我们存储玩userList后,我们需要将userList返回给客户端,告诉用户,当前那些用户在线,所以这里我们可以使用socket.emit(online)事件去传输给客户端。

const userList=[]
io.on('connection',(socket)=>{
    const username=socket.handshake.query.username;
    if(!username) return;
    const userInfo=userList.find(user=>user.username===username)
    if(userInfo){
        userInfo.id=socket.id
    }
    else{
        userList.push({
            id:socket.id,
            username
        })
    }
    io.emit('online',{
        userList
    })
})

在客户端,我们可以使用socket.om('online')拿到服务端传输的userList

socket.on('online',(data)=>{
    console.log(data)
})

使用Socket.io实现一对一网络通信

在客户端拿到userList后,我们需要将userList存在我们的响应式对象中,所以我们需要在reactive中添加一个useList的对象,然后将data赋值给它。

const state =reactive({
    username:router.currentRoute.value.query.username,
    userList:[],
})
socket.on('online',(data)=>{
    console.log(data)
    state.userList=data.userList
})

当我们在客户端拿到了用户列表之后,我们就需要在视图中显示出来了,这里我就不写样式了。

<template>
    <div>
        <ul>
            <template v-for="userInfo of state.userList" :key="userInfo.id">
                <li v-if="userInfo.username===state.username">
                    {{userInfo.username}}
                </li>
                <li v-else>
                    <a href="javascript:;" ">
                        {{userInfo.username}}
                    </a>
                </li>
            </template>
        </ul>
    </div>
</template>

因为用户在客户端只能跟其他用户通信,所以在这里我们需要做一个区分,我们不可以选择自己,其他用户我们用a标签显示。

使用Socket.io实现一对一网络通信

当我们在客户端选择一个用户通信之后,我们需要记住跟哪一个用户通信,所以我们需要在a标签上添加一个点击事件,然后在响应式对象中添加一个targetUser的对象。

<a href="javascript:;" @click="()=>selectUser(userInfo)">
      {{userInfo.username}}
</a>

const state =reactive({
    username:router.currentRoute.value.query.username,
    userList:[],
    targetUser:null,
})
const selectUser=(userInfo)=>{
    state.targetUser=userInfo
}

随后我们记住了用户跟谁通信之后,我们需要显示出正在跟谁通信和一个input标签。

<template>
    <div>
        <ul>
            <template v-for="userInfo of state.userList" :key="userInfo.id">
                <li v-if="userInfo.username===state.username">
                    {{userInfo.username}}
                </li>
                <li v-else>
                    <a href="javascript:;" @click="()=>selectUser(userInfo)">
                        {{userInfo.username}}
                    </a>
                </li>
            </template>
        </ul>
        <div v-if="state.targetUser">
            <h3>{{state.targetUser.username}}</h3>
            <input type="text" placeholder="input some ..." v-model="state.msgText">
            <button @click="sendMessage">发送</button>
        </div>
    </div>
</template>

使用Socket.io实现一对一网络通信

点击button按钮发送信息,我们需要给button按钮添加一个sendMessage事件,也需要让响应式对象添加一个msgText string类型与input标签双向绑定,我们发送信息的时候,我们需要将发送的信息存储到一个响应式对象中,方便后续显示信息,所以我们需要在响应式中添加一个messageBox对象,去存储用户的信息对象。

const state =reactive({
  username:router.currentRoute.value.query.username,
  userList:[],
  targetUser:null,
  msgText:"",
  messageBox:{}
})

const sendMessage=()=>{
  if(!state.msgText.length){
    return 
  }
  !state.messageBox[state.username] && (state.messageBox[state.username]=[])
  state.messageBox[state.username].push({
    fromUsername:state.username,
    toUsername:state.targetUser.username,
    msg:state.msgText,
    dateTime:new Date().getTime()
  })

  socket.emit('send',{
    fromUsername:state.username,
    targetId:state.targetUser.id,
    msg:state.msgText
  })
}

我们需要判断messageBox中有没有当前用户对象,如果没有则等于一个空数组,有的话我们就往这个数组中添加消息信息(发送用户,接收用户,消息信息,时间),如果是我们自己发送的,我们就把消息对象存在本地,然后我们使用socket.emit触发send事件,就可以将这个消息对象传输给服务端,需要注意的是,我们在这里传输给服务端的target目标需要传输targetId,也就是用户id,因为我们的服务端,需要使用用户id拿到具体的用户实例。

io.on('connection',(socket)=>{
    const username=socket.handshake.query.username;
    if(!username) return;


    const userInfo=userList.find(user=>user.username===username)


    if(userInfo){
        userInfo.id=socket.id
    }
    else{
        userList.push({
            id:socket.id,
            username
        })
    }
    io.emit('online',{
        userList
    })
    
    socket.on('send',({fromUsername,targetId,msg})=>{
        const targetSocket=io.sockets.sockets.get(targetId)
        const toUser=userList.find(user=>user.id===targetId)
        targetSocket.emit('receive',{
            fromUsername,
            toUsername:toUser.username,
            msg,
            dateTime:new Date().getTime()
        })
    })
})

在服务端,我们通过socket.on监听send事件,拿到发送方,目标Id,,信息后,因为io.sockets.sockets是一个map对象,存储着所有的用户实例,所以我们可以用get(targetId)方法拿到目标用户实例,然后我们再通过targetId找到目标名称,使用目标实例(targetSocket)触发receive事件,将对应的消息对象传送给目标用户(只有目标实例的用户可以接收到信息,其他用户接收不到)

使用Socket.io实现一对一网络通信

使用Socket.io实现一对一网络通信

当我们给alibaba用户发送信息时,只有alibaba可以看到,其他用户则接受不到信息,

在客户端用户接收信息我们需要用到socket.on('receive')事件去监听。

socket.on('receive',(data)=>{
    console.log(data)
    !state.messageBox[state.username] && (state.messageBox[state.username]=[])
    state.messageBox[state.username].push(data)
    console.log(state.messageBox[state.username])
})

我们拿到服务端传输过来的信息(data)后,我们一样需要判断messageBox是否有当前用户的信息对象,如果没有则等于空数组,随后将data添加到数组中,因为接受方能收到data,所以我们这个data只存储在了接收方的messageBox中,发送方也是如此。。

现在,我们就可以实现一对一用户通信,通信信息都存储在messageBox中,我们需要将通信信息显示在页面中,但是我们需要将messageBox做一次剔除,只有两个人彼此的通信记录可以观看,因为同一个用户给很多其他用户发送的信息都会存储在messageBox中,所以我们现在需要判断正在通信的人是否是targetUser,然后在message中找到信息,展示出来,这里我们用到了computed方法

const messageList=computed(()=>{
    if(state.messageBox[state.username] && state.targetUser){
        return state.messageBox[state.username].filter(item=>{
            return item.fromUsername===state.targetUser.username || item.toUsername===state.targetUser.username
        })
    }
    else{
        return []
    }
})

最后,我们将messageList渲染到页面即可。

<template>
    <div>
        <ul>
            <template v-for="userInfo of state.userList" :key="userInfo.id">
                <li v-if="userInfo.username===state.username">
                    {{userInfo.username}}
                </li>
                <li v-else>
                    <a href="javascript:;" @click="()=>selectUser(userInfo)">
                        {{userInfo.username}}
                    </a>
                </li>
            </template>
        </ul>
        <div v-if="state.targetUser">
            <h3>{{state.targetUser.username}}</h3>
            <input type="text" placeholder="input some ..." v-model="state.msgText">
            <button @click="sendMessage">发送</button>
        </div>
        <div>
            <ul>
                <li v-for="(msg,index) of messageList" :key="index">
                    <h3>{{msg.fromUsername}}</h3>
                    <h4>{{msg.msg}}</h4>
                </li>
            </ul> 
        </div>
    </div>
</template>

看看最终效果吧。

使用Socket.io实现一对一网络通信

使用Socket.io实现一对一网络通信

使用Socket.io实现一对一网络通信

使用Socket.io实现一对一网络通信

源码

服务端

const express=require('express');
const app=express();
const {Server} =require('socket.io');
const io = new Server(3000,{
    cors:{
        origin:['http://localhost:8080']
    }
})


const userList=[]


io.on('connection',(socket)=>{
    const username=socket.handshake.query.username;
    if(!username) return;


    const userInfo=userList.find(user=>user.username===username)


    if(userInfo){
        userInfo.id=socket.id
    }
    else{
        userList.push({
            id:socket.id,
            username
        })
    }
    io.emit('online',{
        userList
    })
    
    socket.on('send',({fromUsername,targetId,msg})=>{
        const targetSocket=io.sockets.sockets.get(targetId)
        const toUser=userList.find(user=>user.id===targetId)
        targetSocket.emit('receive',{
            fromUsername,
            toUsername:toUser.username,
            msg,
            dateTime:new Date().getTime()
        })
    })
})




app.listen(8000,()=>{
    console.log("服务器启动咯~")
})

客户端

<template>
    <div>
        <ul>
            <template v-for="userInfo of state.userList" :key="userInfo.id">
                <li v-if="userInfo.username===state.username">
                    {{userInfo.username}}
                </li>
                <li v-else>
                    <a href="javascript:;" @click="()=>selectUser(userInfo)">
                        {{userInfo.username}}
                    </a>
                </li>
            </template>
        </ul>
        <div v-if="state.targetUser">
            <h3>{{state.targetUser.username}}</h3>
            <input type="text" placeholder="input some ..." v-model="state.msgText">
            <button @click="sendMessage">发送</button>
        </div>
        <div>
            <ul>
                <li v-for="(msg,index) of messageList" :key="index">
                    <h3>{{msg.fromUsername}}</h3>
                    <h4>{{msg.msg}}</h4>
                </li>
            </ul> 
        </div>
    </div>
</template>


<script setup>
import {io} from "socket.io-client";
import {useRouter} from "vue-router"
import { computed, reactive } from "vue";


const router =useRouter();
const state =reactive({
    username:router.currentRoute.value.query.username,
    userList:[],
    targetUser:null,
    msgText:"",
    messageBox:{}
})
const socket = io('http://localhost:3000',{
    query:{
        username:state.username
    }
})


const selectUser=(userInfo)=>{
    state.targetUser=userInfo
}


const messageList=computed(()=>{
    if(state.messageBox[state.username] && state.targetUser){
        return state.messageBox[state.username].filter(item=>{
            return item.fromUsername===state.targetUser.username || item.toUsername===state.targetUser.username
        })
    }
    else{
        return []
    }
})


const sendMessage=()=>{
    if(!state.msgText.length){
        return 
    }
    !state.messageBox[state.username] && (state.messageBox[state.username]=[])
    state.messageBox[state.username].push({
        fromUsername:state.username,
        toUsername:state.targetUser.username,
        msg:state.msgText,
        dateTime:new Date().getTime()
    })


    socket.emit('send',{
        fromUsername:state.username,
        targetId:state.targetUser.id,
        msg:state.msgText
    })
}


socket.on('online',(data)=>{
    console.log(data)
    state.userList=data.userList
})


socket.on('receive',(data)=>{
    console.log(data)
    !state.messageBox[state.username] && (state.messageBox[state.username]=[])
    state.messageBox[state.username].push(data)
    console.log(state.messageBox[state.username])
})


</script>
<style socpe>
    
</style>
转载自:https://juejin.cn/post/7256983702811377720
评论
请登录