TCP聊天室(二):用户上线和广播

上次我们了解了怎么用Go语言来创建和连接一个socket,这里来看一看怎么封装用户行为以及怎么实现用户广播上线功能

Server的封装

server

这是在上一节中提到的server结构

1
2
3
4
type Server struct {
    Ip   string
    Port int
} 

可以看到,我们只有两个简单的成员属性,为了实现用户上线后全部广播的操作,我们需要在server中记录下来每次连接到serverclient,这里可以使用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对象会向Servermessage发送信息
  • servermessage接收到信息之后会遍历整个Online表,把User上线的消息发送给在Online表中的每一个User

其中,消息的转发依赖于conn.WriteUser上线后把上线消息写入SerevrchanServer再把该User上线的消息通过Online表写入Userchan

Licensed under CC BY-NC-SA 4.0
花有重开日,人无再少年
使用 Hugo 构建
主题 StackJimmy 设计