海量用户通讯系统-显示在线用户列表(7)|学习笔记

简介: 快速学习海量用户通讯系统-显示在线用户列表(7)

开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:海量用户通讯系统-显示在线用户列表(7)】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址:https://developer.aliyun.com/learning/course/626/detail/9819


海量用户通讯系统-显示在线用户列表(7)

 

内容简介:

一、思路分析

二、代码测试

三、代码整理

 

一、思路分析

1. 代码整体思路分析

现在开始写客户端,客户端这块要想:它已经把东西给你了,那应该将东西要送到哪个位置?它把结构送到哪儿了?比如上线了之后,通过服务器端之后遍历了,上来过后调用了一下NotifyOthersOnlineUser, 然后它里面就去遍历所有在线用户,然后再将其一个发回去,最后它的关键点是WritePkg ,这时需要想write到哪儿去了?把这个NotifyUser Message包会送到什么地方去?这个地方一定要分析出来,因为都知道在客户端这边,其实它们早就一直在等待,终于发现有人发东西回来了,且在chatroom/process/server.go内启了一个跟服务器保持通讯的一个协程,一直在这儿等即客户端正在等待读取服务器发送的消息,终于等到了一个类型,那么这个类型现在干什么?把message拿到后直接跑路了,其中写了一句话:如果读到消息,下一步处理逻辑,这是曾经编写代码时留的逻辑口在这里, 会发现这地方都是有思考的,并不是乱七八糟去写的。

2.代码实现处理方法分析

现在去处理,处理最好的方式肯定就是用先看返回的消息的类型,这就又是一个套路了,所以返回什么类型就处理什么类型,那代码应该这样去写,如下:

for {

fmt.Println(“客户端正在等待读取服务器发送的消息”)

mes, err := tf.ReadPkg()

if err != nil {

fmt.Println(“tf.ReadPkg err=”, err)

return

}

//如果读取到消息,又是下一步处理逻辑

switch mes.Type {

case message.NotifyUserStatusMesType : //有人上线了

//1.取出NotifyUserStatusMesType

//2.把这个用户的信息,状态保存到客户map[int]User中

//处理

default :

fmt.Println(“服务器端返回了未知的消息类型”)

}

//fmt.Println(“mes=%v\n”,mes)

}

说明:其中方法1因为NotifyUserStatusMes里面包含了一个用户的ID和其状态,就是common/message/message.go中的UserId和Status,有Id和Status就说明知道有个人来了,但是这个人来了过后怎么处理?不能直接显示,这个地方就意味着第二步得把这个加入到本身客户端内维护的map里面去,因为它既然来了,肯定要把它加进去,比如它的状态是什么,之前讲过一个重要的思路就是让客户端也维护一个map,若不维护它,那服务器只能硬生生的把所有的都返回,代价太高了,所以现在还得有一个事情,就是上文代码中的方法2,有个问题即没有map,map内还是空的,分析到这个地方会马上觉得map还没有写完,所以现在还不能着急,还得先把map写出来,所以当看这个代码就感觉有点跳跃,就是突然一下会感觉怎么到这边还得写这个东西,还得又回头写,还得管服务器那边,所以说写网络通讯时如果是两个人写,那么这个项目经理最主要的就是先订一份协议,定好他们给编写代码人员什么编写人员再给其公司带来什么,实际上给的message便是这样的,返回一个东西就可以了,其他不用管,之前在做近看协同执行的时候便是这样订的,就是老大就说了一句话:我给你一个这样的包,你返回的格式是什么就行了,至于你怎么返回都是你的事情。现在没有别的办法,就是还得搞一个map,这个map刚才已经分析出来了,它是长这个样子的:map【int】User,那此时把它放在哪里比较合适呢?

3.map的放置位置分析

现在处理这个东西也需要写一个专门来管理此东西的文件会比较好,所以在chatroom内的client内的process中新建一个文件为userMgr.go,这个跟服务器不一样,这是它管理(维护)客户端的User,这个是一直在的。首先在chatroom/client/process/userMgr.go中编写代码,写package,它的包是process,把这个写完之后再写一个聊天,基本就通了,即通讯这块基本上以后就可以没问题了,假设你将来做通讯,因为Go语言最核心的是做服务器的,而做服务器里面最核心的功能就是做数据通道的,通讯、协程、协议、后台的服务器全部都在这里,Go语言学的精华就是这里,比如学Java时,Java想学好学到位,面向对象它整体的东西都在网络这块全部体现出来,面向对象、文件的操作、数组的操作、机制、包括思维的锻炼全部在这里,所以为什么很多老师在讲这个Java时他们一定是把这个最重要的放在这里,就是全部都讲完了,包括多线程,多线程学完了才去讲综合的东西,包括文件、数据库等全部都在这里面了,所以此案例很重要,不要觉得这个是不是没用,这个已经是核心了,那些天天写界面的,那个没有用,那是前端的人该做的事情。继续在chatroom/client/process/userMgr.go将代码编写完成,如下:

