国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁(yè) > news >正文

網(wǎng)站設(shè)計(jì)需求說(shuō)明書(shū)企業(yè)網(wǎng)站大全

網(wǎng)站設(shè)計(jì)需求說(shuō)明書(shū),企業(yè)網(wǎng)站大全,做網(wǎng)站接私活怎么收費(fèi),php 電子商務(wù)網(wǎng)站建設(shè)文章目錄 二十三、海量用戶即時(shí)通訊系統(tǒng)1、項(xiàng)目開(kāi)發(fā)前技術(shù)準(zhǔn)備2.實(shí)現(xiàn)功能-顯示客戶端登錄菜單3.實(shí)現(xiàn)功能-完成用戶登錄-1.完成客戶端可以該長(zhǎng)度值發(fā)送消息長(zhǎng)度,服務(wù)器端可以正常接收到-2.完成客戶端可以發(fā)送消息,服務(wù)器端可以接收到消息并根據(jù)客戶端發(fā)送…

文章目錄

    • 二十三、海量用戶即時(shí)通訊系統(tǒng)
      • 1、項(xiàng)目開(kāi)發(fā)前技術(shù)準(zhǔn)備
      • 2.實(shí)現(xiàn)功能-顯示客戶端登錄菜單
      • 3.實(shí)現(xiàn)功能-完成用戶登錄
        • -1.完成客戶端可以該長(zhǎng)度值發(fā)送消息長(zhǎng)度,服務(wù)器端可以正常接收到
        • -2.完成客戶端可以發(fā)送消息,服務(wù)器端可以接收到消息并根據(jù)客戶端發(fā)送的消息判斷用戶的合法性并返回相應(yīng)的消息
        • -3.能夠完成登錄,并提示信息
        • -4.程序結(jié)構(gòu)的改進(jìn)
          • 1)畫(huà)出程序框架圖
          • 2)步驟
            • server層后端項(xiàng)目結(jié)構(gòu)圖
            • client層后端項(xiàng)目結(jié)構(gòu)圖
        • -5.應(yīng)用redis
          • 1)在Redis手動(dòng)添加測(cè)試用戶,并畫(huà)圖+說(shuō)明注意(后面通過(guò)程序注冊(cè)用戶)
          • 2)如輸入的用戶名密碼正確在Redis中存在則登錄,否則退出系統(tǒng),并給出相應(yīng)的提示信息
          • 3)代碼實(shí)現(xiàn)
            • (1)先編寫(xiě)了server/model/user.go
            • (2)先編寫(xiě)了server/model/error.go
            • (3)編寫(xiě)了server/model/userDao.go
            • (4)編寫(xiě)了server/main.redis.go
            • (5)編寫(xiě)了server/process/userProcess.go改進(jìn)登錄方式以及錯(cuò)誤類型
            • (6)改進(jìn)server/main/main.go(加了一個(gè)初始化redis連接池的函數(shù))
      • 4.完成用戶注冊(cè)操作
        • 1)要求
        • 2)具體代碼
          • (1)common/message/user.go(從server/model下復(fù)制過(guò)來(lái)的。記住要復(fù)制而不是剪切還要改包名)
          • (2)common/message/message.go增加了關(guān)于注冊(cè)消息的代碼
          • (3)server/process/userProcess(增加了一個(gè)方法)
          • (4)server/model/userDao(增加了一個(gè)Register方法對(duì)數(shù)據(jù)庫(kù)進(jìn)行添加的操作)
          • (5)在client/main/main.go進(jìn)行了調(diào)用操作
          • (6)client/process/userProcess.go(添加一個(gè)Register的方法)
      • 5.實(shí)現(xiàn)功能-完成登錄時(shí)能返回當(dāng)前在線用戶
        • 3)代碼實(shí)現(xiàn)
          • (1)編寫(xiě)了server/process/userMgr.go
          • (2)server/process/userProcess.go(在login成功的地方加入代碼)
        • 4)當(dāng)一個(gè)新的用戶上線后,其他已經(jīng)登錄的用戶也能獲取最新在新用戶列表
      • 6.完成登錄可以完成群聊操作
        • -1.步驟1 :
          • 1)思路分析
          • 2)代碼實(shí)現(xiàn)
        • -2.步驟2.
          • 1)思路分析
          • 2)代碼實(shí)現(xiàn)
          • 3)拓展功能要求

二十三、海量用戶即時(shí)通訊系統(tǒng)

項(xiàng)目展示
在這里插入圖片描述
開(kāi)始此項(xiàng)目之前請(qǐng)確保安裝好redis,golang
源碼下載:https://github.com/BeAlrightc/go-study.git

1、項(xiàng)目開(kāi)發(fā)前技術(shù)準(zhǔn)備

項(xiàng)目要保存用戶信息和信息數(shù)據(jù),因此我們需要學(xué)習(xí)數(shù)據(jù)(redis或者mysql),這里我們選擇redis

2.實(shí)現(xiàn)功能-顯示客戶端登錄菜單

在這里插入圖片描述

代碼編寫(xiě)

clien包下的main.go

package main
import ("fmt""os"
)//定義兩個(gè)變量,一個(gè)表示用戶的id,一個(gè)表示用戶的密碼
var userId int
var userPwd stringfunc main() {//接收用戶的選擇var key int//判斷是否還繼續(xù)顯示菜單var loop = truefor loop{fmt.Println("-----------歡迎登錄多人聊天系統(tǒng)------")fmt.Println("\t\t\t 1 登錄聊天室")fmt.Println("\t\t\t 2 注冊(cè)用戶")fmt.Println("\t\t\t 3 退出系統(tǒng)")fmt.Println("\t\t\t 請(qǐng)選擇 1-3:")fmt.Scanf("%d\n",&key)switch key {case 1 :fmt.Println("登錄聊天室")loop=falsecase 2 :fmt.Println("注冊(cè)用戶")	loop=falsecase 3 :fmt.Println("退出系統(tǒng)")	//loop=falseos.Exit(0)default:fmt.Println("輸入有誤,請(qǐng)輸入1-3")	}}//根據(jù)用戶的輸入,顯示新的提示信息if key ==1 {//說(shuō)明用戶要登錄了fmt.Println("請(qǐng)輸入用戶的id")fmt.Scanf("%d\n",&userId)fmt.Println("請(qǐng)輸入用戶的密碼")fmt.Scanf("%s\n",&userPwd)//先把登錄函數(shù),寫(xiě)到另外一個(gè)文件,先寫(xiě)login.goerr := login(userId,userPwd)if err != nil {fmt.Println("登錄失敗")}else {fmt.Println("登錄成功")}}else if key ==2 {fmt.Println("進(jìn)行用戶注冊(cè)的邏輯....")}}

clien包下的login.go

package main
import ("fmt"
)
//寫(xiě)一個(gè)函數(shù),完成登錄操作
func login(userId int,userPwd string) (err error) {//下一個(gè)就要開(kāi)始定協(xié)議fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)return nil
}

3.實(shí)現(xiàn)功能-完成用戶登錄

要求:完成指定用戶的驗(yàn)證,用戶id=100,密碼pwd=123456可以登錄,其他用戶不能登錄

理解從client到server中的程序執(zhí)行流程,如圖所示【Message組成的示意圖。并發(fā)送一個(gè)message的流程介紹】

在這里插入圖片描述

-1.完成客戶端可以該長(zhǎng)度值發(fā)送消息長(zhǎng)度,服務(wù)器端可以正常接收到

分析思路

1)先確定消息Message的格式

2)發(fā)送消息示意圖

在這里插入圖片描述

代碼展示

sever

main.go

