溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用Go語(yǔ)言怎么實(shí)現(xiàn)一個(gè)多人聊天室

發(fā)布時(shí)間:2021-06-11 17:20:18 來(lái)源:億速云 閱讀:172 作者:Leah 欄目:編程語(yǔ)言

今天就跟大家聊聊有關(guān)使用Go語(yǔ)言怎么實(shí)現(xiàn)一個(gè)多人聊天室,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

功能需求

  • 實(shí)現(xiàn)單撩

  • 實(shí)現(xiàn)群撩

  • 實(shí)現(xiàn)用戶上線的全網(wǎng)通知

  • 實(shí)現(xiàn)用戶昵稱(chēng)

  • 實(shí)現(xiàn)聊天日志的存儲(chǔ)和查看

服務(wù)端實(shí)現(xiàn)

type Client struct {
 conn net.Conn
 name string
 addr string
}

var (
 //客戶端信息,用昵稱(chēng)為鍵
 //clientsMap = make(map[string]net.Conn)
 clientsMap = make(map[string]Client)
)

func SHandleError(err error, why string) {
 if err != nil {
 fmt.Println(why, err)
 os.Exit(1)
 }
}

func main() {

 //建立服務(wù)端監(jiān)聽(tīng)
 listener, e := net.Listen("tcp", "127.0.0.1:8888")
 SHandleError(e, "net.Listen")
 defer func() {
 for _, client := range clientsMap {
 client.conn.Write([]byte("all:服務(wù)器進(jìn)入維護(hù)狀態(tài),大家都洗洗睡吧!"))
 }
 listener.Close()
 }()

 for {
 //循環(huán)接入所有女朋友
 conn, e := listener.Accept()
 SHandleError(e, "listener.Accept")
 clientAddr := conn.RemoteAddr()

 //TODO:接收并保存昵稱(chēng)
 buffer := make([]byte, 1024)
 var clientName string
 for {
 n, err := conn.Read(buffer)
 SHandleError(err, "conn.Read(buffer)")
 if n > 0 {
 clientName = string(buffer[:n])
 break
 }
 }
 fmt.Println(clientName + "上線了")

 //TODO:將每一個(gè)女朋友丟入map
 client := Client{conn, clientName, clientAddr.String()}
 clientsMap[clientName] = client

 //TODO:給已經(jīng)在線的用戶發(fā)送上線通知——使用昵稱(chēng)
 for _, client := range clientsMap {
 client.conn.Write([]byte(clientName + "上線了"))
 }

 //在單獨(dú)的協(xié)程中與每一個(gè)具體的女朋友聊天
 go ioWithClient(client)
 }

 //設(shè)置優(yōu)雅退出邏輯

}

//與一個(gè)Client做IO
func ioWithClient(client Client) {
 //clientAddr := conn.RemoteAddr().String()
 buffer := make([]byte, 1024)

 for {
 n, err := client.conn.Read(buffer)
 if err != io.EOF {
 SHandleError(err, "conn.Read")
 }

 if n > 0 {
 msg := string(buffer[:n])
 fmt.Printf("%s:%s\n", client.name, msg)

 //將客戶端說(shuō)的每一句話記錄在【以他的名字命名的文件里】
 writeMsgToLog(msg, client)

 strs := strings.Split(msg, "#")
 if len(strs) > 1 {
 //all#hello
 //zqd#hello

 //要發(fā)送的目標(biāo)昵稱(chēng)
 targetName := strs[0]
 targetMsg := strs[1]

 //TODO:使用昵稱(chēng)定位目標(biāo)客戶端的Conn
 if targetName == "all" {
  //群發(fā)消息
  for _, c := range clientsMap {
  c.conn.Write([]byte(client.name + ":" + targetMsg))
  }
 } else {
  //點(diǎn)對(duì)點(diǎn)消息
  for key, c := range clientsMap {
  if key == targetName {
  c.conn.Write([]byte(client.name + ":" + targetMsg))

  //在點(diǎn)對(duì)點(diǎn)消息的目標(biāo)端也記錄日志
  go writeMsgToLog(client.name + ":" + targetMsg,c)
  break
  }
  }
 }

 } else {

 //客戶端主動(dòng)下線
 if msg == "exit" {
  //將當(dāng)前客戶端從在線用戶中除名
  //向其他用戶發(fā)送下線通知
  for name, c := range clientsMap {
  if c == client {
  delete(clientsMap, name)
  } else {
  c.conn.Write([]byte(name + "下線了"))
  }
  }
 }else if strings.Index(msg,"log@")==0 {
  //log@all
  //log@張全蛋
  filterName := strings.Split(msg, "@")[1]
  //向客戶端發(fā)送它的聊天日志
  go sendLog2Client(client,filterName)
 } else {
  client.conn.Write([]byte("已閱:" + msg))
 }

 }

 }
 }

}

