go 聊天系统项目-1

1、登录界面

说明:这一节的内容采用 go mod 管理【GO111MODULE=‘’】的模块,从第二节开始使用【GO111MODULE=‘off’】GOPATH 管理模块。具体参见 go 包相关知识

1.1登录界面代码目录结构

代码所在目录/Users/zld/Go-project/day8/chatroom/
在这里插入图片描述

1.2登录界面代码

main.go

package mainimport ("fmt"
)var userId int
var userPwd stringfunc main() {var key intvar loop = truefor loop {fmt.Println("---------------欢迎登录多人聊天系统-------------------")fmt.Println("\t\t\t 1、登录聊天室")fmt.Println("\t\t\t 2、注册用户")fmt.Println("\t\t\t 3、退出系统")fmt.Println("\t\t\t 请选择(1-3):")fmt.Scanf("%d\n", &key)switch key {case 1:fmt.Println("登录聊天室")loop = falsecase 2:fmt.Println("注册用户")loop = falsecase 3:fmt.Println("退出系统")loop = falsedefault:fmt.Println("你的输入有误,请重新输入")}}if key == 1 {fmt.Println("请输入用户的id")fmt.Scanf("%d\n", &userId)fmt.Println("请输入用户密码")fmt.Scanf("%s\n", &userPwd)err := login(userId, userPwd)if err != nil {fmt.Println("登录失败")} else {fmt.Println("登录成功")}} else if key == 2 {fmt.Println("进行用户注册的逻辑")}
}

login.go

package mainimport ("fmt"
)func login(userId int, userPwd string) (err error) {fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)return nil
}

1.3初始化模块

go mod init client 

注意:init 后跟的名字和二进制文件名字(go build -o 后的名字)一样

go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
go: creating new go.mod: module client
go: to add module requirements and sums:go mod tidy

1.4编译

cd /Users/zld/Go-project/day8/chatroom/client/
go build -o client ./
输出
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject

1.5演示代码

go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
1
登录聊天室
请输入用户的id
123
请输入用户密码
qwe
userId = 123 userPwd = qwe
登录成功
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
3
退出系统
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
2
注册用户
进行用户注册的逻辑
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
>
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
?
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):
5
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):

2、客户端服务端简单交互

2.1代码目录结构

GOPATH=‘/Users/zld/Go-project’
项目目录结构,项目在 /Users/zld/Go-project/src 【GOPATH指定的目录】下

tree
.
└── chatroom├── client│   ├── login.go│   ├── main.go  ├── common│   └── message│       └── message.go└── server└── main.go6 directories, 4 files

2.2代码

2.2.1 day8/chatroom/common/message/message.go
package messageconst (LoginMesType    = "LoginMes"LoginResMesType = "LoginResMes"
)type Message struct {Type string `josn: "type"`Data string `json: "Data"`
}
type LoginMes struct {UserId   int    `json: "userId"`UserPwd  string `json: "userPwd"`UserName string `json: "userName"`
}
type LoginResMes struct {Code  int    `json: "code"`Error string `json: "error"`
}
2.2.2 day8/chatroom/server/main.go
package mainimport ("fmt""net"
)// 处理和客户端的通信
func process(conn net.Conn) {//这里需要延时关闭conndefer conn.Close()//循环的客户端发送的信息for {buf := make([]byte, 8096)n, err := conn.Read(buf[:4])if n != 4 || err != nil {fmt.Println("conn.Read err=", err)return}fmt.Printf("读到的buf=%d\n", buf[:4])}}func main() {//提示信息fmt.Println("服务器在 8889 端口监听......")listen, err := net.Listen("tcp", "0.0.0.0:8889")if err != nil {fmt.Println("net.Listen err=", err)return}for {fmt.Println("等待客户端连接服务器......")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept() err=", err)}//一旦连接成功,则启动一个协程和客户端保持通讯go process(conn)}
}
2.2.3 day8/chatroom/client/client.go
package mainimport ("fmt"
)var userId int
var userPwd stringfunc main() {var key intvar loop = truefor loop {fmt.Println("---------------欢迎登录多人聊天系统-------------------")fmt.Println("\t\t\t 1、登录聊天室")fmt.Println("\t\t\t 2、注册用户")fmt.Println("\t\t\t 3、退出系统")fmt.Println("\t\t\t 请选择(1-3):")fmt.Scanf("%d\n", &key)switch key {case 1:fmt.Println("登录聊天室")loop = falsecase 2:fmt.Println("注册用户")loop = falsecase 3:fmt.Println("退出系统")loop = falsedefault:fmt.Println("你的输入有误,请重新输入")}}if key == 1 {fmt.Println("请输入用户的id")fmt.Scanf("%d\n", &userId)fmt.Println("请输入用户密码")fmt.Scanf("%s\n", &userPwd)err := login(userId, userPwd)if err != nil {fmt.Println("登录失败")} else {fmt.Println("登录成功")}} else if key == 2 {fmt.Println("进行用户注册的逻辑")}
}
2.2.4 day8/chatroom/client/main.go
package mainimport ("day8/chatroom/common/message" // 这里是写 go mod init 的 chatroom,然后最后是文件夹"encoding/binary""encoding/json""fmt""net"
)func login(userId int, userPwd string) (err error) {//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)//return nil//连接到服务器conn, err := net.Dial("tcp", "localhost:8889")if err != nil {fmt.Println("net.Dial err=", err)return}//延时关闭defer conn.Close()//准备通过 conn 发送消息给服务器var mes message.Messagemes.Type = message.LoginMesTypevar loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd//将 loginMes 序列化data, err := json.Marshal(loginMes)if err != nil {fmt.Println("json.Marshal err=", err)return}//将data赋值给 message 结构体 Data 字段mes.Data = string(data)//将 mes 进行序列化data, err = json.Marshal(mes)if err != nil {fmt.Println("json.Marshal err=", err)return}//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4], pkgLen)//发送长度n, err := conn.Write(buf[0:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))return
}