package main
import ("fmt""net"
)//處理和客戶端的通訊
func process(conn net.Conn){//這里需要延時(shí)關(guān)閉defer conn.Close()//循環(huán)地讀客戶端發(fā)送的信息for {buf := make([]byte,8096)fmt.Println("讀取客戶端發(fā)送的數(shù)據(jù)...")n, err :=conn.Read(buf[:4])if n != 4 || err !=nil {fmt.Println("conn.Read err=",err)return}fmt.Println("獨(dú)到的buf=",buf[:4])}}
func main() {//提示信息fmt.Println("服務(wù)器在8889端口監(jiān)聽(tīng)....")listen, err := net.Listen("tcp","0.0.0.0:8889")defer listen.Close()if err != nil {fmt.Println("net.Listen err=",err)return} //一旦監(jiān)聽(tīng)成功,就等待客戶端來(lái)連接服務(wù)器for {fmt.Println("等待客戶端來(lái)連接服務(wù)器")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=",err)} //一旦連接成功,則則啟動(dòng)一個(gè)協(xié)程和客戶端保持通訊。。go process(conn)}
}

common層的message

message.go

package messageconst (LoginMesType   = "LoginMes"LoginResMesType  = "LoginResMes"
)type Message struct {Type string `json:"type"`//消息的類型Data string `json:"data"`//消息的數(shù)據(jù)
}//定義兩個(gè)消息。。后面需要再添加
type LoginMes struct {UserId int `json:"userId"`//用戶IdUserPwd string `json:"userPwd"`//用戶密碼UserName string `json:"userName"`//用戶名
}type LoginResMes struct {Code int `json:"code"`//返回狀態(tài)碼 500表示該用戶未注冊(cè) 200表示登錄成功Error string `json:"error"`//返回錯(cuò)誤信息
}

client層

login.go

package main
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message"
)
//寫(xiě)一個(gè)函數(shù),完成登錄操作
func login(userId int,userPwd string) (err error) {//下一個(gè)就要開(kāi)始定協(xié)議// fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)// return nil//1.連接到服務(wù)器端conn, err :=net.Dial("tcp","localhost:8889")if err != nil {fmt.Println("net.Dial err=",err)return}//延時(shí)關(guān)閉defer conn.Close()//2.準(zhǔn)備通過(guò)conn發(fā)送消息給服務(wù)器var mes message.Messagemes.Type = message.LoginMesType //3.創(chuàng)建一個(gè)LoginMes 結(jié)構(gòu)體var loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd //4.將loginMes序列化data, err :=json.Marshal(loginMes)if err != nil {fmt.Println("json.Mashal err=",err)return}//5.將data賦給了mes.Data字段mes.Data = string(data)//6.將mes進(jìn)行序列化data, err =json.Marshal(mes)if err != nil {fmt.Println("json.Mashal err=",err)return}//7.到這個(gè)時(shí)候,data就是我們要發(fā)送的消息//7.1先把data的長(zhǎng)度發(fā)送給服務(wù)器//先獲取data的長(zhǎng)度->轉(zhuǎn)成一個(gè)表示長(zhǎng)度的byte切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //將該、長(zhǎng)度轉(zhuǎn)成了byte類型是數(shù)據(jù)//發(fā)送長(zhǎng)度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}fmt.Printf("客戶端發(fā)送數(shù)據(jù)的消息長(zhǎng)度=%d 內(nèi)容是=%s",len(data),string(data))return}

main.go

package main
import ("fmt""os"
)//定義兩個(gè)變量,一個(gè)表示用戶的id,一個(gè)表示用戶的密碼
var userId int
var userPwd stringfunc main() {//接收用戶的選擇var key int//判斷是否還繼續(xù)顯示菜單var loop = truefor loop{fmt.Println("-----------歡迎登錄多人聊天系統(tǒng)------")fmt.Println("\t\t\t 1 登錄聊天室")fmt.Println("\t\t\t 2 注冊(cè)用戶")fmt.Println("\t\t\t 3 退出系統(tǒng)")fmt.Println("\t\t\t 請(qǐng)選擇 1-3:")fmt.Scanf("%d\n",&key)switch key {case 1 :fmt.Println("登錄聊天室")loop=falsecase 2 :fmt.Println("注冊(cè)用戶")	loop=falsecase 3 :fmt.Println("退出系統(tǒng)")	//loop=falseos.Exit(0)default:fmt.Println("輸入有誤,請(qǐng)輸入1-3")	}}//根據(jù)用戶的輸入,顯示新的提示信息if key ==1 {//說(shuō)明用戶要登錄了fmt.Println("請(qǐng)輸入用戶的id")fmt.Scanf("%d\n",&userId)fmt.Println("請(qǐng)輸入用戶的密碼")fmt.Scanf("%s\n",&userPwd)//先把登錄函數(shù),寫(xiě)到另外一個(gè)文件,先寫(xiě)login.goerr := login(userId,userPwd)if err != nil {fmt.Println("登錄失敗")}else {fmt.Println("登錄成功")}}else if key ==2 {fmt.Println("進(jìn)行用戶注冊(cè)的邏輯....")}}
-2.完成客戶端可以發(fā)送消息,服務(wù)器端可以接收到消息并根據(jù)客戶端發(fā)送的消息判斷用戶的合法性并返回相應(yīng)的消息

思路分析

1)讓客戶端發(fā)送消息本身

2)服務(wù)器端接收到消息,然后反序列化成對(duì)應(yīng)的消息結(jié)構(gòu)體

3)服務(wù)器端根據(jù)反序列化的消息,判斷是否登錄用戶是合法,返回LoginReMes

4)客戶端解析返回的LoginReMes,顯示對(duì)應(yīng)界面

5)這里我們需要做一些函數(shù)的封裝

cient/login.go在結(jié)尾添加這些coding

//發(fā)送消息本身_, err = conn.Write(data)if err !=nil {fmt.Println("connWrite(data) fail ",err)return}//休眠20秒time.Sleep(10 * time.Second)fmt.Println("休眠了20秒..")//這里還需要處理服務(wù)器端返回的消息return

在server/main.go中我們做了以下改動(dòng)

將讀數(shù)據(jù)的過(guò)程封裝了一個(gè)函數(shù)

package main
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message"//"errors""io"
)
func readPkg(conn net.Conn)(mes message.Message,err error){buf := make([]byte,8096)fmt.Println("讀取客戶端發(fā)送的數(shù)據(jù)...")//conn.Read()只有在conn沒(méi)有被關(guān)閉的情況下,才會(huì)阻塞//如果客戶端關(guān)閉conn則,就不會(huì)阻塞_, err =conn.Read(buf[:4]) //read出buf中的數(shù)據(jù)if err !=nil {//fmt.Println("conn.Read err=",err)//err = errors.New("read pkg header error")return}//根據(jù)buf[:4]轉(zhuǎn)成uint32類型var pkgLen uint32pkgLen=binary.BigEndian.Uint32(buf[0:4])//根據(jù)pkgLen讀取消息內(nèi)容n, err :=conn.Read(buf[:pkgLen])if n != int(pkgLen) || err !=nil {//err = errors.New("read pkg body error")return}//把pkgLen 反序列化成 -->message.Message//技術(shù)就是一層窗戶紙json.Unmarshal(buf[:pkgLen],&mes)if err != nil {fmt.Println("json.Unmarshal err=",err)return}return
}//處理和客戶端的通訊
func process(conn net.Conn) {//這里需要延時(shí)關(guān)閉defer conn.Close()//循環(huán)地讀客戶端發(fā)送的信息for {//這里我們將讀取數(shù)據(jù)包,直接封裝成一個(gè)函數(shù)readPkg(),返回Message,Errmes, err :=readPkg(conn)if err != nil {if err == io.EOF {fmt.Println("客戶端退出,服務(wù)器端也退出...")return}else {fmt.Println("readpkg err=",err)}return}fmt.Println("mes=",mes)}
}
//main函數(shù)下的則沒(méi)有改變
func main() {//提示信息fmt.Println("服務(wù)器在8889端口監(jiān)聽(tīng)....")listen, err := net.Listen("tcp","0.0.0.0:8889")defer listen.Close()if err != nil {fmt.Println("net.Listen err=",err)return} //一旦監(jiān)聽(tīng)成功,就等待客戶端來(lái)連接服務(wù)器for {fmt.Println("等待客戶端來(lái)連接服務(wù)器")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=",err)} //一旦連接成功,則則啟動(dòng)一個(gè)協(xié)程和客戶端保持通訊。。go process(conn)}
}
-3.能夠完成登錄,并提示信息

server/main.go

