基于Go?goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)
對(duì)于聊天服務(wù),想必大家都不會(huì)陌生,因?yàn)樵谖覀兊纳钪薪?jīng)常會(huì)用到。
我們用 Go 并發(fā)來(lái)實(shí)現(xiàn)一個(gè)聊天服務(wù)器,這個(gè)程序可以讓一些用戶(hù)通過(guò)服務(wù)器向其它所有用戶(hù)廣播文本消息。
這個(gè)程序中有四種 goroutine。
main 和 broadcaster 各自是一個(gè) goroutine 實(shí)例,每一個(gè)客戶(hù)端的連接都會(huì)有一個(gè)handleConn 和 clientWriter 的 goroutine。
broadcaster 是 select 用法的不錯(cuò)的樣例,因?yàn)樗枰幚砣N不同類(lèi)型的消息。
下面我們來(lái)演示的 main goroutine 的工作,是 listen 和 accept (網(wǎng)絡(luò)編程里的概念)從客戶(hù)端過(guò)來(lái)的連接。對(duì)每一個(gè)連接,程序都會(huì)建立一個(gè)新的 handleConn 的 goroutine。
func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } go broadcaster() for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } go handleConn(conn) } }
然后是broadcaster的goroutine。
他的內(nèi)部變量clients會(huì)記錄當(dāng)前建立連接的客戶(hù)端集合,其記錄的內(nèi)容是每一個(gè)客戶(hù)端的消息發(fā)出channel的“資格”信息。
type client chan<- string // an outgoing message channel var ( entering = make(chan client) leaving = make(chan client) messages = make(chan string) // all incoming client messages ) func broadcaster() { clients := make(map[client]bool) // all connected clients for { select { case msg := <-messages: // Broadcast incoming message to all // clients' outgoing message channels. for cli := range clients { cli <- msg } case cli := <-entering: clients[cli] = true case cli := <-leaving: delete(clients, cli) close(cli) } } }
broadcaster監(jiān)聽(tīng)來(lái)自全局的entering和leaving的channel來(lái)獲知客戶(hù)端的到來(lái)和離開(kāi)事件。
當(dāng)其接收到其中的一個(gè)事件時(shí),會(huì)更新clients集合,當(dāng)該事件是離開(kāi)行為時(shí),它會(huì)關(guān)閉客戶(hù)端的消息發(fā)送channel。broadcaster也會(huì)監(jiān)聽(tīng)全局的消息channel,所有的客戶(hù)端都會(huì)向這個(gè)channel中發(fā)送消息。當(dāng)broadcaster接收到什么消息時(shí),就會(huì)將其廣播至所有連接到服務(wù)端的客戶(hù)端。
現(xiàn)在讓我們看看每一個(gè)客戶(hù)端的goroutine。
handleConn函數(shù)會(huì)為它的客戶(hù)端創(chuàng)建一個(gè)消息發(fā)送channel并通過(guò)entering channel來(lái)通知客戶(hù)端的到來(lái)。然后它會(huì)讀取客戶(hù)端發(fā)來(lái)的每一行文本,并通過(guò)全局的消息channel來(lái)將這些文本發(fā)送出去,并為每條消息帶上發(fā)送者的前綴來(lái)標(biāo)明消息身份。當(dāng)客戶(hù)端發(fā)送完畢后,handleConn會(huì)通過(guò)leaving這個(gè)channel來(lái)通知客戶(hù)端的離開(kāi)并關(guān)閉連接。
func handleConn(conn net.Conn) { ch := make(chan string) // outgoing client messages go clientWriter(conn, ch) who := conn.RemoteAddr().String() ch <- "You are " + who messages <- who + " has arrived" entering <- ch input := bufio.NewScanner(conn) for input.Scan() { messages <- who + ": " + input.Text() } // NOTE: ignoring potential errors from input.Err() leaving <- ch messages <- who + " has left" conn.Close() } func clientWriter(conn net.Conn, ch <-chan string) { for msg := range ch { fmt.Fprintln(conn, msg) // NOTE: ignoring network errors } }
另外,handleConn為每一個(gè)客戶(hù)端創(chuàng)建了一個(gè)clientWriter的goroutine,用來(lái)接收向客戶(hù)端發(fā)送消息的channel中的廣播消息,并將它們寫(xiě)入到客戶(hù)端的網(wǎng)絡(luò)連接??蛻?hù)端的讀取循環(huán)會(huì)在broadcaster接收到leaving通知并關(guān)閉了channel后終止。
下面演示的是當(dāng)服務(wù)器有兩個(gè)活動(dòng)的客戶(hù)端連接,并且在兩個(gè)窗口中運(yùn)行的情況,使用netcat來(lái)聊天:
$ go build gopl.io/ch8/chat $ go build gopl.io/ch8/netcat3 $ ./chat & $ ./netcat3 You are 127.0.0.1:64208 $ ./netcat3 127.0.0.1:64211 has arrived You are 127.0.0.1:64211 Hi! 127.0.0.1:64208: Hi! 127.0.0.1:64208: Hi! Hi yourself. 127.0.0.1:64211: Hi yourself. 127.0.0.1:64211: Hi yourself. ^C 127.0.0.1:64208 has left $ ./netcat3 You are 127.0.0.1:64216 127.0.0.1:64216 has arrived Welcome. 127.0.0.1:64211: Welcome. 127.0.0.1:64211: Welcome. ^C 127.0.0.1:64211 has left”
當(dāng)與n個(gè)客戶(hù)端保持聊天session時(shí),這個(gè)程序會(huì)有2n+2個(gè)并發(fā)的goroutine,然而這個(gè)程序卻并不需要顯式的鎖。clients這個(gè)map被限制在了一個(gè)獨(dú)立的goroutine中,broadcaster,所以它不能被并發(fā)地訪(fǎng)問(wèn)。
多個(gè)goroutine共享的變量只有這些channel和net.Conn的實(shí)例,兩個(gè)東西都是并發(fā)安全的。
到此這篇關(guān)于基于Go goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)的文章就介紹到這了,更多相關(guān)Go goroutine 聊天服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法
這篇文章主要介紹了go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法,實(shí)例分析了Go語(yǔ)言中zlib的使用技巧,需要的朋友可以參考下2015-03-03Redis?BloomFilter布隆過(guò)濾器原理與實(shí)現(xiàn)
你在開(kāi)發(fā)或者面試過(guò)程中,有沒(méi)有遇到過(guò)?海量數(shù)據(jù)需要查重,緩存穿透怎么避免等等這樣的問(wèn)題呢?下面這個(gè)東西超棒,好好了解下,面試過(guò)關(guān)斬將,凸顯你的不一樣2022-10-10使用Go重構(gòu)流式日志網(wǎng)關(guān)的實(shí)戰(zhàn)分享
流式日志網(wǎng)關(guān)的主要功能是提供?HTTP?接口,接收?CDN?邊緣節(jié)點(diǎn)上報(bào)的各類(lèi)日志(訪(fǎng)問(wèn)日志/報(bào)錯(cuò)日志/計(jì)費(fèi)日志等),將日志作預(yù)處理并分流到多個(gè)的?Kafka?集群和?Topic?中,本文就給大家分享如何使用?Go?重構(gòu)流式日志網(wǎng)關(guān)2023-06-06Go單體服務(wù)開(kāi)發(fā)最佳實(shí)踐總結(jié)
這篇文章主要介紹了Go單體服務(wù)開(kāi)發(fā)最佳實(shí)踐,通過(guò)本文詳細(xì)跟大家分享一下如何使用?go-zero?快速開(kāi)發(fā)一個(gè)有多個(gè)模塊的單體服務(wù),需要的朋友可以參考下2022-04-04xorm根據(jù)數(shù)據(jù)庫(kù)生成go model文件的操作
這篇文章主要介紹了xorm根據(jù)數(shù)據(jù)庫(kù)生成go model文件的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go網(wǎng)絡(luò)編程TCP抓包實(shí)操示例探究
作為一名軟件開(kāi)發(fā)者,網(wǎng)絡(luò)編程是必備知識(shí),本文通過(guò)?Go?語(yǔ)言實(shí)現(xiàn)?TCP?套接字編程,并結(jié)合?tcpdump?工具,展示它的三次握手、數(shù)據(jù)傳輸以及四次揮手的過(guò)程,幫助讀者更好地理解?TCP?協(xié)議與?Go?網(wǎng)絡(luò)編程2024-01-01