海量用户及时通讯系统

需求分析

  • 用户注册
  • 用户登录
  • 显示用户在线的用户列表
  • 群聊
  • 点对点聊天
  • 离线留言

界面设计

界面设计

项目开发前技术储备

项目要保存用户信息和消息数据,因此我们需要学习数据库(redis或者是MySQL)这里我们选择Redis

go中使用redis

Redis的基本介绍

redis是nosql型数据库 不是关系型数据库
Redis是远程字典服务器,性能比较高,单机性能高,适合做缓存,也可以持久化

是完全开元免费的,高性能的分布式内存数据库

redis的操作指令

redis的操作指令

操作指令
redis的基本使用
  • 添加key-val [set]
  • 获取当前redis所有key的值 [key *]
  • 获取key对应的值[get key]
  • 切换redis数据库[select index]
  • 如何查看当前数据库的key-value值[dbsize]
  • 清空当前数据库的key-val和清空所有数据库的key-val[flushdb flushall]

Redis的五大数据类型

Redis的五大数据类型:string hash List set(集合) 和 zset(有序集合)

string介绍

string是redis的最基本的类型,一个key对应一个value
string类型是二进制安全的 除普通字符外,也可以存放图片数据

set [如果存在就相当于修改 如果不存在就相当于增加]/get/del[删除]

setex键秒值

setex key seconds value

setex

超过10秒后就自动消失了

mset 同时可以设置多个key val

mset mget

Hash(哈希,类似go中的map)

redis hash是一个键值对集合。var user1 map[string]string

redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

举例存放一个user信息

user1 name 张三 age30
说明:
key:user1
name 张三 和 age 30 是两对 field-value

hset

hset 增加
hget 获取

hgetall 获取所有的

用这个就不用一个一个get了

hgetall

list 介绍

list是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部或者是尾部

list本质是个链表,list元素是有序的,元素的值可以重复

list操作

lpush

增加

lrange

取出字段

list注意事项

  • index按照索引下标获取元素(从左到右,编号从0开始)
  • LLen key
    返回当前key的长度,如果key不存在,则key被解释为一个空列表,返回0
  • List的其他说明
    list数据,可以从左或者右插入添加
    如果值全移除了,对应的键也消失了

set集合的介绍

Redis的set是string类型的无序集合

底层是hashTable数据结构,set也是存放很多字符串的元素,字符串元素是无序的,而且元素的值不能重复

举例,存放多个邮件列表信息:
email sgg@email.com

set

可以看出加入后取出是无序的

集合的其他指令

  • sadd 添加
  • smembers 取出所有值
  • sismember 判断是否是成员
  • srem 删除指定值

golang 操作redis

安装redis库

  • 使用第三方库redis: go get github.com/garyburd/redigo/redis
    在gopath下执行

  • 安装好有个github.com的包

代码连接操作

package main

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

func main()  {
    //通过go向redis 写入数据读取数据
    //  连接到redis
    conn,err := redis.Dial("tcp","127.0.0.1:6379")
    if err!=nil{
        fmt.Println("redis Dial err=",err)
        return
    }
    fmt.Println("conn succ=",conn)
}

通过Set、Get结构进行操作数据

package main

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

func main()  {
    //通过go向redis 写入数据读取数据
    //  连接到redis
    conn,err := redis.Dial("tcp","127.0.0.1:6379")
    if err!=nil{
        fmt.Println("redis Dial err=",err)
        return
    }

    defer conn.Close()//关闭一定不能忘记
    
    //通过go向redis中添加数据  string【key-value】
    _, err = conn.Do("Set","name","tom")
    if err != nil{
        fmt.Println("set err=",err)
        return
    }

    //读取数据

    r, err := redis.String(conn.Do("Get","name"))
    if err != nil{
        fmt.Println("Get err=",err)
        return
    }

    //因为返回的r是interface{}
    //因为name对应的值是string  因此我们需要转换


    fmt.Println("操作成功  r=",r)
}

操作hash

    _, err := conn.Do("HSet","user01","name","tom")

    if err != nil{
        fmt.Println("set err=",err)
        return
    }

    r, err := redis.String(conn.Do("HGet","name"))
    if err != nil{
        fmt.Println("Get err=",err)
        return
    }

也就是把set换成 HSet
这样就可以了

操作List