添加了發(fā)送信息給客戶端的代碼
func writePkg(conn net.Conn,data []byte)(err error) {//先發(fā)送一個(gè)長(zhǎng)度給對(duì)方var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //將該、長(zhǎng)度轉(zhuǎn)成了byte類型是數(shù)據(jù)//發(fā)送長(zhǎng)度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}//發(fā)送data本身n, err = conn.Write(data)if n != int(pkgLen) || err !=nil {fmt.Println("connWrite(data) fail ",err)return}return}//編寫(xiě)一個(gè)函數(shù)serverProcessLogin函數(shù),專門(mén)處理登錄請(qǐng)求
func serverProcessLogin(conn net.Conn,mes *message.Message)(err error){//核心代碼//1.先從mes中取出mes.Data,并直接反序列化成LoginMesvar loginMes message.LoginMeserr =json.Unmarshal([]byte(mes.Data),&loginMes)if err != nil {fmt.Println("json.Unmarshal fail err=",err)return}//1.先聲明一個(gè)resMesvar resMes message.MessageresMes.Type=message.LoginResMesType//2.再聲明一個(gè)LoginResMesvar loginResMes message.LoginResMes//如果用戶的id=100,密碼=123456認(rèn)為合法,否則不合法if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {//合法loginResMes.Code = 200} else {//不合法loginResMes.Code = 500  //500狀態(tài)碼表示用戶不存在loginResMes.Error = "該用戶不存在,請(qǐng)注冊(cè)再使用。。。"}//3.將loginResMes 序列化data, err := json.Marshal(loginResMes)if err != nil {fmt.Println("Marshal fail err=",err)}//4.將data賦值給resMesresMes.Data = string(data) //5.對(duì)resMes進(jìn)行序列化,準(zhǔn)備發(fā)送data, err = json.Marshal(resMes)if err != nil {fmt.Println("Marshal fail err=",err)return}//6.發(fā)送data 我們將其封裝為writePkgerr = writePkg(conn,data)return}//編寫(xiě)一個(gè)ServerProcessMes函數(shù)
//功能 :根據(jù)客戶端發(fā)送的消息種類不同,決定調(diào)用哪個(gè)函數(shù)處理
func serverProcessMes(conn net.Conn,mes *message.Message)(err error) {switch mes.Type {case message.LoginMesType ://處理登錄的邏輯err = serverProcessLogin(conn,mes)case message.RegisterMesType ://處理注冊(cè)default :fmt.Println("消息類型不存在,無(wú)法處理...")}return
}在process進(jìn)行了改定
//處理和客戶端的通訊
func process(conn net.Conn) {//這里需要延時(shí)關(guān)閉defer conn.Close()//循環(huán)地讀客戶端發(fā)送的信息for {//這里我們將讀取數(shù)據(jù)包,直接封裝成一個(gè)函數(shù)readPkg(),返回Message,Errmes, err :=readPkg(conn)if err != nil {if err == io.EOF {fmt.Println("客戶端退出,服務(wù)器端也退出...")return}else {fmt.Println("readpkg err=",err)}return}//增加了這段代碼進(jìn)行調(diào)用這個(gè)函數(shù)err = serverProcessMes(conn,&mes)if err != nil {return}}
}

client/utils(增加了一個(gè)utils.go用于read的write的操作)

package main
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message"
)func readPkg(conn net.Conn)(mes message.Message,err error){buf := make([]byte,8096)fmt.Println("讀取客戶端發(fā)送的數(shù)據(jù)...")//conn.Read()只有在conn沒(méi)有被關(guān)閉的情況下,才會(huì)阻塞//如果客戶端關(guān)閉conn則,就不會(huì)阻塞_, err =conn.Read(buf[:4]) //先讀取之前發(fā)送的數(shù)據(jù)長(zhǎng)度if err !=nil {//fmt.Println("conn.Read err=",err)//err = errors.New("read pkg header error")return}//根據(jù)buf[:4]轉(zhuǎn)成uint32類型var pkgLen uint32pkgLen=binary.BigEndian.Uint32(buf[0:4])//根據(jù)pkgLen(data數(shù)據(jù)的長(zhǎng)度)讀取消息內(nèi)容n, err :=conn.Read(buf[:pkgLen])if n != int(pkgLen) || err !=nil {//err = errors.New("read pkg body error")return}//把pkgLen 反序列化成 -->message.Message//技術(shù)就是一層窗戶紙json.Unmarshal(buf[:pkgLen],&mes)if err != nil {fmt.Println("json.Unmarshal err=",err)  //json的反序列化失敗!return}return
}func writePkg(conn net.Conn,data []byte)(err error) {//先發(fā)送一個(gè)長(zhǎng)度給對(duì)方var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //將該、長(zhǎng)度轉(zhuǎn)成了byte類型是數(shù)據(jù)//發(fā)送長(zhǎng)度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}//發(fā)送data本身n, err = conn.Write(data)if n != int(pkgLen) || err !=nil {fmt.Println("connWrite(data) fail ",err)return}return}

client/login.go

//在末尾加入了如下的代碼
//這里還需要處理服務(wù)器端返回的消息mes, err = readPkg(conn) //mes 就是if err != nil {fmt.Println("readPkg(conn) err=",err)return}//將mes的Data部分反序列化為L(zhǎng)oginResMesvar loginResMes message.LoginResMeserr = json.Unmarshal([]byte(mes.Data),&loginResMes)if loginResMes.Code == 200 {fmt.Println("登錄成功")}else if loginResMes.Code == 500 {fmt.Println(loginResMes.Error)}return}
-4.程序結(jié)構(gòu)的改進(jìn)

說(shuō)明:前面的程序雖然完成了功能,但是沒(méi)有結(jié)構(gòu),系統(tǒng)的可讀性、拓展性和維護(hù)性都不好,因此需要對(duì)程序的結(jié)構(gòu)進(jìn)行改進(jìn)

1)畫(huà)出程序框架圖

在這里插入圖片描述

2)步驟

(1)先把分析出來(lái)的文件,創(chuàng)建好,然后放到相應(yīng)的文件夾中

server層后端項(xiàng)目結(jié)構(gòu)圖

在這里插入圖片描述

(2)現(xiàn)在根據(jù)各個(gè)文件完成的任務(wù)和作用不同,將main.go的代碼剝離到對(duì)應(yīng)的文件即可

(3)先修改了utils.go

package utilsimport ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message")//將這些方法關(guān)聯(lián)到結(jié)構(gòu)體當(dāng)中type Transfer struct {//分析應(yīng)該有哪些字段Conn net.ConnBuf [8096]byte  //這是傳輸時(shí)使用緩沖}func (this *Transfer) ReadPkg()(mes message.Message,err error){fmt.Println("讀取客戶端發(fā)送的數(shù)據(jù)...")//conn.Read()只有在conn沒(méi)有被關(guān)閉的情況下,才會(huì)阻塞//如果客戶端關(guān)閉conn則,就不會(huì)阻塞_, err =this.Conn.Read(this.Buf[:4]) //先讀取之前發(fā)送的數(shù)據(jù)長(zhǎng)度if err !=nil {//fmt.Println("conn.Read err=",err)//err = errors.New("read pkg header error")return}//根據(jù)buf[:4]轉(zhuǎn)成uint32類型var pkgLen uint32pkgLen=binary.BigEndian.Uint32(this.Buf[0:4])//根據(jù)pkgLen(data數(shù)據(jù)的長(zhǎng)度)讀取消息內(nèi)容n, err :=this.Conn.Read(this.Buf[:pkgLen])if n != int(pkgLen) || err !=nil {//err = errors.New("read pkg body error")return}//把pkgLen 反序列化成 -->message.Message//技術(shù)就是一層窗戶紙json.Unmarshal(this.Buf[:pkgLen],&mes)if err != nil {fmt.Println("json.Unmarshal err=",err)  //json的反序列化失敗!return}return
}func (this *Transfer) WritePkg(data []byte)(err error) {//先發(fā)送一個(gè)長(zhǎng)度給對(duì)方var pkgLen uint32pkgLen = uint32(len(data))binary.BigEndian.PutUint32(this.Buf[0:4],pkgLen) //將該、長(zhǎng)度轉(zhuǎn)成了byte類型是數(shù)據(jù)//發(fā)送長(zhǎng)度n, err := this.Conn.Write(this.Buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(this.Buf) fail ",err)return}//發(fā)送data本身n, err = this.Conn.Write(data)if n != int(pkgLen) || err !=nil {fmt.Println("connWrite(data) fail ",err)return}return}

(4)修改了process2/userProcess.go

package process2
import ("fmt""net""encoding/json""go_code/chatroom/common/message""go_code/chatroom/server/utils"
)type UserProcess struct {//字段Conn net.Conn
}//編寫(xiě)一個(gè)函數(shù)serverProcessLogin函數(shù),專門(mén)處理登錄請(qǐng)求
func (this *UserProcess) ServerProcessLogin(mes *message.Message)(err error){//核心代碼//1.先從mes中取出mes.Data,并直接反序列化成LoginMesvar loginMes message.LoginMeserr =json.Unmarshal([]byte(mes.Data),&loginMes)if err != nil {fmt.Println("json.Unmarshal fail err=",err)return}//1.先聲明一個(gè)resMesvar resMes message.MessageresMes.Type=message.LoginResMesType//2.再聲明一個(gè)LoginResMesvar loginResMes message.LoginResMes//如果用戶的id=100,密碼=123456認(rèn)為合法,否則不合法if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {//合法loginResMes.Code = 200} else {//不合法loginResMes.Code = 500  //500狀態(tài)碼表示用戶不存在loginResMes.Error = "該用戶不存在,請(qǐng)注冊(cè)再使用。。。"}//3.將loginResMes 序列化data, err := json.Marshal(loginResMes)if err != nil {fmt.Println("Marshal fail err=",err)}//4.將data賦值給resMesresMes.Data = string(data) //5.對(duì)resMes進(jìn)行序列化,準(zhǔn)備發(fā)送data, err = json.Marshal(resMes)if err != nil {fmt.Println("Marshal fail err=",err)return}//6.發(fā)送data 我們將其封裝為writePkg//因?yàn)槭褂昧朔謱幽J?mvc),我們先創(chuàng)建一個(gè)Transfer實(shí)例,然后讀取tf := &utils.Transfer{Conn : this.Conn,}err = tf.WritePkg(data)return
}

(5)修改了main/processor.go

package main
import ("fmt""net""go_code/chatroom/common/message""go_code/chatroom/server/utils""go_code/chatroom/server/process""io"
)//先創(chuàng)建一個(gè)Processor的結(jié)構(gòu)體
type Processor struct {Conn net.Conn
}//編寫(xiě)一個(gè)ServerProcessMes函數(shù)
//功能 :根據(jù)客戶端發(fā)送的消息種類不同,決定調(diào)用哪個(gè)函數(shù)處理
func (this *Processor) serverProcessMes(mes *message.Message)(err error) {switch mes.Type {case message.LoginMesType ://處理登錄的邏輯//創(chuàng)建一個(gè)UserProcess實(shí)例up := &process2.UserProcess{Conn : this.Conn,}err = up.ServerProcessLogin(mes)case message.RegisterMesType ://處理注冊(cè)default :fmt.Println("消息類型不存在,無(wú)法處理...")}return
}func (this *Processor) process2()(err error){//循環(huán)地讀客戶端發(fā)送的信息for {//這里我們將讀取數(shù)據(jù)包,直接封裝成一個(gè)函數(shù)readPkg(),返回Message,Err//創(chuàng)建一個(gè)Transfer實(shí)例完成讀包任務(wù)tf := &utils.Transfer{Conn : this.Conn,}mes, err :=tf.ReadPkg()if err != nil {if err == io.EOF {fmt.Println("客戶端退出,服務(wù)器端也退出...")return err}else {fmt.Println("readpkg err=",err)}return err}err = this.serverProcessMes(&mes)if err != nil {return err}}
}

修改了main/main.go

package main
import ("fmt""net"
)//處理和客戶端的通訊
func process(conn net.Conn) {//這里需要延時(shí)關(guān)閉defer conn.Close()//這里調(diào)用總控,創(chuàng)建一個(gè)processor實(shí)例processor := &Processor{Conn : conn,}err := processor.process2()if err != nil {fmt.Println("客戶端和服務(wù)器通訊的協(xié)程錯(cuò)誤=err",err)return }
}
func main() {//提示信息fmt.Println("服務(wù)器[新的結(jié)構(gòu)]在8889端口監(jiān)聽(tīng)....")listen, err := net.Listen("tcp","0.0.0.0:8889")defer listen.Close()if err != nil {fmt.Println("net.Listen err=",err)return} //一旦監(jiān)聽(tīng)成功,就等待客戶端來(lái)連接服務(wù)器for {fmt.Println("等待客戶端來(lái)連接服務(wù)器")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=",err)} //一旦連接成功,則則啟動(dòng)一個(gè)協(xié)程和客戶端保持通訊。。go process(conn)}
}

修改客戶端。先畫(huà)出程序的框架圖,再寫(xiě)代碼

client層后端項(xiàng)目結(jié)構(gòu)圖

在這里插入圖片描述

(2)先把各個(gè)文件放到對(duì)應(yīng)的文件夾[包]

在這里插入圖片描述

(3)將server/utils.go拷貝到client/utils/utils.go

(4)創(chuàng)建了client/process/userProcess.go

package process
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message""go_code/chatroom/client/utils"
)
type UserProcess struct {//暫時(shí)不需要字段
}
//給關(guān)聯(lián)一個(gè)用戶登錄的方法
//寫(xiě)一個(gè)函數(shù),完成登錄操作
func (this *UserProcess) Login(userId int,userPwd string) (err error) {//下一個(gè)就要開(kāi)始定協(xié)議// fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)// return nil//1.連接到服務(wù)器端conn, err :=net.Dial("tcp","localhost:8889")if err != nil {fmt.Println("net.Dial err=",err)return}//延時(shí)關(guān)閉defer conn.Close()//2.準(zhǔn)備通過(guò)conn發(fā)送消息給服務(wù)器var mes message.Messagemes.Type = message.LoginMesType //3.創(chuàng)建一個(gè)LoginMes 結(jié)構(gòu)體var loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd //4.將loginMes序列化data, err :=json.Marshal(loginMes)if err != nil {fmt.Println("json.Mashal err=",err)return}//5.將data賦給了mes.Data字段mes.Data = string(data)//6.將mes進(jìn)行序列化data, err =json.Marshal(mes)if err != nil {fmt.Println("json.Mashal err=",err)return}//7.到這個(gè)時(shí)候,data就是我們要發(fā)送的消息//7.1先把data的長(zhǎng)度發(fā)送給服務(wù)器//先獲取data的長(zhǎng)度->轉(zhuǎn)成一個(gè)表示長(zhǎng)度的byte切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //將該、長(zhǎng)度轉(zhuǎn)成了byte類型是數(shù)據(jù)//發(fā)送長(zhǎng)度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}//fmt.Printf("客戶端發(fā)送數(shù)據(jù)的消息長(zhǎng)度=%d 內(nèi)容是=%s",len(data),string(data))//發(fā)送消息本身_, err = conn.Write(data)if err !=nil {fmt.Println("connWrite(data) fail ",err)return}//休眠20秒// time.Sleep(10 * time.Second)// fmt.Println("休眠了20秒..")//這里還需要處理服務(wù)器端返回的消息//創(chuàng)建一個(gè)Transfer實(shí)例tf := &utils.Transfer{Conn : conn,}mes, err = tf.ReadPkg() //mes 就是if err != nil {fmt.Println("readPkg(conn) err=",err)return}//將mes的Data部分反序列化為L(zhǎng)oginResMesvar loginResMes message.LoginResMeserr = json.Unmarshal([]byte(mes.Data),&loginResMes)if loginResMes.Code == 200 {//fmt.Println("登錄成功")//這里我們還需要再客戶端啟動(dòng)一個(gè)協(xié)程//該協(xié)程保持和服務(wù)器端的通訊,如果服務(wù)器有數(shù)據(jù)推送給客戶端//則可以接受并顯示在客戶端的終端go serverProcessMes(conn)//1.顯示登錄成功后的菜單[循環(huán)顯示]for {ShowMenu()}}else if loginResMes.Code == 500 {fmt.Println(loginResMes.Error)}return
}

說(shuō)明:該文件就是在原來(lái)login.go做了一個(gè)改進(jìn),封裝到userProcess結(jié)構(gòu)體

(5)創(chuàng)建了server/process/server.go

package process
import ("fmt""os""go_code/chatroom/client/utils""net"
)//顯示登錄后的界面..
func ShowMenu(){fmt.Println("----------恭喜xxx登錄成功--------")fmt.Println("          1.顯示用戶在線列表     ")fmt.Println("          2.發(fā)送消息            ")fmt.Println("          3.信息列表            ")fmt.Println("          4.退出系統(tǒng)            ")fmt.Println("請(qǐng)選擇(1-4): ")var key intfmt.Scanf("%d\n",&key)switch key {case 1:fmt.Println("顯示用戶在線列表")case 2:fmt.Println("發(fā)送消息")case 3:fmt.Println("信息列表")case 4:fmt.Println("你選擇退出系統(tǒng) ")	os.Exit(0)	default:fmt.Println("你輸入的選項(xiàng)不正確")		}
}
//和服務(wù)器保持通訊
func serverProcessMes(conn net.Conn) {//創(chuàng)建一個(gè)transfer實(shí)例,不停的讀取服務(wù)器發(fā)送的消息tf := &utils.Transfer{Conn : conn,}for {fmt.Printf("客戶端正在等待讀取服務(wù)器發(fā)送的消息")mes, err:=tf.ReadPkg()if err != nil {fmt.Println("tf.ReadPkg err=",err)return}//如果讀取到消息,又是下一步處理邏輯fmt.Printf("mes=%v",mes)}}

(6)client/main/main.go

package main
import ("fmt""os""go_code/chatroom/client/process"
)//定義兩個(gè)變量,一個(gè)表示用戶的id,一個(gè)表示用戶的密碼
var userId int
var userPwd stringfunc main() {//接收用戶的選擇var key int//判斷是否還繼續(xù)顯示菜單// loop = truefor true{fmt.Println("-----------歡迎登錄多人聊天系統(tǒng)------")fmt.Println("\t\t\t 1 登錄聊天室")fmt.Println("\t\t\t 2 注冊(cè)用戶")fmt.Println("\t\t\t 3 退出系統(tǒng)")fmt.Println("\t\t\t 請(qǐng)選擇 1-3:")fmt.Scanf("%d\n",&key)switch key {case 1 :fmt.Println("登錄聊天室")fmt.Println("請(qǐng)輸入用戶的id")fmt.Scanf("%d\n",&userId)fmt.Println("請(qǐng)輸入用戶的密碼")fmt.Scanf("%s\n",&userPwd)//完成登錄//1.創(chuàng)建一個(gè)UserProcess的實(shí)例up :=&process.UserProcess{}up.Login(userId,userPwd)//loop=falsecase 2 :fmt.Println("注冊(cè)用戶")	//loop=falsecase 3 :fmt.Println("退出系統(tǒng)")	//loop=falseos.Exit(0)default:fmt.Println("輸入有誤,請(qǐng)輸入1-3")	}}
}
-5.應(yīng)用redis
1)在Redis手動(dòng)添加測(cè)試用戶,并畫(huà)圖+說(shuō)明注意(后面通過(guò)程序注冊(cè)用戶)

在這里插入圖片描述

手動(dòng)直接在redis增加一個(gè)用戶信息

在這里插入圖片描述

2)如輸入的用戶名密碼正確在Redis中存在則登錄,否則退出系統(tǒng),并給出相應(yīng)的提示信息
  • 1.用戶不存在,你也可以重新注冊(cè),再登錄
  • 2.你的密碼不正確
