news 2026/9/9 21:47:03

【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/9/3 3:37:02

AnimeGANv2教程:情侣照转动漫情侣头像的详细步骤

AnimeGANv2教程&#xff1a;情侣照转动漫情侣头像的详细步骤 1. 引言 1.1 学习目标 本文将详细介绍如何使用 AnimeGANv2 模型&#xff0c;将真实情侣照片一键转换为具有二次元风格的动漫头像。通过本教程&#xff0c;读者将掌握从环境部署、图像上传到风格迁移全流程的操作方…

作者头像 李华
网站建设 2026/9/9 14:42:42

微信自动化新纪元:开源机器人开发完全指南

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

作者头像 李华
网站建设 2026/9/9 14:42:18

没技术背景玩转AI动作捕捉?MediaPipe镜像一键即用

没技术背景玩转AI动作捕捉&#xff1f;MediaPipe镜像一键即用 引言&#xff1a;艺术创作的新工具 想象一下&#xff0c;你站在舞蹈教室里&#xff0c;不需要穿戴任何传感器设备&#xff0c;仅凭普通摄像头就能实时捕捉舞者的每一个优雅动作——这就是MediaPipe带来的魔法。对…

作者头像 李华
网站建设 2026/9/9 9:27:00

DeepLX免费翻译API深度解析:零成本构建企业级翻译服务

DeepLX免费翻译API深度解析&#xff1a;零成本构建企业级翻译服务 【免费下载链接】DeepLX DeepL Free API (No TOKEN required) 项目地址: https://gitcode.com/gh_mirrors/de/DeepLX 在当今全球化时代&#xff0c;翻译服务已成为开发者和企业不可或缺的工具。然而&…

作者头像 李华
网站建设 2026/9/8 15:58:26

LCD1602在51单片机系统中的应用:超详细版时序分析

从时序到实战&#xff1a;彻底搞懂51单片机如何精准驱动LCD1602你有没有遇到过这种情况&#xff1f;接好线路&#xff0c;烧录代码&#xff0c;上电之后LCD1602只显示一排黑块——字符就是不出来&#xff1b;或者屏幕闪一下、乱码一堆&#xff0c;初始化永远失败。别急&#xf…

作者头像 李华
网站建设 2026/9/9 12:15:31

SMAPI安卓安装器终极指南:3分钟解锁星露谷物语MOD新世界

SMAPI安卓安装器终极指南&#xff1a;3分钟解锁星露谷物语MOD新世界 【免费下载链接】SMAPI-Android-Installer SMAPI Installer for Android 项目地址: https://gitcode.com/gh_mirrors/smapi/SMAPI-Android-Installer SMAPI安卓安装器是一款专为星露谷物语手机版设计的…

作者头像 李华