package process

import (

“fmt”

“go_code/chatroom/common/message”

)

//客户端要维护的map

var onlineUsers map[int]*message.User = make(map[int]*message.User, 10)

4.关于map初始化工作位置的分析

那问题来了,如果客户端要维护map,那它初始化的工作是在哪里发生的?这个后面是来一个加一个,但是它初始化的工作在哪里完成是比较合理的?之前在客户端登录的时候即它第一次登录的时候,它会首先把服务器所有在线的关心的ID整个拉下来,在那里面有UserId的切片,即它初始化的工作应该是在用户登录的时候给它搞进去的,因为它第一次登录时会把所有的在线的人一股脑全部扯下来, 所以说这个初始化的工作我们肯定是要在chatroom/client/process/userProcess.go中输入以下代码:

//fmt.Println(“登录成功”)

//可以显示当前在线用户列表,遍历loginResMes.UsersId

fmt.Println(“当前在线用户列表如下”)

for _, v := range loginResMes.UsersId {

//如果我们要求不显示自己在线,下面我们增加一个代码

if v == userId {

continue

}

fmt.Println(“用户id:\t”, v)

//完成客户端的onlineUsers完成初始化

user := &message.User{

UserId : v,

UserStatus : message.UserOnline,

}

onlineUsers[v]= user

}

fmt.Print(“\n\n”)

5.如何将用户状态保存到map

上面已经有两个方法了,现在就差client/process/server.go中 客户端在这里有个server 怎么去处理?把用户状态保存到map中去,那怎么保存?这就需要专门写一个方法再做这个事情,它不是那么简单的,因为它返回的是消息结构体,并不是一个简单的User,所以说还要对它进行一个解析来处理,现在这个代码呢就开始写了 在chatroom/client/process/userMgr.go中继续编写代码,如下:

//在客户端显示当前在线用户

func outpuOnlineUser() {

//遍历一把onlineUsers

fmt.Println(“当前在线用户列表:”)

for id, _ := range onlineUsers{

//如果不显示自己

fmt.Println(用户id:\t”, id)

}

}

//编写一个方法,处理返回的NotifyUserStatusMes

func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) {

//适当优化

user, ok := onlineUsers[notifyUserStatusMes.UserId]

if lok {  //原来没有

user := &message.User{

UserId : notifyUserStatusMes.UserId,

}

}

UserStatus : notifyUserStatusMes.Status,

onlineUsers[notifyUserStatusMes.UserId] = user

outputOnlineUser()

}

6.调用问题的分析

这文件内代码就写完了,写完以后回头到chatroom/client/process/server.go中,这里还等着调用,在这地方拿到NotifyUserStatusMes过后,要更新的时候是要传一个NotifyUserStatusMes,所以需要将其反序列化一下,因为message已经拿到了,再去取出其中的NotifyUserStatusMes就可以,在chatroom/client/process/server.go中做一个简单的处理,如下:

//如果读取到消息,又是下一步处理逻辑

switch mes.Type {

case message.NotifyUserStatusMesType : //有人上线了

//1.取出NotifyUserStatusMesType

var notifyUserStatusMes message.NotifyUserStatusMes

json.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes)

//2.把这个用户的信息,状态保存到客户map[int]User中

updateUserStatus(¬ifyUserStatusMes)

//处理

default :

fmt.Println(“服务器端返回了未知的消息类型”)

}

//fmt.Println(“mes=%v\n”,mes)

}

全部保存过后会发现message和json包没引,把message和json包引一下,如下:

package process

import (

“fmt”

“os”

“go_code/chatroom/client/utils”

“go_code/chatroom/common/message”

“encoding/json”

“net”

)

 

二、代码测试

1.编译服务器端