3)代碼實(shí)現(xiàn)
(1)先編寫(xiě)了server/model/user.go
package model//定義一個(gè)用戶的結(jié)構(gòu)體
type User struct {//確定字段信息//為了序列化和反序列化成功//用戶信息的json字符串與結(jié)構(gòu)體字段對(duì)應(yīng)的Tag名字一致UserId int `json:"userId"`UserPwd string `json:"userPwd"`UserName string `json:"userName"`
}
(2)先編寫(xiě)了server/model/error.go
package model
import ("errors"
)//根據(jù)業(yè)務(wù)邏輯的需要,自定義一些錯(cuò)誤var (ERROR_USER_NOTEXIST = errors.New("用戶不存在。。")ERROR_USER_EXIST = errors.New("用戶已存在。。")ERROR_USER_PWD = errors.New("密碼錯(cuò)誤"))
(3)編寫(xiě)了server/model/userDao.go
package model
import ("fmt""github.com/garyburd/redigo/redis""encoding/json"
)//我們?cè)诜?wù)器啟動(dòng)后,就初始化一個(gè)UserDao實(shí)例
//把它做成全局的變量,在需要和redis操作時(shí),就直接使用即可
var (MyUserDao *UserDao
)
//定義一個(gè)UserDao結(jié)構(gòu)體
//完成對(duì)User 結(jié)構(gòu)體的各種操作type UserDao struct {pool *redis.Pool 
}//使用工廠模式創(chuàng)建一個(gè)UserDao實(shí)例
func NewUserDao(pool *redis.Pool) (userDao *UserDao){userDao = &UserDao{pool:pool,}return
}//寫(xiě)方法,應(yīng)該提供哪個(gè)方法呢
//1,根據(jù)用戶id返回一個(gè)User實(shí)例+err
func (this *UserDao) getUserById(conn redis.Conn,id int) (user *User,err error) {//通過(guò)給定的id去redis去查詢用戶res,err := redis.String(conn.Do("HGet","users",id))if err != nil {//錯(cuò)誤if err == redis.ErrNil {//表示在users中沒(méi)有找到對(duì)應(yīng)的iderr= ERROR_USER_NOTEXIST}return}user = &User{}//這里我們需要反序列化成一個(gè)User實(shí)例err = json.Unmarshal([]byte(res),user)if err != nil {fmt.Println("json.Unmarshal Err=",err)return}return}//完成登錄的校驗(yàn) Login
//1.Login 完成對(duì)用戶的驗(yàn)證
//2.如果用戶的id和pwd都正確,則返回一個(gè)User實(shí)例
//3.如果用戶的id和pwd有錯(cuò)誤,則返回對(duì)應(yīng)的錯(cuò)誤信息func (this *UserDao)Login(userId int,userPwd string)(user *User,err error){//先從UserDao鏈接池中取出一根連接conn := this.pool.Get()defer conn.Close()user,err = this.getUserById(conn,userId)if err != nil {return}//這時(shí)證明用戶是獲取到了if user.UserPwd != userPwd {err = ERROR_USER_PWDreturn}return
}
(4)編寫(xiě)了server/main.redis.go
package main
import ("github.com/garyburd/redigo/redis""time")//定義一個(gè)全局的pool
var pool *redis.Poolfunc initPool(address string,maxIdle,maxActive int,idleTimeout time.Duration) {pool = &redis.Pool{MaxIdle: maxIdle, //最大空閑連接數(shù)MaxActive: maxActive,//表示和數(shù)據(jù)庫(kù)的最大連接數(shù),0表示沒(méi)有限制IdleTimeout: idleTimeout,//最大空閑時(shí)間Dial:func()(redis.Conn,error){//初始化連接的代碼。連接哪個(gè)ipreturn redis.Dial("tcp",address)},}
}
(5)編寫(xiě)了server/process/userProcess.go改進(jìn)登錄方式以及錯(cuò)誤類型
//我們需要到redis數(shù)據(jù)庫(kù)去完成驗(yàn)證//1.使用model.MyUserDao到redis去驗(yàn)證user, err := model.MyUserDao.Login(loginMes.UserId,loginMes.UserPwd)if err != nil {if err ==model.ERROR_USER_NOTEXIST {loginResMes.Code = 500loginResMes.Error = err.Error()}else if err ==model.ERROR_USER_PWD {loginResMes.Code = 403loginResMes.Error = err.Error()}else {loginResMes.Code = 505loginResMes.Error = "服務(wù)器內(nèi)部錯(cuò)誤..."}//這里我們先測(cè)試成功,然后再返回具體的錯(cuò)誤信息}else{loginResMes.Code = 200fmt.Println(user,"登錄成功")}
(6)改進(jìn)server/main/main.go(加了一個(gè)初始化redis連接池的函數(shù))
func init(){//當(dāng)服務(wù)器啟動(dòng)時(shí),我們就去初始化我們的redis的連接池initPool("localhost:6379",16,0,300 * time.Second)initUserDao()
}//這里我們編寫(xiě)一個(gè)函數(shù)完成對(duì)UserDao的初始化任務(wù)
func initUserDao() {//這里的pool本身就是一個(gè)全局的變量//這里需要注意一個(gè)初始化的順序問(wèn)題//initPool,在initUserDaomodel.MyUserDao =model.NewUserDao(pool)
}

4.完成用戶注冊(cè)操作

1)要求