核心代码

    _, err := conn.Do("Lpush","heroList","宋江","武松")
    r, err := redis.String(conn.Do("rpop","heroList"))

redis 连接池问题

  • 事先初始化一定数量的连接,放入到连接池
  • 当go需要操作redis时,直接从redis连接池中取出连接就行了
  • 这样可以节省获取redis连接的时间,提高效率


    连接池

运行前一定要初始化

代码案例

package main

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

//定义一个全局的pool
var pool *redis.Pool

//当启动程序时 就初始化连接池

func init()  {
    pool = &redis.Pool{
        Dial: func() (conn redis.Conn, err error) {
            return redis.Dial("tcp","localhost:6379")  //初始化连接代码
        },
        MaxIdle:         8, //最大空闲连接数
        MaxActive:       0,// 表示和数据库的最大连接数,0 表示没有限制
        IdleTimeout:     100, //最大空闲时间

    }
}

func main()  {
    //先从pool中取出一个连接
    conn := pool.Get()
    defer conn.Close()

    _,err :=conn.Do("Set","name","tomcat")
    if err != nil{
        fmt.Println("conn do err=",err)
        return
    }

    r,err :=redis.String(conn.Do("Get","name"))
    if err!= nil{
        fmt.Println("conn.do err = ",err)
        return
    }
    fmt.Println("r=",r)
    //如果我们从pool中取出连接,一定要保证连接池是没有关闭的
    
}

实现功能-显示客户登录菜单

界面


界面

代码实现

package main

import (
    "fmt"

)

//一个表示用户id 一个表示用户密码
var usrId int
var usrpwd string

func main()  {
    //接收用户的选择
    var key int
    //判断是否继续显示菜单
    var loop = true

    for  loop{
        fmt.Println("-----------------欢迎登录多人聊天系统---------------")
        fmt.Println("\t\t\t\t 1 登录聊天室")
        fmt.Println("\t\t\t\t 2 注册用户")
        fmt.Println("\t\t\t\t 3 退出系统")
        fmt.Println("\t\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",&usrId)
        fmt.Println("请用户输入自己的ID")
        fmt.Scanf("%s\n",&usrpwd)
        //先把登录的函数  写到另外一个文件中  login.go

        err := login(usrId,usrpwd)
        if err!=nil {
            fmt.Println("登录失败")
        }else {
            fmt.Println("登录成功")
        }
    }else if key == 2{
        fmt.Println("进行用户注册")
    }

}

login.go

package main

import "fmt"

//写一个函数 完成一个登录

func login(usrId int,usrpwd string)(err error)  {
    //下一步要开始定协议
    fmt.Printf("userId= %d   usrpwd=%s\n",usrId,usrpwd)
    return nil
}

实现用户登录

结构

message的组成示意图 和发送一个message的流程

流程图

1 完成客户端可以发送的消息的长度,服务端可以正常收到该长度

确定message格式和结构
根据上图的分析完成代码

示意图


示意图

2 完成客户端可以正常发送消息本身,服务端可以正常接收消息,并根据客户端发送的(LoginMes)判断用户的合法性,并返回相应的LoginResMes

思路分析:

  • 让客户端发送消息
  • 服务器端接收到消息,然后反序列化成对应的消息结构体
  • 服务器端根据反序列化成对应的消息,判断是否登录用户是合法的用户,返回LoginResMes
  • 客户解析返回的LoginResMes,显示对应的界面
  • 我们这里需要做函数的封装
协成处理

程序结构的改进

程序结构的改进,前面的程序虽然完成了功能,但是没有结构,系统的可读性,扩展性和维护性,都不好,因此需要对程序结构进行改进

1 先改进服务器端,先画出程序的框架图,再写代码

思路

步骤

  • 先把分析出来的文件创建好,然后放到相应的文件夹中

代码结构


代码结构

详细代码

package main

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

//写一个函数 完成一个登录