将代码全部保存,保存之后会出现这样一个效果,就是一个客户登录以后,另外一个客户一旦出来登录过后,这边客户会马上看到有一个新的用户上线了,显示谁上线了,即新的用户列表能够看到,现在测试一下,现在能看到的效果是当有一个用户上线后,比如A客户端上线、B客户端上线,于是C客户端一上线,A客户端和B客户端能够看到当前所有在线用户,现在是不是就能达到效果?显然还不知道,还需要走代码,因为这个并不好说,所以来走一把代码,先将服务器端和客户端全部重新编译一下,首先编译服务器端,代码显示在服务器端即server/process/userProcess.go中有错误,这时需要打开server/process/userProcess.go文件并找到23行的位置,其中是当时定的onlineUsers出错了,将其修改为:

//遍历一把onlineUsers,然后一个一个的发送NotifyUserStatusMes

for id, up := range userMgr.onlineUsers{

修改完之后保存,再重新编译,服务器端显示无错误,如下:

D:\goproject>go build -o server.exc go_code/chatroom/server/main

2.编译客户端

再把客户端也重新编译一下,客户端编译显示:

D:\goproject>go build -o client.exc go_code/chatroom/client/main

显示没有错误。再把另一个客户端也显示出来,此客户端就不需要再重新编译了。

(3)代码测试结果及分析

①先在第一个客户端登录,测试结果如下:

net.Dial err= dial tcp 127.0.0.1:8889: conncctex: No conncction could be made because the target machine activel

②显示这地方有个错误,即没有办法连接到8889,因为服务器端还没启动 ,启动服务器端后服务器端显示:

服务器[新的结构]在8889端口监听。。。。

等待客户端来链接服务器。。。。

③现在再接着在第一个客户端登录,检测结果显示:

D:\goproject>client.exe

-----------------欢迎登录多人聊天系统--------------------

1 登陆聊天室

2 注册用户

3 退出系统

请选择<1-3>:

1

登陆聊天室

请输入用户的id

100

请输入用户的密码

123456

客户端,发送消息的长度=86 内容=(“type ”=“LoginMe”)

当前在线用户列表如下:

-------------恭喜xxx登录成功----------

-------------1.显示在线用户列表--------------

-------------2.发送消息-----------------

-------------3.信息列表-----------------

-------------4.退出系统-----------------

请选择<1-4>:

客户端正在等待读取服务器发送的消息

读取客户端发送的数据。。。

④可以看出检测结果成功了,可以看到目前在线用户列表

(4)增添新功能

第二个客户端没有看到新的消息,他登录过后是不是看到100号也在登录,这时要加一个小功能,即输入一个1,便能看到除了自己以外的所有在线用户,这个将其快速加进去就完成了,很简单,就是显示在线用户列表工作怎么做,这个代码非常简单只需要做到在client/process/server.go 文件中加一个逻辑进去就可以,即:

switch key {

case 1:

//fmt.Println(“显示在线用户列表”)

outputOnlineUser()

case 2:

fmt.Println(“发送消息”)

case 3:

fmt.Println(“信息列表”)

case 4:

fmt.Println(“你选择了退出系统。。。”)

os.Exit(0)

default :

fmt.Println(“你输入的选项不正确。。”)

}

(5)增添新功能过后的代码测试结果

①再来走一把代码,将客户端重新编译,编译过后再来重新启动,在第一个客户端显示:

D:\goproject>client.exe

-----------------欢迎登录多人聊天系统--------------------

1 登陆聊天室

2 注册用户

3 退出系统

请选择<1-3>:

1

登陆聊天室

请输入用户的id

100

请输入用户的密码

123456

客户端,发送消息的长度=86 内容=(“type ”=“LoginMe”)

当前在线用户列表如下:

用户id: 200

②为什么第一个客户端能看到200?因为服务器没有关闭过,服务器没有关闭说明服务器列表还在工作中,所以把服务器关闭才能看到完整的东西,再重新测试一遍,会发现写网络编程恐怖的地方在一个人要照顾两头,所以难度肯定是要大一点的,接着在第一个客户端测试,结果显示为:

D:\goproject>client.exe

-----------------欢迎登录多人聊天系统--------------------

1 登陆聊天室

2 注册用户

3 退出系统

请选择<1-3>:

1

登陆聊天室

请输入用户的id

100

请输入用户的密码

123456

客户端,发送消息的长度=86 内容=(“type ”=“LoginMe”)

当前在线用户列表如下:

-------------恭喜xxx登录成功----------

-------------1.显示在线用户列表--------------

-------------2.发送消息-----------------

-------------3.信息列表-----------------

-------------4.退出系统-----------------

请选择<1-4>:

客户端正在等待读取服务器发送的消息

读取客户端发送的数据。。。

③这时什么都看不到,这时再登录第二个客户端登录200号

④可以看到第二个客户端是200号登录,第一个客户端是100号登录,那现在假设再想看输入1 时还看到100号,在第二个客户端中测试,结果显示为:

请选择<1-4>:

客户端正在等待读取服务器发送的消息

读取客户端发送的数据。。。

1

当前在线用户列表如下:

用户id: 100

-------------恭喜xxx登录成功----------

-------------1.显示在线用户列表--------------

-------------2.发送消息-----------------

-------------3.信息列表-----------------

-------------4.退出系统-----------------

请选择<1-4>:

⑤因为列表里面已经维护了这个信息,所以同样在第一个客户端输入1 也还是能看到200号,因为map里面也维护了这个信息

(6)新添一个客户端代码测试

①再来最后一个用户,只要三个人能够将代码走起来,那就没有代码上的逻辑错误了,输入cmd,在其客户端内再来登录这个人,因为现在只有两个用户,所以需要先注册一个用户,假设此用户为300号

②与此同时第一个客户端也更新了在线用户列表,结果显示:

当前在线用户列表:

用户id: 200

用户id: 300

客户端正在等待读取服务器发送的消息

读取客户端发送的数据。。。

同时第二个客户端也更新了在线用户列表 显示:

当前在线用户列表:

用户id: 100

用户id: 300

客户端正在等待读取服务器发送的消息

读取客户端发送的数据。。。

③那么在第三个客户端中输入1后,它的测试结果显示:

1

当前在线用户列表:

用户id: 100

用户id: 300

-------------恭喜xxx登录成功----------

-------------1.显示在线用户列表--------------

-------------2.发送消息-----------------

-------------3.信息列表-----------------

-------------4.退出系统-----------------

请选择<1-4>:

(7)小结

代码就测试完成,当把这条线打通过后,可以做很多事情了,因为现在可以看到用户在线了,如果用户离线了,他离线的时候发一个包说“我要走了”, 只需要在服务器端把这个人找到就行,现在接口就全都写完了,可以看到代码写的比较多。为了完成登录时能返回当前在线用户,写了很多代码,将来做数据通道、做Go语言的服务器肯定要用数据通讯,因为Go语言它本质有一部分C语言的特性,如果是做过C语言的都知道,C语言其实就是做服务器,那谁做界面呢?这时肯定要和网络、通讯打交道,所以此机制一定要去深刻理解。

 

四、代码整理

1.server/process/userProcess.go

//这里我们编写通知所有在线用户的方法

//userId要通知其他的在线用户,我上线

func (this *UserProcess) NotifyOthersOnlineUser(userId int) {

//遍历onlineUsers,然后一个一个的发送NotifyUserStatusMes

for id, up := range userMgr.onlineUsers {

//过滤到自己

if id == userId {

continue

}

//开始通知【单独写一个方法】

up.NotifyMeOnline(userId)

}

}

func (this *UserProcess) NotifyMeOnline(userId int) {

//组装我们的NotifyUserStatusMes

var mes message.Message

mes.Type = message.NotifyUserStatusMesType

var notifyUserStatusMes message.NotifyUserStatusMes

notifyUserStatusMes.UserId = userId

notifyUserStatusMes.Status = message.UserOnline

//将notifyUserStatusMes序列化

data, err := json.Marshal(notifyUserStatusMes)

if err != nil {

fmt.Println(“json.Marshal err=”, err)

return

}

//将序列化后的notifyUserStatusMes赋值给mes.Data

mes.Data = string(data)

//对mes两次序列化,准备发送

data, err = json.Marshal(mes)

if err != nil {

fmt.Println(“json.Marshal err=”, err)

return

}

//发送,创建我们Transfer实例,发送

tf := &utils.Transfer{

Conn : this.Conn,

}

err = tf.WritePkg(data)

if err != nil {

fmt.Println(“NotifyMeOnline err=”, err)

return

}

}

2.server/process/userProcess.go[的Login]

} else {

loginResMes.Code = 200

//这里,因为用户登录成功,我们就把该登录成功的用放入到userMgr中

//将登录成功的用户的userId赋给this

this.UserId = loginMes.UserId

userMgr.AddOnlineUser(this)

//通知其他的在线用户,我上线了

this.NotifyOthersOnlineUser(loginMes.UserId)

//将当前在线用户的id 放入到loginMes.UserId

//遍历userMgr.onlineUsers

3.common/message/message.go

//为了配合服务器端推送用户状态变化的消息

type NotifyUserStatusMes struct {

UserId int `json:”userId”`  //用户id

Status int `json:”status”`  //用户的状态

}

4.client/process/userMgr.go

package process

import (

“fmt”
”go_code/chatroom/common/message”

)

//客户端要维护的map

var onlineUsers map[int]*message.User = make(map[int]*message.User, 10)

//在客户端显示当前在线的用户

func outputOnlineUser(){

//遍历一把onlineUsers

fmt.Println(“当前在线用户列表:”)

for id, _ := range onlineUsers{

//如果不显示自己.

fmt.Println(“用户id:\t”,id)

}

}

//编写一个方法,处理返回的NotifyUserStatusMes

func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) {

//适当优化

user, ok := onlineUsers[notifyUserStatusMes.UserId]

if !ok { //原来没有

user = &message.User{

UserId : notifyUserStatusMes.UserId,

}

}

user.UserStatus = notifyUserStatusMes.Status

onlineUsers[notifyUserStatusMes.UserId] = user

OutputOnlineUser()

}