完成注冊(cè)功能,將用戶信息錄入到Redis中

思路分析,并完成代碼

思路分析的示意圖

2)具體代碼
(1)common/message/user.go(從server/model下復(fù)制過(guò)來(lái)的。記住要復(fù)制而不是剪切還要改包名)
package message// User 定義一個(gè)用戶的結(jié)構(gòu)體
type User struct {//確定字段信息//為了序列化和反序列化成功//用戶信息的json字符串與結(jié)構(gòu)體字段對(duì)應(yīng)的Tag名字一致UserId   int    `json:"userId"`UserPwd  string `json:"userPwd"`UserName string `json:"userName"`
}
(2)common/message/message.go增加了關(guān)于注冊(cè)消息的代碼
type RegisterMes struct {User User `json:"user"` //類型就是User結(jié)構(gòu)體}
type RegisterResMes struct {Code int `json:"code"` //返回狀態(tài)碼400表示該用戶已經(jīng)占用 200表示登錄注冊(cè)成功Error string `json` //返回錯(cuò)誤信息
}
(3)server/process/userProcess(增加了一個(gè)方法)
func (this *UserProcess) ServerProcessRegister(mes *message.Message) (err error){//1.先從mes中取出mes.Data,并直接反序列化成RegisterMesvar registerMes message.RegisterMeserr = json.Unmarshal([]byte(mes.Data), &registerMes)if err != nil {fmt.Println("json.Unmarshal fail err=", err)return}//1.先聲明一個(gè)resMesvar resMes message.MessageresMes.Type = message.RegisterResMesType//2.再聲明一個(gè)RegisterMesvar registerResMes message.RegisterResMes//我們需要到redis數(shù)據(jù)庫(kù)去完成注冊(cè)//1.使用model.MyUserDao到redis去注冊(cè)err= model.MyUserDao.Register(&registerMes.User)if err !=nil {if err == model.ERROR_USER_EXISTS {registerResMes.Code = 505registerResMes.Error = model.ERROR_USER_EXISTS.Error()} else {registerResMes.Code = 506registerResMes.Error = "注冊(cè)時(shí)發(fā)生未知錯(cuò)誤"}} else {registerResMes.Code = 200}//3.將loginResMes 序列化data, err := json.Marshal(registerResMes)if err != nil {fmt.Println("Marshal fail err=", err)}//4.將data賦值給resMesresMes.Data = string(data)//5.對(duì)resMes進(jìn)行序列化,準(zhǔn)備發(fā)送data, err = json.Marshal(resMes)if err != nil {fmt.Println("Marshal fail err=", err)return}//6.發(fā)送data 我們將其封裝為writePkg//因?yàn)槭褂昧朔謱幽J?mvc),我們先創(chuàng)建一個(gè)Transfer實(shí)例,然后讀取tf := &utils.Transfer{Conn: this.Conn,}err = tf.WritePkg(data)return}
(4)server/model/userDao(增加了一個(gè)Register方法對(duì)數(shù)據(jù)庫(kù)進(jìn)行添加的操作)
func (this *UserDao)Register(user *message.User)(err error){//先從UserDao鏈接池中取出一根連接conn := this.pool.Get()defer conn.Close()_,err = this.getUserById(conn,user.UserId)if err == nil {err = ERROR_USER_EXISTSreturn}//這時(shí)說(shuō)明id在redis還沒(méi)有,則可以完成注冊(cè)data, err :=json.Marshal(user) //序列化if err != nil {return}//入庫(kù)_,err = conn.Do("HSet","users",user.UserId,string(data))if err != nil {fmt.Println("保存注冊(cè)用戶錯(cuò)誤 err=",err)return}return}
(5)在client/main/main.go進(jìn)行了調(diào)用操作
case 2 :fmt.Println("注冊(cè)用戶")	fmt.Println("請(qǐng)輸入用戶id")	fmt.Scanf("%d\n",&userId)fmt.Println("請(qǐng)輸入用戶的密碼")fmt.Scanf("%s\n",&userPwd)fmt.Println("請(qǐng)輸入用戶的名字(昵稱)")fmt.Scanf("%s\n",&userName)//2.調(diào)用UserProcess,完成注冊(cè)的請(qǐng)求up :=&process.UserProcess{}up.Register(userId,userPwd,userName)
(6)client/process/userProcess.go(添加一個(gè)Register的方法)
func (this *UserProcess) Register(userId int,userPwd string,userName string)(err error){//1.連接到服務(wù)器端conn, err :=net.Dial("tcp","localhost:8889")if err != nil {fmt.Println("net.Dial err=",err)return}//延時(shí)關(guān)閉defer conn.Close()//2.準(zhǔn)備通過(guò)conn發(fā)送消息給服務(wù)器var mes message.Messagemes.Type = message.RegisterMesType//3.創(chuàng)建一個(gè)RegisterMes 結(jié)構(gòu)體var registerMes message.RegisterMesregisterMes.User.UserId = userIdregisterMes.User.UserPwd = userPwd registerMes.User.UserName = userName//4.將registerMes序列化data, err :=json.Marshal(registerMes)if err != nil {fmt.Println("json.Mashal err=",err)return}//5.將data賦給了mes.Data字段mes.Data = string(data)//6.將mes進(jìn)行序列化data, err =json.Marshal(mes)if err != nil {fmt.Println("json.Mashal err=",err)return}//7.到這個(gè)時(shí)候,data就是我們要發(fā)送的消息//7.1先把data的長(zhǎng)度發(fā)送給服務(wù)器//先獲取data的長(zhǎng)度->轉(zhuǎn)成一個(gè)表示長(zhǎng)度的byte切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //將該、長(zhǎng)度轉(zhuǎn)成了byte類型是數(shù)據(jù)//發(fā)送長(zhǎng)度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}fmt.Printf("客戶端發(fā)送數(shù)據(jù)的消息長(zhǎng)度=%d 內(nèi)容是=%s",len(data),string(data))//發(fā)送消息本身_, err = conn.Write(data)if err !=nil {fmt.Println("connWrite(data) fail ",err)return}//創(chuàng)建一個(gè)Transfer實(shí)例tf := &utils.Transfer{Conn : conn,}//發(fā)送data給服務(wù)器端err = tf.WritePkg(data)if err != nil {fmt.Println("注冊(cè)發(fā)送信息錯(cuò)誤 err=",err)}mes, err = tf.ReadPkg() //mes 就是RegisterResMesif err != nil {fmt.Println("readPkg(conn) err=",err)return}//將mes的Data部分反序列化為RegisterResMesvar registerResMes message.RegisterResMeserr = json.Unmarshal([]byte(mes.Data),&registerResMes)if registerResMes.Code == 200 {fmt.Println("注冊(cè)成功,你重新登錄一把")os.Exit(0)}else {fmt.Println(registerResMes.Error)os.Exit(0)}return}

5.實(shí)現(xiàn)功能-完成登錄時(shí)能返回當(dāng)前在線用戶

1)用戶登陸后,可以得到當(dāng)前在線用戶列表 思路分析、示意圖代碼實(shí)現(xiàn)

用戶登陸后,可以得到當(dāng)前在線用戶列表

(1)在服務(wù)器端維護(hù)一個(gè)onlineUsers map[int] *UserProcess

(2)創(chuàng)建一個(gè)新的文件userMgr.go,完成功能,對(duì)onlineUsers這個(gè)map進(jìn)行增刪改查

(3)在loginResMes增加一個(gè)字段 User []int 將在線的用戶ID返回

(4)當(dāng)用戶登陸后,可以顯示當(dāng)前在線用戶列表

2)示意圖

在這里插入圖片描述

3)代碼實(shí)現(xiàn)
(1)編寫(xiě)了server/process/userMgr.go
package process
import ("fmt"
)
//因?yàn)閁serMge實(shí)例在服務(wù)其中有且只有一個(gè)
//因?yàn)樵诤芏嗟牡胤?#xff0c;都會(huì)使用,因此,我們
//將其定義為全局變量
var (userMgr *UserMgr
)
type UserMgr struct {onlineUsers map[int]*UserProcess
}//完成對(duì)userMge的初始化工作
func init() {userMgr = &UserMgr{onlineUsers : make(map[int]*UserProcess,1024),}
}//完成對(duì)onlineUsers的添加
func (this *UserMgr) AddOnlinesUser(up *UserProcess) {this.onlineUsers[up.UserId] = up
}//刪除
func (this *UserMgr) DeleteOnlinesUser(userId int ) {delete(this.onlineUsers,userId)
}//返回當(dāng)前所有在線的用戶
func (this *UserMgr)GetAllUsers() map[int]*UserProcess {return this.onlineUsers
}//根據(jù)id返回對(duì)應(yīng)的值
func(this *UserMgr) GetOnlineUserById(userId int) (up *UserProcess,err error){//如何從map中取出一個(gè)值,待檢測(cè)的方式up, ok := this.onlineUsers[userId]if !ok { //說(shuō)明你要查找的用戶,當(dāng)前不在線err = fmt.Errorf("用戶id不存在",userId)return} return
}
(2)server/process/userProcess.go(在login成功的地方加入代碼)
} else {loginResMes.Code = 200//這里,因?yàn)橛脩舻卿洺晒?#xff0c;我們就把登錄成功的用戶放入到userMgr中//將登錄成功的用戶的userId賦給thisthis.UserId = loginMes.UserIduserMgr.AddOnlinesUser(this)//將當(dāng)前在線用戶的id放入到loginResMes.UsersId//遍歷userMgr.onlineUsersfor id, _ := range userMgr.onlineUsers{loginResMes.UsersId = append(loginResMes.UsersId,id)}fmt.Println(user, "登錄成功")}

(3)client

/process/userProcess.go(在login成功的地方加入代碼)

//現(xiàn)在可以顯示當(dāng)前在線的列表 遍歷loginResMes.UsersIdfmt.Println("當(dāng)前在線用戶列表如下")for _, v := range loginResMes.UsersId {//如果我們要求不顯示自己在線,下面我們?cè)黾右粋€(gè)代碼if v == userId {continue}fmt.Println("用戶id:\t",v)}fmt.Println("\n\n")
4)當(dāng)一個(gè)新的用戶上線后,其他已經(jīng)登錄的用戶也能獲取最新在新用戶列表