func login(usrId int,usrpwd string)(err error)  {
    ////下一步要开始定协议
    //fmt.Printf("userId= %d   usrpwd=%s\n",usrId,usrpwd)
    //return nil
    conn,err := net.Dial("tcp","localhost:8889")
    if err!=nil{
        fmt.Println("net Dail err=",err)
        return
    }
    //延时关闭
    defer conn.Close()

    //2 通过conn发送消息给服务
    var mes message.Message
    mes.Type = message.LoginMesType
    //3 创建一个LoginMes 结构体
    var LoginMes message.LoginMes
    LoginMes.UserId = usrId
    LoginMes.UserPwd = usrpwd
    //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 进行序列化

    data,err = json.Marshal(mes)
    if err!= nil{
        fmt.Println("json marshal err=",err)
        return
    }
    //7 这个时候data 就是我们要发送的数据
    //7.1  先把data的长度发送给服务器
    // 先获得到 data的长度-》转成一个表示长度的byte切片
    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 err!=nil||n != 4 {
        fmt.Println("conn write err=",err)
        return
    }
    fmt.Printf("客户端,发送消息长度ok  有%d 字节 %s",len(data),string(data))

    // 发送消息本身
    _,err = conn.Write(data)
    if err != nil{
        fmt.Println("conn write err=",err)
        return
    }

    //这里还需要处理服务器端返回的消息
    mes,err = readPkg(conn)

    if err != nil{
        fmt.Println("readpkg 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
}

login.go

package main

import (
    "fmt"

)

//一个表示用户id 一个表示用户密码
var usrId int
var usrpwd string

func main()  {
    //接收用户的选择
    var key int
    //判断是否继续显示菜单
    var loop = true

    for  loop{
        fmt.Println("-----------------欢迎登录多人聊天系统---------------")
        fmt.Println("\t\t\t\t 1 登录聊天室")
        fmt.Println("\t\t\t\t 2 注册用户")
        fmt.Println("\t\t\t\t 3 退出系统")
        fmt.Println("\t\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",&usrId)
        fmt.Println("请用户输入自己的密码")
        fmt.Scanf("%s\n",&usrpwd)
        //先把登录的函数  写到另外一个文件中  login.go
        login(usrId,usrpwd)
        //if err!=nil {
        //  fmt.Println("登录失败")
        //}else {
        //  fmt.Println("登录成功")
        //}
    }else if key == 2{
        fmt.Println("进行用户注册")
    }

}

client/main.go

package main

import (
    "awesomeProject/chatroom/common/message"
    "encoding/binary"
    "encoding/json"
    "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{
        fmt.Println("conn read err=",err)
        return
    }
    // 根据读到的buf长度 转换为uint32 的类型
    var pkgLen uint32
    pkgLen = binary.BigEndian.Uint32(buf[0:4])
    // 根据pkgLen 读取消息内容
    n,err := conn.Read(buf[:pkgLen])
    if n != int(pkgLen) || err != nil{
        fmt.Println("conn read err=",err)
        return
    }
    //pkgLen 反序列化成_>message.Message 的类型
    err = json.Unmarshal(buf[:pkgLen],&mes)
    if err != nil{
        fmt.Println("json err=",err)
        return
    }
    return
}

func writePkg(conn net.Conn,data []byte)(err error)  {
    //先发送一个长度给对方
    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 err!=nil||n != 4 {
        fmt.Println("conn write err=",err)
        return
    }
    //发送data 本身
    n,err = conn.Write(data)
    if n != int(pkgLen)||err !=nil{
        fmt.Println("conn write er=",err)
        return
    }
    return
}

client/utils.go

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"`//用户ID
    UserPwd     string `json:"userpwd"`//用户密码
    UserName    string `json:"username"`//用户名

}

type LoginResMes struct {
    Code    int `json:"code"`//返回的状态码   500表示用户未注册 200表示登录成功
    Error   string `json:"error"`//返回错误信息
}

type RegisterMes struct {

}

message.go

package main

