golang-网络编程经典项目(下)

第九步:

在这里插入图片描述
在这里插入图片描述

在redis中手动添加测试用户并画图分析后续通过程序添加用户

在这里插入图片描述
手动添加用户信息

127.0.0.1:6379> hset users 100 "{\"userId\":100,\"userPwd\":\"123456\",\"userName\":\"showmaker\"}"
(integer) 0

在这里插入图片描述
server包结构
做出修改的地方:
main.go/redis.go
model/*.go
process2/userProcess.go
在这里插入图片描述
server/main/main.go

package main

import (
	"fmt"
	"net"
	"time"
	"gotcp/chatroom/server/model"
)

func init(){
	// 当服务器开启后就初始化连接池
	initPool("localhost:6379",8,0,180 * time.Second)
	initUserDao()
}

// 编写一个函数完成对UserDao的初始化
func initUserDao(){
	// 这里要注意一个初始化顺序问题
	// 先调用initPool 再调用 initUserDao
	model.MyUserDao = model.NewUserDao(pool) // 这里的pool来源于redis中的pool
}

func process(conn net.Conn){
	defer conn.Close()
	// 循环读取
	processall := &Processall{
		Conn : conn,
	}
	processall.processalltwo()
}

func main(){
	listener,err := net.Listen("tcp","0.0.0.0:8889")
	defer listener.Close()
	if err != nil{
		fmt.Println("net.Listener err = ",err)
		return
	}

	fmt.Printf("服务器网络类型为:%v 地址为:%v\n",listener.Addr().Network(),listener.Addr().String())
	for{
		// Accept返回listen服务端的连接并等待下一个连接
		conn,err := listener.Accept()
		defer conn.Close()
		if err != nil{
			fmt.Println("listener.Accept err = ",err)
			return
		}

		fmt.Println("客户端连接成功!ip地址为:",conn.RemoteAddr())
		go process(conn)
	}
}

server/main/processall.go

package main

import (
	"io"
	"fmt"
	"net"
	"gotcp/chatroom/common/message"
	"gotcp/chatroom/server/process2"
	"gotcp/chatroom/server/utils"
)

type Processall struct{
	Conn net.Conn
}

// 编写一个ServerProcessMsg函数判断消息请求类型进行相应处理
func (this *Processall)serverProcessMsg(msg *message.Message)(err error){
	switch msg.Type{
		case message.LoginMsgType:
			up := &process2.Userprocess{
				Conn : this.Conn,
			}
			err = up.ServerProcessLogin(msg)
			// err = serverProcessLogin(conn,msg)
		case message.LoginResMsgType:
			// 处理注册请求
		default :
			fmt.Println("消息类型不存在")
	}
	return 
}

func (this *Processall)processalltwo(){
	for {
		tf := &utils.Transfer{
			Conn : this.Conn,
		}
		
		msg,err := tf.ReadMsg()
		if err != nil{
			// fmt.Println("readMsg(conn) err = ",err)
			if err == io.EOF{
				fmt.Println("客户端退出服务端也退出...")
				return
			}else{
				fmt.Println("tf.ReadMsg() err = ",err)
				return
			}
		}

		fmt.Println("msg反序列化后 = ",msg)

		err = this.serverProcessMsg(&msg)
		if err != nil{
			fmt.Println("ServerProcessMsg(conn,msg) err:",err)
		}
	}
}

server/main/redis.go

// 初始化pool连接池
package main

import (
	"time"
	"github.com/garyburd/redigo/redis"
)

// 声明一个redis.Pool结构体类型的pool
// 定义一个全局的pool
var pool *redis.Pool

// 程序启动初始化连接池
func initPool(address string,maxIdle int,maxActive int,idleTimeout time.Duration){
	pool = &redis.Pool{
		// 最大空闲连接数
		MaxIdle : maxIdle,
		// 表示和数据库的最大连接数,0表示不限制
		MaxActive : maxActive,
		// 最大空闲时间
		IdleTimeout : idleTimeout,
		// 初始化连接,表示连接到哪个地址
		Dial : func() (redis.Conn, error){
			return redis.Dial("tcp",address)
		},
	}
}

server/model/err.go

// 用户自定义错误
package model

import (
	"errors"
)

var (
	ERROR_USER_EXISTENCE = errors.New("用户不存在")
	ERROR_USER_EXISTS = errors.New("用户存在")
	ERROR_USER_PWD = errors.New("密码错误")
)

server/model/user.go

package model

type User struct{
	/* 为了序列化和反序列化成功必须保证用户信息的json字符串的key和结构体
	的字段对应的tag名字一致!!! */
	UserId int `json:"userId"`
	UserPwd string `json:"userPwd"`
	UserName string `json:"userName"`
}