思路1:

當(dāng)有一個(gè)用戶上線后,服務(wù)其就馬上把維護(hù)的onlineUser map整體推送

思路2:

服務(wù)其有自己的策略,每隔一段時(shí)間,把維護(hù)的onlineUsers map整體推送

思路3:

(1)當(dāng)一個(gè)用戶上線后,服務(wù)器就把A用戶的上線信息推送給所有在線用戶即可

(2)客戶端也要維護(hù)一個(gè)map,map中記錄了他的好友(目前就是所有人)map[int]User

(3)客戶端和服務(wù)器的通訊通道要依賴于serverProcess協(xié)程

代碼實(shí)現(xiàn)

(1)在server/process/userMgr.go

package process
import ("fmt""go_code/chatroom/common/message"
)//客戶端要維護(hù)的Map
var onlineUsers map[int]*message.User = make(map[int]*message.User,10)//在客戶端顯示當(dāng)前在線的用戶
func outputOnlineUser() {//遍歷一把onlineUsersfmt.Println("當(dāng)前在線用戶列表:")for id,_ := range onlineUsers{//如果不顯示自己fmt.Println("用戶id:\t\t",id)}
}//編寫(xiě)一個(gè)方法,處理返回的NotifyUserStatusMes
func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) {//適當(dāng)?shù)膬?yōu)化user,ok :=onlineUsers[notifyUserStatusMes.UserId]if !ok { //原來(lái)沒(méi)有user = &message.User{UserId : notifyUserStatusMes.UserId,}	}user.UserStatus = string(notifyUserStatusMes.Status)onlineUsers[notifyUserStatusMes.UserId] = useroutputOnlineUser()
}