import (
    "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{
//      fmt.Println("conn read err=",err)
//      return
//  }
//  // 根据读到的buf长度 转换为uint32 的类型
//  var pkgLen uint32
//  pkgLen = binary.BigEndian.Uint32(buf[0:4])
//  // 根据pkgLen 读取消息内容
//  n,err := conn.Read(buf[:pkgLen])
//  if n != int(pkgLen) || err != nil{
//      fmt.Println("conn read err=",err)
//      return
//  }
//  //pkgLen 反序列化成_>message.Message 的类型
//  err = json.Unmarshal(buf[:pkgLen],&mes)
//  if err != nil{
//      fmt.Println("json err=",err)
//      return
//  }
//  return
//}
//
//func writePkg(conn net.Conn,data []byte)(err error)  {
//  //先发送一个长度给对方
//  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 err!=nil||n != 4 {
//      fmt.Println("conn write err=",err)
//      return
//  }
//  //发送data 本身
//  n,err = conn.Write(data)
//  if n != int(pkgLen)||err !=nil{
//      fmt.Println("conn write er=",err)
//      return
//  }
//  return
//}

//编写一个函数专门处理登录请求
//func serverProcessLogin(conn net.Conn,mes *message.Message) (err error) {
//  //核心代码
//  //1 先从mes中取 mes.data,并这接反序列化成LoginMes
//  var loginMes message.LoginMes
//  err = json.Unmarshal([]byte(mes.Data),&loginMes)
//  if err != nil{
//      fmt.Println("json Unmarshal err=",err)
//      return
//  }
//  //1先申明一个  resMes
//  var resMes message.Message
//  resMes.Type = message.LoginResMesType
//
//  //2在声明一个 LoginResMes  并完成赋值
//  var loginResMes message.LoginResMes
//
//  //如果用户id = 100 密码=123456 认为合法
//  if loginMes.UserId == 100 && loginMes.UserPwd =="123456"{
//      //合法
//      loginResMes.Code = 200
//
//  }else {
//      //不合法
//      loginResMes.Code = 500 //500状态码 表示用户不存在
//      loginResMes.Error = "该用户不存在,请重新注册"
//  }
//  // 3 将 loginResMes 进行序列化
//  data ,err := json.Marshal(loginResMes)
//  if err != nil{
//      fmt.Println("序列化失败 err=",err)
//      return
//  }
//  //4 将data 赋值给resMes
//  resMes.Data = string(data)
//
//
//  //5 对resMes进行序列化  准备发送
//  data ,err = json.Marshal(resMes)
//  if err != nil{
//      fmt.Println("Marshal err=",err)
//      return
//  }
//  //6 发送data  我们将其封装到writePkg 函数中去
//  err = writePkg(conn,data)
//  return
//}

//编写一个ServerProcessMessage  函数
//功能:根据客户端发送的消息种类不同,决定调用哪个函数来处理
//func serverProcessMessage(conn net.Conn,mes *message.Message)(err error)  {
//  switch mes.Type {
//  case message.LoginMesType:
//      //处理登录
//      err = serverProcessLogin(conn,mes)
//      case message.RegisterMesType:
//      //处理注册
//      default:
//      fmt.Println("消息类型不存在无法处理")
//
//  }
//  return
//}

//处理和客户端 的通讯
func process(conn net.Conn)  {
    //这里需要延时关闭
    defer conn.Close()

    //读客户端发送的信息
    //for {
    //  //这里我们将读取数据包,直接封装成一个函数readPkg(),返回Message,Err
    //  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 = serverProcessMessage(conn,&mes)
    //  if err != nil{
    //      return
    //  }
    //}

    //这里要调用总控  先创建一个总控实例
    processor := &Processor{Conn:conn}
    err := processor.process1()
    if err != nil{
        fmt.Println("客户端和服务器通讯协成错误=",err)
        return
    }

}

func main()  {

    //提示信息
    fmt.Println("服务器在8889端口监听")
    listen,err := net.Listen("tcp","0.0.0.0:8889")
    defer listen.Close()
    if err!= nil {
        fmt.Println("listen err = ",err)
        return
    }
    //一旦监听成功
    for  {
        fmt.Println("等待客户端来连接服务器")
        conn,err := listen.Accept()
        if err != nil{
            fmt.Println("lsten accept err=",err)
        }
        //一旦连接成功,则启动一个协成和客户端保持通讯
        go process(conn)
    }
}

server/main.go

package main

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

//先创建一个process的结构体
type Processor struct {
    Conn    net.Conn
}

func (this *Processor)serverProcessMessage(mes *message.Message)(err error)  {
    switch mes.Type {
    case message.LoginMesType:
        //处理登录
        //创建一个UserProcess 实例
        up := &process2.UserProcess{
            Conn : this.Conn,
        }
        err = up.ServerProcessLogin(mes)
    case message.RegisterMesType:
    //处理注册
    default:
        fmt.Println("消息类型不存在无法处理")

    }
    return
}

func (this *Processor)process1()(err error)  {
    for {
        //这里我们将读取数据包,直接封装成一个函数readPkg(),返回Message,Err

        //创建一个Transfer 实例完成读包任务
        tf := &utils.Transfer{
            Conn: this.Conn,
        }
        mes,err := tf.ReadPkg()
        if err != nil{
            if err == io.EOF{
                fmt.Println("客户端退出,服务器也退出了")
                return
            }else {
                fmt.Println("readpkg err =",err)
                return
            }
        }
        fmt.Println("mes = ",mes)
        err = this.serverProcessMessage(&mes)
        if err != nil{
            return
        }
    }
}

server/main/processor.go

package process

smsProcessor.go

package process

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

type UserProcess struct {
    Conn net.Conn
}

func (this *UserProcess)ServerProcessLogin(mes *message.Message) (err error) {
    //核心代码
    //1 先从mes中取 mes.data,并这接反序列化成LoginMes
    var loginMes message.LoginMes
    err = json.Unmarshal([]byte(mes.Data),&loginMes)
    if err != nil{
        fmt.Println("json Unmarshal err=",err)
        return
    }
    //1先申明一个  resMes
    var resMes message.Message
    resMes.Type = message.LoginResMesType

    //2在声明一个 LoginResMes  并完成赋值
    var loginResMes message.LoginResMes

    //如果用户id = 100 密码=123456 认为合法
    if loginMes.UserId == 100 && loginMes.UserPwd =="123456"{
        //合法
        loginResMes.Code = 200

    }else {
        //不合法
        loginResMes.Code = 500 //500状态码 表示用户不存在
        loginResMes.Error = "该用户不存在,请重新注册"
    }
    // 3 将 loginResMes 进行序列化
    data ,err := json.Marshal(loginResMes)
    if err != nil{
        fmt.Println("序列化失败 err=",err)
        return
    }
    //4 将data 赋值给resMes
    resMes.Data = string(data)


    //5 对resMes进行序列化  准备发送
    data ,err = json.Marshal(resMes)
    if err != nil{
        fmt.Println("Marshal err=",err)
        return
    }
    //6 发送data  我们将其封装到writePkg 函数中去
    //因为使用分层模式(MVC) 我们先创建一个Transfer 实例 然后读取
    tf := &utils.Transfer{
        Conn: this.Conn,
    }
    err = tf.WritePkg(data)
    return
}

userProcess.go

package utils

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


//这里将这些方法关联到结构体中
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长度 转换为uint32 的类型
    var pkgLen uint32
    pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
    // 根据pkgLen 读取消息内容
    n,err := this.Conn.Read(this.Buf[:pkgLen])
    if n != int(pkgLen) || err != nil{
        fmt.Println("conn read err=",err)
        return
    }
    //pkgLen 反序列化成_>message.Message 的类型
    err = json.Unmarshal(this.Buf[:pkgLen],&mes)
    if err != nil{
        fmt.Println("json err=",err)
        return
    }
    return
}

