news 2026/5/1 10:57:02

【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/5/1 8:58:26

AutoCAD智能字体管理插件:一键搞定字体缺失的终极解决方案

AutoCAD智能字体管理插件&#xff1a;一键搞定字体缺失的终极解决方案 【免费下载链接】FontCenter AutoCAD自动管理字体插件 项目地址: https://gitcode.com/gh_mirrors/fo/FontCenter "字体缺失"——这个让无数AutoCAD设计师头疼的问题&#xff0c;现在终于…

作者头像 李华
网站建设 2026/5/1 7:46:58

MediaPipe Holistic运动分析教程:从零到1小时出结果

MediaPipe Holistic运动分析教程&#xff1a;从零到1小时出结果 引言&#xff1a;为什么选择MediaPipe Holistic&#xff1f; 作为一名体育专业的学生&#xff0c;你是否遇到过这样的困境&#xff1a;论文需要大量运动姿态数据&#xff0c;但实验室设备紧张&#xff0c;个人笔…

作者头像 李华
网站建设 2026/5/1 5:47:10

MediaPipe Holistic手把手教学:零基础10分钟部署,1块钱体验

MediaPipe Holistic手把手教学&#xff1a;零基础10分钟部署&#xff0c;1块钱体验 引言&#xff1a;不用万元显卡也能玩转AI姿态识别 最近谷歌开源的MediaPipe Holistic技术火了——它能同时追踪人体540多个关键点&#xff0c;包括面部表情、手势动作和全身姿态。这种技术原…

作者头像 李华
网站建设 2026/5/1 9:31:46

XOutput终极指南:如何将老式游戏手柄快速转换为XInput设备

XOutput终极指南&#xff1a;如何将老式游戏手柄快速转换为XInput设备 【免费下载链接】XOutput A small DirectInput to Xinput wrapper 项目地址: https://gitcode.com/gh_mirrors/xou/XOutput 在现代PC游戏世界中&#xff0c;兼容性往往是玩家面临的最大挑战之一。XO…

作者头像 李华
网站建设 2026/5/1 6:52:22

HunyuanVideo-Foley稳定性测试:长时间运行表现与容错机制

HunyuanVideo-Foley稳定性测试&#xff1a;长时间运行表现与容错机制 1. 引言 1.1 技术背景与测试动机 随着AIGC在多媒体内容生成领域的快速演进&#xff0c;视频音效自动生成技术正逐步从辅助工具走向核心生产环节。传统音效制作依赖专业音频工程师手动匹配动作与声音&…

作者头像 李华
网站建设 2026/5/1 5:43:38

WeChatFerry微信自动化机器人:免费开源完整指南

WeChatFerry微信自动化机器人&#xff1a;免费开源完整指南 【免费下载链接】WeChatFerry 微信逆向&#xff0c;微信机器人&#xff0c;可接入 ChatGPT、ChatGLM、讯飞星火、Tigerbot等大模型。Hook WeChat. 项目地址: https://gitcode.com/GitHub_Trending/we/WeChatFerry …

作者头像 李华