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.xdnf.cn/news/664.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Kimi出考题,考题提示词Prompt附上,培训机构试题、期中考试、人事入职试题全搞定

大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 AI工具集1:大厂AI工具【共23款…

蓝牙资讯|苹果AirPods Pro 2推出听力测试、助听器和听力保护等功能

苹果推送iOS 18.1 系统版本更新,AirPods Pro 2 用户也在 iOS 18.1 中获得了强大的新功能。 运行固件 7B19 的 AirPods Pro 2 用户,搭配 iOS 18.1 系统的 iPhone,将获得三项强大的听力健康功能:听力测试、助听器和听力保护。 听力…

【unique_str 源码学习】

文章目录 &#xff11;&#xff0e;删除器定义2. operator->() 运算符重载3. add_lvalue_reference<element_type>::type 使用 基本原理这篇博主写的很详细 https://yngzmiao.blog.csdn.net/article/details/105725663 &#xff11;&#xff0e;删除器定义 deleter_…

计算机网络:网络层 —— 多播路由选择协议

文章目录 多播路由选择协议多播转发树构建多播转发树基于源树的多播路由选择建立广播转发树建立多播转发树 组共享树的多播路由选择基于核心的生成树的建立过程 因特网的多播路由选择协议 多播路由选择协议 仅使用 IGMP 并不能在因特网上进行IP多播。连接在局域网上的多播路由…

【力扣打卡系列】删除链表重复节点

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为go&#xff0c;Day14 删除链表中的节点 题目描述 解题思路 删除指定节点&#xff08;只知道Node&#xff0c;不知道Node上一个节点的情况下&#xff09;将该节点&#xff08;node&#xff09;的后一个节点的值…

【大模型LLM面试合集】大语言模型架构_tokenize分词

tokenize分词 0.总览 分词方法特点被提出的时间典型模型BPE采用合并规则&#xff0c;可以适应未知词2016年GPT-2、RoBERTaWordPiece采用逐步拆分的方法&#xff0c;可以适应未知词2016年BERTUnigram LM采用无序语言模型&#xff0c;训练速度快2018年XLMSentencePiece采用汉字、…

opencv - py_imgproc - py_grabcut GrabCut 算法提取前景

文章目录 使用 GrabCut 算法进行交互式前景提取目标理论演示 使用 GrabCut 算法进行交互式前景提取 目标 在本章中 我们将了解 GrabCut 算法如何提取图像中的前景我们将为此创建一个交互式应用程序。 理论 GrabCut 算法由英国剑桥微软研究院的 Carsten Rother、Vladimir K…

内存马浅析

之前在jianshu上写了很多博客&#xff0c;但是安全相关的最近很多都被锁了。所以准备陆陆续续转到csdn来。内存马前几年一直是个很热门的漏洞攻击手段&#xff0c;因为相对于落地的木马&#xff0c;无文件攻击的内存马隐蔽性、持久性更强&#xff0c;适用的漏洞场景也更多。 J…

串口接收,不定长数据接收

###1.CUBE-MX配置串口 2.我采用串口中断接收&#xff0c;打开中断接口 3.时钟同样8倍频&#xff0c;1分频&#xff0c;使用内部时钟 打开串口中断 main() { __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 启用空闲中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_R…

CentOS 7 更换软件仓库

CentOS 7 于2024年6月30日停止维护&#xff0c;官方仓库已经没有软件了&#xff0c;想要继续使用 &#xff0c;需要更换软件仓库&#xff0c;这里更换到阿里云的软件仓库 https://developer.aliyun.com/mirror/ 查看目前可用的软件数量 yum repolist 更换软件仓库&#xff1a…

Gorilla Mk1机器人:CubeMars电机加持,助力高空作业新突破

在澳大利亚输电网络的高空作业领域&#xff0c;一款由Crest Robotics研发的创新机器人正悄然改变着工作方式。这款名为Gorilla Mk1的机器人&#xff0c;凭借先进的技术和精密的动力系统&#xff0c;在高压输电线路的维护和检修作业中提供了前所未有的安全性和高效性。而这背后&…

DDRPHY数字IC后端设计实现系列专题之后端设计导入,IO Ring设计

本章详细分析和论述了 LPDDR3 物理层接口模块的布图和布局规划的设计和实 现过程&#xff0c;包括设计环境的建立&#xff0c;布图规划包括模块尺寸的确定&#xff0c;IO 单元、宏单元以及 特殊单元的摆放。由于布图规划中的电源规划环节较为重要&#xff0c; 影响芯片的布线资…

Pinia-状态管理

Pinia-状态管理 特点&#xff1a; 1. 轻量和模块化 Pinia 是一个轻量级的状态管理库&#xff0c;支持模块化管理&#xff0c;即可以将应用的状态分成多个 store 以实现更好的组织。使用 Pinia&#xff0c;可以定义多个 store&#xff0c;每个 store 都是一个独立的模块&#x…

WPF界面控件Essential Studio for WPF更新至2024 v3,具有更高性能 | 附下载

Essential Studio for WPF界面控件包含了利于分析且高性能的Windows应用程序开发中所需的所有控件&#xff0c;如 grids、charts、gauges、menus、calendars、editors等等。同时&#xff0c;我们的文件格式库还允许您导出资料到Excel、World和PDF文件中&#xff0c;以及对这些格…

相关衍生 pika+mongo

衍生相关 pikamongo 很多平台不提供完整的数据展示, 翻页只能翻几页,不过提供相关推荐等方法可获取更多的数据; 使用 rabbitmq 是因为数据量可能有几十上百万, 且能持久化 mongo对于数据并不实时的更新到查询里 def main():# mongodb# client MongoClient(localhost, 27017)cl…

软件测试--BUG篇

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:MySQL数据库 JavaEE专栏:JavaEE 软件测试专栏:软件测试 关注博主带你了解更多知识 目录 1. 软件测试的⽣命周期 2. BUG 1. BUG 的概念 2. 描述bug的要素 3.bug级别 4.bug的⽣命周期 5 与开发产⽣争执怎…

【Linux】————进程间通信(匿名管道)

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;Linux 创作时间 &#xff1a;2024年6月20日 进程间通信的目的&#xff1a; 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享资源通知事件&#xff1a;一个进程需…

Android [调试方法]如何在编译日志中快速找出报错信息

问题描述&#xff1a; 在进行Android完整编译时&#xff0c;经常遇到各种编译导致编译失败的情况&#xff0c;但其日志信息的打印数量十分巨大&#xff0c;无法仔细阅读逐行阅读。对于不熟悉的方法的同学定位报错问题的位置往往需要耗费较长时间。因此本作者将较为典型的方法总…

论文阅读:Computational Long Exposure Mobile Photography (一)

这篇文章是谷歌发表在 2023 ACM transaction on Graphic 上的一篇文章&#xff0c;介绍如何在手机摄影中实现长曝光的一些拍摄效果。 Abstract 长曝光摄影能拍出令人惊叹的影像&#xff0c;用运动模糊来呈现场景中的移动元素。它通常有两种模式&#xff0c;分别产生前景模糊或…

将 IBM WatsonX 数据与 Milvus 结合使用,构建用于知识检索的智能 Slack 机器人

在当今快节奏的工作环境中&#xff0c;快速轻松地访问信息对于保持生产力和效率至关重要。无论是在 Runbook 中查找特定说明&#xff0c;还是访问关键知识转移 &#xff08;KT&#xff09; 文档&#xff0c;快速检索相关信息的能力都可以产生重大影响。 本教程将指导您构建一个…