您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(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è)資訊頻道,感謝大家的支持。
免責(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)容。