Go語言實踐_實現一(服務器端)對多(客戶端)在線聊天室


一、目的

運用Go語言中的goroutine和通道實現一個簡單的一個服務器端對多個客戶端的在線聊天

軟件環境:Goland,Go1.9

代碼倉庫鏈接

 

二、設計思路

與一對一的設計思路類似,就是加了個線程的操作。

1,服務器端聲明一個map,並打開監聽端口;

2,客戶端打開監聽端口,同時連入服務器端;

3,在客戶端上給自己起一個昵稱,並輸出,同時啟動一個線程;

4,服務器端接收一個昵稱,並存入map;

5,聲明一個空的字符串,並寫入要群發的消息;

6,服務器端解析發送的消息(msg_str[0]的值):

  • nick:使該客戶端加入聊天室並廣播連上服務器端的所有其他客戶端;
  • say:廣播客戶端發出的消息;
  • quit:使該客戶端退出,斷開與服務器端的連接,並將退出消息廣播給其他連上服務器端的所有其他客戶端;

 

三、Go代碼

Server端

// one sever to more client chat room
//This is chat sever
package main

import (
    "fmt"
    "net"
    "strings"
)

var ConnMap map[string]net.Conn = make(map[string]net.Conn)  //聲明一個集合

//ConnMap := make(map[string]net.Conn)

func main() {
    listen_socket, err := net.Listen("tcp", "127.0.0.1:8000")  //打開監聽接口
    if err != nil {
        fmt.Println("server start error")
    }

    defer listen_socket.Close()
    fmt.Println("server is wating ....")

    for {
        conn, err := listen_socket.Accept()  //收到來自客戶端發來的消息
        if err != nil {
            fmt.Println("conn fail ...")
        }
        fmt.Println(conn.RemoteAddr(), "connect successed")

        go handle(conn)  //創建線程
    }
}

func handle(conn net.Conn) {
    for {
        data := make([]byte, 255)  //創建字節流 (此處同 一對一 通信)
        msg_read, err := conn.Read(data)  //聲明並將從客戶端讀取的消息賦給msg_read 和err
        if msg_read == 0 || err != nil {
            continue
        }

        //解析協議
        msg_str := strings.Split(string(data[0:msg_read]), "|")  //將從客戶端收到的字節流分段保存到msg_str這個數組中

        switch msg_str[0] {
        case "nick":  //加入聊天室
            fmt.Println(conn.RemoteAddr(), "-->", msg_str[1])  //nick占在數組下標0上,客戶端上寫的昵稱占在數組下標1上
            for k, v := range ConnMap {  //遍歷集合中存儲的客戶端消息
                if k != msg_str[1] {
                    v.Write([]byte("[" + msg_str[1] + "]: join..."))
                }
            }
            ConnMap[msg_str[1]] = conn
        case "say":   //轉發消息
            for k, v := range ConnMap {  //k指客戶端昵稱   v指客戶端連接服務器端后的地址
                if k != msg_str[1] {  //判斷是不是給自己發,如果不是
                    fmt.Println("Send "+msg_str[2]+" to ", k)  //服務器端將消息轉發給集合中的每一個客戶端
                    v.Write([]byte("[" + msg_str[1] + "]: " + msg_str[2]))  //給除了自己的每一個客戶端發送自己之前要發送的消息
                }
            }
        case "quit":  //退出
            for k, v := range ConnMap {  //遍歷集合中的客戶端昵稱
                if k != msg_str[1] {  //如果昵稱不是自己
                    v.Write([]byte("[" + msg_str[1] + "]: quit"))  //給除了自己的其他客戶端昵稱發送退出的消息,並使Write方法阻塞
                }
            }
            delete(ConnMap, msg_str[1])  //退出聊天室
        }
    }
}

 

Client端

// one sever to more client chat room
//This is chat client
package main

import (
    "fmt"
    "net"
)

var nick string = ""  //聲明聊天室的昵稱

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8000")  //打開監聽端口
    if err != nil {
        fmt.Println("conn fail...")
    }
    defer conn.Close()
    fmt.Println("client connect server successed \n")

    //給自己取一個聊天室的昵稱
    fmt.Printf("Make a nickname:")
    fmt.Scanf("%s", &nick)  //輸入昵稱
    fmt.Println("hello : ", nick)  //客戶端輸出
    conn.Write([]byte("nick|" + nick))  //將信息發送給服務器端

    go Handle(conn)  //創建線程

    var msg string
    for {
        msg = ""  //聲明一個空的消息
        fmt.Scan(&msg)  //輸入消息
        conn.Write([]byte("say|" + nick + "|" + msg))  //三段字節流 say | 昵稱 | 發送的消息
        if msg == "quit" {  //如果消息為quit
            conn.Write([]byte("quit|" + nick))  //將quit字節流發送給服務器端
            break  //程序結束運行
        }
    }
}

func Handle(conn net.Conn) {

    for {

        data := make([]byte, 255)  //創建一個字節流
        msg_read, err := conn.Read(data)  //將讀取的字節流賦值給msg_read和err
        if msg_read == 0 || err != nil {  //如果字節流為0或者有錯誤
            break
        }

        fmt.Println(string(data[0:msg_read]))  //把字節流轉換成字符串
    }
}

 

四、參考資料

Split

 

五、總結與感受

着重關注收發消息的判定,收消息后的解包過程和開多線程;注意發消息與收消息時字節流與字符串的轉換。

從初學Go到一對一再到一對多,我已經逐漸體會到使用Go語言做服務器端的方便與強大。

 

六、補充:還存在的問題

昨天把代碼發給服務器主程大佬看,他看過后提出以下需要考慮和完善的問題,先忽略程序設計上的問題:

程序正確性無法保證

  1. Read可能一次性收到兩個包,也可能收到半包。出現以上兩種情況的時候協議解析都會出現問題。
  2. Write不保證一次調用時全部寫完,存在短寫的情況。
  3. ConnMap非線程安全。func handle(conn net.Conn)是多線程環境運行的。
  4. 連接出錯及正常短開的情況未處理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM