package main
import (
"chartroom/client/process"
"fmt"
"os"
)
//定义两个全局变量,一个表示用户的id一个表示用户的密码
var userId int
var userPwd string
var userName string
func main() {
//接受用户的选择
var key int
//判断是否继续显示菜单
//var loop = true
for true {
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)
fmt.Println("登录成功")
case 2:
fmt.Println("注册用户")
fmt.Println("请输入用户id:")
fmt.Scanf("%d\n",&userId)
fmt.Println("请输入用户的密码")
fmt.Scanf("%s\n",&userPwd)
fmt.Println("请输入用户的名字")
fmt.Scanf("%s\n",&userName)
//loop = false
//2.调用Userprocess来完成注册的请求
up := &process.UserProcess{}
up.Register(userId,userPwd,userName)
case 3:
fmt.Println("退出系统")
os.Exit(0)
default:
fmt.Println("你的输入有误,请重新输入")
}
}
如果是case2的话,会创建一个up实例,用这个up实例来调用Register的方法。下面将对up实例所绑定的一些方法进行分析,以及对Register进行分析。
up是UserProcess结构体,这个结构体绑定了一个Register和一个Login方法。
package process
import (
utils "chartroom/client"
message "chartroom/common"
"encoding/binary"
"encoding/json"
"fmt"
"net"
"os"
)
type UserProcess struct{
//字段
}
func (this *UserProcess) Register (userId int, userPwd string,userName string) (err error) {
//1.链接到服务器
conn, err := net.Dial("tcp", "localhost:8884")
if err != nil {
fmt.Println("net.Dial err=", err)
return
}
defer conn.Close()
//2.准备通过conn发送消息给服务器,这个mes的类型是LoginMesType的类型
var mes message.Message
mes.Type = message.RegisterMesType
//3.创建一个LoginMes结构体
var registerMes message.RegisterMes
registerMes.UserId = userId
registerMes.UserPwd = userPwd
registerMes.UserName = userName
//4.将loginMes序列化
data, err := json.Marshal(registerMes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
//5.将data赋给mes.Data字段
mes.Data = string(data)
//6.将mes进行序列化 最后这个数据就是我们要发送的数据了 他是一个byte切片
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
tf := utils.Transfer{
Conn: conn,
}
//发送data给服务器端
err = tf.WritePkg(data)
if err != nil {
fmt.Println("注册发送信息错误 err=", err)
}
mes, err = tf.ReadPkg()
if err != nil {
fmt.Println("出错")
return
}
//将mes的Data反序列化成LoginResMes
var registerResMes message.RegisterResMes
err = json.Unmarshal([]byte(mes.Data), ®isterResMes)
if registerResMes.Code == 200 {
fmt.Println("注册成功")
os.Exit(0)
} else {
fmt.Println(registerResMes.Error)
os.Exit(0)
}
return
}
//写一个函数,完成登录
func (up *UserProcess)Login(userId int, userPwd string) (err error) {
fmt.Println("userPwd=",userPwd)
//下一步就要开始定协议..
//fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)
//return nil
//1.链接到服务器
conn, err := net.Dial("tcp", "localhost:8887")
if err != nil {
fmt.Println("net.Dial err=", err)
return
}
//延时关闭
defer conn.Close()
//2.准备通过conn发送消息给服务器,这个mes的类型是LoginMesType的类型
var mes message.Message
mes.Type = message.LoginMesType
//3.创建一个LoginMes结构体
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.UserPwd = userPwd
//4.将loginMes序列化
data, err := json.Marshal(loginMes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
//5.将data赋给mes.Data字段
mes.Data = string(data)
//6.将mes进行序列化 最后这个数据就是我们要发送的数据了 他是一个byte切片
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
//7.到这个时候 data就是我们要发送的消息
//7.1先把data的长度发送给服务器
//而conn.Write接受的数据类型是byte切片,所以首先要获取到data的长度->转成一个表示长度的byte切片
var pkglen uint32
//先定义包的长度,因为他说一会要发送的所以定义成unit32类型的,使用type ByteOrder来转换成byte类型的
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
}
fmt.Printf("客户端发送的消息长度成功,长度=%d 内容是=%s", len(data), string(data))
//发送消息本身,之前发送的是data的长度
_ , err = conn.Write(data)
if err != nil {
fmt.Println("conn.Write(data) fail", err)
return
}
//这里还需要处理服务器端返回的消息,
tf := utils.Transfer{
Conn: conn,
}
mes,err = tf.ReadPkg()
//将mes的Data反序列化成LoginResMes
if err!=nil{
fmt.Println("出错")
return
}
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
}
对Register方法进行分析,
func (this *UserProcess) Register (userId int, userPwd string,userName string) (err error) {
//1.链接到服务器
conn, err := net.Dial("tcp", "localhost:8884")
if err != nil {
fmt.Println("net.Dial err=", err)
return
}
defer conn.Close()
//2.准备通过conn发送消息给服务器,这个mes的类型是LoginMesType的类型
var mes message.Message
mes.Type = message.RegisterMesType
//3.创建一个LoginMes结构体
var registerMes message.RegisterMes
registerMes.UserId = userId
registerMes.UserPwd = userPwd
registerMes.UserName = userName
//4.将loginMes序列化
data, err := json.Marshal(registerMes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
//5.将data赋给mes.Data字段
mes.Data = string(data)
//6.将mes进行序列化 最后这个数据就是我们要发送的数据了 他是一个byte切片
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
tf := utils.Transfer{
Conn: conn,
}
//发送data给服务器端
err = tf.WritePkg(data)
if err != nil {
fmt.Println("注册发送信息错误 err=", err)
}
mes, err = tf.ReadPkg()
if err != nil {
fmt.Println("出错")
return
}
//将mes的Data反序列化成LoginResMes
var registerResMes message.RegisterResMes
err = json.Unmarshal([]byte(mes.Data), ®isterResMes)
if registerResMes.Code == 200 {
fmt.Println("注册成功")
os.Exit(0)
} else {
fmt.Println(registerResMes.Error)
os.Exit(0)
}
return
}
第一步,会建立一个连接,这个连接可以和server端进行交互。
conn, err := net.Dial("tcp", "localhost:8884")
if err != nil {
fmt.Println("net.Dial err=", err)
return
}
defer conn.Close()
第二步,会创建一个mes对象,
//2.准备通过conn发送消息给服务器,这个mes的类型是LoginMesType的类型
var mes message.Message
mes.Type = message.RegisterMesType
这个mes的形式如下,也就是说Message是一个大类型的结构体,他有data和type,type来进行类型的辨析。
LoginMes是登录的结构体类型,包含用户的基本信息。
LoginResMes是登录的返回值类型,从code和error进行分析。
同理Register。
package message
const (
//消息类型给成常量
LoginMesType = "LoginMes"
LoginResMesType = "LoginResMes"
RegisterMesType = "RegisterMes"
)
type Message struct {
Type string `json:"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"`// 返回状态码 500 表示该用户还没注册
Error string `json:"error"`//返回错误信息
}
type RegisterMes struct {
User `json:"user"`//他的类型就是User结构体
}
type RegisterResMes struct {
Code int `json:"code"` //返回状态码 400 表示该用户以及占有 200表示注册成功
Error string `json:"error"`
}
第三步, 会创建一个RegisterMes结构体,利用Register的方法进行赋值。
//3.创建一个RegisterMes结构体
var registerMes message.RegisterMes
registerMes.UserId = userId
registerMes.UserPwd = userPwd
registerMes.UserName = userName
第四步,会将registerMes这个包含注册用户基本信息的结构体进行序列化,会得到一个data,这个data是序列化后的 registerMes,这个registerMes包含注册用户的基本信息。
//4.将RegisterMes序列化
data, err := json.Marshal(registerMes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
第五步,将所得到的序列化后的包含注册用户基本信息的数据,赋值给mes,因为mes这个结构体的data是string类型的,所以需要强制转换。
//5.将data赋给mes.Data字段
mes.Data = string(data)
第六步,将所得到的mes进行序列化,这就是最终要发送的数据了
//6.将mes进行序列化 最后这个数据就是我们要发送的数据了 他是一个byte切片
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err= ", err)
return
}
查看Marshal这个函数我们发现, Marshal返回的是一个byte切片,也就是说所得到的data是一个byte切片类型。
func Marshal(v interface{}) ([]byte, error)
第七步,会创建一个Transfer的实例,会利用他进行于server的交互。
tf := utils.Transfer{
Conn: conn,
}
查看utils下的Transfer我们会发现,他包含了读包和写包两个方法,而他本身所带两个字段,一个字段是conn也就是注册的时候建立的连接,还有一个字段是buf也就是一个缓冲区。
type Transfer struct{
//分析,它应该有哪些字段
Conn net.Conn
Buf [8096]byte //这是传输的时候,使用的缓冲
}
func (this *Transfer) ReadPkg() (mes message.Message, err error) {
//buf := make([]byte, 8096)
fmt.Println("等待读取数据")
//conn.Read 在conn没有被关闭的情况下,才会阻塞
//如果客户端关闭了 conn 则,就不会阻塞了
_, err = this.Conn.Read(this.Buf[:4])
if err != nil {
fmt.Println("conn.read err=", err)
return
}
//根据buf[:4]转成一个uint32类型
var pkgLen uint32 //buf[:4]
pkgLen = binary.BigEndian.Uint32(this.Buf[:4])
//根据pkgLen 读取消息内容
n, err := this.Conn.Read(this.Buf[:pkgLen]) //这个函数的意思是,从conn这个套接字里面读[:pkgLen]这个长度,扔到buf里面去
if n != int(pkgLen) || err != nil {
//fmt.Println("conn.Read fail err=", err)
return
}
//这个时候,还要讲pkgLen反序列化成 -> message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarsha err=", err)
return
}
fmt.Println("这里的mes",mes)
return mes,err
}
func (this *Transfer) WritePkg(data []byte)(err error){
//先发送一个长度给对方
var pkglen uint32 //先定义包的长度,因为他说一会要发送的所以定义成unit32类型的,使用type ByteOrder来转换成byte类型的
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 uint32(n) != pkglen || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
return
}
第八步,利用Transfer中的writePkg进行将数据data发送给服务器
//发送data给服务器端
err = tf.WritePkg(data)
if err != nil {
fmt.Println("注册发送信息错误 err=", err)
}
看一下WritePkg的实现
func (this *Transfer) WritePkg(data []byte)(err error){
//先发送一个长度给对方
var pkglen uint32 //先定义包的长度,因为他说一会要发送的所以定义成unit32类型的,使用type ByteOrder来转换成byte类型的
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 uint32(n) != pkglen || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
return
}
这里一般是不会出错的,因为Buf[:4]里面的内容是长度,而n是这个写入的长度,并不是真实数据的长度。
n, err := this.Conn.Write(this.Buf[:4])
if n != 4 || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
第九步,客户端从conn中读取信息来获得code,返回结果。
mes, err = tf.ReadPkg()
if err != nil {
fmt.Println("出错")
return
}
//将mes的Data反序列化成LoginResMes
var registerResMes message.RegisterResMes
err = json.Unmarshal([]byte(mes.Data), ®isterResMes)
if registerResMes.Code == 200 {
fmt.Println("注册成功")
os.Exit(0)
} else {
fmt.Println(registerResMes.Error)
os.Exit(0)
}
return
需要一个registerResMes来接收这个mes这个反序列化后的mes。
var registerResMes message.RegisterResMes
func (this *Transfer) ReadPkg() (mes message.Message, err error) {
//buf := make([]byte, 8096)
fmt.Println("等待读取数据")
//conn.Read 在conn没有被关闭的情况下,才会阻塞
//如果客户端关闭了 conn 则,就不会阻塞了
_, err = this.Conn.Read(this.Buf[:4])
if err != nil {
fmt.Println("conn.read err=", err)
return
}
//根据buf[:4]转成一个uint32类型
var pkgLen uint32 //buf[:4]
pkgLen = binary.BigEndian.Uint32(this.Buf[:4])
//根据pkgLen 读取消息内容
n, err := this.Conn.Read(this.Buf[:pkgLen]) //这个函数的意思是,从conn这个套接字里面读[:pkgLen]这个长度,扔到buf里面去
if n != int(pkgLen) || err != nil {
//fmt.Println("conn.Read fail err=", err)
return
}
//这个时候,还要讲pkgLen反序列化成 -> message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarsha err=", err)
return
}
fmt.Println("这里的mes",mes)
return mes,err
}
从server端查看发现,他返回的数据类型 是Message的数据类型,这样客户端就能读取了。
func (this *UserProcess)ServerProcessRegister(mes *message.Message)(err error)
type Message struct {
Type string `json:"type"`//消息类型
Data string `json:"data"`//消息的内容
}