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