Go語言專案實戰:多人聊天室
阿新 • • 發佈:2018-12-21
功能需求
- 實現單撩
- 實現群撩
- 實現使用者上線的全網通知
- 實現使用者暱稱
- 實現聊天日誌的儲存和檢視
服務端實現
type Client struct { conn net.Conn name string addr string } var ( //客戶端資訊,用暱稱為鍵 //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() { //建立服務端監聽 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:伺服器進入維護狀態,大家都洗洗睡吧!")) } listener.Close() }() for { //迴圈接入所有女朋友 conn, e := listener.Accept() SHandleError(e, "listener.Accept") clientAddr := conn.RemoteAddr() //TODO:接收並儲存暱稱 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:將每一個女朋友丟入map client := Client{conn, clientName, clientAddr.String()} clientsMap[clientName] = client //TODO:給已經線上的使用者傳送上線通知——使用暱稱 for _, client := range clientsMap { client.conn.Write([]byte(clientName + "上線了")) } //在單獨的協程中與每一個具體的女朋友聊天 go ioWithClient(client) } //設定優雅退出邏輯 } //與一個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) //將客戶端說的每一句話記錄在【以他的名字命名的檔案裡】 writeMsgToLog(msg, client) strs := strings.Split(msg, "#") if len(strs) > 1 { //all#hello //zqd#hello //要傳送的目標暱稱 targetName := strs[0] targetMsg := strs[1] //TODO:使用暱稱定位目標客戶端的Conn if targetName == "all" { //群發訊息 for _, c := range clientsMap { c.conn.Write([]byte(client.name + ":" + targetMsg)) } } else { //點對點訊息 for key, c := range clientsMap { if key == targetName { c.conn.Write([]byte(client.name + ":" + targetMsg)) //在點對點訊息的目標端也記錄日誌 go writeMsgToLog(client.name + ":" + targetMsg,c) break } } } } else { //客戶端主動下線 if msg == "exit" { //將當前客戶端從線上使用者中除名 //向其他使用者傳送下線通知 for name, c := range clientsMap { if c == client { delete(clientsMap, name) } else { c.conn.Write([]byte(name + "下線了")) } } }else if strings.Index(msg,"
[email protected]")==0 { //[email protected] //[email protected]張全蛋 filterName := strings.Split(msg, "@")[1] //向客戶端傳送它的聊天日誌 go sendLog2Client(client,filterName) } else { client.conn.Write([]byte("已閱:" + msg)) } } } } } //向客戶端傳送它的聊天日誌 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"{ //查詢與某個人的聊天記錄 //從內容中篩選出帶有【filterName#或filterName:】的行,拼接起來 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{ //查詢所有的聊天記錄 //向客戶端傳送 client.conn.Write(logBytes) } } //將客戶端說的一句話記錄在【以他的名字命名的檔案裡】 func writeMsgToLog(msg string, client Client) { //開啟檔案 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)) }
客戶端實現
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:在命令列引數中攜帶暱稱 nameInfo := [3]interface{}{"name", "無名氏", "暱稱"} retValuesMap := GetCmdlineArgs(nameInfo) name := retValuesMap["name"].(string) //撥號連線,獲得connection var e error conn, e = net.Dial("tcp", "127.0.0.1:8888") CHandleError(e, "net.Dial") defer func() { conn.Close() }() //在一條獨立的協程中輸入,併發送訊息 go handleSend(conn,name) //在一條獨立的協程中接收服務端訊息 go handleReceive(conn) //設定優雅退出邏輯 <-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:傳送暱稱到服務端 _, err := conn.Write([]byte(name)) CHandleError(err,"conn.Write([]byte(name))") reader := bufio.NewReader(os.Stdin) for { //讀取標準輸入 lineBytes, _, _ := reader.ReadLine() //傳送到服務端 _, 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) //初始化返回結果 retValuesMap = map[string]interface{}{} //預定義【使用者可能輸入的各種型別的指標】 var strValuePtr *string var intValuePtr *int //預定義【使用者可能輸入的各種型別的指標】的容器 //使用者可能輸入好幾個string型的引數值,存放在好幾個string型的指標中,將這些同種型別的指標放在同種型別的map中 //例如:flag.Parse()了以後,可以根據【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 { /* 先把每個命令的名稱和用法拿出來, 這倆貨都是string型別的,所有都可以通過argArray[i].(string)輕鬆愉快地獲得其字串 一個叫“cmd”,一個叫“你想幹嘛” "cmd"一會會用作map的key */ //[3]interface{} //["cmd" "未知型別" "你想幹嘛"] //["gid" 0 "要查詢的商品ID"] //上面的破玩意型別[string 可能是任意型別 string] nameValue := argArray[0].(string) //拿到第一個元素的string值,是命令的name usageValue := argArray[2].(string) //拿到最後一個元素的string值,是命令的usage //判斷argArray[1]的具體型別 switch argArray[1].(type) { case string: //得到【存放cmd的指標】,cmd的值將在flag.Parse()以後才會有 //cmdValuePtr = flag.String("cmd", argArray[1].(string), "你想幹嘛") strValuePtr = flag.String(nameValue, argArray[1].(string), usageValue) //將這個破指標以"cmd"為鍵,存在【專門放置string型指標的map,即strValuePtrsMap】中 strValuePtrsMap[nameValue] = strValuePtr case int: //得到【存放gid的指標】,gid的值將在flag.Parse()以後才會有 //gidValuePtr = flag.String("gid", argArray[1].(int), "商品ID") intValuePtr = flag.Int(nameValue, argArray[1].(int), usageValue) //將這個破指標以"gid"為鍵,存在【專門放置int型指標的map,即intValuePtrsMap】中 intValuePtrsMap[nameValue] = intValuePtr } } /* 程式執行到這裡,所有不同型別的【存值指標】都放在對相應型別的map中了 flag.Parse()了以後,可以從map中以引數名字獲取出【存值指標】,進而獲得【使用者輸入的值】 */ //使用者輸入完了,解析,【使用者輸入的值】全都放在對應的【存值指標】中 flag.Parse() /* 遍歷各種可能型別的【存值指標的map】 */ if len(strValuePtrsMap) > 0 { //從【cmd存值指標的map】中拿取cmd的值,還以cmd為鍵存入結果map中 for k, vPtr := range strValuePtrsMap { retValuesMap[k] = *vPtr } } if len(intValuePtrsMap) > 0 { //從【gid存值指標的map】中拿取gid的值,還以gid為鍵存入結果map中 for k, vPtr := range intValuePtrsMap { retValuesMap[k] = *vPtr } } //返回結果map return }
[清華團隊帶你實戰區塊鏈開發]