Go語言多人聊天室項目實戰(zhàn)
更新時間:2019年08月20日 15:14:33 作者:尹成
這篇文章主要為大家詳細介紹了Go語言多人聊天室項目實戰(zhàn),實現(xiàn)單撩或多撩等多種功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
本文為大家分享了Go語言多人聊天室項目實戰(zhàn),供大家參考,具體內容如下
功能需求
- 實現(xiàn)單撩
- 實現(xiàn)群撩
- 實現(xiàn)用戶上線的全網通知
- 實現(xiàn)用戶昵稱
- 實現(xiàn)聊天日志的存儲和查看
服務端實現(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() {
//建立服務端監(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:服務器進入維護狀態(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:將每一個女朋友丟入map
client := Client{conn, clientName, clientAddr.String()}
clientsMap[clientName] = client
//TODO:給已經在線的用戶發(fā)送上線通知——使用昵稱
for _, client := range clientsMap {
client.conn.Write([]byte(clientName + "上線了"))
}
//在單獨的協(xié)程中與每一個具體的女朋友聊天
go ioWithClient(client)
}
//設置優(yōu)雅退出邏輯
}
//與一個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
//要發(fā)送的目標昵稱
targetName := strs[0]
targetMsg := strs[1]
//TODO:使用昵稱定位目標客戶端的Conn
if targetName == "all" {
//群發(fā)消息
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" {
//將當前客戶端從在線用戶中除名
//向其他用戶發(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"{
//查找與某個人的聊天記錄
//從內容中篩選出帶有【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{
//查詢所有的聊天記錄
//向客戶端發(fā)送
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))
}
客戶端實現(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", "無名氏", "昵稱"}
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()
}()
//在一條獨立的協(xié)程中輸入,并發(fā)送消息
go handleSend(conn,name)
//在一條獨立的協(xié)程中接收服務端消息
go handleReceive(conn)
//設置優(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ā)送昵稱到服務端
_, err := conn.Write([]byte(name))
CHandleError(err,"conn.Write([]byte(name))")
reader := bufio.NewReader(os.Stdin)
for {
//讀取標準輸入
lineBytes, _, _ := reader.ReadLine()
//發(fā)送到服務端
_, 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型的參數(shù)值,存放在好幾個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 {
/*
先把每個命令的名稱和用法拿出來,
這倆貨都是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中以參數(shù)名字獲取出【存值指針】,進而獲得【用戶輸入的值】
*/
//用戶輸入完了,解析,【用戶輸入的值】全都放在對應的【存值指針】中
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語言中處理JSON數(shù)據(jù)的編碼和解碼的方法
在Go語言中,處理JSON數(shù)據(jù)的編碼和解碼主要依賴于標準庫中的encoding/json包,這個包提供了兩個核心的函數(shù):Marshal和Unmarshal,本文給大家介紹了Go語言中處理JSON數(shù)據(jù)的編碼和解碼的方法,需要的朋友可以參考下2024-04-04
golang使用json格式實現(xiàn)增刪查改的實現(xiàn)示例
這篇文章主要介紹了golang使用json格式實現(xiàn)增刪查改的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-05-05
基于HLS創(chuàng)建Golang視頻流服務器的優(yōu)缺點
HLS 是 HTTP Live Streaming 的縮寫,是蘋果開發(fā)的一種基于 HTTP 的自適應比特率流媒體傳輸協(xié)議。這篇文章主要介紹了基于 HLS 創(chuàng)建 Golang 視頻流服務器,需要的朋友可以參考下2021-08-08

