网络通信编程 海量用户通信 注册功能的实现 对客户端进行分析

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), &registerResMes)
	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), &registerResMes)
	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), &registerResMes)
	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"`//消息的内容
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值