GO語(yǔ)言初級(jí)學(xué)習(xí)之代碼案例13 (QQ群聊)

@(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程
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容