案例:并发的网络聊天室服务
import (
"bufio"
"fmt"
"log"
"net"
)
type client chan<- string //声明一个数据类型client 是一个只能写字符串的channel
var (
//entering 表示登陆 channel,
//如果有新的client登陆,
//应该向该channel发送数据,告知是哪个client登陆
entering = make(chan client) //用法比较特殊,entering是一个channel,里面存的元素也是一个client channel
//leaving 表示离开 channel,
//如果有某个cilent退出链接,
//应该向该channel发送数据,告知是哪个client离开
leaving = make(chan client)
//message 表示正常聊天消息,广播消息事件,
//client发送的正常聊天信息都应该向该channel发送数据
messages = make(chan string)
)
func broadcaster() {
//用来记录所有已经链接的client客户端
var clients = make(map[client]bool)
for {
select {
case msg := <-messages:
//客户端正常消息事件
//取出全部在线的客户端client,将该消息发送给每个客户端
for cli := range clients {
cli <- msg
}
case cli := <-entering:
//client登陆事件
//给clients map增添一对 key-value
clients[cli] = true
case cli := <-leaving:
//client离开事件
//给clients map删除一堆 key-value
delete(clients, cli)
//将该登出的client channel关闭
close(cli)
}
}
}
func clientWrite(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg)
}
}
func handleConn(conn net.Conn) {
//给当前客户端创建一个channel 这个channel和conn套接字保持同步
//只要想ch写数据, clientWrite 这个goroutine就会想对应的conn套接字写数据给客户端
ch := make(chan string) //ch就是当前客户端的client
go clientWrite(conn, ch)
who := conn.RemoteAddr().String()
//给当前客户端回应 client的id
ch <- "You are" + who
//向全部已经在线的client广播 当前新的client已经登录
messages <- who + "已经登录"
//发送当前ch登录事件 broadcaster会处理这个事件,将ch放在clients中
entering <- ch
//阻塞等待客户端输入消息
input := bufio.NewScanner(conn)
for input.Scan() {
//将客户端输入消息进行广播 发给其他client
messages <- who + ":" + input.Text()
}
//如果input.Scan退出,表示客户端断开连接 此处可以添加超时机制
//发送当前ch退出事件
leaving <- ch
//想其他client广播 当前ch已经退出
messages <- who + "已经退出"
//关闭当前客户端套接字conn
conn.Close()
}
func main() {
//启动服务器监听本地端口 得到listenner
listener, err := net.Listen("tcp", "localhost:8001")
if err != nil {
log.Fatal(err)
}
//开一个goroutine去处理3个channel事件
go broadcaster()
for {
//等待新的客户端链接请求过来
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
//开一个goroutine去处理client请求
go handleConn(conn)
}
}