5.client/process/server.go

//如果读取到消息,又是下一步处理逻辑

switch mes.Type {

case message.NotifyUserStatusMesType : //有人上线了

//1.取出NotifyUserStatusMesType

var notifyUserStatusMes message.NotifyUserStatusMes

json.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes)

//2.把这个用户的信息,状态保存到客户map[int]User中

updateUserStatus(¬ifyUserStatusMes)

//处理

default :

fmt.Println(“服务器端返回了未知的消息类型”)

}

//fmt.Println(“mes=%v\n”,mes)

}

//fmt.Println(“mes=%v\n”, mes)

client/process/server.go

fmt ShowMenu() {

fmt.Println(“----------恭喜xxx登录成功----------”)

fmt.Println(“----------1.显示在线用户列表----------”)

fmt.Println(“----------2.发送消息----------”)

fmt.Println(“----------3.信息列表----------”)

fmt.Println(“----------4.退出系统----------”)

fmt.Println(“请选择(1-4):”)

var key int

fmt.Scanf(“%d\n”, &key)

switch key {

case 1:

//fmt.Println(“显示在线用户列表”)

outputOnlineUser()

case 2:

fmt.Println(“发送消息”)

相关文章
|
网络协议 前端开发 测试技术
海量用户通讯系统——服务端结构改进1|学习笔记
快速学习海量用户通讯系统——服务端结构改进1
海量用户通讯系统——服务端结构改进1|学习笔记
|
前端开发 Java 应用服务中间件
基于websocket的实时通告功能,推送在线用户,新登录用户
SpringBoot 部署与Spring部署都有一些差别,但现在用Srpingboot的公司多,SpringBoot创建项目快,所以使用该方式来讲解,有一个问题就是开发WebSocket时发现无法通过@Autowired注入bean,一直为空。
基于websocket的实时通告功能,推送在线用户,新登录用户
|
网络协议 测试技术 Go
海量用户通讯系统-显示在线用户列表(1)|学习笔记
快速学习海量用户通讯系统-显示在线用户列表(1)
海量用户通讯系统-显示在线用户列表(1)|学习笔记
|
NoSQL 网络协议 关系型数据库
海量用户通讯系统-完成界面|学习笔记
快速学习海量用户通讯系统-完成界面
海量用户通讯系统-完成界面|学习笔记
|
缓存 JSON 网络协议
海量用户系统-客户端结构改进2|学习笔记
快速学习海量用户系统-客户端结构改进2
海量用户系统-客户端结构改进2|学习笔记
|
JSON 网络协议 测试技术
海量用户通讯系统-项目小结|学习笔记
快速学习海量用户通讯系统-项目小结
海量用户通讯系统-项目小结|学习笔记
|
网络协议 测试技术 Go
海量用户通讯系统——客户端结构改进1|学习笔记
快速学习海量用户通讯系统——客户端结构改进1
海量用户通讯系统——客户端结构改进1|学习笔记
|
网络协议 测试技术 Go
海量用户通讯系统-显示在线用户列表(2)|学习笔记
快速学习海量用户通讯系统-显示在线用户列表(2)
|
机器学习/深度学习 JSON 网络协议
海量用户通讯系统-显示在线用户列表(3)|学习笔记
快速学习海量用户通讯系统-显示在线用户列表(3)
|
JSON 网络协议 测试技术
海量用户通讯系统-显示在线用户列表(6)|学习笔记
快速学习海量用户通讯系统-显示在线用户列表(6)