分享
  1. 首页
  2. 文章

海量用户及时通讯系统

乔大叶_803e · · 1180 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

需求分析

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

界面设计

界面设计

项目开发前技术储备

项目要保存用户信息和消息数据,因此我们需要学习数据库(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


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:乔大叶_803e

查看原文:海量用户及时通讯系统

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1180 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