server/model/userDo.go

// 此文件用来操作user结构体
package model

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"encoding/json"
)

// 在服务器启动后就初始化一个UserDao实例
// 把它做成全局变量方便使用
// 声明一个UserDao结构体,实例化一个pool连接池
type rootUserDao struct{
	pool *redis.Pool
}

// 实例化一个 rootUserDao 指针类型
var (
	MyUserDao *rootUserDao
)

// 使用工厂模式创建UserDao实例
// 工厂模式主要是为了程序的隔离性,在资源和使用者之间提供一个中介服务
// 通过工厂模式函数返回一个 pool实例再赋值给 MyUserDao 进行使用
func NewUserDao(pool *redis.Pool)(userDao *rootUserDao){
	userDao = &rootUserDao{
		pool:pool,
	}
	// 返回userDao
	return
}

// 根据id返回User
// 第一层对信息校验,校验是否能查到对应的ID
func (this *rootUserDao)getUserById(conn redis.Conn,id int)(user *User,err error){
	res,err := redis.String(conn.Do("hget","users",id))
	if err != nil{
		if err == redis.ErrNil{
			err = ERROR_USER_EXISTENCE
		}
		return
	}

	user = &User{}

	err = json.Unmarshal([]byte(res),user)
	if err != nil{
		fmt.Println("json.Unmarshal([]byte(res),user) err=",err)
	}
	return
}

// 根据返回的User进行校验
// 如果id和pwd都正确返回User实例
// 如果id和pwd有误返回对应错误信息
func (this *rootUserDao)Login(userId int,userPwd string)(user *User,err error){
	// 从rootUserDao中取出一根连接
	conn := this.pool.Get()
	defer conn.Close()
	user,err = this.getUserById(conn,userId)
	if err != nil{
		// fmt.Println("this.getUserById(conn,UserId) err",err)
		// return
		// 进行信息校验
		if err == ERROR_USER_EXISTENCE{
			return		
		}else{
			fmt.Println("this.getUserById err",err)
		}
	}
	// 第二层校验校验查出来的用户密码是否正确
	if userPwd != user.UserPwd{
		err = ERROR_USER_PWD
		return
	}
	return
}

server/process2/smsProcess.go

package process2
// 处理和短消息相关的请求
// 群聊,点对点聊天

server/process2/userProcess.go

// 处理和用户相关的请求,登录,注册,注销,用户列表管理
package process2

import (
	"fmt"
	"net"
	"gotcp/chatroom/common/message"
	"gotcp/chatroom/server/utils"
	"encoding/json"
	"gotcp/chatroom/server/model"
)

type Userprocess struct{
	Conn net.Conn
}

