news 2026/6/10 1:20:32

【Java转Go】即时通信系统代码分析(四)在线用户查询修改用户名

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java转Go】即时通信系统代码分析(四)在线用户查询修改用户名

在上一篇文章和这个之间还有一个41-用户业务封装,这一块我觉得主要就是OOP封装思想的体现,没有涉及过多的Go语言特性,所以就略过代码解析了。这一篇对应的是在线用户查询和修改用户名的业务逻辑,因为代码比较简单,流程相似,合并在一篇中解析。代码是在进行业务封装的基础之上的。

本期课件

视频:42-在线用户查询
43-修改用户名
代码:

packagemainimport("net""strings")typeUserstruct{NamestringAddrstringCchanstringconn net.Conn server*Server}func(this*User)Online(){this.server.mapLock.Lock()this.server.OnlineMap[this.Name]=this this.server.mapLock.Unlock()//广播当前用户上线消息this.server.BroadCast(this,"已上线")}func(this*User)Offline(){this.server.mapLock.Lock()delete(this.server.OnlineMap,this.Name)this.server.mapLock.Unlock()this.server.BroadCast(this,"下线")}func(this*User)SendMessage(msgstring){this.conn.Write([]byte(msg))}func(this*User)DoMessage(msgstring){ifmsg=="who"{//定义通讯规则,如果用户输入who,则表示查询在线用户this.server.mapLock.Lock()for_,usr:=rangethis.server.OnlineMap{onlineMsg:="["+usr.Addr+"]"+usr.Name+"在线...\n"this.SendMessage(onlineMsg)}this.server.mapLock.Unlock()}elseiflen(msg)>7&&msg[:7]=="rename|"{//定义通信协议,如果用户以rename|XXX这种格式输入,则表示要修改用户名newName:=strings.Split(msg,"|")[1]_,ok:=this.server.OnlineMap[newName]ifok{this.SendMessage("当前用户名被占用\n")}else{this.server.mapLock.Lock()delete(this.server.OnlineMap,this.Name)this.server.OnlineMap[newName]=this this.server.mapLock.Unlock()this.Name=newName this.SendMessage("更新用户名成功:"+this.Name+"\n")}}else{this.server.BroadCast(this,msg)}}funcNewUser(conn net.Conn,server*Server)*User{userAddr:=conn.RemoteAddr().String()user:=&User{Name:userAddr,Addr:userAddr,C:make(chanstring),conn:conn,server:server,}//启动监听当前user channel的goroutinegouser.ListenMessage()returnuser}func(this*User)ListenMessage(){for{msg:=<-this.C this.conn.Write([]byte(msg+"\n"))}}

逐行解析

这两个功能主要涉及的就是DoMessage方法中的if分支。

  1. this.server.mapLock.Lock()因为要对OnlineMap进行遍历,而这个DoMessage方法是在协程中执行的,因此是并发环境。Go 中的map不带锁,不是Java那种ConcurrentHashMap,因此读写要上锁
  2. for _, usr := range this.server.OnlineMap这句是对map 进行遍历,range 是Go语言的关键字,它会根据你遍历的对象类型,返回不同数量和含义的值。相当于比Java的iterator 封装级别更高的指令,对于Java来说,每一种容器的遍历代码是要自己写的,但是Go 把它们都封装成了range关键词,编译器会根据具体遍历的对象,执行遍历并返回每个元素的副本。比如map 返回的是两个值,K, V。如果要忽略返回值,则需要用_下划线占位。注意for i := range slice:表示只拿索引
  3. _, ok := this.server.OnlineMap[newName]这是Go 语言中处理 Map的一种语法,通常被称为 “comma ok” 断言。ok 代表着一个bool 类型的值。在Go 中,访问map 中的Key会返回两个值,一个是Value,如果不存在是零值,一个是布尔类型,表示是否存在,存在时true。当前代码表示不关系返回的具体值,而是更关注是否存在。
  4. delete(this.server.OnlineMap, this.Name)delete() 和make()一样,是Go 语言的内置函数,作用是删除map 中的key。如果key 不存在,什么都不做,也不报错。而且这个函数没有返回值

一些问题

  1. 为什么 SendMessage 是安全的?内部执行的是 this.conn.Write()。非 Channel 阻塞:conn.Write 是直接将数据写入 TCP 缓冲区。虽然缓冲区满了也会阻塞,但它不依赖另一个 Goroutine 的配合。在并发编程中有一条铁律:持锁期间,绝对不要执行任何可能导致永久阻塞的操作(如无缓冲 Channel、远程网络请求等)。

  2. 对于onlineMsg,(只)发送在线信息给当前用户this.SendMessage(onlineMsg) ,为什么不能是this.C <- onlineMsg?
    这个是我自己写代码的时候的直觉,this.C <- onlineMsg:是一种跨协程的同步行为,在持有全局锁时做这件事极其危险。可能会造成死锁。这里C是一个无缓冲channel, 即写入是阻塞的,直到读取发生。读取指的是ListenMessage,在另一个协程里跑的。那么,如果在持有 mapLock 的时候去写一个可能导致阻塞的 chan,而此时ListenMessage因为某种原因阻塞了,无法读取,那么mapLock 这个全局锁就无法释放,这个服务会卡死。

  3. 什么是死锁?
    两个或多个进程(协程),在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。不是只有传统的经典死锁:两个锁互相把自己锁死了,解不开,才叫死锁。学术上,死锁必须满足四个条件:
    互斥:mapLock 只能被一个协程持有。
    占有且等待:DoMessage 占有了锁,同时在等待 Channel 写入。
    不可剥夺:锁一旦被占有,除非自己释放,别人抢不走。
    循环等待:A 等 B 读 Channel,B 等网络(或 A 释放锁后产生的后续动作)。

  4. 如果在 range 循环遍历 Map 的过程中执行 delete 操作,会像 Java 那样抛出 ConcurrentModificationException 吗?
    不会。在 Go 语言中,在 range 循环里 delete 当前 Map 的 Key 是绝对安全的,不会抛出任何异常。Java 报异常是因为在for 循环中低层对map 的遍历有一个modCount值比较,如果值不相等,则抛异常。但是Go在range设计的时候就考虑到了这一点,Go 在开始 range 循环时,并不是对整个 Map 做了一个快照,而是通过一个专门的迭代器结构体来跟踪进度,当迭代器移动到下一个“桶”(Bucket)时,它只关心当前桶里还剩下什么。如果你刚刚删除了某个桶里的数据,迭代器只会简单地跳过它,继续寻找下一个有效数据。

  5. 此时只能说明在同一个协程内部,边删边查实不会报错的,但是在并发场景下,不能一个协程删除,另一个协程查询。此时要对被并发协程访问的map上锁。

今天遇到一个主要的问题是并发模型的问题。其它的疑问不是很多。写到今天的时候已经对Go语言的简洁有所领悟了,因为发现代码已经可以上手了……这要是Java,那还差得多呢……

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 20:11:00

ESLyric歌词增强工具终极指南:5分钟解锁音乐播放器新体验

ESLyric歌词增强工具终极指南&#xff1a;5分钟解锁音乐播放器新体验 【免费下载链接】ESLyric-LyricsSource Advanced lyrics source for ESLyric in foobar2000 项目地址: https://gitcode.com/gh_mirrors/es/ESLyric-LyricsSource 还在为单调的音乐播放体验而烦恼吗&…

作者头像 李华
网站建设 2026/6/9 18:10:53

Keil5安装教程详细步骤:工业控制项目实战配置指南

Keil5安装与工业控制开发实战&#xff1a;从零搭建高可靠嵌入式环境在工业自动化、电机驱动和电力电子系统中&#xff0c;一个稳定高效的开发环境是项目成败的关键。对于基于ARM Cortex-M系列微控制器的工程团队而言&#xff0c;Keil MDK&#xff08;Microcontroller Developme…

作者头像 李华
网站建设 2026/6/9 20:03:33

中兴光猫配置工具深度解析与应用实践

中兴光猫配置工具深度解析与应用实践 【免费下载链接】ZET-Optical-Network-Terminal-Decoder 项目地址: https://gitcode.com/gh_mirrors/ze/ZET-Optical-Network-Terminal-Decoder 中兴光猫配置工具作为网络设备管理的得力助手&#xff0c;为家庭用户和网络技术人员提…

作者头像 李华
网站建设 2026/6/9 20:15:06

HunyuanVideo-Foley计费系统:按调用次数统计与扣费逻辑设计

HunyuanVideo-Foley计费系统&#xff1a;按调用次数统计与扣费逻辑设计 1. 引言 1.1 业务场景描述 HunyuanVideo-Foley是由腾讯混元于2025年8月28日宣布开源的端到端视频音效生成模型。该模型支持用户通过输入视频和文字描述&#xff0c;自动生成电影级别的音效&#xff0c;…

作者头像 李华
网站建设 2026/6/9 21:15:17

VibeVoice-TTS语音一致性难题破解:多说话人身份保持实战

VibeVoice-TTS语音一致性难题破解&#xff1a;多说话人身份保持实战 1. 引言&#xff1a;多说话人TTS的现实挑战与VibeVoice的突破 在播客、有声书、虚拟角色对话等长文本语音合成场景中&#xff0c;传统文本转语音&#xff08;TTS&#xff09;系统长期面临三大核心瓶颈&…

作者头像 李华
网站建设 2026/6/9 21:15:05

VibeVoice-TTS从零开始:新手部署全流程详细步骤

VibeVoice-TTS从零开始&#xff1a;新手部署全流程详细步骤 1. 引言 随着人工智能在语音合成领域的不断演进&#xff0c;传统文本转语音&#xff08;TTS&#xff09;系统在长文本、多说话人场景下的局限性日益凸显。尤其是在播客、有声书、对话式内容生成等应用中&#xff0c…

作者头像 李华