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

相关推荐
CryptoRzz10 小时前
印度尼西亚数据源对接技术指南
开发语言·python·websocket·金融·区块链
{{uname}}19 小时前
利用WebSocket实现实时通知
网络·spring boot·websocket·网络协议
2501_915909061 天前
iOS App 安全性探索:源码保护、混淆方案与逆向防护日常
websocket·网络协议·tcp/ip·http·网络安全·https·udp
为美好的生活献上中指1 天前
java每日精进 5.11【WebSocket】
java·javascript·css·网络·sql·websocket·网络协议
2501_915918412 天前
多账号管理与自动化中的浏览器指纹对抗方案
websocket·网络协议·tcp/ip·http·网络安全·https·udp
Zhen (Evan) Wang2 天前
.NET 8 API 实现websocket,并在前端angular实现调用
前端·websocket·.net
zhangzuying10262 天前
在uni-app中实现类似文心一言的流式对话功能:从fetch到websocket的实践
websocket·uni-app·文心一言
xsh2193 天前
HTTP 和 WebSocket 的区别
websocket·网络协议·http
大G哥3 天前
【WebSocket&IndexedDB】node+WebSocket&IndexedDB开发简易聊天室
网络·websocket·网络协议
九班长4 天前
JMeter 中实现 双 WebSocket(双WS)连接
websocket·jmeter·proto