// 编写一个serverProcessLogin处理登录请求
func (this *Userprocess)ServerProcessLogin(msg *message.Message)(err error){
	// 核心代码 先从msg中取出msg.Data并直接反序列化为loginMsg
	// 用作对消息进行判断进而返回client消息
	var loginMsg message.LoginMsg
	err = json.Unmarshal([]byte(msg.Data),&loginMsg)
	if err != nil{
		fmt.Println("serverProcessLogin json.Marshal err:",err)
		return
	}

	// 声明一个Message标识消息类型
	var resMsg message.Message
	resMsg.Type = message.LoginResMsgType

	// 声明一个LoginResMsg完成赋值
	var loginResMsg message.LoginResMsg

	// 使用model.MyUserDao去redis验证
	user,err := model.MyUserDao.Login(loginMsg.UserId,loginMsg.UserPwd)
	if err != nil{
		if err == model.ERROR_USER_EXISTENCE{
			// 用户不存在返回500
			loginResMsg.Code = 500
			loginResMsg.Error = err.Error()
		}else if err == model.ERROR_USER_PWD{
			// 密码不正确返回300
			loginResMsg.Code = 300
			loginResMsg.Error = err.Error()
		}else{
			loginResMsg.Code = 500
			loginResMsg.Error = "内部信息错误"
		}
	}else{
		loginResMsg.Code = 200
		fmt.Println(user,"登录成功")
	}


	// if loginMsg.UserId == 100 && loginMsg.UserPwd == "123456"{
	// 	loginResMsg.Code = 200
	// }else{
	// 	loginResMsg.Code = 500
	// 	fmt.Println("user notfund fpx.no1")
	// }

	data,err := json.Marshal(loginResMsg)
	if err != nil{
		fmt.Println("serverProcessLogin json.Marshal(loginResMsg) err:",err)
		return
	}

	resMsg.Data = string(data)
	data,err = json.Marshal(resMsg)
	if err != nil{
		fmt.Println("serverProcessLogin json.Marshal(resMsg) err:",err)
		return
	}

	// 消息序列化完毕准备发送
	tf := &utils.Transfer{
		Conn : this.Conn, // 调用本方法(this *Userprocess) this绑定struct里的Conn
	}

	err = tf.WritePkg(data)
	return
}

server/utils/utils.go

package utils

import (
	"fmt"
	"net"
	"gotcp/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
)

type Transfer struct{
	Conn net.Conn
	Buf [9600]byte // 传输时使用缓冲
}

func (this *Transfer)ReadMsg()(msg message.Message,err error){
	// buf := make([]byte,9600)
	fmt.Println("准备读取客户端传输数据...")
	// 从conn中读取4个字节放入bug中
	_,err = this.Conn.Read(this.Buf[:4])
	if err != nil{
		// fmt.Println("conn.Read err = ",err)
		return
	}

	// Uint32传入一个切片返回一个uint32类型长度
	relay := binary.BigEndian.Uint32(this.Buf[:4])
	// 根据relay读取消息内容
	n,err := this.Conn.Read(this.Buf[:relay])
	if n != int(relay) || err != nil{
		// fmt.Println("conn.Read file err = ",err)
		return
	}

	// 这里序列化buf[:relay]并存入msg中一定要加&符号,地址否则不会返回给函数返回值
	err = json.Unmarshal(this.Buf[:relay],&msg)
	if err != nil{
		fmt.Println("json.Unmarshal err = ",err)
		return
	}
	return
}

// 编写一个writePkg函数用来发送请求
func (this *Transfer)WritePkg(data []byte)(err error){
	// 先把data转为uint32再转换并放入切片中把切片发送给对方
	var relay uint32
	relay = uint32(len(data))
	// var buf [4]byte
	binary.BigEndian.PutUint32(this.Buf[:4],relay)
	n,err := this.Conn.Write(this.Buf[0:4])
	if err != nil || n != 4{
		fmt.Println("conn.Write err = ",err)
		return
	}

	// 发送data本身
	n,err = this.Conn.Write(data)
	if n != int(relay) || err != nil{
		fmt.Println("writePkg conn.Write err",err)
		return
	}
	return
}

在这里插入图片描述
在这里插入图片描述
2019-11-23 今天刚好把这个项目做完,也恰好被裁掉,一切都是那么的巧合,祝自己未来一帆风顺,

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值