Go語(yǔ)言多人聊天室項(xiàng)目實(shí)戰(zhàn)
本文為大家分享了Go語(yǔ)言多人聊天室項(xiàng)目實(shí)戰(zhàn),供大家參考,具體內(nèi)容如下
功能需求
- 實(shí)現(xiàn)單撩
- 實(shí)現(xiàn)群撩
- 實(shí)現(xiàn)用戶上線的全網(wǎng)通知
- 實(shí)現(xiàn)用戶昵稱
- 實(shí)現(xiàn)聊天日志的存儲(chǔ)和查看
服務(wù)端實(shí)現(xiàn)
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() {
//建立服務(wù)端監(jiān)聽
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:接收并保存昵稱
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ā)送上線通知——使用昵稱
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)昵稱
targetName := strs[0]
targetMsg := strs[1]
//TODO:使用昵稱定位目標(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) {
//打開文件
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ù)中攜帶昵稱
nameInfo := [3]interface{}{"name", "無(wú)名氏", "昵稱"}
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ā)送昵稱到服務(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ù)定義【用戶可能輸入的各種類型的指針】
var strValuePtr *string
var intValuePtr *int
//預(yù)定義【用戶可能輸入的各種類型的指針】的容器
//用戶可能輸入好幾個(gè)string型的參數(shù)值,存放在好幾個(gè)string型的指針中,將這些同種類型的指針放在同種類型的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è)命令的名稱和用法拿出來(lái),
這倆貨都是string類型的,所有都可以通過(guò)argArray[i].(string)輕松愉快地獲得其字符串
一個(gè)叫“cmd”,一個(gè)叫“你想干嘛”
"cmd"一會(huì)會(huì)用作map的key
*/
//[3]interface{}
//["cmd" "未知類型" "你想干嘛"]
//["gid" 0 "要查詢的商品ID"]
//上面的破玩意類型[string 可能是任意類型 string]
nameValue := argArray[0].(string) //拿到第一個(gè)元素的string值,是命令的name
usageValue := argArray[2].(string) //拿到最后一個(gè)元素的string值,是命令的usage
//判斷argArray[1]的具體類型
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"為鍵,存在【專門放置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"為鍵,存在【專門放置int型指針的map,即intValuePtrsMap】中
intValuePtrsMap[nameValue] = intValuePtr
}
}
/*
程序運(yùn)行到這里,所有不同類型的【存值指針】都放在對(duì)相應(yīng)類型的map中了
flag.Parse()了以后,可以從map中以參數(shù)名字獲取出【存值指針】,進(jìn)而獲得【用戶輸入的值】
*/
//用戶輸入完了,解析,【用戶輸入的值】全都放在對(duì)應(yīng)的【存值指針】中
flag.Parse()
/*
遍歷各種可能類型的【存值指針的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
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
go mod tidy加載模塊超時(shí)的問(wèn)題及解決
go mod tidy加載模塊超時(shí)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Go語(yǔ)言學(xué)習(xí)教程之goroutine和通道的示例詳解
這篇文章主要通過(guò)A?Tour?of?Go中的例子進(jìn)行學(xué)習(xí),以此了解Go語(yǔ)言中的goroutine和通道,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09
Go?iota關(guān)鍵字與枚舉類型實(shí)現(xiàn)原理
這篇文章主要介紹了Go?iota關(guān)鍵字與枚舉類型實(shí)現(xiàn)原理,iota是go語(yǔ)言的常量計(jì)數(shù)器,只能在常量的表達(dá)式中使用,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-07-07
基于Go和PHP語(yǔ)言實(shí)現(xiàn)爬樓梯算法的思路詳解
這篇文章主要介紹了Go和PHP 實(shí)現(xiàn)爬樓梯算法,本文通過(guò)動(dòng)態(tài)規(guī)劃和斐波那契數(shù)列兩種解決思路給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Go語(yǔ)言中處理JSON數(shù)據(jù)的編碼和解碼的方法
在Go語(yǔ)言中,處理JSON數(shù)據(jù)的編碼和解碼主要依賴于標(biāo)準(zhǔn)庫(kù)中的encoding/json包,這個(gè)包提供了兩個(gè)核心的函數(shù):Marshal和Unmarshal,本文給大家介紹了Go語(yǔ)言中處理JSON數(shù)據(jù)的編碼和解碼的方法,需要的朋友可以參考下2024-04-04
golang使用json格式實(shí)現(xiàn)增刪查改的實(shí)現(xiàn)示例
這篇文章主要介紹了golang使用json格式實(shí)現(xiàn)增刪查改的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Go語(yǔ)言標(biāo)準(zhǔn)輸入輸出庫(kù)的基本使用教程
輸入輸出在任何一門語(yǔ)言中都必須提供的一個(gè)功能,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言標(biāo)準(zhǔn)輸入輸出庫(kù)的基本使用,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02
基于HLS創(chuàng)建Golang視頻流服務(wù)器的優(yōu)缺點(diǎn)
HLS 是 HTTP Live Streaming 的縮寫,是蘋果開發(fā)的一種基于 HTTP 的自適應(yīng)比特率流媒體傳輸協(xié)議。這篇文章主要介紹了基于 HLS 創(chuàng)建 Golang 視頻流服務(wù)器,需要的朋友可以參考下2021-08-08
golang基礎(chǔ)之Gocurrency并發(fā)
這篇文章主要介紹了golang基礎(chǔ)之Gocurrency并發(fā),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07

