上次我们了解了怎么用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。