likes
comments
collection
share

【计算机网络实战】简易IM(四)私聊和群聊的全套逻辑

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

前言

这一篇主要分析与KIM的聊天逻辑相关的业务代码。我认为这部分核心的模块有三个:

  • 通信协议实体的定义
  • 通信对象的定义
  • 聊天逻辑的handler部分

它们都在主目录下的services包下。

需要说明的是,因为笔者并不是通信专业出身的,所以我全文所说的“顶层”、“底层”是从软件思维上考虑的:被高度封装的就是顶层,需要一点点实现的是底层。

例如handler里面这段代码:

···
	// 1. 解包
	var req pkt.MessageReq
	if err := ctx.ReadBody(&req); err != nil {
		_ = ctx.RespWithError(pkt.Status_InvalidPacketBody, err)
		return
	}
	// 2. 获取接收方的位置信息
	receiver := ctx.Header().GetDest()
	loc, err := ctx.GetLocation(receiver, "")
	if err != nil && err != kim.ErrSessionNil {
		_ = ctx.RespWithError(pkt.Status_SystemException, err)
		return
	}
···

像这类内容就是我说的“顶层”,因为ReadBody()、GetLocation()这些都已经被封装好了,顾名即可思义,就算没有注释也不妨碍对代码的理解。在这部分中,程序员只需要关注大体的实现逻辑,而不需要关注函数内部的具体实现细节。

但是我觉得对于通信专业的同学而言,“顶层”指的应该是物理意义上的、最靠近云端的协议。

大家都没有错,只是思考的出发点不一样哈。

通信协议实体

// services/service/database/model.go
type MessageIndex struct {
	ID        int64  `gorm:"primarykey"`
	AccountA  string `gorm:"index;size:60;not null;comment:队列唯一标识"`
	AccountB  string `gorm:"size:60;not null;comment:另一方"`
	Direction byte   `gorm:"default:0;not null;comment:1表示AccountA为发送者"`
	MessageID int64  `gorm:"not null;comment:关联消息内容表中的ID"`
	Group     string `gorm:"size:30;comment:群ID,单聊情况为空"`
	SendTime  int64  `gorm:"index;not null;comment:消息发送时间"`
}

type MessageContent struct {
	ID       int64  `gorm:"primarykey"`
	Type     byte   `gorm:"default:0"`
	Body     string `gorm:"size:5000;not null"`
	Extra    string `gorm:"size:500"`
	SendTime int64  `gorm:"index"`
}

在一个package中,Message.Req前面会被加上一个Header,在这里KIM的作者只定义了type、body、extra三部分,可根据实际情况删减。

// wire/proto/protocol.proto
// chat message
message MessageReq {
    int32 type = 1;
    string body = 2;
    string extra = 3;
}

message MessageResp {
    int64 messageId = 1;
    int64 sendTime = 2;
}

message MessagePush {
    int64 messageId = 1;
    int32 type = 2;
    string body = 3;
    string extra = 4;
    string sender = 5;
    int64 sendTime = 6;
}
...

通信对象的定义

type User struct {
	Model
	App      string `gorm:"size:30"`
	Account  string `gorm:"uniqueIndex;size:60"`
	Password string `gorm:"size:30"`
	Avatar   string `gorm:"size:200"`
	Nickname string `gorm:"size:20"`
}

type Group struct {
	Model
	Group        string `gorm:"uniqueIndex;size:30"`
	App          string `gorm:"size:30"`
	Name         string `gorm:"size:50"`
	Owner        string `gorm:"size:60"`
	Avatar       string `gorm:"size:200"`
	Introduction string `gorm:"size:300"`
}

// GroupMember GroupMember
type GroupMember struct {
	Model
	Account string `gorm:"uniqueIndex:uni_gp_acc;size:60"`
	Group   string `gorm:"uniqueIndex:uni_gp_acc;index;size:30"`
	Alias   string `gorm:"size:30"`
}

其中Model是一个通用的结构体:

type Model struct {
	ID        int64 `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
}

聊天逻辑的handler部分

以单聊为例,下面我将以图片说明它做的几件事情:

【计算机网络实战】简易IM(四)私聊和群聊的全套逻辑

先暂存后判断的设计能较大程度地保证信息不丢失。

推送的消息实际上是一堆协议字段及body内容:

	// 4. 如果接收方在线,就推送一条消息过去。
	if loc != nil {
		if err = ctx.Dispatch(&pkt.MessagePush{
			MessageId: msgId,
			Type:      req.GetType(),
			Body:      req.GetBody(),
			Extra:     req.GetExtra(),
			Sender:    ctx.Session().GetAccount(),
			SendTime:  sendTime,
		}, loc); err != nil {
			_ = ctx.RespWithError(pkt.Status_SystemException, err)
			return
		}
	}

群聊的逻辑比单聊多了一步: 读取成员列表

	membersResp, err := h.groupService.Members(ctx.Session().GetApp(), &rpc.GroupMembersReq{
		GroupId: group,
	})
	if err != nil {
		_ = ctx.RespWithError(pkt.Status_SystemException, err)
		return
	}
	var members = make([]string, len(membersResp.Users))
	for i, user := range membersResp.Users {
		members[i] = user.Account
	}

并把单聊中获取单个成员的位置信息和进行单条推送改为批量方式

注意消息推送是同时推的,不是for遍历列表然后一条条推的。不然如果列表数量过大,群成员之间收到消息的时间相差较大,是一个很严重的失误。

关于阅读开源代码的一点碎碎念

以上提到的内容虽然很简单、也很好理解,但实际上如果不借助任何作者本人提供的详尽资料,要在短时间内彻底捋顺别人写的代码是很困难的,举个例子: 前面提到在handler中已经封装好了所有的逻辑,放眼一看,大多和context下的内容有关。但是当你跳转进去看的时候,又会感觉像套娃一样。

有的是在主目录下的context.go中,有的是在storage.go中,跳转一层不能解决问题,往往要跳转很多层,跳着跳着又会跳到一些引用的外部库中,跳到最后都忘记最初是因为什么才跳过来了。

作为局外人很难理解作者那种跳跃的思维:ta为什么要这么设计?这段代码为什么要放在那里?

看别人的代码真是比自己写代码要痛苦一百倍不止的事情。

但也许只有跳出舒适圈,才能有真正的进步吧。

转载自:https://juejin.cn/post/7250283546367393852
评论
请登录