基于Go?goroutine實現(xiàn)一個簡單的聊天服務
對于聊天服務,想必大家都不會陌生,因為在我們的生活中經(jīng)常會用到。
我們用 Go 并發(fā)來實現(xiàn)一個聊天服務器,這個程序可以讓一些用戶通過服務器向其它所有用戶廣播文本消息。
這個程序中有四種 goroutine。
main 和 broadcaster 各自是一個 goroutine 實例,每一個客戶端的連接都會有一個handleConn 和 clientWriter 的 goroutine。
broadcaster 是 select 用法的不錯的樣例,因為它需要處理三種不同類型的消息。
下面我們來演示的 main goroutine 的工作,是 listen 和 accept (網(wǎng)絡編程里的概念)從客戶端過來的連接。對每一個連接,程序都會建立一個新的 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。
他的內部變量clients會記錄當前建立連接的客戶端集合,其記錄的內容是每一個客戶端的消息發(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)聽來自全局的entering和leaving的channel來獲知客戶端的到來和離開事件。
當其接收到其中的一個事件時,會更新clients集合,當該事件是離開行為時,它會關閉客戶端的消息發(fā)送channel。broadcaster也會監(jiān)聽全局的消息channel,所有的客戶端都會向這個channel中發(fā)送消息。當broadcaster接收到什么消息時,就會將其廣播至所有連接到服務端的客戶端。
現(xiàn)在讓我們看看每一個客戶端的goroutine。
handleConn函數(shù)會為它的客戶端創(chuàng)建一個消息發(fā)送channel并通過entering channel來通知客戶端的到來。然后它會讀取客戶端發(fā)來的每一行文本,并通過全局的消息channel來將這些文本發(fā)送出去,并為每條消息帶上發(fā)送者的前綴來標明消息身份。當客戶端發(fā)送完畢后,handleConn會通過leaving這個channel來通知客戶端的離開并關閉連接。
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為每一個客戶端創(chuàng)建了一個clientWriter的goroutine,用來接收向客戶端發(fā)送消息的channel中的廣播消息,并將它們寫入到客戶端的網(wǎng)絡連接??蛻舳说淖x取循環(huán)會在broadcaster接收到leaving通知并關閉了channel后終止。
下面演示的是當服務器有兩個活動的客戶端連接,并且在兩個窗口中運行的情況,使用netcat來聊天:
$ 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”
當與n個客戶端保持聊天session時,這個程序會有2n+2個并發(fā)的goroutine,然而這個程序卻并不需要顯式的鎖。clients這個map被限制在了一個獨立的goroutine中,broadcaster,所以它不能被并發(fā)地訪問。
多個goroutine共享的變量只有這些channel和net.Conn的實例,兩個東西都是并發(fā)安全的。
到此這篇關于基于Go goroutine實現(xiàn)一個簡單的聊天服務的文章就介紹到這了,更多相關Go goroutine 聊天服務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Redis?BloomFilter布隆過濾器原理與實現(xiàn)
你在開發(fā)或者面試過程中,有沒有遇到過?海量數(shù)據(jù)需要查重,緩存穿透怎么避免等等這樣的問題呢?下面這個東西超棒,好好了解下,面試過關斬將,凸顯你的不一樣2022-10-10xorm根據(jù)數(shù)據(jù)庫生成go model文件的操作
這篇文章主要介紹了xorm根據(jù)數(shù)據(jù)庫生成go model文件的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12