//向客戶端發(fā)送它的聊天日志
func sendLog2Client(client Client,filterName string) {
 //讀取聊天日志
 logBytes, e := ioutil.ReadFile("D:/BJBlockChain1801/demos/W4/day1/01ChatRoomII/logs/" + client.name + ".log")
 SHandleError(e,"ioutil.ReadFile")

 if filterName != "all"{
 //查找與某個(gè)人的聊天記錄
 //從內(nèi)容中篩選出帶有【filterName#或filterName:】的行,拼接起來(lái)
 logStr := string(logBytes)
 targetStr := ""
 lineSlice := strings.Split(logStr, "\n")
 for _,lineStr := range lineSlice{
 if len(lineStr)>20{
 contentStr := lineStr[20:]
 if strings.Index(contentStr,filterName+"#")==0 || strings.Index(contentStr,filterName+":")==0{
  targetStr += lineStr+"\n"
 }
 }
 }
 client.conn.Write([]byte(targetStr))
 }else{
 //查詢所有的聊天記錄
 //向客戶端發(fā)送
 client.conn.Write(logBytes)
 }

}

//將客戶端說(shuō)的一句話記錄在【以他的名字命名的文件里】
func writeMsgToLog(msg string, client Client) {
 //打開(kāi)文件
 file, e := os.OpenFile(
 "D:/BJBlockChain1801/demos/W4/day1/01ChatRoomII/logs/"+client.name+".log",
 os.O_CREATE|os.O_WRONLY|os.O_APPEND,
 0644)
 SHandleError(e, "os.OpenFile")
 defer file.Close()

 //追加這句話
 logMsg := fmt.Sprintln(time.Now().Format("2006-01-02 15:04:05"), msg)
 file.Write([]byte(logMsg))
}

客戶端實(shí)現(xiàn)

import (
 "net"
 "fmt"
 "os"
 "bufio"
 "io"
 "flag"
)

var (
 chanQuit = make(chan bool, 0)
 conn  net.Conn
)

func CHandleError(err error, why string) {
 if err != nil {
 fmt.Println(why, err)

 os.Exit(1)
 }
}

func main() {

 //TODO:在命令行參數(shù)中攜帶昵稱(chēng)
 nameInfo := [3]interface{}{"name", "無(wú)名氏", "昵稱(chēng)"}
 retValuesMap := GetCmdlineArgs(nameInfo)
 name := retValuesMap["name"].(string)

 //撥號(hào)連接,獲得connection
 var e error
 conn, e = net.Dial("tcp", "127.0.0.1:8888")
 CHandleError(e, "net.Dial")
 defer func() {
 conn.Close()
 }()

 //在一條獨(dú)立的協(xié)程中輸入,并發(fā)送消息
 go handleSend(conn,name)

 //在一條獨(dú)立的協(xié)程中接收服務(wù)端消息
 go handleReceive(conn)

 //設(shè)置優(yōu)雅退出邏輯
 <-chanQuit

}

func handleReceive(conn net.Conn) {
 buffer := make([]byte, 1024)
 for {
 n, err := conn.Read(buffer)
 if err != io.EOF {
 CHandleError(err, "conn.Read")
 }

 if n > 0 {
 msg := string(buffer[:n])
 fmt.Println(msg)
 }
 }

}

func handleSend(conn net.Conn,name string) {
 //TODO:發(fā)送昵稱(chēng)到服務(wù)端
 _, err := conn.Write([]byte(name))
 CHandleError(err,"conn.Write([]byte(name))")

 reader := bufio.NewReader(os.Stdin)
 for {
 //讀取標(biāo)準(zhǔn)輸入
 lineBytes, _, _ := reader.ReadLine()

 //發(fā)送到服務(wù)端
 _, err := conn.Write(lineBytes)
 CHandleError(err, "conn.Write")

 //正常退出
 if string(lineBytes) == "exit" {
 os.Exit(0)
 }

 }
}

