Go語言專案實戰:多人聊天室
功能需求
- 實現單撩
- 實現群撩
- 實現使用者上線的全網通知
- 實現使用者暱稱
- 實現聊天日誌的儲存和檢視
服務端實現
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,"log@")==0 {
//log@all
//log@張全蛋
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
}
學院Go語言視訊主頁
https://edu.csdn.net/lecturer/1928
[清華團隊帶你實戰區塊鏈開發]
(https://ke.qq.com/course/344443?tuin=3d17195d)
掃碼獲取海量視訊及原始碼 QQ群:721929980
相關文章
- go語言實戰教程:Redis實戰專案應用GoRedis
- Go語言專案實戰:併發爬蟲Go爬蟲
- go語言實戰教程:實戰專案資源匯入和專案框架搭建Go框架
- go語言實戰教程:專案檔案配置和專案初始化執行Go
- Go 語言實戰 GraphQLGo
- Go語言專案實戰:基於開源資料的成語查詢Go
- Go語言SQL操作實戰GoSQL
- [練手專案]Gin+websocket 的多人聊天室Web
- 手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰Web框架Go筆記
- 手把手和你一起實現一個Web框架實戰——EzWeb框架(三)[Go語言筆記]Go專案實戰Web框架Go筆記
- 手把手和你一起實現一個Web框架實戰——EzWeb框架(四)[Go語言筆記]Go專案實戰Web框架Go筆記
- 手把手和你一起實現一個Web框架實戰——EzWeb框架(五)[Go語言筆記]Go專案實戰Web框架Go筆記
- 帶讀 |《Go in Action》(中文:Go語言實戰)(一)Go
- Go 語言專案程式碼品質Go
- Drone 搭配 Kubernetes 部署 Go 語言專案Go
- 用 Go 語言實戰 Limit Concurrency 方法GoMIT
- Go語言實戰(三)- 內建容器Go
- GO 語言 Web 開發實戰一GoWeb
- Go 語言實戰: 編寫可維護 Go 語言程式碼建議Go
- 有Go語言實戰培訓班嗎?go語言開發環境搭建Go開發環境
- [譯] Go 語言實戰: 編寫可維護 Go 語言程式碼建議Go
- 帶讀 |《Go in Action》(中文:Go語言實戰) 語法和語言結構概覽(三)Go
- 帶讀 |《Go in Action》(中文:Go語言實戰)語法和語言結構概覽 (二)Go
- 為什麼那麼多人要學習go語言?go語言有什麼特點?Go
- 檔案複製(Go語言實現)Go
- 非常適合GO語言新手學習的《Go語言從入門到實戰——簡明高效的Go語言實戰指南》課程——推薦分享Go
- Go語言核心36講(Go語言實戰與應用二十)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十九)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十八)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十七)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十三)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十四)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十五)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十六)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用九)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十二)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十一)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用七)--學習筆記Go筆記