3.22 Go之聊天服务器
项目结构
-
服务端
-
一个主
goroutine-->监听端口,每一个链接创建一个新的handleConn的goroutine -
一个广播(
broadcaster)goroutine--->使用select对消息进行响应 -
一个(
handleConn)goroutine--->用户连接处理 -
一个写入(
clientWriter)goroutine
-
-
客户端
-
一个简单的
TCP服务读/写客户端
-
-
同步机制使用通道而非
java等程序的多线程
示例代码
服务器端:
广播部分
// 广播goroutine
func broadCaster() {
// 收集客户端
clients := make(map[client]bool)
// 循环结合select选择器将消息发送
for {
select {
case msg := <-messages:
// 如果成功的读取出客户通道内信息则广播出去
/* 将收到的信息广播 */
for cli := range clients {
// 将消息写入
cli <- msg
}
/* 判断是否进入服务端 */
case cli := <-entering:
// 改变客户端的布尔量
clients[cli] = true
/* 判断客户端是否离开服务器 */
case cli := <-leaving:
// 离开了就删除
delete(clients, cli)
// 关闭客户端
close(cli)
}
}
}
分析:
使用字典保护用户clients,字典的key是各连接申明的单向并发队列
使用select开启多路复用:
-
每当有广播消息从
messages发送进来,都会循环clients对里面的每个channel发消息。 -
每当有消息从
entering里面发送过来,就生成一个新的key - value,给clients里面增加一个新的client。 -
每当有消息从
leaving里面发送过来,就删掉这个key - value对,并关闭对应的channel。
客户端部分
// 客户写入goroutine
func clientWriter(conn net.Conn, ch <-chan string) {
// 循环获取通道中的值
for msg := range ch {
// 打印连接结果和通道信息
fmt.Fprintln(conn, msg)
}
}
分析:
-
创建一个对外发送消息的新通道,然后通过
entering通道通知广播者新客户到来 -
读取客户发来的每一行文本,通过全局接收消息通道将每一行发送给广播者
-
客户端读取完毕消息,
handleConn通过leaving通道通知客户离开,然后关闭连接
客户端连接交互部分
// 用户连接客户端
func handleConn(conn net.Conn) {
// 对外发送客户信息的通道
ch := make(chan string)
// 开启一个客户端协程
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
// 同时写入三个通道当中--->客户消息、服务端客户记录、进入通道
ch <- "欢迎:" + who
messages <- who + "上线"
entering <- ch
// 记录输入
input := bufio.NewScanner(conn)
// 循环监听输入--->记录聊天内容
for input.Scan() {
// 写入信息通道
messages <- who + ":" + input.Text()
}
// 记录下线
leaving <- ch
messages <- who + "下线!"
// 关闭连接
conn.Close()
}
服务端主程序
// main函数总调用
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
// 判断错误信息
if err != nil {
// 输出日志
log.Fatal(err)
}
// 创建一个完成通道
done := make(chan struct{})
// 开启协程
go func() {
// 忽略错误
io.Copy(os.Stdout, conn)
// 日志打印
log.Println("done")
// 告诉主goroutine
done <- struct{}{}
}()
// 调用读写函数
mustCopy(conn, os.Stdout)
// 连接关闭
conn.Close()
// 通道接收--->等待后台goroutine完成
<-done
}
分析:
获得listener对象,然后不停的获取链接上来的conn对象,最后把这些对象丢给处理链接函数去进行处理。
在使用handleConn方法处理conn对象的时候,对不同的链接都启一个goroutine去并发处理每个conn
It's a lonely road!!!

浙公网安备 33010602011771号