第四章节:WebSocket实现用户一对一聊天

前言

上一章节中我们实现了在WebSocket Server端管理用户和WebSocket连接的绑定和释放,本章节我们尝试实现两个client端之间的消息收发。

消息实体

首先我们需要定义一个统一的消息模型

在models.go中添加Message结构体

go 复制代码
type Message struct {
    ID         int64
    Type       string
    FromUserID int64
    ToUserID   int64
    Data       interface{}
}

消息发送服务

在conn.go中定义接口Sender

go 复制代码
type Sender interface {
    Send(msg model.Message)
}

编写服务实现LocalSender

go 复制代码
type LocalSender struct {
}

func (sender *LocalSender) Send(msg model.Message) error {
    // 获取对方的WebSocket连接
    conn := GetConnManager().FindConn(msg.ToUserID)
    if conn == nil {
        return fmt.Errorf("%v does not online", msg.ToUserID)
    }
    // 发送JSON消息
    return conn.WriteJSON(msg)
}

WebSocket Server端

修改server.go

go 复制代码
package main

import (
    "log"
    "net/http"

    "aoki.com/go-im/src/conn"
    "aoki.com/go-im/src/model"
    "github.com/gin-gonic/gin"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    // Allow Cross Origin
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

var ConnManager *conn.ConnManager = conn.GetConnManager()
var Sender = conn.LocalSender{}

func ws(c *gin.Context) {
    conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    user := model.ResolveUser(c.Request)
    if user == nil {
        log.Println("ResolveUser failed...")
        return
    }
    ConnManager.AddConn(user.ID, conn)
    log.Printf("%s connected... \n", user.Name)
    defer OnDisconnect(*user)
    for {
        v := &model.Message{}
        err = conn.ReadJSON(v)
        if err != nil {
            log.Println("Read Json Message Failed", err)
        }
        err = Sender.Send(*v)
        if err != nil {
            log.Println("Send Message Failed", err)
        }
    }
}

func OnDisconnect(user model.User) {
    log.Printf("%v disconnect...\n", user.Name)
    ConnManager.DelConn(user.ID)
}

func main() {
    server := gin.Default()
    server.GET("/ws", ws)
    server.Run("localhost:8848")
}

WebSocket Client端

修改client.go

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"

    "aoki.com/go-im/src/model"
    "github.com/gorilla/websocket"
)

func main() {
    uri := "ws://localhost:8848/ws"
    // 从命令行获取登录用户ID和Name
    args := os.Args[1:]
    userID, er := strconv.ParseInt(args[0], 10, 64)
    if er != nil {
        log.Fatal("Parse UserId Failed:", er)
    }
    u := model.User{
        ID:   userID,
        Name: args[1],
    }
    header := http.Header{
        "X-TOKEN": []string{u.GenToken()},
    }
    conn, _, err := websocket.DefaultDialer.Dial(uri, header)
    if err != nil {
        log.Fatal("dial failed:", err)
    }
    defer conn.Close()

    go func() {
        for {
            // 读取JSON消息
            m := &model.Message{}
            err = conn.ReadJSON(m)
            if err != nil {
                log.Println("Read WS message failed:", err)
                return
            }
            log.Printf("Received Message %v From %v \n", m.Data, m.FromUserID)
        }
    }()

    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        // 从控制台读取内容,前一段为收信人的用户ID,后一段为消息内容
        str := strings.Split(scanner.Text(), " ")
        ToUserID, er := strconv.ParseInt(str[0], 10, 64)
        if er != nil {
            log.Println("cannot recognize ToUserId ", err)
            break
        }
        m := model.Message{
            FromUserID: u.ID,
            ToUserID:   ToUserID,
            Type:       "Text",
            Data:       str[1],
        }
        if err := conn.WriteJSON(m); err != nil {
            log.Println("Send WS message failed:", err)
        }
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

模拟交互

在Terminal中启动server.go, 然后新开3个Terminal,分别启动3个client进程

shell 复制代码
go run src/main/client.go 1001 AOKI
go run src/main/client.go 1002 Tony
go run src/main/client.go 1003 Frank

模拟3个用户登录IM服务,然后分别使用3个Terminal向其他用户发送消息,并在Terminal中确认收到的消息

发送方和接收方消息都能对上,消息都能正常接收、发送

小结

本章节我们实现了使用WebSocket让用户间能够正常收发消息,但是还存在一些问题,比如当对方不在线时,消息将会丢失,这是不合常理的,正常应该等对方上线时会立即收到这条未读消息,所以在下一章节我们探讨将消息持久化并做已读未读状态标记。

相关推荐
拾伍廿肆20 小时前
python - websocket
python·websocket·django
Sun_Sherry1 天前
FastAPI: websocket的用法及举例
websocket·网络协议·fastapi
貂蝉空大1 天前
uni-app 封装websocket 心跳检测,开箱即用
websocket·网络协议·uni-app
白鹭float.2 天前
【Unity AI】基于 WebSocket 和 讯飞星火大模型
人工智能·websocket·unity
滔滔不绝tao3 天前
五子棋双人对战项目(4)——匹配模块(解读代码)
spring boot·websocket
极客小张3 天前
智能教室云平台管理系统:基于Spring Boot、WebSocket与传感器的设计方案
c语言·spring boot·后端·stm32·物联网·websocket·毕业设计
IT小白33 天前
使用 Node.js 创建一个 WebSocket 服务器
websocket·node.js
Jiaberrr4 天前
解锁微信小程序新技能:ECharts动态折线图搭配WebSocket,数据刷新快人一步!
前端·javascript·websocket·微信小程序·echarts
沥川同学5 天前
计算机网络自顶向下(2)----socket编程
linux·网络·websocket·tcp/ip·计算机网络·udp
浩水浮生6 天前
redis 的发布订阅解决分布式下的websocket session 共享问题
redis·分布式·websocket