func (this *Transfer)WritePkg(data []byte)(err error)  {
    //先发送一个长度给对方
    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 err!=nil||n != 4 {
        fmt.Println("conn write err=",err)
        return
    }
    //发送data 本身
    n,err = this.Conn.Write(data)
    if n != int(pkgLen)||err !=nil{
        fmt.Println("conn write er=",err)
        return
    }
    return
}

utils/utils.go

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • NOSQL类型简介键值对:会使用到一个哈希表,表中有一个特定的键和一个指针指向特定的数据,如redis,volde...
    MicoCube阅读 3,956评论 2 27
  • 1 Redis介绍1.1 什么是NoSql为了解决高并发、高可扩展、高可用、大数据存储问题而产生的数据库解决方...
    克鲁德李阅读 5,263评论 0 36
  • Redis是啥 Redis是一个开源的key-value存储系统,由于拥有丰富的数据结构,又被其作者戏称为数据结构...
    一凡呀阅读 1,170评论 0 5
  • 本文为笔者对在学习Redis过程中所收集资料的一个总结,目的是为了以后方便回顾相关的知识,大部分为非原创内容。特此...
    EakonZhao阅读 14,404评论 0 9
  • 欢迎大家关注我的其他 Github博客 和 Csdn ,互相交流! 1. Redis 简介 •Redis是一款开源...
    程序员祝融阅读 566评论 2 9