2.3 编译项目代码

注意:如果在 GO111MODULE=‘off’ 的情况下,编译代码一定要进到 $GOPATH 目录。

cd $GOPATH
go build -o server day8/chatroom/server/
go build -o client day8/chatroom/client/

2.4 演示代码

./server 
服务器在 8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
qwe
客户端,发送消息的长度=83,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"qwe\",\"UserName\":\"\"}"}
登录成功

server

等待客户端连接服务器......
读到的buf=[0 0 0 83]
conn.Read err= EOF

3、判断用户输入账户密码并改进代码结构

3.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom├── client│   ├── login.go│   ├── main.go│   └── utils.go├── common│   └── message│       └── message.go└── server└── main.go6 directories, 5 files

3.2 代码

3.2.1 day8/chatroom/client/login.go
package mainimport ("day8/chatroom/common/message" // 这里是写 go mod init 的 chatroom,然后最后是文件夹"encoding/binary""encoding/json""fmt""net"//"time"
)func login(userId int, userPwd string) (err error) {//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)//return nil//连接到服务器conn, err := net.Dial("tcp", "localhost:8889")if err != nil {fmt.Println("net.Dial err=", err)return}//延时关闭defer conn.Close()//准备通过 conn 发送消息给服务器var mes message.Messagemes.Type = message.LoginMesTypevar loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd//将 loginMes 序列化data, err := json.Marshal(loginMes)if err != nil {fmt.Println("json.Marshal err=", err)return}//将data赋值给 message 结构体 Data 字段mes.Data = string(data)//将 mes 进行序列化data, err = json.Marshal(mes)if err != nil {fmt.Println("json.Marshal err=", err)return}//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4], pkgLen)//发送长度n, err := conn.Write(buf[0:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))_, err = conn.Write(data)if err != nil {fmt.Printf("conn.Write(data) fail", err)return}//time.sleep(20*time.Second)//fmt.Println("休眠了20S")//这里还需要处理服务器返回的消息mes, err = readPkg(conn) //mes 就是if err != nil {fmt.Println("readPkg(conn) err=", err)return}//将 mes 的 data 部分反序列化成 LoginResMesvar 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
}
3.2.2 day8/chatroom/client/main.go
package mainimport ("fmt"
)var userId int
var userPwd stringfunc main() {var key intvar loop = truefor loop {fmt.Println("---------------欢迎登录多人聊天系统-------------------")fmt.Println("\t\t\t 1、登录聊天室")fmt.Println("\t\t\t 2、注册用户")fmt.Println("\t\t\t 3、退出系统")fmt.Println("\t\t\t 请选择(1-3):")fmt.Scanf("%d\n", &key)switch key {case 1:fmt.Println("登录聊天室")loop = falsecase 2:fmt.Println("注册用户")loop = falsecase 3:fmt.Println("退出系统")loop = falsedefault:fmt.Println("你的输入有误,请重新输入")}}if key == 1 {fmt.Println("请输入用户的id")fmt.Scanf("%d\n", &userId)fmt.Println("请输入用户密码")fmt.Scanf("%s\n", &userPwd)login(userId, userPwd)//err := login(userId, userPwd)// if err != nil {// 	fmt.Println("登录失败")// } else {// 	fmt.Println("登录成功")// }} else if key == 2 {fmt.Println("进行用户注册的逻辑")}
}
3.2.3 day8/chatroom/client/utils.go
package mainimport ("day8/chatroom/common/message""encoding/binary""encoding/json""errors""fmt""net"
)func readPkg(conn net.Conn) (mes message.Message, err error) {buf := make([]byte, 8096)fmt.Println("读取客户端发送的数据...")// conn.Read 在 conn 没有被关闭的情况下,才会阻塞//如果客户端关闭了conn 就不会阻塞_, err = conn.Read(buf[:4])if err != nil {//err = errors.New("read pkg header error")return}//根据读到的  buf[:4] 转成一个 unit32 类型var pkgLen uint32pkgLen = binary.BigEndian.Uint32(buf[0:4])n, err := conn.Read(buf[:pkgLen])if n != int(pkgLen) || err != nil {//err = errors.New("read pkg body error")return}//把 pkgLen 反序列化成  -> message.Messageerr = json.Unmarshal(buf[:pkgLen], &mes)if err != nil {err = errors.New("json.Unmarshal error")return}return}func writePkg(conn net.Conn, data []byte) (err error) {//先发送一个长度给对方//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4], pkgLen)//发送长度n, err := conn.Write(buf[0:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}//发送 data本身n, err = conn.Write(data)if n != int(pkgLen) || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}return
}
3.2.4 day8/chatroom/common/message/message.go
package messageconst (LoginMesType    = "LoginMes"LoginResMesType = "LoginResMes"RegisterMesType = "RegisterMes"
)type Message struct {Type string `josn: "type"`Data string `json: "Data"`
}
type LoginMes struct {UserId   int    `json: "userId"`UserPwd  string `json: "userPwd"`UserName string `json: "userName"`
}
type LoginResMes struct {Code  int    `json: "code"`Error string `json: "error"`
}type RegisterMes struct{//
}
3.2.5 day8/chatroom/server/main.go
package mainimport ("day8/chatroom/common/message""encoding/binary""encoding/json""errors""fmt""io""net"
)func readPkg(conn net.Conn) (mes message.Message, err error) {buf := make([]byte, 8096)// conn.Read 在 conn 没有被关闭的情况下,才会阻塞//如果客户端关闭了conn 就不会阻塞_, err = conn.Read(buf[:4])if err != nil {//err = errors.New("read pkg header error")return}//根据读到的  buf[:4] 转成一个 unit32 类型var pkgLen uint32pkgLen = binary.BigEndian.Uint32(buf[0:4])n, err := conn.Read(buf[:pkgLen])if n != int(pkgLen) || err != nil {//err = errors.New("read pkg body error")return}//把 pkgLen 反序列化成  -> message.Messageerr = json.Unmarshal(buf[:pkgLen], &mes)if err != nil {err = errors.New("json.Unmarshal error")return}return}func writePkg(conn net.Conn, data []byte) (err error) {//先发送一个长度给对方//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4], pkgLen)//发送长度n, err := conn.Write(buf[:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}//发送 data本身n, err = conn.Write(data)if n != int(pkgLen) || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}return
}func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) {//核心代码//先从mes 中取出 mes.Data,并直接反序列化成 LoginMesvar loginMes message.LoginMeserr = json.Unmarshal([]byte(mes.Data), &loginMes)if err != nil {fmt.Println("json.Unmarshal fail err=", err)return}// 先声明一个 resMesvar resMes message.MessageresMes.Type = message.LoginResMesType//再声明一个 LoginResMesvar loginResMes message.LoginResMes//如果用户id=100,密码=123456,认为合法,否则不合法if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {//合法loginResMes.Code = 200} else {//不合法loginResMes.Code = 500loginResMes.Error = "该用户不存在,请注册再使用..."}//将 loginResMes 序列化data, err := json.Marshal(loginResMes)if err != nil {fmt.Println("json.Marshal fail", err)return}//将data赋值给resMesresMes.Data = string(data)//对 resMes 进行序列化,准备发送data, err = json.Marshal(resMes)if err != nil {fmt.Println("json.Marshal fail", err)return}//发送 data,将其封装到函数中err = writePkg(conn, data)return
}func serverProcessMes(conn net.Conn, mes *message.Message) (err error) {switch mes.Type {case message.LoginMesType://处理登录err = serverProcessLogin(conn, mes)case message.RegisterMesType://处理注册default:fmt.Printf("消息类型不存在,无法处理")}return
}// 处理和客户端的通信
func process(conn net.Conn) {//这里需要延时关闭conndefer conn.Close()//循环客户端发送信息for {mes, err := readPkg(conn)if err != nil {if err == io.EOF {fmt.Println("客户端退出,服务器端也退出..")return} else {fmt.Println("readPkg err=", err)return}}//fmt.Println("mes=", mes)err = serverProcessMes(conn, &mes)if err != nil {return}}}func main() {//提示信息fmt.Println("服务器在 8889 端口监听......")listen, err := net.Listen("tcp", "0.0.0.0:8889")if err != nil {fmt.Println("net.Listen err=", err)return}for {fmt.Println("等待客户端连接服务器......")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept() err=", err)}//一旦连接成功,则启动一个协程和客户端保持通讯go process(conn)}
}

3.3 编译项目代码

go build -o server day8/chatroom/server/
go build -o client day8/chatroom/client/

3.4 演示代码

./server 
服务器在 8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):

