@(go語(yǔ)言 黑馬)[GO語(yǔ)言]
并發(fā)聊天室
- 題目:利用Go語(yǔ)言高并發(fā)的特性,編寫(xiě)一個(gè)類(lèi)似QQ群聊功能的并發(fā)聊天服務(wù)器
- 主要知識(shí)點(diǎn):TCP通信,channel的使用
- 邏輯思路(詳細(xì)的步驟在代碼注釋中):
_1. 建立TCP通信連接,并且循環(huán)監(jiān)聽(tīng),成功連接一個(gè)客戶(hù)端就啟動(dòng)一個(gè)go程
_2. 所有消息都會(huì)由 message 通道發(fā)給用戶(hù)自帶的 channel ,最終由 WriteMsgToClient 方法發(fā)送到客戶(hù)端
- 要想試試該服務(wù)器的效果,需要安裝客戶(hù)端:這里下載,然后命令行輸入命令:nc 127.0.0.1 8001 ,即可連接該服務(wù)器;每個(gè)cmd終端相當(dāng)于一個(gè)客戶(hù)端,可以多開(kāi)幾個(gè)客戶(hù)端模擬群聊
服務(wù)器代碼如下:
package main
//并發(fā)聊天服務(wù)器
import (
"net"
"fmt"
"strings"
"time"
)
type Client struct {
//用戶(hù)有三個(gè)屬性:用戶(hù)名,用戶(hù)地址,C
C chan string // 該通道是用來(lái)接收需要發(fā)送給客戶(hù)端的數(shù)據(jù)
Name string // 用戶(hù)名
Addr string // 客戶(hù)端地址
}
// 存儲(chǔ)在線用戶(hù)的鍵值數(shù)據(jù)庫(kù),用map模擬
var onlin_client_Map = make(map[string]Client)
// 用于廣播消息給用戶(hù)的通道
var message = make(chan string)
// 廣播消息給所有在線用戶(hù)的方法
func Messager() {
for {
// 循環(huán)監(jiān)聽(tīng)讀取message中的數(shù)據(jù)
msg := <-message
// 遍歷所有在線的用戶(hù),再將消息寫(xiě)入用戶(hù)用于接收消息的通道中
for _, cli := range onlin_client_Map {
cli.C <- msg
}
}
}
// 用戶(hù)將自己通道的消息寫(xiě)給用戶(hù)客戶(hù)端的方法
func WriteMsgToClient(cli Client, conn net.Conn) {
for msg := range cli.C { // 遍歷出通道中的信息
conn.Write([]byte(msg + "\n")) // 利用通信socket,將信息傳輸給客戶(hù)端
}
}
// 消息生產(chǎn)方法
func MakeMessage(cli Client, msg string) string {
str := "[" + cli.Addr + "] " + cli.Name + ": " + msg
return str
}
// 這是程序的核心,上線提醒、發(fā)送消息、更換用戶(hù)名、查看所有在線用戶(hù)列表 的功能都在這個(gè)函數(shù)實(shí)現(xiàn)
func HandleConnect(conn net.Conn) {
defer conn.Close() //不要忘記關(guān)閉
// 獲取 客戶(hù)端 地址
cli_addr := conn.RemoteAddr().String() //“.String()” 作用是:轉(zhuǎn)成string類(lèi)型
// 初始化新用戶(hù)
cli := Client{make(chan string), cli_addr, cli_addr}
// 添加新上線用戶(hù)到Map中
onlin_client_Map[cli_addr] = cli
// 往全局通道中寫(xiě)入 登錄信息
message <- MakeMessage(cli, "login")
// 用戶(hù)將信息發(fā)送到客戶(hù)端的go程
go WriteMsgToClient(cli, conn)
// 這兩個(gè)通道是用來(lái)控制客戶(hù)端的在線時(shí)間的
isQuit := make(chan bool)
hasData := make(chan bool)
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf) // 讀取客戶(hù)端發(fā)來(lái)的信息
if n == 0 {
fmt.Printf("客戶(hù)端%s斷開(kāi)\n", cli.Name)
isQuit <- true
return
}
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
msg := string(buf[:n-1]) //去掉空格行
if msg == "who" && len(msg) == 3 { // 查看在線用戶(hù)列表
// 不需要廣播,所以不用message,直接往conn中寫(xiě)數(shù)據(jù)
conn.Write([]byte("user list:\n"))
for _, cli := range onlin_client_Map { // 遍歷map中所有在線客戶(hù),將信息組織好后再發(fā)送
msg = cli.Addr + ":" + cli.Name + "\n" // 這里沒(méi)有使用MakeMessage,直接自己生成
conn.Write([]byte(msg))
}
} else if len(msg) >= 8 && msg[:7] == "rename|" { // 更換用戶(hù)名功能
cli.Name = strings.Split(msg, "|")[1]
onlin_client_Map[cli_addr] = cli
conn.Write([]byte("rename success!\n"))
} else {
message <- MakeMessage(cli, msg)
}
hasData <- true
}
}()
for {
select {
case <-isQuit:
delete(onlin_client_Map, cli.Addr)
message <- MakeMessage(cli, "log out")
case <-hasData:
case <-time.After(time.Second * 30):// 如果30s不發(fā)言,系統(tǒng)將強(qiáng)制將用戶(hù)退出
delete(onlin_client_Map, cli.Addr)
message <- MakeMessage(cli, "time out leave")
return
}
}
}
func main() {
//創(chuàng)建與客戶(hù)端的連接地址
listener, err := net.Listen("tcp", "127.0.0.1:8001") //利用的是tcp通信
if err != nil {
fmt.Println("net.Listen err:", err)
return
}
defer listener.Close()
// 啟動(dòng)go程 廣播方法,等待接收數(shù)據(jù),并廣播 到每個(gè)客戶(hù)
go Messager()
// 循環(huán)監(jiān)聽(tīng)客戶(hù)端連接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err:", err)
return
}
go HandleConnect(conn) //收到一個(gè)客戶(hù)連接,啟動(dòng)一個(gè)go程
}
}