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