上次我们了解了怎么用Go
语言来创建和连接一个socket
,这里来看一看怎么封装用户行为以及怎么实现用户广播上线功能
Server的封装
server
这是在上一节中提到的server
结构
1
2
3
4
|
type Server struct {
Ip string
Port int
}
|
可以看到,我们只有两个简单的成员属性,为了实现用户上线后全部广播的操作,我们需要在server
中记录下来每次连接到server
的client
,这里可以使用map
来进行记录,同时,为了实现全局广播的效果,我们可以在server
中使用一个chan
来进行管理。
为什么要实现server
要实现一个chan
通道呢,当有用户上线建立连接后,我们就可以把上线的这个消息发送给chan
来进行管理,然后遍历map
就可以实现广播的操作。
server的实现
需要在原来的基础上多增加一些属性
1
2
3
4
5
6
7
8
9
10
|
type Server struct {
Ip string
Port int
// 创建用户表
OnlineMap map[string]*User
// 同步的锁
mapLock sync.RWMutex
// 负责全局广播的chan
Message chan string
}
|
在创建好一个server
后,与上一篇文章一样,使用协程去处理连接之后的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
func (this *Server) Start() {
// 创建好一个监听对象
// 这个函数会有两个返回值
// 一个是创建的 listen对象, 一个是是否创建成功
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
// 创建失败的话, err就会有一个失败code
// err != nil 就是说明创建失败
if err != nil {
fmt.Println("创建监听对象失败!, err", err.Error())
return
}
// 启动之后记得关闭,避免浪费资源
defer listener.Close()
// 然后就是使用accept方法
// 在一个循环里面不停的接受数据
// 监听
// 全局管道
go this.ListenMessage()
for {
// 这里的 meaage 是net.Coon类型
conn, err := listener.Accept()
// 说明接收到了数据
if err != nil {
fmt.Println("监听失败!")
continue
}
// 打开一个协程去处理
go this.Handle(conn)
// 下面的代码不会阻塞
}
}
|
这里的Handle
方法可以去处理连接请求,有哪些请求呢?
- 当一个用户上线后,应该把这个用户添加到
Online
表中
- 广播这个用户上线的消息
看到这里,起始我们缺少封装的user
类,我们可以再封装一个user
类
User的封装
user类的实现
在user
里面,基本的属性有Name, Address
这些操作,为了更加方便User
把消息转发给client
(转发操作指的是conn.Write
操作),在消息接收的上我们可以初始化一个chan
来进行连接转发
1
2
3
4
5
6
|
type User struct {
Name string
Addr string
C chan string
conn net.Conn
}
|
C
是为了接受来自server
的消息,conn
是为了把消息转发给client
,那么在初始化的时候,就得去监听,看是否有消息写回来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func NewUser(conn net.Conn) *User {
userAddr := conn.RemoteAddr().String() // 可以得到客户端的地址
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: conn,
}
go user.ListenMessage()
return user
}
// ListenMessage 监听User的chan
func (this *User) ListenMessage() {
for {
mes := <-this.C
this.conn.Write([]byte(mes + "\n"))
}
}
|
这样,在实现连接之后,我们就可以添加用户到在线表里面去
1
2
3
4
5
6
7
8
9
10
11
12
|
func (this *Server) Handle(conn net.Conn) {
// fmt.Println("连接成功!")
// 执行到这里,说明已经有一个用户上线
newUser := NewUser(conn)
this.mapLock.Lock()
this.OnlineMap[newUser.Name] = newUser
this.mapLock.Unlock()
// 广播该用户已上线
this.Boardcast(newUser, "I am in!")
}
|
user用户上线的广播
怎么实现Boardcast
方法呢?可以直接利用server
里面的chan
来实现
1
2
3
4
5
|
func (this *Server) Boardcast(u User, mes string) {
// 把上线的消息发送给message chan
sendMes := "[" + user.Name + user.Addr + mes + "]"
this.Message <- sendMes // 发送给管道
}
|
在发送给管道后,server
中的管道就就得到了数据,因此,就可以直接通过server
中的chan
进行消息的传输,遍历Onlinemap
即可
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func (this *Server) ShareMessage() {
// 只要message有消息
// 那么就发送给在线的所有用户
for {
mes := <-this.Message
for _, user := range this.OnlineMap {
this.mapLock.Lock()
user.C <- mes
this.mapLock.Unlock()
}
}
}
|
最后在服务启动的时候去监听转发信息功能即可
总结
整个流程看起来是这样的
- 创建
tcp
套接字并开启连接
- 连接后会创建
User
对象
User
对象会向Server
的message
发送信息
server
的message
接收到信息之后会遍历整个Online
表,把User
上线的消息发送给在Online
表中的每一个User
其中,消息的转发依赖于conn.Write
,User
上线后把上线消息写入Serevr
的chan
,Server
再把该User
上线的消息通过Online
表写入User
的chan
。