基于Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)聊天室(命令行模式)
實(shí)戰(zhàn)簡(jiǎn)介
網(wǎng)絡(luò)聊天室(命令行模式)
要求:
- 輸入網(wǎng)名,可以進(jìn)入聊天室
- 聊天內(nèi)信息實(shí)時(shí)更新
- 利用協(xié)程處理多任務(wù)并發(fā)
基于tcp協(xié)議實(shí)現(xiàn)功能
服務(wù)器端
接受用戶消息和循環(huán)轉(zhuǎn)發(fā)
對(duì)功能命令進(jìn)行處理
- ./cd1 或 ./menu 功能菜單
- ./cd2 或 ./changeName 更改昵稱
- ./cd3 或 ./online 在線用戶數(shù)量查詢
- ./cd4 或 ./quit 退出聊天室
客戶端
接受服務(wù)器發(fā)送的信息并處理
接受用戶的輸入處理后發(fā)往服務(wù)器
結(jié)構(gòu)和示例
用戶登錄示例
功能行命令測(cè)試
發(fā)送消息廣播測(cè)試
用戶退出示例
服務(wù)器端
基本流程
1. 初始化
init() 函數(shù)被調(diào)用,用于初始化全局變量:
- 創(chuàng)建一個(gè)映射 onlineList 用來(lái)保存在線用戶的信息。
- 創(chuàng)建一個(gè)帶緩沖的消息通道 message 用于廣播消息給所有在線用戶。
2. 主函數(shù) main()
- 使用 net.Listen 監(jiān)聽(tīng) TCP 地址 "127.0.0.1:8080"。
- 啟動(dòng) manger() 協(xié)程來(lái)監(jiān)聽(tīng)消息通道 message,并將消息廣播給所有在線用戶。
- 主循環(huán)中,使用 Accept 接受新的客戶端連接,并為每個(gè)客戶端啟動(dòng)一個(gè)新的協(xié)程 handleConnection(conn)。
3. 處理客戶端連接 handleConnection(conn)
對(duì)于每個(gè)客戶端連接,首先增加在線用戶計(jì)數(shù) count。
調(diào)用 addUser(conn) 添加新用戶到在線用戶列表,并返回一個(gè) client 結(jié)構(gòu)體實(shí)例。
創(chuàng)建一個(gè) quit 通道,用于在客戶端斷開(kāi)連接時(shí)發(fā)送信號(hào)。
啟動(dòng)兩個(gè)協(xié)程:
- writeMsgToClient(conn, quit):從用戶的 userChannel 中讀取消息并發(fā)送給客戶端。
- readClient(conn, quit):從客戶端讀取消息并處理。
監(jiān)聽(tīng) quit 通道以檢測(cè)客戶端是否斷開(kāi)連接。
4. 添加新用戶 addUser(conn)
創(chuàng)建一個(gè)新的 client 實(shí)例,其中包含一個(gè)用于消息的通道 userChannel、客戶端連接 conn 和默認(rèn)名稱(客戶端地址)。
將新用戶添加到 onlineList 映射中。
5. 管理消息廣播 manger()
從消息通道 message 中讀取消息,并將消息廣播給所有在線用戶。
6. 寫(xiě)消息到客戶端 writeMsgToClient(conn, quit)
從客戶端的 userChannel 讀取消息,使用 module.Encode 對(duì)消息進(jìn)行編碼,并通過(guò)客戶端連接 conn 發(fā)送給客戶端。
如果發(fā)生錯(cuò)誤或客戶端斷開(kāi)連接,關(guān)閉客戶端連接并發(fā)送信號(hào)到 quit 通道。
7. 讀取客戶端消息 readClient(conn, quit)
從客戶端讀取消息,根據(jù)消息的內(nèi)容執(zhí)行不同的操作:
- 如果消息是以 !@#$@!cd1changeName 開(kāi)頭,則處理昵稱更改請(qǐng)求。
- 如果消息是以 !@#$@!cd4exit 開(kāi)頭,則處理客戶端退出請(qǐng)求。
- 如果消息是以 !@#$@!menu 開(kāi)頭,則向客戶端發(fā)送菜單命令。
- 如果消息是以 !@#$@!cd3online 開(kāi)頭,則向客戶端發(fā)送在線用戶數(shù)量。
- 其他情況下,將消息廣播給所有在線用戶。
如果客戶端斷開(kāi)連接,則發(fā)送信號(hào)到 quit 通道。
8. 處理客戶端退出
當(dāng) quit 通道接收到信號(hào)時(shí),從在線用戶列表中刪除該客戶端,并減少在線用戶計(jì)數(shù)。
如果所有客戶端都已斷開(kāi)連接,則輸出“等待用戶連接中…”。
代碼
package main import ( "bufio" "chatRoom/chatRoom/module" // 消息的編碼和解碼模塊 "fmt" "io" "log" "net" "strconv" "strings" "sync" "time" ) // 定義客戶端結(jié)構(gòu)體 type client struct { userChannel chan string // 用戶的消息通道 conn net.Conn // 網(wǎng)絡(luò)連接 name string // 用戶名 addr string // 客戶端地址 } // 定義在線用戶計(jì)數(shù)器 var count int // 定義互斥鎖 var mu sync.Mutex // 定義在線用戶列表 var onlineList map[string]*client // 定義消息廣播通道 var message chan string // 初始化函數(shù) func init() { onlineList = make(map[string]*client) // 初始化在線用戶列表 message = make(chan string, 1024) // 初始化消息廣播通道 } // 主函數(shù) func main() { fmt.Println("端口監(jiān)聽(tīng)中...") listener, err := net.Listen("tcp", "127.0.0.1:8080") if err != nil { log.Fatal(err) // 如果監(jiān)聽(tīng)失敗,記錄錯(cuò)誤并退出程序 } defer listener.Close() time.Sleep(time.Second) fmt.Println("端口監(jiān)聽(tīng)成功") // 啟動(dòng)管理消息廣播的協(xié)程 go manger() // 主循環(huán),接受客戶端連接 for { conn, err := listener.Accept() if err != nil { continue } go handleConnection(conn) } } // 處理客戶端連接的函數(shù) func handleConnection(conn net.Conn) { defer conn.Close() count++ // 增加在線用戶計(jì)數(shù) fmt.Println("有新用戶連接服務(wù),當(dāng)前連接數(shù):", count) // 添加新用戶 addUser(conn) // 創(chuàng)建退出信號(hào)通道 var quit = make(chan bool) // 啟動(dòng)寫(xiě)消息到客戶端的協(xié)程 go writeMsgToClient(conn, quit) // 啟動(dòng)讀取客戶端消息的協(xié)程 go readClient(conn, quit) // 監(jiān)聽(tīng)退出信號(hào) select { case <-quit: // 用戶下線處理 connName := onlineList[conn.RemoteAddr().String()].name mu.Lock() close(onlineList[conn.RemoteAddr().String()].userChannel) mu.Unlock() mu.Lock() delete(onlineList, conn.RemoteAddr().String()) mu.Unlock() count-- message <- "< 系統(tǒng)消息 > [ " + connName + " ]" + "下線了 當(dāng)前在線人數(shù) " + strconv.Itoa(len(onlineList)) + " 人" fmt.Println("有用戶下線了,當(dāng)前連接數(shù):", count) if count == 0 { fmt.Println("等待用戶連接中...") } return } } // 修改用戶名的方法 func (c *client) changeName(newUserName string) bool { mu.Lock() defer mu.Unlock() // 更新用戶名 c.name = newUserName return true } // 管理消息廣播的函數(shù) func manger() { fmt.Println("開(kāi)始監(jiān)聽(tīng) message通道") defer fmt.Println("結(jié)束監(jiān)聽(tīng) message通道") for msg := range message { mu.Lock() for _, v := range onlineList { v.userChannel <- msg } mu.Unlock() } } // 寫(xiě)消息到客戶端的協(xié)程 func writeMsgToClient(conn net.Conn, quit chan bool) { fmt.Println(onlineList[conn.RemoteAddr().String()].name, "的信息通道監(jiān)聽(tīng)成功") defer fmt.Println(onlineList[conn.RemoteAddr().String()].name, "的信息通道監(jiān)聽(tīng)結(jié)束") for msg := range onlineList[conn.RemoteAddr().String()].userChannel { king, err := module.Encode(msg + "\n") if err != nil { fmt.Println("發(fā)送消息失敗") continue } _, err = conn.Write(king) if err != nil { fmt.Println("發(fā)送消息失敗") quit <- true } } fmt.Println("函數(shù)writeMsgToClient函數(shù)結(jié)束") } // 添加新用戶 func addUser(conn net.Conn) client { fmt.Println("開(kāi)始使用添加新用戶" + conn.RemoteAddr().String()) newUser := client{ make(chan string), // 創(chuàng)建用戶消息通道 conn, // 網(wǎng)絡(luò)連接 conn.RemoteAddr().String(), // 用戶名,初始化為客戶端地址 conn.RemoteAddr().String(), // 客戶端地址 } onlineList[conn.RemoteAddr().String()] = &newUser // 添加到在線用戶列表 fmt.Println("addUser函數(shù)結(jié)束,用戶" + conn.RemoteAddr().String() + "添加成功") return newUser } // 讀取客戶端消息的協(xié)程 func readClient(conn net.Conn, quit chan bool) { fmt.Println("開(kāi)始讀取客戶端發(fā)送的信息") defer fmt.Println("客戶端發(fā)送信息讀取結(jié)束") userChannel := onlineList[conn.RemoteAddr().String()].userChannel reader := bufio.NewReader(conn) for { msg, err := module.Decode(reader) if err == io.EOF { quit <- true } if err != nil { fmt.Println("decode msg failed, err:", err) quit <- true } if len(msg) == 0 { continue } fmt.Println("收到client發(fā)來(lái)的數(shù)據(jù):", msg) // 處理客戶端發(fā)送的不同類型的消息 switch { case strings.HasPrefix(msg, "!@#$@!cd1changeName"): king := true oldName := onlineList[conn.RemoteAddr().String()].name newName := strings.TrimPrefix(msg, "!@#$@!cd1changeName") if strings.HasPrefix(msg, "!@#$@!cd1changeNameFirst") { newName = strings.TrimPrefix(msg, "!@#$@!cd1changeNameFirst") } if newName == "" { newName = conn.RemoteAddr().String() } for _, v := range onlineList { mapName := v.name if mapName == newName { king = false break } } if strings.HasPrefix(msg, "!@#$@!cd1changeNameFirst") && king == false { message <- "< 系統(tǒng)消息 > [ " + conn.RemoteAddr().String() + " ] [ " + oldName + " ] 上線了!" userChannel <- "< 系統(tǒng)消息 > [ " + onlineList[conn.RemoteAddr().String()].name + " ]" + "名字: " + newName + " 已存在,請(qǐng)更換一個(gè)名字嘗試" userChannel <- "< 系統(tǒng)消息 > 你當(dāng)前昵稱為: " + oldName + " ( 輸入cd2可進(jìn)行名字修改 )" continue } if king == false { userChannel <- "< 系統(tǒng)消息 > 昵稱修改失?。。。? userChannel <- "< 系統(tǒng)消息 > [ " + onlineList[conn.RemoteAddr().String()].name + " ]" + "名字: " + newName + " 已存在,請(qǐng)更換一個(gè)名字嘗試" userChannel <- "< 系統(tǒng)消息 > 你當(dāng)前昵稱為: " + oldName continue } isSuccess := onlineList[conn.RemoteAddr().String()].changeName(newName) if isSuccess { userChannel <- "!@#$@!cd1changeName" + newName if strings.HasPrefix(msg, "!@#$@!cd1changeNameFirst") { message <- "< 系統(tǒng)消息 > [ " + conn.RemoteAddr().String() + " ] [ " + newName + " ] 上線了!" time.Sleep(time.Millisecond * 50) userChannel <- "< 系統(tǒng)消息 > 你當(dāng)前的昵稱為:" + newName continue } userChannel <- "< 系統(tǒng)消息 > 昵稱修改成功 你當(dāng)前昵稱為: " + newName } else { userChannel <- "< 系統(tǒng)消息 > 昵稱修改失敗?。?!" } message <- "< 系統(tǒng)消息 > [ " + conn.RemoteAddr().String() + " ]" + " 舊昵稱為: " + oldName + " 新昵稱為: " + newName case strings.HasPrefix(msg, "!@#$@!cd4exit"): fmt.Println("[ " + onlineList[conn.RemoteAddr().String()].name + " ] " + "下線了") quit <- true return case strings.HasPrefix(msg, "!@#$@!menu"): userChannel <- "< 系統(tǒng)消息 > \n * ./cd1 或 ./menu 功能菜單\n * ./cd2 或 ./changeName 更改昵稱\n * ./cd3 或 ./online 在線用戶數(shù)量查詢\n * ../cd4 或 ./quit 退出聊天室" case strings.HasPrefix(msg, "!@#$@!cd3online"): fmt.Println("在線人數(shù):", count) userChannel <- "< 系統(tǒng)消息 > 當(dāng)前在線人數(shù):" + strconv.Itoa(count) default: message <- "[ " + onlineList[conn.RemoteAddr().String()].name + " ]" + ": " + msg fmt.Println("信息廣播成功") } } }
客戶端
基本流程
1. 主函數(shù) main()
- 嘗試連接到服務(wù)器 "127.0.0.1:8080"。
- 如果連接失敗,打印錯(cuò)誤信息并退出程序。
- 如果連接成功,獲取用戶的昵稱并發(fā)送給服務(wù)器。
- 創(chuàng)建一個(gè) exit 通道,用于接收退出信號(hào)。
- 打印歡迎信息和命令提示。
- 啟動(dòng)一個(gè)協(xié)程 readMsg(conn) 用于讀取消息。
- 啟動(dòng)另一個(gè)協(xié)程用于處理用戶輸入。
- 主循環(huán)中監(jiān)聽(tīng) exit 通道,如果接收到信號(hào)則退出程序。
2. 獲取用戶輸入 getUserInput(prompt string)
- 根據(jù)傳入的提示信息顯示相應(yīng)的提示。
- 讀取用戶從標(biāo)準(zhǔn)輸入的輸入。
- 返回去除空白字符的輸入字符串。
3. 讀取消息 readMsg(conn)
- 從服務(wù)器讀取消息。
- 如果讀取到 EOF (文件結(jié)束),則表示服務(wù)器連接已斷開(kāi),終止程序。
- 如果讀取過(guò)程中出現(xiàn)其他錯(cuò)誤,則打印錯(cuò)誤信息并退出函數(shù)。
- 如果接收到的消息為空,則跳過(guò)本次循環(huán)。
- 如果接收到的消息是以 !@#$@!cd1changeName 開(kāi)頭,則更新用戶的昵稱。
- 打印接收到的消息的時(shí)間戳和內(nèi)容。
4. 處理用戶輸入
- 循環(huán)讀取用戶輸入,并根據(jù)不同的命令構(gòu)建消息。
- 如果命令是 ./cd1 或 ./menu,則發(fā)送 !@#$@!menu 消息。
- 如果命令是 ./cd2 或 ./changeName,則請(qǐng)求用戶輸入新的昵稱,并發(fā)送 !@#$@!cd1changeName 加上新的昵稱。
- 如果命令是 ./cd3 或 ./online,則發(fā)送 !@#$@!cd3online 消息。
- 如果命令是 cd4 或 ./quit,則發(fā)送 !@#$@!cd4exit 消息,并發(fā)送退出信號(hào)到 exit 通道。
- 對(duì)于其他消息,編碼并發(fā)送到服務(wù)器。
代碼
package main import ( "bufio" "chatRoom/chatRoom/module" // 消息的編碼和解碼模塊 "fmt" "io" "net" "os" "strings" "time" ) // 定義一個(gè)全局變量用于存儲(chǔ)用戶的昵稱 var name string // 主函數(shù) func main() { // 嘗試連接到服務(wù)器 conn, err := net.Dial("tcp", "127.0.0.1:8080") if err != nil { fmt.Println("服務(wù)器連接失敗 err =", err) return } defer conn.Close() // 打印連接成功的消息 fmt.Println("服務(wù)器連接成功") // 獲取用戶的昵稱 name = getUserInput("請(qǐng)輸入你的昵稱:") // 構(gòu)建一條特殊的消息,用于通知服務(wù)器用戶昵稱 data, err := module.Encode("!@#$@!cd1changeNameFirst" + name) if err != nil { fmt.Println("encode msg failed, err:", err) return } // 發(fā)送消息到服務(wù)器 _, err = conn.Write(data) if err != nil { fmt.Println("發(fā)送數(shù)據(jù)失敗1 err =", err) } // 創(chuàng)建一個(gè)通道,用于接收退出信號(hào) var exit = make(chan bool) // 確保在函數(shù)退出時(shí)關(guān)閉通道 defer close(exit) // 顯示歡迎信息和命令提示 fmt.Println("--------------歡迎進(jìn)入多人聊天室系統(tǒng)----------------") fmt.Println(" * ./cd1 或 ./menu 功能菜單") fmt.Println(" * ./cd2 或 ./changeName 更改昵稱") fmt.Println(" * ./cd3 或 ./online 在線用戶數(shù)量查詢") fmt.Println(" * ./cd4 或 ./quit 退出聊天室") fmt.Println("---------------指令字母不區(qū)分大小寫(xiě)-----------------") // 啟動(dòng)一個(gè)協(xié)程用于讀取消息 go readMsg(conn) // 啟動(dòng)一個(gè)協(xié)程用于處理用戶輸入 go func() { for { // 獲取用戶輸入 msg := getUserInput("") // 根據(jù)用戶輸入特殊消息處理命令 if strings.EqualFold(msg, "./cd1") || strings.EqualFold(msg, "./menu") { msg = "!@#$@!menu" } if strings.EqualFold(msg, "./cd2") || strings.EqualFold(msg, "./changeName") { newMsg := getUserInput("請(qǐng)輸入新的昵稱:") msg = "!@#$@!cd1changeName" + newMsg } if strings.EqualFold(msg, "./cd3") || strings.EqualFold(msg, "./online") { msg = "!@#$@!cd3online" } if strings.EqualFold(msg, "./cd4") || strings.EqualFold(msg, "./quit") { msg = "!@#$@!cd4exit" // 編碼并發(fā)送退出消息 data, err := module.Encode(msg) if err != nil { fmt.Println("消息數(shù)據(jù)失敗1, err:", err) return } _, err = conn.Write(data) if err != nil { // 如果發(fā)送失敗,打印錯(cuò)誤信息 fmt.Println("發(fā)送數(shù)據(jù)失敗2 err =", err) } // 打印退出信息 fmt.Println("正在退出...") // 發(fā)送退出信號(hào) exit <- true return } // 編碼并發(fā)送普通消息 data, err := module.Encode(msg) if err != nil { // 如果消息編碼失敗,打印錯(cuò)誤信息并退出協(xié)程 fmt.Println("發(fā)送數(shù)據(jù)失敗3, err:", err) return } _, err = conn.Write(data) if err != nil { // 如果發(fā)送失敗,打印錯(cuò)誤信息 fmt.Println("發(fā)送數(shù)據(jù)失敗4 err =", err) } } }() // 主循環(huán),監(jiān)聽(tīng)退出信號(hào) for { select { case <-exit: // 當(dāng)收到退出信號(hào)時(shí),打印退出成功并退出程序 fmt.Println("退出成功") return } } } // getUserInput 函數(shù)用于獲取用戶輸入 func getUserInput(prompt string) string { time.Sleep(time.Millisecond * 100) // 根據(jù)不同的提示信息顯示相應(yīng)的提示 switch prompt { case "請(qǐng)輸入你的昵稱:": fmt.Print("請(qǐng)輸入你的昵稱:") case "請(qǐng)輸入新的昵稱:": fmt.Println("請(qǐng)輸入新的昵稱:") } // 創(chuàng)建一個(gè)標(biāo)準(zhǔn)輸入的緩沖讀取器 reader := bufio.NewReader(os.Stdin) // 讀取一行輸入 input, err := reader.ReadString('\n') if err != nil { // 如果讀取失敗,打印錯(cuò)誤信息并返回錯(cuò)誤信息 fmt.Println("用戶輸入獲取失?。篹rr =", err) return "客戶端信息讀取錯(cuò)誤" } // 返回去掉空格的輸入字符串 return strings.TrimSpace(input) } // readMsg 函數(shù)用于讀取消息 func readMsg(conn net.Conn) { defer conn.Close() // 創(chuàng)建一個(gè)緩沖讀取器來(lái)讀取連接中的數(shù)據(jù) reader := bufio.NewReader(conn) for { // 解碼消息 msg, err := module.Decode(reader) if err == io.EOF { // 如果遇到EOF(文件結(jié)束),表示連接已斷開(kāi) fmt.Println("服務(wù)器連接已斷開(kāi) ") // 終止程序 os.Exit(1) } if err != nil { fmt.Println("服務(wù)器斷開(kāi)連接 2 err =", err) return } if msg == "" { // 如果消息為空,則跳過(guò)本次循環(huán) continue } if strings.HasPrefix(msg, "!@#$@!cd1changeName") { // 如果消息是更改昵稱的通知 msg1 := strings.TrimPrefix(msg, "!@#$@!cd1changeName") name = strings.TrimRight(msg1, "\n") // 更新昵稱 continue } // 打印消息的時(shí)間戳和內(nèi)容 fmt.Print("【 ", time.Now().Format("15:04"), " 】", msg) } }
消息封包和解包的函數(shù)
作用:防止tcp粘包的情況影響消息的讀取
1.為什么會(huì)出現(xiàn)粘包
主要原因就是tcp數(shù)據(jù)傳遞模式是流模式,在保持長(zhǎng)連接的時(shí)候可以進(jìn)行多次的收和發(fā)。
“粘包"可發(fā)生在發(fā)送端也可發(fā)生在接收端:
- 由Nagle算法造成的發(fā)送端的粘包:Nagle算法是一種改善網(wǎng)絡(luò)傳輸效率的算法。簡(jiǎn)單來(lái)說(shuō)就是當(dāng)我們提交一段數(shù)據(jù)給TCP發(fā)送時(shí),TCP并不立刻發(fā)送此段數(shù)據(jù),而是等待一小段時(shí)間看看在等待期間是否還有要發(fā)送的數(shù)據(jù),若有則會(huì)一次把這兩段數(shù)據(jù)發(fā)送出去。
- 接收端接收不及時(shí)造成的接收端粘包:TCP會(huì)把接收到的數(shù)據(jù)存在自己的緩沖區(qū)中,然后通知應(yīng)用層取數(shù)據(jù)。當(dāng)應(yīng)用層由于某些原因不能及時(shí)的把TCP的數(shù)據(jù)取出來(lái),就會(huì)造成TCP緩沖區(qū)中存放了幾段數(shù)據(jù)。
2.解決辦法
出現(xiàn)"粘包"的關(guān)鍵在于接收方不確定將要傳輸?shù)臄?shù)據(jù)包的大小,因此我們可以對(duì)數(shù)據(jù)包進(jìn)行封包和拆包的操作。
封包:封包就是給一段數(shù)據(jù)加上包頭,這樣一來(lái)數(shù)據(jù)包就分為包頭和包體兩部分內(nèi)容了(過(guò)濾非法包時(shí)封包會(huì)加入"包尾"內(nèi)容)。包頭部分的長(zhǎng)度是固定的,并且它存儲(chǔ)了包體的長(zhǎng)度,根據(jù)包頭長(zhǎng)度固定以及包頭中含有包體長(zhǎng)度的變量就能正確的拆分出一個(gè)完整的數(shù)據(jù)包。
我們可以自己定義一個(gè)協(xié)議,比如數(shù)據(jù)包的前4個(gè)字節(jié)為包頭,里面存儲(chǔ)的是發(fā)送的數(shù)據(jù)的長(zhǎng)度。
代碼
package module import ( "bufio" "bytes" "encoding/binary" ) func Encode(message string) ([]byte, error) { // 讀取消息的長(zhǎng)度,轉(zhuǎn)換成int32類型(占4個(gè)字節(jié)) var length = int32(len(message)) var pkg = new(bytes.Buffer) // 寫(xiě)入消息頭 err := binary.Write(pkg, binary.LittleEndian, length) if err != nil { return nil, err } // 寫(xiě)入消息實(shí)體 err = binary.Write(pkg, binary.LittleEndian, []byte(message)) if err != nil { return nil, err } return pkg.Bytes(), nil } // Decode 解碼消息 func Decode(reader *bufio.Reader) (string, error) { // 讀取消息的長(zhǎng)度 lengthByte, _ := reader.Peek(4) // 讀取前4個(gè)字節(jié)的數(shù)據(jù) lengthBuff := bytes.NewBuffer(lengthByte) var length int32 err := binary.Read(lengthBuff, binary.LittleEndian, &length) if err != nil { return "", err } // Buffered返回緩沖中現(xiàn)有的可讀取的字節(jié)數(shù)。 if int32(reader.Buffered()) < length+4 { return "", err } // 讀取真正的消息數(shù)據(jù) pack := make([]byte, int(4+length)) _, err = reader.Read(pack) if err != nil { return "", err } return string(pack[4:]), nil }
以上就是基于Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)聊天室(命令行模式)的詳細(xì)內(nèi)容,更多關(guān)于Go聊天室的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- golang實(shí)現(xiàn)一個(gè)簡(jiǎn)單的websocket聊天室功能
- GO使用socket和channel實(shí)現(xiàn)簡(jiǎn)單控制臺(tái)聊天室
- Go語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的并發(fā)聊天室的項(xiàng)目實(shí)戰(zhàn)
- 300行代碼實(shí)現(xiàn)go語(yǔ)言即時(shí)通訊聊天室
- Golang使用gin框架實(shí)現(xiàn)一個(gè)完整的聊天室功能
- Go構(gòu)建WiFi局域網(wǎng)聊天室示例詳解
- Go語(yǔ)言通過(guò)TCP協(xié)議實(shí)現(xiàn)聊天室功能
相關(guān)文章
淺析Go語(yǔ)言容器之?dāng)?shù)組和切片的使用
在?Java?的核心庫(kù)中,集合框架可謂鼎鼎大名:Array?、List、Set等等,隨便拎一個(gè)出來(lái)都值得開(kāi)發(fā)者好好學(xué)習(xí)如何使用甚至是背后的設(shè)計(jì)源碼。雖然Go語(yǔ)言沒(méi)有如此豐富的容器類型,但也有一些基本的容器供開(kāi)發(fā)者使用,接下來(lái)讓我們認(rèn)識(shí)一下這些容器類型吧2022-11-11Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例
這篇文章主要為大家介紹了Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景的基本示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12