news 2026/6/21 23:30:54

【Java转Go】即时通信系统代码分析(三)用户消息广播

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java转Go】即时通信系统代码分析(三)用户消息广播

接上文,改动不大

本期课件

视频: 39-用户消息广播
代码:server.go的Handler方法增加业务

func(this*Server)Handler(conn net.Conn){//用户上线,加入到online map中user:=NewUser(conn)this.mapLock.Lock()this.OnlineMap[user.Name]=user this.mapLock.Unlock()//广播当前用户上线消息this.BroadCast(user,"已上线")//接收客户端发送的消息gofunc(){buf:=make([]byte,4096)for{n,err:=conn.Read(buf)ifn==0{this.BroadCast(user,"下线")return}iferr!=nil&&err!=io.EOF{fmt.Println("Conn Read err:",err)return}//提取用户消息,去掉\nmsg:=string(buf[:n-1])this.BroadCast(user,msg)}}()}

逐行分析

新代码主要是://接收客户端发送的消息 业务逻辑的内容

  1. go func() { ... }()匿名函数。匿名体现在它没有名字,不像func() Handler…有名字。在这个例子中func()内部没有参数,说明它是无参方法,{}前也没有定义返回值,说明它没有返回值。最后的()表示立即调用。开始的go表示是开启一个协程去调用。
  2. 匿名函数最常用的地方在于它能“捕获”外部变量。这个匿名函数直接使用了外部的 user 变量,这种特性就叫闭包
  3. buf := make([]byte, 4096)这行中,byte[]是Slice 切片,它是一个引用类型,所以需要用make进行创建。大小是4096,一个byte在Go 中代表uint8,占一个字节,所以4096这里意味着在堆上开辟4096个byte缓冲区空间,占4KB。切片的创建语法是make([]T, len, cap),如果只有一个值,那么既是长度也是容量。
  4. n, err := conn.Read(buf)尝试从 TCP 连接的内核缓冲区中读取数据,并拷贝到 buf 切片中。如果连接中没有数据,当前代码阻塞。返回的n是字节Byte长度
  5. err != nil && err != io.EOFio.EOF 是 Go 语言中用来表示“数据读完了”的一个特殊标识,它并不是一个真正的“错误”。如果连接关闭,下一次再调用 Read,它就会返回 n=0 和 err=io.EOF。那么为什么前面判断的是if n== 0 而不是 if err== io.EOF?因为n == 0 更健壮。n==0是最终结局,如果判断err,其实有多种可能
  6. msg := string(buf[:n-1])中的buf[:n-1]是Go 中的切片重切语法:buf[low:high],low如果省略就是0。这是一个左闭右开区间[ l o w , h i g h ) [low, high)[low,high)。buf[:n-1] 只是在原来的内存块上重新定义了一个边界,没有任何内存拷贝发生。当发生string()时,把这块新内存包装成一个不可变的 string 对象赋值给 msg

一些问题

  1. 在Java 中,方法的调用是建立在对象上的,如果不考虑静态方法的话,因为方法是对象的。在Go的代码片中:this.BroadCast(user, “下线”) 这一句this是一个指针?那在Go中方法的调用是基于什么呢?
    之前有解释过,Go中的this不是一个保留的关键字,只是一个普通变量名。它在func (this *Server) Handler(conn net.Conn)
    这个方法中指代的是Server的内存地址。方法本质上是绑定到类型上的函数。 当你执行 s.BroadCast() 时,编译器实际上将其看作: BroadCast(s, user, msg) 它将调用者 s 作为第一个参数显式地传递给了函数。Go 的方法调用是基于 类型 (Type) 的。只要一个类型定义了某个方法,该类型的实例就能调用它。this.BroadCast 的意思是:“以当前这个指针指向的内存作为上下文,执行 BroadCast 逻辑”。
  2. Go 中数组和切片的区别是什么?
    在 Go 中,[4096]byte(带长度)叫数组,而 []byte(不带长度)叫切片。切片就是动态数组,数组是固定长度的。数组是值类型的,切片是引用类型的。切片可以动态扩容。通过内置的 append 函数,当容量不足时,Go 会自动申请更大的内存,并将旧数据搬迁过去。注意,因为数组是值传递,如果在方法中直接传数组,即为传入数组的拷贝,那么对原数组的修改是不会生效的。如果想通过方法修改原数组,要传入指针。这点比较像C++。当然也可以用Slice传递,不用数组传递,因为Slice就是引用类型了
  3. 创建Slice 时,长度小于容量的意义是什么?有怎样的使用场景?
    在 Go 语言中,将长度(len)设得比容量(cap)小,核心意义在于:预留空间,减少内存分配的次数。相当于性能预投资。就相当于Java 的HashMap,如果你创建的大一点,那么扩容发生的次数就比较少,扩容是很消耗资源的。
  4. 为什么是要开辟原容量2倍的内存,进行旧数据拷贝,而不是直接开辟一块新内存,原来的就内存不变?是因为Slice 需要连续内存空间吗?
    是的,因为Slice 的底层必须是连续的内存空间,连续内存空间是为了O(1)的访问效率。

本篇代码很少,但着重探讨了Slice这一Go语言特性。Go确实更像C++一些。从Java 转的话,还是需要一些思维的建立。

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

手把手教你使用Proteus 8.9继电器元件对照表进行仿真

从零开始搞定继电器仿真:Proteus 8.9实战全解析你有没有遇到过这种情况?想用单片机控制一盏灯、一个电机,甚至家里那台老式空调——但直接驱动显然不行。这时候,继电器就成了你的“电力开关手”。可问题是,在焊板子之前…

作者头像 李华
网站建设 2026/6/12 6:55:33

鸿蒙应用的性能优化与用户体验提升实战:智能待办的极致优化

🚀 鸿蒙应用的性能优化与用户体验提升实战:智能待办的极致优化 一、章节概述 ✅ 学习目标 掌握鸿蒙性能分析工具(DevEco Studio Profiler、AGC APM、方舟性能分析工具)的核心原理落地《全生态智能待办》的性能优化方案&#xff1a…

作者头像 李华
网站建设 2026/6/17 14:32:23

Go 语言中的集合体系:从语言设计到工程实践

在 Go 语言中,并不存在像 Java Collection Framework 那样完整、统一的集合类体系。相反,Go 选择了一条更克制、更贴近底层的数据结构路线:通过少量内建类型,配合明确的语义约束,支撑绝大多数工程场景。这种设计取向&a…

作者头像 李华
网站建设 2026/6/16 18:22:15

UDS协议诊断服务通信流程全面讲解

UDS协议诊断通信流程深度解析:从会话控制到安全解锁的实战指南在一辆现代智能汽车中,遍布着数十甚至上百个电子控制单元(ECU)。这些“大脑”如何被统一管理?当车辆出现故障时,维修设备是如何精准读取内部信…

作者头像 李华
网站建设 2026/6/12 17:36:49

Vibe Coding AI 开发实战:0基础用 Trae CN 开发待办事项应用

今天小编将介绍如何使用 Vibe Coding(Trae CN)开发一个简单而功能完整的 Todo List 待办事项应用程序。通过这个项目,你可以学习如何利用 AI 辅助编程工具快速构建 Web 应用,同时掌握 HTML、CSS 和 JavaScript 的基础开发技能。开…

作者头像 李华