(2)server/process/userProcess.go

//這里我們編寫(xiě)通知所有在線用戶的方法
//這個(gè)id要通知其他的在線用戶,我上線
func (this *UserProcess) NotifyOthersOnlineUser(userId int) {//遍歷 onlineUsers ,然后一個(gè)一個(gè)的發(fā)送 NotifyUserStatusMesfor id, up := range userMgr.onlineUsers {//過(guò)濾掉自己if id == userId {continue}//開(kāi)始通知【單獨(dú)的寫(xiě)一個(gè)方法】up.NotifyMeOnline(userId)}}func (this *UserProcess) NotifyMeOnline(userId int){//組裝我們的NotifyUserStatusMesvar mes message.Messagemes.Type = message.NotifyUserStatusMesTypevar notifyUserStatusMes message.NotifyUserStatusMesnotifyUserStatusMes.UserId = userIdnotifyUserStatusMes.Status = message.UserOnline//將notifyUserStatusMes序列化data, err := json.Marshal(notifyUserStatusMes)if err != nil {fmt.Println("json.Marshal err",err)return}//將序列化后的notifyUserStatusMes賦值給mes.Datames.Data = string(data)//對(duì)message再次序列化data, err = json.Marshal(mes)if err != nil {fmt.Println("json.Marshal err",err)return}//發(fā)送,創(chuàng)建一個(gè)transfer實(shí)例發(fā)送tf := &utils.Transfer{Conn : this.Conn,}err = tf.WritePkg(data)if err != nil {fmt.Println("NotifyMeOline err=",err)return}}下面調(diào)用
//通知其他的用戶我上線了this.NotifyOthersOnlineUser(loginMes.UserId)

(3)common/message/message.go

//為了配合服務(wù)器端推送用戶狀態(tài)變化類型
type NotifyUserStatusMes struct {UserId int `json:"userId"` //用戶idStatus int `json:"status"` //用戶的狀態(tài)
}

(4)客戶端client/process/userMgr.go

package process
import ("fmt""go_code/chatroom/common/message"
)//客戶端要維護(hù)的Map
var onlineUsers map[int]*message.User = make(map[int]*message.User,10)//在客戶端顯示當(dāng)前在線的用戶
func outputOnlineUser() {//遍歷一把onlineUsersfmt.Println("當(dāng)前在線用戶列表:")for id,_ := range onlineUsers{//如果不顯示自己fmt.Println("用戶id:\t\t",id)}
}//編寫(xiě)一個(gè)方法,處理返回的NotifyUserStatusMes
func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) {//適當(dāng)?shù)膬?yōu)化user,ok :=onlineUsers[notifyUserStatusMes.UserId]if !ok { //原來(lái)沒(méi)有user = &message.User{UserId : notifyUserStatusMes.UserId,}	}user.UserStatus = string(notifyUserStatusMes.Status)onlineUsers[notifyUserStatusMes.UserId] = useroutputOnlineUser()
}

(5)client/main/server.go

case 1://fmt.Println("顯示用戶在線列表")outputOnlineUser()case 2://如果讀取到消息,又是下一步處理邏輯switch mes.Type {case message.NotifyUserStatusMesType : //有人上線了//1.取出 NotifyUserStatusMesvar notifyUserStatusMes message.NotifyUserStatusMesjson.Unmarshal([]byte(mes.Data),&notifyUserStatusMes)//2.把這個(gè)用戶的信息,狀態(tài)保存在客戶端的map[int]User中updateUserStatus(&notifyUserStatusMes)//處理default :fmt.Println("服務(wù)其端返回了未知的消息類型")	}

6.完成登錄可以完成群聊操作

-1.步驟1 :

當(dāng)一個(gè)用戶上線后,可以將群聊消息發(fā)給服務(wù)器。服務(wù)器可以接收到

1)思路分析

在這里插入圖片描述

(1)新增一個(gè)消息結(jié)構(gòu)體

(2)新增一個(gè)model CurUser

(3)在smsProcess增加相應(yīng)的方法 SendGroupMes,

2)代碼實(shí)現(xiàn)

(1)common/message/message.go

