av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

創(chuàng)新互聯(lián)GO教程:Go語(yǔ)言聊天服務(wù)器

本節(jié)將帶領(lǐng)大家結(jié)合咱們前面所學(xué)的知識(shí)開(kāi)發(fā)一個(gè)聊天的示例程序,它可以在幾個(gè)用戶之間相互廣播文本消息。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:主機(jī)域名網(wǎng)站空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、萊西網(wǎng)站維護(hù)、網(wǎng)站推廣。

服務(wù)端程序

服務(wù)端程序中包含 4 個(gè) goroutine,分別是一個(gè)主 goroutine 和廣播(broadcaster)goroutine,每一個(gè)連接里面又包含一個(gè)連接處理(handleConn)goroutine 和一個(gè)客戶寫(xiě)入(clientwriter)goroutine。

廣播器(broadcaster)是用于如何使用 select 的一個(gè)規(guī)范說(shuō)明,因?yàn)樗枰獙?duì)三種不同的消息進(jìn)行響應(yīng)。

主 goroutine 的工作是監(jiān)聽(tīng)端口,接受連接客戶端的網(wǎng)絡(luò)連接,對(duì)每一個(gè)連接,它將創(chuàng)建一個(gè)新的 handleConn goroutine。

完整的示例代碼如下所示:

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
)

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)
    }
}

type client chan<- string // 對(duì)外發(fā)送消息的通道

var (
    entering = make(chan client)
    leaving  = make(chan client)
    messages = make(chan string) // 所有連接的客戶端
)

func broadcaster() {
    clients := make(map[client]bool)
    for {
        select {
        case msg := <-messages:
            // 把所有接收到的消息廣播給所有客戶端
            // 發(fā)送消息通道
            for cli := range clients {
                cli <- msg
            }

        case cli := <-entering:
            clients[cli] = true

        case cli := <-leaving:
            delete(clients, cli)
            close(cli)
        }
    }
}
func handleConn(conn net.Conn) {
    ch := make(chan string) // 對(duì)外發(fā)送客戶消息的通道
    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()
    }
    // 注意:忽略 input.Err() 中可能的錯(cuò)誤

    leaving <- ch
    messages <- who + " 下線"
    conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
    for msg := range ch {
        fmt.Fprintln(conn, msg) // 注意:忽略網(wǎng)絡(luò)層面的錯(cuò)誤
    }
}

代碼中 main 函數(shù)里面寫(xiě)的代碼非常簡(jiǎn)單,其實(shí)服務(wù)器要做的事情總結(jié)一下無(wú)非就是獲得 listener 對(duì)象,然后不停的獲取鏈接上來(lái)的 conn 對(duì)象,最后把這些對(duì)象丟給處理鏈接函數(shù)去進(jìn)行處理。

在使用 handleConn 方法處理 conn 對(duì)象的時(shí)候,對(duì)不同的鏈接都啟一個(gè) goroutine 去并發(fā)處理每個(gè) conn 這樣則無(wú)需等待。

由于要給所有在線的用戶發(fā)送消息,而不同用戶的 conn 對(duì)象都在不同的 goroutine 里面,但是Go語(yǔ)言中有 channel 來(lái)處理各不同 goroutine 之間的消息傳遞,所以在這里我們選擇使用 channel 在各不同的 goroutine 中傳遞廣播消息。

下面來(lái)介紹一下 broadcaster 廣播器,它使用局部變量 clients 來(lái)記錄當(dāng)前連接的客戶集合,每個(gè)客戶唯一被記錄的信息是其對(duì)外發(fā)送消息通道的 ID,下面是細(xì)節(jié):

type client chan<- string // 對(duì)外發(fā)送消息的通道

var (
    entering = make(chan client)
    leaving  = make(chan client)
    messages = make(chan string) // 所有連接的客戶端
)

func broadcaster() {
    clients := make(map[client]bool)
    for {
        select {
        case msg := <-messages:
            // 把所有接收到的消息廣播給所有客戶端
            // 發(fā)送消息通道
            for cli := range clients {
                cli <- msg
            }

        case cli := <-entering:
            clients[cli] = true

        case cli := <-leaving:
            delete(clients, cli)
            close(cli)
        }
    }
}

在 main 函數(shù)里面使用 goroutine 開(kāi)啟了一個(gè) broadcaster 函數(shù)來(lái)負(fù)責(zé)廣播所有用戶發(fā)送的消息。

這里使用一個(gè)字典來(lái)保存用戶 clients,字典的 key 是各連接申明的單向并發(fā)隊(duì)列。