client

1
登录聊天室
请输入用户的id
123
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":123,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
读取客户端发送的数据...
该用户不存在,请注册再使用...

server

等待客户端连接服务器......
客户端退出,服务器端也退出..

4、改进服务端代码结构

客户端 client 目录下的代码不变

4.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom├── client│   ├── login.go│   ├── main.go│   └── utils.go├── common│   └── message│       └── message.go└── server├── main│   ├── main.go│   └── processor.go├── model├── process│   ├── smsProcess.go│   └── userProcess.go└── utils└── utils.go10 directories, 9 files

4.2 代码

这里只展示改动的 server 目录下的代码。

4.2.1 day8/chatroom/server/main/main.go
package mainimport (// "day8/chatroom/common/message"// "encoding/binary"// "encoding/json"// "errors""fmt"// "io""net"
)// func readPkg(conn net.Conn) (mes message.Message, err error) {
// 	buf := make([]byte, 8096)
// 	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
// 	//如果客户端关闭了conn 就不会阻塞
// 	_, err = conn.Read(buf[:4])
// 	if err != nil {
// 		//err = errors.New("read pkg header error")
// 		return
// 	}
// 	//根据读到的  buf[:4] 转成一个 unit32 类型
// 	var pkgLen uint32
// 	pkgLen = binary.BigEndian.Uint32(buf[0:4])
// 	n, err := conn.Read(buf[:pkgLen])
// 	if n != int(pkgLen) || err != nil {
// 		//err = errors.New("read pkg body error")
// 		return
// 	}
// 	//把 pkgLen 反序列化成  -> message.Message
// 	err = json.Unmarshal(buf[:pkgLen], &mes)
// 	if err != nil {
// 		err = errors.New("json.Unmarshal error")
// 		return
// 	}
// 	return// }// func writePkg(conn net.Conn, data []byte) (err error) {
// 	//先发送一个长度给对方
// 	//data是 我们要发送的消息,先发送 data 长度
// 	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
// 	var pkgLen uint32
// 	pkgLen = uint32(len(data))
// 	var buf [4]byte
// 	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// 	//发送长度
// 	n, err := conn.Write(buf[:4])
// 	if n != 4 || err != nil {
// 		fmt.Println("conn.Write(bytes) fail", err)
// 		return
// 	}
// 	//发送 data本身
// 	n, err = conn.Write(data)
// 	if n != int(pkgLen) || err != nil {
// 		fmt.Println("conn.Write(bytes) fail", err)
// 		return
// 	}
// 	return
// }// func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) {
// 	//核心代码
// 	//先从mes 中取出 mes.Data,并直接反序列化成 LoginMes
// 	var loginMes message.LoginMes
// 	err = json.Unmarshal([]byte(mes.Data), &loginMes)
// 	if err != nil {
// 		fmt.Println("json.Unmarshal fail err=", err)
// 		return
// 	}// 	// 先声明一个 resMes
// 	var resMes message.Message
// 	resMes.Type = message.LoginResMesType// 	//再声明一个 LoginResMes
// 	var loginResMes message.LoginResMes// 	//如果用户id=100,密码=123456,认为合法,否则不合法
// 	if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// 		//合法
// 		loginResMes.Code = 200
// 	} else {
// 		//不合法
// 		loginResMes.Code = 500
// 		loginResMes.Error = "该用户不存在,请注册再使用..."
// 	}
// 	//将 loginResMes 序列化
// 	data, err := json.Marshal(loginResMes)
// 	if err != nil {
// 		fmt.Println("json.Marshal fail", err)
// 		return
// 	}
// 	//将data赋值给resMes
// 	resMes.Data = string(data)// 	//对 resMes 进行序列化,准备发送
// 	data, err = json.Marshal(resMes)
// 	if err != nil {
// 		fmt.Println("json.Marshal fail", err)
// 		return
// 	}
// 	//发送 data,将其封装到函数中
// 	err = writePkg(conn, data)
// 	return
// }// func serverProcessMes(conn net.Conn, mes *message.Message) (err error) {
// 	switch mes.Type {
// 	case message.LoginMesType:
// 		//处理登录
// 		err = serverProcessLogin(conn, mes)
// 	case message.RegisterMesType:
// 	//处理注册
// 	default:
// 		fmt.Printf("消息类型不存在,无法处理")
// 	}
// 	return
// }// 处理和客户端的通信
func process(conn net.Conn) {//这里需要延时关闭conndefer conn.Close()//这里调用总控,创建一个processor := &Processor{Conn : conn,}err := processor.process2()if err != nil{fmt.Println("客户端和服务器端通讯的协程错误=err",err)return}// //循环客户端发送信息// for {// 	mes, err := readPkg(conn)// 	if err != nil {// 		if err == io.EOF {// 			fmt.Println("客户端退出,服务器端也退出..")// 			return// 		} else {// 			fmt.Println("readPkg err=", err)// 			return// 		}// 	}// 	//fmt.Println("mes=", mes)// 	err = serverProcessMes(conn, &mes)// 	if err != nil {// 		return// 	}// }}func main() {//提示信息fmt.Println("服务器[新的结构]在 8889 端口监听......")listen, err := net.Listen("tcp", "0.0.0.0:8889")if err != nil {fmt.Println("net.Listen err=", err)return}for {fmt.Println("等待客户端连接服务器......")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept() err=", err)}//一旦连接成功,则启动一个协程和客户端保持通讯go process(conn)}
}
4.2.2 day8/chatroom/server/main/processor.go
package main
import("fmt""net""day8/chatroom/common/message""day8/chatroom/server/process""day8/chatroom/server/utils""io"
)//先创建一个processor的结构体
type Processor struct{Conn net.Conn
}func (this *Processor) serverProcessMes(mes *message.Message) (err error) {switch mes.Type {case message.LoginMesType://处理登录up := &process2.UserProcess{Conn : this.Conn,}err = up.ServerProcessLogin(mes)case message.RegisterMesType://处理注册default:fmt.Printf("消息类型不存在,无法处理")}return
}func (this *Processor) process2() (err error) {//循环客户端发送信息for {//创建一个 Transfer 实例完成读包的任务tf := &utils.Transfer{Conn : this.Conn,}mes,err := tf.ReadPkg()if err != nil {if err == io.EOF {fmt.Println("客户端退出,服务器端也退出..")return err} else {fmt.Println("readPkg err=", err)return err}}//fmt.Println("mes=", mes)err = this.serverProcessMes(&mes)if err != nil {return err}}
}
4.2.3 day8/chatroom/server/process/smsProcess.go
package process2
4.2.4 day8/chatroom/server/process/userProcess.go
package process2import("fmt""net""day8/chatroom/common/message""day8/chatroom/server/utils""encoding/json")type UserProcess struct{//Conn net.Conn
}
func (this *UserProcess) ServerProcessLogin(mes *message.Message) (err error) {//核心代码//先从mes 中取出 mes.Data,并直接反序列化成 LoginMesvar loginMes message.LoginMeserr = json.Unmarshal([]byte(mes.Data), &loginMes)if err != nil {fmt.Println("json.Unmarshal fail err=", err)return}// 先声明一个 resMesvar resMes message.MessageresMes.Type = message.LoginResMesType//再声明一个 LoginResMesvar loginResMes message.LoginResMes//如果用户id=100,密码=123456,认为合法,否则不合法if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {//合法loginResMes.Code = 200} else {//不合法loginResMes.Code = 500loginResMes.Error = "该用户不存在,请注册再使用..."}//将 loginResMes 序列化data, err := json.Marshal(loginResMes)if err != nil {fmt.Println("json.Marshal fail", err)return}//将data赋值给resMesresMes.Data = string(data)//对 resMes 进行序列化,准备发送data, err = json.Marshal(resMes)if err != nil {fmt.Println("json.Marshal fail", err)return}//发送 data,将其封装到函数中//因为使用分层模式(mvc),我们先创建一个 Transfer 实例,然后读取tf := &utils.Transfer{Conn : this.Conn,}err = tf.WritePkg(data)return
}
4.2.5 day8/chatroom/server/utils/utils.go
package utils
import("fmt""net""day8/chatroom/common/message""encoding/binary""encoding/json""errors"
)
//这里将这些方法关联到结构体中
type Transfer struct{//分析它应该有哪些字段Conn net.ConnBuf [8096]byte // 这是传输时,使用缓冲
}func (this *Transfer) ReadPkg() (mes message.Message, err error) {//buf := make([]byte, 8096)// conn.Read 在 conn 没有被关闭的情况下,才会阻塞//如果客户端关闭了conn 就不会阻塞_, err = this.Conn.Read(this.Buf[:4])if err != nil {//err = errors.New("read pkg header error")return}//根据读到的  buf[:4] 转成一个 unit32 类型var pkgLen uint32pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])n, err := this.Conn.Read(this.Buf[:pkgLen])if n != int(pkgLen) || err != nil {//err = errors.New("read pkg body error")return}//把 pkgLen 反序列化成  -> message.Messageerr = json.Unmarshal(this.Buf[:pkgLen], &mes)if err != nil {err = errors.New("json.Unmarshal error")return}return}func (this *Transfer) WritePkg(data []byte) (err error) {//先发送一个长度给对方//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))//var buf [4]bytebinary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)//发送长度n, err := this.Conn.Write(this.Buf[:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}//发送 data本身n, err = this.Conn.Write(data)if n != int(pkgLen) || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}return
}

4.3 编译项目代码

go build -o server day8/chatroom/server/main
go build -o client day8/chatroom/client/

4.4 演示代码

./server 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
读取客户端发送的数据...
登录成功

server

等待客户端连接服务器......
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF

5、改进客户端代码结构

5.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom├── client│   ├── main│   │   └── main.go│   ├── model│   ├── process│   │   ├── server.go│   │   ├── smsProcess.go│   │   └── userProcess.go│   └── utils│       └── utils.go├── common│   └── message│       └── message.go└── server├── main│   ├── main.go│   └── processor.go├── model├── process│   ├── smsProcess.go│   └── userProcess.go└── utils└── utils.go14 directories, 11 files

5.2 代码

5.2.1 day8/chatroom/client/main/main.go
package mainimport ("day8/chatroom/client/process""fmt"
)var userId int
var userPwd stringfunc main() {var key intvar loop = truefor loop {fmt.Println("---------------欢迎登录多人聊天系统-------------------")fmt.Println("\t\t\t 1、登录聊天室")fmt.Println("\t\t\t 2、注册用户")fmt.Println("\t\t\t 3、退出系统")fmt.Println("\t\t\t 请选择(1-3):")fmt.Scanf("%d\n", &key)switch key {case 1:fmt.Println("登录聊天室")fmt.Println("请输入用户的id")fmt.Scanf("%d\n", &userId)fmt.Println("请输入用户密码")fmt.Scanf("%s\n", &userPwd)//完成登录//1.创建一个UserProcess的实例up := &process.UserProcess{}up.Login(userId, userPwd)//loop = falsecase 2:fmt.Println("注册用户")loop = falsecase 3:fmt.Println("退出系统")loop = falsedefault:fmt.Println("你的输入有误,请重新输入")}}//if key == 1 {//这里需要重新调用//因为使用了新的程序结构,因此我们创建//login(userId, userPwd)//err := login(userId, userPwd)// if err != nil {// 	fmt.Println("登录失败")// } else {// 	fmt.Println("登录成功")// }//} else if key == 2 {//	fmt.Println("进行用户注册的逻辑")//}
}
5.2.2 day8/chatroom/client/process/server.go
package processimport ("day8/chatroom/client/utils""fmt""net""os"
)func ShowMenu() {fmt.Println("----------恭喜xxx登录成功--------")fmt.Println("--------1、显示在线用户列表--------")fmt.Println("--------2、发送消息--------")fmt.Println("--------3、信息列表--------")fmt.Println("--------4、退出系统--------")var key intfmt.Scanf("%d\n", &key)switch key {case 1:fmt.Println("显示在线用户列表")case 2:fmt.Println("发送消息")case 3:fmt.Println("信息列表")case 4:fmt.Println("你选择退出了系统...")os.Exit(0)default:fmt.Println("你输入的选项不正确..")}
}// 和服务器保持通讯
func serverProcessMes(conn net.Conn) {//创建一个transfer实例,不停的读取服务器发送的消息tf := &utils.Transfer{Conn: conn,}for {fmt.Println("客户端%s正在等待读取服务器发送的消息")mes, err := tf.ReadPkg()if err != nil {fmt.Println("tf.ReadPkg err=", err)return}//如果读取到消息,又是下一步处理逻辑fmt.Printf("mes=%v", mes)}
}
5.2.3 day8/chatroom/client/process/smsProcess.go
package process
5.2.4 day8/chatroom/client/process/userProcess.go
package processimport ("day8/chatroom/client/utils""day8/chatroom/common/message""encoding/binary""encoding/json""fmt""net"
)type UserProcess struct {
}func (this *UserProcess) Login(userId int, userPwd string) (err error) {//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)//return nil//连接到服务器conn, err := net.Dial("tcp", "localhost:8889")if err != nil {fmt.Println("net.Dial err=", err)return}//延时关闭defer conn.Close()//准备通过 conn 发送消息给服务器var mes message.Messagemes.Type = message.LoginMesTypevar loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd//将 loginMes 序列化data, err := json.Marshal(loginMes)if err != nil {fmt.Println("json.Marshal err=", err)return}//将data赋值给 message 结构体 Data 字段mes.Data = string(data)//将 mes 进行序列化data, err = json.Marshal(mes)if err != nil {fmt.Println("json.Marshal err=", err)return}//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4], pkgLen)//发送长度n, err := conn.Write(buf[0:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))_, err = conn.Write(data)if err != nil {fmt.Printf("conn.Write(data) fail", err)return}//time.sleep(20*time.Second)//fmt.Println("休眠了20S")//这里还需要处理服务器返回的消息tf := &utils.Transfer{Conn: conn,}mes, err = tf.ReadPkg() //mes 就是if err != nil {fmt.Println("readPkg(conn) err=", err)return}//将 mes 的 data 部分反序列化成 LoginResMesvar loginResMes message.LoginResMeserr = json.Unmarshal([]byte(mes.Data), &loginResMes)if loginResMes.Code == 200 {//fmt.Println("登录成功")//这里我们还需要在客户端启动一个协程//该协程保持和服务器端的通讯,如果服务器有数据推送给客户端//则接收并显示在客户端的终端go serverProcessMes(conn)//1.显示登录成功后的菜单for {ShowMenu()}} else if loginResMes.Code == 500 {fmt.Println(loginResMes.Error)}return
}
5.2.5 day8/chatroom/client/utils/utils.go
package utils
import("fmt""net""day8/chatroom/common/message""encoding/binary""encoding/json""errors"
)
//这里将这些方法关联到结构体中
type Transfer struct{//分析它应该有哪些字段Conn net.ConnBuf [8096]byte // 这是传输时,使用缓冲
}func (this *Transfer) ReadPkg() (mes message.Message, err error) {//buf := make([]byte, 8096)// conn.Read 在 conn 没有被关闭的情况下,才会阻塞//如果客户端关闭了conn 就不会阻塞_, err = this.Conn.Read(this.Buf[:4])if err != nil {//err = errors.New("read pkg header error")return}//根据读到的  buf[:4] 转成一个 unit32 类型var pkgLen uint32pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])n, err := this.Conn.Read(this.Buf[:pkgLen])if n != int(pkgLen) || err != nil {//err = errors.New("read pkg body error")return}//把 pkgLen 反序列化成  -> message.Messageerr = json.Unmarshal(this.Buf[:pkgLen], &mes)if err != nil {err = errors.New("json.Unmarshal error")return}return}func (this *Transfer) WritePkg(data []byte) (err error) {//先发送一个长度给对方//data是 我们要发送的消息,先发送 data 长度//由于 conn 接口的 Write 方法参数要求是 bytes 切片var pkgLen uint32pkgLen = uint32(len(data))//var buf [4]bytebinary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)//发送长度n, err := this.Conn.Write(this.Buf[:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}//发送 data本身n, err = this.Conn.Write(data)if n != int(pkgLen) || err != nil {fmt.Println("conn.Write(bytes) fail", err)return}return
}

5.3 编译项目代码

go build -o server day8/chatroom/server/main
go build -o client day8/chatroom/client/main

5.4 演示代码

 ./server 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------1、登录聊天室2、注册用户3、退出系统请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
客户端%s正在等待读取服务器发送的消息

server

等待客户端连接服务器......

client

1
显示在线用户列表
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
2
发送消息
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
3
信息列表
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
4
你选择退出了系统...

server

客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/884206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt QCheckBox详解

QCheckBox 是 Qt 框架中的一个控件,用于创建复选框,允许用户进行选择和取消选择。它通常用于表单、设置界面和任何需要用户选择的场景。 QCheckBox继承自QAbstractButton类,因此继承了按钮的特性。它表示一个复选框,用户可以通过…

使用Postman发送POST请求的指南

作为一名软件测试工程师,掌握如何使用Postman发送POST请求是非常重要的技能。POST请求通常用于向服务器发送数据,以创建或更新资源。本文将详细介绍如何在Postman中发送POST请求,帮助你高效地进行接口测试。 什么是POST请求? PO…

2024年meme币走势分析:最后两个月的市场趋势与机会 #交易所#dapp#KOL社区合作

2024年即将步入尾声,meme币市场经历了显著的波动。对于加密市场来说,年底的走势尤为关键,尤其是meme币这种受市场情绪影响较大的加密资产。本文将从市场环境、宏观经济因素、投资者情绪、技术分析等方面分析meme币在2024年最后两个月的潜在走…

python读取视频并转换成gif图片

1. 安装三方库 moviepy 将视频转换成gif,需要使用 moviepy库 确保已经安装了moviepy库 pip install moviepy2. 代码实现: from moviepy.editor import VideoFileClipmyclip VideoFileClip("video.mp4") myclip2 myclip.subclip(0, 10).re…

opencv - py_imgproc - py_filtering filtering 过滤-卷积平滑

文章目录 平滑图像目标2D 卷积(图像过滤)图像模糊(图像平滑)1. 平均2. 高斯模糊3. 中值模糊4. 双边滤波 其他资源 平滑图像 目标 学习: 使用各种低通滤波器模糊图像将定制滤波器应用于图像(2D 卷积&…

大数据新视界 -- 大数据大厂之数据质量管理全景洞察:从荆棘挑战到辉煌策略与前沿曙光

💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

如何保护网站安全

1. 使用 Web 应用防火墙(WAF) 功能:WAF 可以实时检测和阻止 SQL 注入、跨站脚本(XSS)、文件包含等常见攻击。它通过分析 HTTP 流量来过滤恶意请求。 推荐:可以使用像 雷池社区版这样的 WAF,它提…

大模型中的token是什么;常见大语言模型的 token 情况

目录 大模型中的token是什么 常见大语言模型的 token 情况 大模型中的token是什么 定义 在大模型中,token 是文本处理的基本单位。它可以是一个字、一个词,或者是其他被模型定义的语言单元。简单来说,模型在理解和生成文本时,不是以完整的句子或段落为单位进行一次性处理…

redis安装使用

1. 下载地址 下载最新稳定版本的 redis-windows 7.x 版本(本实例以 7.2.3 为例) # 下载地址 https://download.csdn.net/download/qq827245563/89923840 2. 解压文件 3. 下载可视化工具 # 下载地址 https://download.csdn.net/download/qq827245563/89940627 4. 启动运行 …

ELK + Spring Boot:日志分析入门与实践(一)

目录 一、安装ELK 1.1 版本选择 1.2 linux环境安装 1.3 ES配置启动 1.4 Kibana配置启动 1.5 Logstash配置启动 二、项目调整 2.1 pom依赖调整 2.2 yml配置文件调整 2.3 logback-spring配置 三、日志查询分析 3.1 创建索引模式 3.2 查询日志数据 一、安装ELK 1.1 …

elasticsearch 8.x 插件安装(六)之Hanlp插件

elasticsearch 8.x 插件安装(六)之Hanlp插件 elasticsearch插件安装合集 elasticsearch插件安装(一)之ik分词器安装(含MySQL更新) elasticsearch 8.x插件(二)之同义词安装如何解决…

vi —— 终端中的编辑器

目标 vi 简介打开和新建文件三种工作模式常用命令分屏命令常用命令速查图 01. vi 简介 1.1 学习 vi 的目的 在工作中,要对 服务器 上的文件进行 简单 的修改,可以使用 ssh 远程登录到服务器上,并且使用 vi 进行快速的编辑即可常见需要修改…

stm32cubeIde 使用笔记

划分flash空间 需要更改STM32xxx_FLASH.ld文件 输出其他格式文件

图片批量处理神器将每个文件夹中的多张图片拼接,一键实现横向和纵向的长图拼接效果,让你的图片处理更高效

是不是经常面对一堆图片文件夹,想要把它们里面的宝贝图片一一拼接起来,却又被繁琐的操作吓得直摇头?别担心,今天我要给大家介绍一位图片处理界的超级英雄——首助编辑高手软件!它就像是一位拥有魔法的图片大师&#xf…

【JVM详解JVM优化】聊聊JVM优化

简介: 前面两期文章讲了JVM内存模型:【JVM详解&JVM优化】JVM内存模型-CSDN博客 以及JVM垃圾回收机制:【JVM详解&JVM优化】JVM垃圾回收机制-CSDN博客 在本篇文章中,我们将深入探讨Java虚拟机(JVM)…

通俗易懂的餐厅例子来讲解JVM

餐厅版本 JVM(Java虚拟机)可以想象成一个虚拟的计算机,它能够运行Java程序。为了让你更容易理解,我们可以用一个餐厅的比喻来解释JVM: 菜单(Java源代码): 想象一下,Java…

一文搞懂各种Attention机制

1.各种Attention 最近在重读Transformer论文的过程中,结合其他看过的资料,对各种Attention概念有进一步的了解。回顾最初刚接触时候的迷糊,觉得有必要写一篇文章记录一下对各种attention新的理解。 2.论文中的Transformer架构图 先上经典的…

Python+Appium+Pytest+Allure自动化测试框架-代码篇

文章目录 自动化测试框架工程目录示例测试代码示例结果查看allurepytest编写pytest测试样例的规则pytest conftest.py向测试函数传参 appium启动appium服务代码端通过端口与appium服务通信对设备进行操作在pytest测试用例中调用appium 更多功能 PythonAppiumPytestAllure自动化…

【C++】红黑树的Iterator改造以及mapset的模拟实现与封装

目录 01.红黑树的迭代器 operator: operator*、-> operator、! 02.红黑树的改造 begin和end方法 keyOfValue insert方法 find方法 size方法 clear方法 03.map&set的模拟实现 01.红黑树的迭代器 前面的博客我们介绍了红黑树的底层原理并手撕了一个自己的红…

微信小程序服务通知

项目中用到了小程序的服务消息通知,通知订单状态信息,下边就是整理的一下代码,放到项目中,把项目的小程序appid和小程序的secret写进去,直接运行即可 提前申请好小程序服务信息通知短信模板,代码需要用到模…