//增加一個(gè)SmsMes //發(fā)送的
type SmsMes struct {Content string   `json:"content"` //內(nèi)容User //匿名結(jié)構(gòu)體,繼承
}

(2)client/model/curUser.go

package model
import ("net""go_code/chatroom/common/message"
)//因?yàn)樵诳蛻舳?#xff0c;我們很多地方會(huì)使用到curUser,我們將其作為一個(gè)全局的
type CurUser struct {Conn net.Connmessage.User
}

(3)client/process/smsProcess.go

package process
import ("fmt""encoding/json""go_code/chatroom/common/message""go_code/chatroom/client/utils"
)type SmsProcess struct {}//發(fā)送群聊的消息
func (this *SmsProcess) SendGroupMes(content string) (err error) {//1.創(chuàng)建一個(gè)Mesvar mes message.Messagemes.Type = message.SmsMesType//2.創(chuàng)建一個(gè)SmsMes 實(shí)例var smsMes message.SmsMessmsMes.Content = content //內(nèi)容smsMes.UserId = CurUser.UserIdsmsMes.UserStatus = CurUser.UserStatus//3.序列化smsMesdata, err := json.Marshal(smsMes)if err != nil {fmt.Println("SendGroupMes json.Marshal err=",err.Error())return}mes.Data = string(data)//4.對(duì)mes再次序列化data, err = json.Marshal(mes)if err != nil {fmt.Println(" json.Marshal err=",err.Error())return}//5.將mes發(fā)送給服務(wù)器tf := &utils.Transfer{Conn : CurUser.Conn,}//6.發(fā)送err = tf.WritePkg(data)if err != nil {fmt.Println("SendGroupsMes err=",err.Error())return}return
}

(4)測(cè)試

在這里插入圖片描述

-2.步驟2.

服務(wù)器可以將接收到的消息,群發(fā)給所有在線用戶(發(fā)送者除外)

1)思路分析

在這里插入圖片描述

(1)在服務(wù)器端接收到SmsMes消息

(2)在server/process/SmsProcess.go文件增加群發(fā)消息的方法

(3)在客戶端還要增加去處理服務(wù)器端轉(zhuǎn)發(fā)的群發(fā)消息

2)代碼實(shí)現(xiàn)

(1)server/main/processor.go[在server中調(diào)用轉(zhuǎn)發(fā)消息的方法]

//處理注冊(cè)up := &process2.UserProcess{Conn : this.Conn,}err = up.ServerProcessRegister(mes)case message.SmsMesType ://創(chuàng)建一個(gè)SmsProcess實(shí)例完成轉(zhuǎn)發(fā)群聊消息。	smsProcess := &process2.SmsProcess{}smsProcess.SendGroupMes(mes)

(2)client/process/smsMes.go

package process
import ("fmt""encoding/json""go_code/chatroom/common/message""go_code/chatroom/client/utils"
)type SmsProcess struct {}//發(fā)送群聊的消息
func (this *SmsProcess) SendGroupMes(content string) (err error) {//1.創(chuàng)建一個(gè)Mesvar mes message.Messagemes.Type = message.SmsMesType//2.創(chuàng)建一個(gè)SmsMes 實(shí)例var smsMes message.SmsMessmsMes.Content = content //內(nèi)容smsMes.UserId = CurUser.UserIdsmsMes.UserStatus = CurUser.UserStatus//3.序列化smsMesdata, err := json.Marshal(smsMes)if err != nil {fmt.Println("SendGroupMes json.Marshal err=",err.Error())return}mes.Data = string(data)//4.對(duì)mes再次序列化data, err = json.Marshal(mes)if err != nil {fmt.Println(" json.Marshal err=",err.Error())return}//5.將mes發(fā)送給服務(wù)器tf := &utils.Transfer{Conn : CurUser.Conn,}//6.發(fā)送err = tf.WritePkg(data)if err != nil {fmt.Println("SendGroupsMes err=",err.Error())return}return
}

(3)client/process/smsMgr.go

package process
import ("fmt""encoding/json""go_code/chatroom/common/message")func outputGroupMes(mes *message.Message) {//這個(gè)地方一定是SmsMes//顯示即可//1.反序列化mes.Datavar smsMes message.SmsMes err := json.Unmarshal([]byte(mes.Data),&smsMes)if err != nil {fmt.Println("json.Unmarshal err=",err.Error())return}//顯示信息info := fmt.Sprintf("用戶id:\t%d 對(duì)大家說(shuō):\t%s",smsMes.UserId,smsMes.Content)fmt.Println(info)fmt.Println()
}

(4)client/process/server.go

case message.SmsMesType : //有人群發(fā)消息了outputGroupMes(&mes)
3)拓展功能要求

1.可以實(shí)現(xiàn)私聊(點(diǎn)對(duì)點(diǎn)聊天)

2.如果一個(gè)登錄用戶離線,就把這個(gè)人從在線列表中去掉

3.實(shí)現(xiàn)離線留言,在群聊時(shí),如果某個(gè)用戶沒(méi)有在線,當(dāng)?shù)卿浐?#xff0c;可以接受到離線的消息

http://m.aloenet.com.cn/news/33763.html

相關(guān)文章:

  • 網(wǎng)站留言系統(tǒng)編寫(xiě)代碼站長(zhǎng)工具 seo查詢
  • 物理網(wǎng)絡(luò)設(shè)計(jì)是什么寧波seo網(wǎng)絡(luò)推廣推薦
  • 網(wǎng)絡(luò)推廣銷(xiāo)售怎么做seo文章生成器
  • 茂名優(yōu)化網(wǎng)站建設(shè)優(yōu)化seo廠家
  • 網(wǎng)站建設(shè)應(yīng)當(dāng)注意韓國(guó)今日特大新聞
  • 網(wǎng)站建設(shè) 化工網(wǎng)絡(luò)推廣的話術(shù)怎么說(shuō)
  • 網(wǎng)站分站的實(shí)現(xiàn)方法微博推廣方式
  • 上海網(wǎng)站建設(shè)聚眾網(wǎng)絡(luò)杭州seo網(wǎng)站排名
  • 把網(wǎng)站傳到服務(wù)器上怎么做友情鏈接怎么交換
  • 有哪些網(wǎng)站可以做視頻百度一下免費(fèi)下載安裝
  • 類似百科式的網(wǎng)站建設(shè)app推廣平臺(tái)
  • asp網(wǎng)站助手北京網(wǎng)站優(yōu)化步
  • 免費(fèi)制圖網(wǎng)站關(guān)鍵詞seo公司推薦
  • 中國(guó)做的電腦系統(tǒng)下載網(wǎng)站免費(fèi)大數(shù)據(jù)網(wǎng)站
  • wordpress怎么修改文字白楊seo教程
  • 甘肅省城鄉(xiāng)與住房建設(shè)廳網(wǎng)站怎么網(wǎng)站推廣
  • 徐州網(wǎng)站建設(shè)方案推廣網(wǎng)址查詢注冊(cè)信息查詢
  • 用新域名做網(wǎng)站排名快嗎上海外貿(mào)seo
  • 怎么模仿別人做網(wǎng)站臺(tái)州專業(yè)關(guān)鍵詞優(yōu)化
  • 公司官網(wǎng)網(wǎng)站如何建立朋友圈的廣告推廣怎么弄
  • 做網(wǎng)站用js的好處seo的中文含義是
  • 丹陽(yáng)網(wǎng)站建設(shè)服務(wù)網(wǎng)絡(luò)銷(xiāo)售是什么
  • 網(wǎng)站的做網(wǎng)站公司哪家好鄭州百度推廣代運(yùn)營(yíng)
  • 星空影視文化傳媒制作公司網(wǎng)站seo綜合診斷
  • 日本男女做受網(wǎng)站公司專業(yè)網(wǎng)站建設(shè)
  • 上海網(wǎng)頁(yè)設(shè)計(jì)班咸寧網(wǎng)站seo
  • 廣州網(wǎng)站建設(shè)怎么做福清市百度seo
  • 網(wǎng)站備案阿里云流程seo網(wǎng)站推廣杭州
  • 做曖暖的免費(fèi)網(wǎng)站windows優(yōu)化軟件排行
  • 免費(fèi)最好網(wǎng)站建設(shè)百度明星搜索量排行榜