使用一個(gè) select 開(kāi)啟一個(gè)多路復(fù)用:

  • 每當(dāng)有廣播消息從 messages 發(fā)送進(jìn)來(lái),都會(huì)循環(huán) cliens 對(duì)里面的每個(gè) channel 發(fā)消息。
  • 每當(dāng)有消息從 entering 里面發(fā)送過(guò)來(lái),就生成一個(gè)新的 key - value,相當(dāng)于給 clients 里面增加一個(gè)新的 client。
  • 每當(dāng)有消息從 leaving 里面發(fā)送過(guò)來(lái),就刪掉這個(gè) key - value 對(duì),并關(guān)閉對(duì)應(yīng)的 channel。

下面再來(lái)看一下每個(gè)客戶自己的 goroutine。

handleConn 函數(shù)創(chuàng)建一個(gè)對(duì)外發(fā)送消息的新通道,然后通過(guò) entering 通道通知廣播者新客戶到來(lái),接著它讀取客戶發(fā)來(lái)的每一行文本,通過(guò)全局接收消息通道將每一行發(fā)送給廣播者,發(fā)送時(shí)在每條消息前面加上發(fā)送者 ID 作為前綴。一旦從客戶端讀取完畢消息,handleConn 通過(guò) leaving 通道通知客戶離開(kāi),然后關(guān)閉連接。

func handleConn(conn net.Conn) {
    ch := make(chan string) // 對(duì)外發(fā)送客戶消息的通道
    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()
    }
    // 注意:忽略 input.Err() 中可能的錯(cuò)誤

    leaving <- ch
    messages <- who + " 下線"
    conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
    for msg := range ch {
        fmt.Fprintln(conn, msg) // 注意:忽略網(wǎng)絡(luò)層面的錯(cuò)誤
    }
}

handleConn 函數(shù)會(huì)為每個(gè)過(guò)來(lái)處理的 conn 都創(chuàng)建一個(gè)新的 channel,開(kāi)啟一個(gè)新的 goroutine 去把發(fā)送給這個(gè) channel 的消息寫(xiě)進(jìn) conn。

handleConn 函數(shù)的執(zhí)行過(guò)程可以簡(jiǎn)單總結(jié)為如下幾個(gè)步驟:

  • 獲取連接過(guò)來(lái)的 ip 地址和端口號(hào);
  • 把歡迎信息寫(xiě)進(jìn) channel 返回給客戶端;
  • 生成一條廣播消息寫(xiě)進(jìn) messages 里;
  • 把這個(gè) channel 加入到客戶端集合,也就是 entering <- ch;
  • 監(jiān)聽(tīng)客戶端往 conn 里寫(xiě)的數(shù)據(jù),每掃描到一條就將這條消息發(fā)送到廣播 channel 中;
  • 如果關(guān)閉了客戶端,那么把隊(duì)列離開(kāi)寫(xiě)入 leaving 交給廣播函數(shù)去刪除這個(gè)客戶端并關(guān)閉這個(gè)客戶端;
  • 廣播通知其他客戶端該客戶端已關(guān)閉;
  • 最后關(guān)閉這個(gè)客戶端的連接 Conn.Close()。

客戶端程序

前面對(duì)服務(wù)端做了簡(jiǎn)單的介紹,下面介紹客戶端,這里將其命名為“netcat.go”,完整代碼如下所示:

// netcat 是一個(gè)簡(jiǎn)單的TCP服務(wù)器讀/寫(xiě)客戶端
package main

import (
    "io"
    "log"
    "net"
    "os"
)

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) // 注意:忽略錯(cuò)誤
        log.Println("done")
        done <- struct{}{} // 向主Goroutine發(fā)出信號(hào)
    }()
    mustCopy(conn, os.Stdin)
    conn.Close()
    <-done // 等待后臺(tái)goroutine完成
}

func mustCopy(dst io.Writer, src io.Reader) {
    if _, err := io.Copy(dst, src); err != nil {
        log.Fatal(err)
    }
}

當(dāng)有 n 個(gè)客戶 session 在連接的時(shí)候,程序并發(fā)運(yùn)行著
2n+2 個(gè)相互通信的 goroutine,它不需要隱式的加鎖操作。clients map 限制在廣播器這一個(gè) goroutine 中被訪問(wèn),所以不會(huì)并發(fā)訪問(wèn)它。唯一被多個(gè) goroutine 共享的變量是通道以及 net.Conn 的實(shí)例,它們又都是并發(fā)安全的。

使用
go build 命令編譯服務(wù)端和客戶端,并運(yùn)行生成的可執(zhí)行文件。

下圖中展示了在同一臺(tái)計(jì)算機(jī)上運(yùn)行的一個(gè)服務(wù)端和三個(gè)客戶端:


新聞標(biāo)題:創(chuàng)新互聯(lián)GO教程:Go語(yǔ)言聊天服務(wù)器
URL標(biāo)題:http://uogjgqi.cn/article/djjdeco.html
掃二維碼與項(xiàng)目經(jīng)理溝通

我們?cè)谖⑿派?4小時(shí)期待你的聲音

解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流