欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)聊天室(命令行模式)

 更新時(shí)間:2025年02月21日 10:19:27   作者:千年死緩  
這篇文章主要為大家詳細(xì)介紹了如何基于Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單網(wǎng)絡(luò)聊天室,文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下

實(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)文章!

相關(guān)文章

  • GO語(yǔ)言延遲函數(shù)defer用法分析

    GO語(yǔ)言延遲函數(shù)defer用法分析

    這篇文章主要介紹了GO語(yǔ)言延遲函數(shù)defer用法,較為詳細(xì)的分析了GO語(yǔ)言的特性與具體用法,并給出了一個(gè)比較典型的應(yīng)用實(shí)例,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2014-12-12
  • Golang Recover處理錯(cuò)誤原理解析

    Golang Recover處理錯(cuò)誤原理解析

    Golang 中的?recover?是一個(gè)鮮為人知但非常有趣和強(qiáng)大的功能,讓我們看看它是如何工作的,以及在 Outreach.io 中如何利用它來(lái)處理 Kubernetes 中的錯(cuò)誤
    2023-12-12
  • 淺析Go語(yǔ)言容器之?dāng)?shù)組和切片的使用

    淺析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-11
  • Go語(yǔ)言中數(shù)組的基本用法演示

    Go語(yǔ)言中數(shù)組的基本用法演示

    這篇文章主要介紹了Go語(yǔ)言中數(shù)組的基本用法演示,包括一個(gè)冒泡排序算法的簡(jiǎn)單實(shí)現(xiàn),需要的朋友可以參考下
    2015-10-10
  • golang中的defer函數(shù)理解

    golang中的defer函數(shù)理解

    defer是Go語(yǔ)言中的延遲執(zhí)行語(yǔ)句,用來(lái)添加函數(shù)結(jié)束時(shí)執(zhí)行的代碼,常用于釋放某些已分配的資源、關(guān)閉數(shù)據(jù)庫(kù)連接、斷開(kāi)socket連接、解鎖一個(gè)加鎖的資源,這篇文章主要介紹了golang中的defer函數(shù)理解,需要的朋友可以參考下
    2022-10-10
  • Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例

    Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例

    這篇文章主要為大家介紹了Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景的基本示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Golang 中的直接依賴和間接依賴管理詳解

    Golang 中的直接依賴和間接依賴管理詳解

    在 Golang 中,依賴管理是非常重要的,直接依賴是指項(xiàng)目代碼中明確引用的其他包的依賴,而間接依賴是指直接依賴所引用的其他包的依賴,這篇文章主要介紹了Golang 中的直接依賴和間接依賴管理,需要的朋友可以參考下
    2023-11-11
  • GO項(xiàng)目配置與使用的方法步驟

    GO項(xiàng)目配置與使用的方法步驟

    本文主要介紹了GO項(xiàng)目配置與使用的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧<BR>
    2022-06-06
  • 淺談Go語(yǔ)言中的次方用法

    淺談Go語(yǔ)言中的次方用法

    這篇文章主要介紹了淺談Go語(yǔ)言中的次方用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Go設(shè)計(jì)模式之生成器模式詳細(xì)講解

    Go設(shè)計(jì)模式之生成器模式詳細(xì)講解

    生成器模式將一個(gè)復(fù)雜對(duì)象的構(gòu)建和它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。生成器模式的主要功能是構(gòu)建復(fù)雜的產(chǎn)品,而且是細(xì)化地、分步驟地構(gòu)建產(chǎn)品,也就是說(shuō)生成器模式重在一步一步解決構(gòu)建復(fù)雜對(duì)象的問(wèn)題
    2023-01-01

最新評(píng)論