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.rhkb.cn/news/463891.html

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

相关文章

Balluff EDI 项目需求分析

电子数据交换(EDI,Electronic Data Interchange)是一种通过电子方式在不同组织之间交换商业文档的技术和标准。它涉及使用标准格式的电子文档,如订单、发票、运输单据等,以实现自动化的数据传输。这种技术通常依赖于专…

如何在 Ubuntu 上安装和配置 GitLab

简介 GitLab是一个开源应用程序,主要用于托管 Git 仓库,并提供与开发相关的附加功能(如问题跟踪)。GitLab 可由用户自己的基础架构托管,可灵活部署为开发团队的内部存储库、与用户对接的公共方式或供稿者托管自己项目…

c语言-常量和变量

文章目录 一、常量是什么?(1)整型常量:(2)实型常量:(3)字符常量:(4)字符串常量(5)地址常量 二、define 和 con…

【Linux】进程间通信(匿/命名管道、共享内存、消息队列、信号量)

文章目录 1. 进程通信的目的2. 管道2.1 原理2.2 匿名管道2.3 管道通信场景:进程池2.4 命名管道 3. System V共享内存3.1 操作共享内存3.2 使用共享内存通信 4. System V 消息队列(了解)5. System V 信号量(了解)5.1 信…

VirtualBox 解决虚拟机Cable Unplugged 无法上网问题

问题描述 VirtualBox 中的虚拟机无法上网,在虚拟机中查看网络设置显示 Cable Unplugged。 解决方案 选择VirtualBox 上方任务栏的控制->设置->网络,勾选接入网线即可解决。

大学适合学C语言还是Python?

在大学学习编程时,选择C语言还是Python,这主要取决于你的学习目标、专业需求以及个人兴趣。以下是对两种语言的详细比较,帮助你做出更明智的选择: C语言 优点: 底层编程:C语言是一种底层编程语言&#x…

【深入浅出】深入浅出Bert(附面试题)

本文的目的是为了帮助大家面试Bert,会结合我的面试经历以及看法去讲解Bert,并非完整的技术细致讲解,介意请移步。 深入浅出】深入浅出Bert(附面试题) 网络结构Pre-TrainingFine-Tuning 输入编码词向量编码句子编码位置…

thrift rpc 四种类型的服务端的实现详细介绍

thrift rpc 四种类型的服务端的实现详细介绍 这里主要是使用 thrift 开发的时候服务器端的实现,以及 thrift 提供给我们多钟的服务的实现,以及每个实现的服务器的特点和 API 介绍,TServer 主要包含以下几种实现 TSimpleServer 阻塞的但线程…

Python | Leetcode Python题解之第530题二叉搜索树的最小绝对差

题目: 题解: # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val x # self.left None # self.right Noneclass Solution(object):def isValidBST(self, root):"…

[Prometheus学习笔记]从架构到案例,一站式教程

文章目录 Prometheus 优势Prometheus 的组件、架构Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标,它在本地存储所有抓取到的样本数据,并对此数据执行一系列规则,以汇总和记录现有数据的新时间序列或生成告警。可以通…

抓住亚马逊、shein新品扶持期,利用测评提升搜索排名与销量

亚马逊的卖家们应该意识到,新发布的产品在上线后的2到4周内,通常会获得平台的流量支持。这一阶段被称为“新品流量黄金期”,在此期间,产品的搜索排名和曝光率通常会比平时更高。因此,如何有效利用这一阶段,…

轻松入门WordPress:在Ubuntu上搭建本地网站并配置公网访问地址

文章目录 前言1. 安装WordPress2. 创建WordPress数据库3. 安装相对URL插件4. 安装内网穿透发布网站4.1 命令行方式:4.2. 配置wordpress公网地址 5. 配置WordPress固定公网地址 前言 本文主要介绍如何在Linux Ubuntu系统上使用WordPress搭建一个本地网站&#xff0c…

华为云计算知识总结——及案例分享

目录 一、华为云计算基础知识二、华为云计算相关案例实战案例一:搭建弹性云服务器(ECS)并部署Web应用案例二:构建基于OBS的图片存储和分发系统案例三:基于RDS的高可用数据库应用案例四:使用华为云DDoS防护保…

银行金融知识竞赛活动策划方案

根据《中国人民银行**市中心支行“创新金融服务,支持经济发展”业务竟赛活动实施方案》安排,中支决定于9月28日举办**市人民银行系统“创新金融服务,支持经济发展”现场业务竞赛,为确保业务竞赛组织工作顺利开展,特制定…

动态规划 01背包(算法)

现有四个物品,小偷的背包容量为8,怎么可以偷得价值较多的物品 如: 物品编号: 1 2 3 4 物品容量: 2 3 4 5 物品价值: 3 4 5 8 记f(k,w) ,当背包容量为w,可以偷k件物品…

引领数字时代:万码优才如何变革IT人才招聘新体验(这里有更精准的推荐)

目录 引领数字时代:万码优才如何变革IT人才招聘新体验引领未来科技,精准链接IT精英精准匹配,高效对接海量资源,覆盖广泛优化体验,简化流程 全面升级:AI赋能数字人才职业成长AI模拟面试职场千问智能简历评估…

Rocky Linux 9安装后无法远程ssh密码登录解决

在Rocky Linux 9版本中,为了增加安全性,默认情况下禁用SSH root密码登录。这是系统默认设定的规则,我们同样也可以更改它。   允许Rocky Linux 9 root用户通过ssh登录方法: 1.编辑SSH配置文件 2.找到以下内容 PermitRootLogin …

1.2 图像处理基本操作

在本实战中,我们将学习如何使用OpenCV进行基本的图像处理操作。首先,我们将通过cv2.imread()函数读取图像,并使用cv2.imshow()在窗口中显示它。接着,我们将探索如何通过cv2.imwrite()保存图像,并设置不同的参数以控制图…

【C++】哈希表模拟:开散列技术与哈希冲突处理

C语法相关知识点可以通过点击以下链接进行学习一起加油!命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriori…

「Mac畅玩鸿蒙与硬件18」鸿蒙UI组件篇8 - 高级动画效果与缓动控制

高级动画可以显著提升用户体验,为应用界面带来更流畅的视觉效果。本篇将深入介绍鸿蒙框架的高级动画,包括弹性动画、透明度渐变和旋转缩放组合动画等示例。 关键词 高级动画弹性缓动自动动画缓动曲线 一、Animation 组件的高级缓动曲线 缓动曲线&#…