func GetCmdlineArgs(argInfos ...[3]interface{}) (retValuesMap map[string]interface{}) {

 fmt.Printf("type=%T,value=%v\n", argInfos, argInfos)

 //初始化返回結(jié)果
 retValuesMap = map[string]interface{}{}

 //預(yù)定義【用戶可能輸入的各種類(lèi)型的指針】
 var strValuePtr *string
 var intValuePtr *int

 //預(yù)定義【用戶可能輸入的各種類(lèi)型的指針】的容器
 //用戶可能輸入好幾個(gè)string型的參數(shù)值,存放在好幾個(gè)string型的指針中,將這些同種類(lèi)型的指針?lè)旁谕N類(lèi)型的map中
 //例如:flag.Parse()了以后,可以根據(jù)【strValuePtrsMap["cmd"]】拿到【存放"cmd"值的指針】
 var strValuePtrsMap = map[string]*string{}
 var intValuePtrsMap = map[string]*int{}

 /* var floatValuePtr *float32
 var floatValuePtrsMap []*float32
 var boolValuePtr *bool
 var boolValuePtrsMap []*bool*/

 //遍歷用戶需要接受的所有命令定義
 for _, argArray := range argInfos {

 /*
 先把每個(gè)命令的名稱(chēng)和用法拿出來(lái),
 這倆貨都是string類(lèi)型的,所有都可以通過(guò)argArray[i].(string)輕松愉快地獲得其字符串
 一個(gè)叫“cmd”,一個(gè)叫“你想干嘛”
 "cmd"一會(huì)會(huì)用作map的key
 */
 //[3]interface{}
 //["cmd" "未知類(lèi)型" "你想干嘛"]
 //["gid"  0  "要查詢的商品ID"]
 //上面的破玩意類(lèi)型[string 可能是任意類(lèi)型 string]
 nameValue := argArray[0].(string) //拿到第一個(gè)元素的string值,是命令的name
 usageValue := argArray[2].(string) //拿到最后一個(gè)元素的string值,是命令的usage

 //判斷argArray[1]的具體類(lèi)型
 switch argArray[1].(type) {
 case string:
 //得到【存放cmd的指針】,cmd的值將在flag.Parse()以后才會(huì)有
 //cmdValuePtr = flag.String("cmd", argArray[1].(string), "你想干嘛")
 strValuePtr = flag.String(nameValue, argArray[1].(string), usageValue)

 //將這個(gè)破指針以"cmd"為鍵,存在【專(zhuān)門(mén)放置string型指針的map,即strValuePtrsMap】中
 strValuePtrsMap[nameValue] = strValuePtr

 case int:
 //得到【存放gid的指針】,gid的值將在flag.Parse()以后才會(huì)有
 //gidValuePtr = flag.String("gid", argArray[1].(int), "商品ID")
 intValuePtr = flag.Int(nameValue, argArray[1].(int), usageValue)

 //將這個(gè)破指針以"gid"為鍵,存在【專(zhuān)門(mén)放置int型指針的map,即intValuePtrsMap】中
 intValuePtrsMap[nameValue] = intValuePtr
 }

 }

 /*
 程序運(yùn)行到這里,所有不同類(lèi)型的【存值指針】都放在對(duì)相應(yīng)類(lèi)型的map中了
 flag.Parse()了以后,可以從map中以參數(shù)名字獲取出【存值指針】,進(jìn)而獲得【用戶輸入的值】
 */

 //用戶輸入完了,解析,【用戶輸入的值】全都放在對(duì)應(yīng)的【存值指針】中
 flag.Parse()

 /*
 遍歷各種可能類(lèi)型的【存值指針的map】
 */
 if len(strValuePtrsMap) > 0 {
 //從【cmd存值指針的map】中拿取cmd的值,還以cmd為鍵存入結(jié)果map中
 for k, vPtr := range strValuePtrsMap {
 retValuesMap[k] = *vPtr
 }
 }
 if len(intValuePtrsMap) > 0 {
 //從【gid存值指針的map】中拿取gid的值,還以gid為鍵存入結(jié)果map中
 for k, vPtr := range intValuePtrsMap {
 retValuesMap[k] = *vPtr
 }
 }

 //返回結(jié)果map
 return
}

看完上述內(nèi)容,你們對(duì)使用Go語(yǔ)言怎么實(shí)現(xiàn)一個(gè)多人聊天室有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI