|
原文地址:http://tchen.me/posts/2014-01-27-golang-chatroom.html?utm_source=tuicool&utm_medium=referral 看了一上午写得很好,可以拿来试试刀
最近在team内部培训golang,目标是看看golang能否被C工程师快速掌握。我定了个一个月,共计20小时的培训计划,首先花10个小时(两周,每天1小时)让大家掌握golang的基本要素,能写一些入门级的程序,之后再花两周时间做一个1000行代码规模的Proof of concept项目。为了能在培训的slides上直接运行go code,我做了个简单的 coderunnerd,可以接受websocket传过来的code,编译运行再把stdout返回给websocket,为了更清晰地说明goroutine和chan的使用,以及golang的一些best practice,我分阶段写了个 chatroom。本文介绍一下如何使用goroutine和chan来做一个简单的聊天室。
需求
聊天室的需求很简单:
- 服务器监听某个端口,客户端可连接并开始聊天。
- 任何客户端的发言都会被广播给所有客户端。
- 客户端可以为自己设定名字或者执行一些聊天命令。
设计与实现
基本想法
服务器(Server):
- Server accept下来的connection被存在一个数据结构Client中,并以connection为key,Client为value,存在map里。
- 每个Client都有自己的goroutine去接受和发送消息。Client和Server之间通过channel来传递消息。
客户端(Client):
- 发送和接收都有各自的goroutine,通过channel和stdin/stdout交互
实现
所有chat相关的逻辑都被封装在 chat package里,client和server的cli只负责将ui和chat粘合起来。
首先,是核心的数据结构:
type Message chan string
type Client struct {
conn net.Conn
incoming Message
outgoing Message
reader *bufio.Reader
writer *bufio.Writer
quiting chan net.Conn
name string
}
Client 是一个服务器和客户端都共享的数据结构。conn是建立的连接,reader/writer是conn上的bufio。Client与外界的接口是incoming/outgoing两个channel,即:Server 会把要发送的内容 push 到 outgoing channel 里,供writer去写;而从reader读入的数据会 push 到 incoming channel 里,供 Server 读。
每个 Client 有自己的名字,服务器端代码会使用这个名字(客户端代码不会使用)。
type Token chan int
type ClientTable map[net.Conn]*Client
type Server struct {
listener net.Listener
clients ClientTable
tokens Token
pending chan net.Conn
quiting chan net.Conn
incoming Message
outgoing Message
}
Server 保存一张 ClientTable。每个 accept 到的 conn 会 push 进 pending channel,等待创建client。Server有 incoming / outgoing 两个 channel,分别和 client 的 incoming / outgoing 关联。
Server 有一组 tokens,决定了一个Server最多能装多少Client(避免Server overloading)。
下面看 Server 的创建流程:
const (
MAXCLIENTS = 50
)
func CreateServer() *Server {
server := &Server{
clients: make(ClientTable, MAXCLIENTS),
tokens: make(Token, MAXCLIENTS),
pending: make(chan net.Conn),
quiting: make(chan net.Conn),
incoming: make(Message),
outgoing: make(Message),
}
server.listen()
return server
}
很简单,无须多说。server.Listen() 实现如下:
func (self *Server) listen() {
go func() {
for {
select {
case message := |
|
|