1、登录界面
说明:这一节的内容采用 go mod 管理【GO111MODULE=‘’】的模块,从第二节开始使用【GO111MODULE=‘off’】GOPATH 管理模块。具体参见 go 包相关知识
1.1登录界面代码目录结构
代码所在目录/Users/zld/Go-project/day8/chatroom/
1.2登录界面代码
main.go
package main
import (
"fmt"
)
var userId int
var userPwd string
func main() {
var key int
var loop = true
for 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 = false
case 2:
fmt.Println("注册用户")
loop = false
case 3:
fmt.Println("退出系统")
loop = false
default:
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 main
import (
"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.go
6 directories, 4 files
2.2代码
2.2.1 day8/chatroom/common/message/message.go
package message
const (
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 main
import (
"fmt"
"net"
)
// 处理和客户端的通信
func process(conn net.Conn) {
//这里需要延时关闭conn
defer 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 main
import (
"fmt"
)
var userId int
var userPwd string
func main() {
var key int
var loop = true
for 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 = false
case 2:
fmt.Println("注册用户")
loop = false
case 3:
fmt.Println("退出系统")
loop = false
default:
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 main
import (
"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.Message
mes.Type = message.LoginMesType
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.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 uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.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.go
6 directories, 5 files
3.2 代码
3.2.1 day8/chatroom/client/login.go
package main
import (
"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.Message
mes.Type = message.LoginMesType
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.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 uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.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 部分反序列化成 LoginResMes
var loginResMes message.LoginResMes
err = 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 main
import (
"fmt"
)
var userId int
var userPwd string
func main() {
var key int
var loop = true
for 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 = false
case 2:
fmt.Println("注册用户")
loop = false
case 3:
fmt.Println("退出系统")
loop = false
default:
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 main
import (
"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 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[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 message
const (
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 main
import (
"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) {
//这里需要延时关闭conn
defer 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.go
10 directories, 9 files
4.2 代码
这里只展示改动的 server 目录下的代码。
4.2.1 day8/chatroom/server/main/main.go
package main
import (
// "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) {
//这里需要延时关闭conn
defer 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 process2
import(
"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,并直接反序列化成 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,将其封装到函数中
//因为使用分层模式(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.Conn
Buf [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 uint32
pkgLen = 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.Message
err = 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 uint32
pkgLen = uint32(len(data))
//var buf [4]byte
binary.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.go
14 directories, 11 files
5.2 代码
5.2.1 day8/chatroom/client/main/main.go
package main
import (
"day8/chatroom/client/process"
"fmt"
)
var userId int
var userPwd string
func main() {
var key int
var loop = true
for 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 = false
case 2:
fmt.Println("注册用户")
loop = false
case 3:
fmt.Println("退出系统")
loop = false
default:
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 process
import (
"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 int
fmt.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 process
import (
"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.Message
mes.Type = message.LoginMesType
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.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 uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.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 部分反序列化成 LoginResMes
var loginResMes message.LoginResMes
err = 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.Conn
Buf [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 uint32
pkgLen = 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.Message
err = 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 uint32
pkgLen = uint32(len(data))
//var buf [4]byte
binary.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