Go語言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器
實(shí)現(xiàn)功能
- 每個(gè)客戶端上線,服務(wù)端可以向其他客戶端廣播上線信息;
- 發(fā)送的消息可以廣播給其他在線的客戶
- 支持改名
- 支持客戶端主動(dòng)退出
- 支持通過who查找當(dāng)前在線的用戶
- 超時(shí)退出
流程
變量
- 用戶結(jié)構(gòu)體 保存用戶的管道,用戶名以及網(wǎng)絡(luò)地址信息
type Client struct { C chan string //用于發(fā)送數(shù)據(jù)的管道 Name string //用戶名 Addr string //網(wǎng)絡(luò)地址 }
- 保存在線用戶的map表
var onlineMap map[string]Client
- 消息通道
var message = make(chan string)
主協(xié)程
- 監(jiān)聽客戶端的連接請(qǐng)求
listener, err := net.Listen("tcp", "127.0.0.1:8000")
- 當(dāng)客戶端有消息發(fā)送,就向當(dāng)前用戶列表中所有在線用戶轉(zhuǎn)發(fā)消息
go Manager()
- 接受客戶端的請(qǐng)求
conn, err1 := listener.Accept()
- 處理用戶連接
go HandleConn(conn)
func main() { //監(jiān)聽 listener, err := net.Listen("tcp", "127.0.0.1:8000") if err != nil { fmt.Println("net.Listen.err=", err) return } defer listener.Close() //新開一個(gè)協(xié)程,轉(zhuǎn)發(fā)消息,只要有消息,就遍歷map,給每個(gè)成員發(fā)送消息 go Manager() //主協(xié)程,循環(huán)阻塞等待用戶連接 for { conn, err1 := listener.Accept() if err1 != nil { fmt.Println("listener.Accept.err1=", err1) continue } //處理用戶連接 go HandleConn(conn) } }
處理用戶連接子協(xié)程
- 獲取客戶端的網(wǎng)絡(luò)地址
cliAddr := conn.RemoteAddr().String()
- 創(chuàng)建一個(gè)用戶結(jié)構(gòu)體,默認(rèn):用戶名和網(wǎng)絡(luò)地址一樣
cli := Client{make(chan string), cliAddr, cliAddr}
,加入map表 - 給客戶端發(fā)送信息
go WriteMsgToClient(cli, conn)
- 廣播某個(gè)人在線
message <- MakeMsg(cli, "login")
- 提示當(dāng)前用戶
cli.C <- MakeMsg(cli, "I am here")
- 判斷用戶狀態(tài)isQuit hasData
- 接收用戶的請(qǐng)求,查看當(dāng)前用戶who,改名rename,發(fā)送消息message
func HandleConn(conn net.Conn) { cliAddr := conn.RemoteAddr().String() cli := Client{make(chan string), cliAddr, cliAddr} //把結(jié)構(gòu)體添加到map onlineMap[cliAddr] = cli //新開一個(gè)協(xié)程,給客戶端發(fā)送信息 go WriteMsgToClient(cli, conn) //廣播某個(gè)人在線 message <- MakeMsg(cli, "login") //提示當(dāng)前用戶 cli.C <- MakeMsg(cli, "I am here") isQuit := make(chan bool) //對(duì)方是否主動(dòng)退出 hasData := make(chan bool) //對(duì)方是否有數(shù)據(jù) //新開一個(gè)協(xié)程,接收用戶的請(qǐng)求 go func() { buf := make([]byte, 2048) for { n, err := conn.Read(buf) if n == 0 { //對(duì)方斷開或者出問題 isQuit <- true fmt.Println("conn.Read.err=", err) return } msg := string(buf[:n-1]) if len(msg) == 3 && msg == "who" { //遍歷map,給當(dāng)前用戶發(fā)送所有成員 conn.Write([]byte("user list:\n")) for _, tmp := range onlineMap { msg := tmp.Addr + ":" + tmp.Name + "\n" conn.Write([]byte(msg)) } } else if len(msg) >= 8 && msg[:6] == "rename" { name := strings.Split(msg, "|")[1] cli.Name = name onlineMap[cliAddr] = cli conn.Write([]byte("rename ok\n")) } else { message <- MakeMsg(cli, msg) } hasData <- true //代表有數(shù)據(jù) } }() for { //通過select檢測(cè)channel的流動(dòng) select { case <-isQuit: delete(onlineMap, cliAddr) //當(dāng)前用戶從map移除 message <- MakeMsg(cli, "login out") //廣播誰下線了 return case <-hasData: case <-time.After(60 * time.Second): delete(onlineMap, cliAddr) message <- MakeMsg(cli, "time out leave out") return } } }
給客戶端發(fā)送信息
func WriteMsgToClient(cli Client, conn net.Conn) { for msg := range cli.C { conn.Write([]byte(msg + "\n")) } }
發(fā)送消息
func MakeMsg(cli Client, msg string) (buf string) { buf = "[" + cli.Addr + "]" + cli.Name + ":" + msg return }
轉(zhuǎn)發(fā)消息子協(xié)程
有消息到來就進(jìn)行廣播
- 給map分配空間
onlineMap = make(map[string]Client)
- 遍歷在線用戶列表,轉(zhuǎn)發(fā)消息;沒有消息之前message通道會(huì)阻塞
func Manager() { //給map分配空間 onlineMap = make(map[string]Client) for { msg := <-message //沒有消息前,會(huì)阻塞 for _, cli := range onlineMap { cli.C <- msg } } }
設(shè)計(jì)到的知識(shí)點(diǎn)
- 網(wǎng)絡(luò)編程,監(jiān)聽客戶端連接,處理連接請(qǐng)求,發(fā)送轉(zhuǎn)發(fā)消息等
- map,切片,結(jié)構(gòu)體數(shù)據(jù),通道.
- 通過select檢測(cè)channel的流動(dòng)
- 并發(fā)編程,開辟子協(xié)程處理當(dāng)前請(qǐng)求等
- 超時(shí)判斷
效果展示
到此這篇關(guān)于Go語言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器的文章就介紹到這了,更多相關(guān)Go語言 廣播式并發(fā)聊天 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)
這篇文章主要為大家詳細(xì)介紹了如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)(循環(huán)字符串、循環(huán)map結(jié)構(gòu)和循環(huán)Struct),文中的示例代碼代碼講解詳細(xì),需要的可以參考一下2022-10-10go日志系統(tǒng)logrus顯示文件和行號(hào)的操作
這篇文章主要介紹了go日志系統(tǒng)logrus顯示文件和行號(hào)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11GO使用阿里云,解決go get下載項(xiàng)目慢或無法下載的情況
這篇文章主要介紹了GO使用阿里云,解決go get下載項(xiàng)目慢或無法下載的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01使用Go實(shí)現(xiàn)健壯的內(nèi)存型緩存的方法
這篇文章主要介紹了使用Go實(shí)現(xiàn)健壯的內(nèi)存型緩存,本文比較了字節(jié)緩存和結(jié)構(gòu)體緩存的優(yōu)劣勢(shì),介紹了緩存穿透、緩存錯(cuò)誤、緩存預(yù)熱、緩存?zhèn)鬏敗⒐收限D(zhuǎn)移、緩存淘汰等問題,并對(duì)一些常見的緩存庫(kù)進(jìn)行了基準(zhǔn)測(cè)試,需要的朋友可以參考下2022-05-05golang語言編碼規(guī)范的實(shí)現(xiàn)
這篇文章主要介紹了golang語言編碼規(guī)范的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03golang post請(qǐng)求常用的幾種方式小結(jié)
這篇文章主要介紹了golang post請(qǐng)求常用的幾種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04Go語言實(shí)現(xiàn)百萬級(jí)WebSocket連接架構(gòu)設(shè)計(jì)及服務(wù)優(yōu)化
本文將詳細(xì)介紹如何在Go中構(gòu)建一個(gè)能夠支持百萬級(jí)WebSocket連接的服務(wù),包括系統(tǒng)架構(gòu)設(shè)計(jì)、性能優(yōu)化策略以及具體的實(shí)現(xiàn)步驟和代碼示例2024-01-01