socket 通信 封装 协议 基础

Big Endian && Little Endian

endian 字节存储次序 端模式

  • Big Endian 是低端地址存放最高有效字节(MSB)
  • Little Endian 低端地址存放最低有效字节(LSB)

以数字 0x12345678 不同字节CPU 中的存储顺序

Big Endian

低地址 高地址
----------------------------------------->
±±±±±±±±±±±±±±±±±±+
| 12 | 34 | 56 | 78 |
±±±±±±±±±±±±±±±±±±+
————————————————

12 是最高有效字节 78 是最低有效字节

Little Endian

低地址 高地址
----------------------------------------->
±±±±±±±±±±±±±±±±±±+
| 78 | 56 | 34 | 12 |
±±±±±±±±±±±±±±±±±±+
————————————————
12 是最高有效字节 在最高地址
78 是最低有效字节 在最低位置


上图而言 bigEndina 方式存储符合人类的思维习惯

网络协议:采用big endian 的方式来传输 有时候称 big endian 方式为网络字节序

当两台不同字节序列的主机通信时候, 在发送数据之前都必须通过字节序后再进行传输

eg

package main

import (
	"encoding/binary"
	"fmt"
)

func main() {
	data := []byte("hello")
	fmt.Printf("data: %v \n", data) //data: [104 101 108 108 111] 
	binary.BigEndian.PutUint16(data, uint16(10))
	fmt.Println("data2:", data)// data2: [0 10 108 108 111]
	binary.LittleEndian.PutUint16(data, uint16(20))
	fmt.Println("data3: ", data)// data3:  [20 0 108 108 111]
}

binary.BigEndian.PutUint16() 是以大端模式 存放字节顺序的0 10 高的有效字节在低端
little endian 就相反

binary.BigEndian.PutUint16() 的作用是一个2个字节的占位去 替换掉原来的字节位置元素(最前面的元素)
前提条件 原来data 的长度要大于等于2 (2 个字节16位)

在 socket 通信中 注意 如果封装数据的时候是用小端的方式 那么拆包的时候也采取小端的方式 就可以 一定要保持一致

在上面代码中国最后一次使用的是little endian 来存放字节 使用 little 来解码

	var l uint16
	binary.Read(reader, binary.LittleEndian, &l)
	fmt.Println("l: ", l) // l:  20

如果 使用 big endian 来解码

	var l uint16
	//binary.Read(reader, binary.LittleEndian, &l)
	binary.Read(reader, binary.BigEndian, &l)
	fmt.Println("l: ", l) // l:  5120 

出现不可预知的错误 在编码解码的时候一定要对应

bytes.NewReader(data) && binary.read() && io.ReadFull(reader, d)

bytes.NewReader() 中data 为b []byte slice 加载数据 返回一个reader 对象

type Reader struct {
	s        []byte
	i        int64 // current reading index
	prevRune int   // index of previous rune; or < 0
}

Reader 中方法

  • Len() 放回 还未读取的slice 部分的数据长度
  • Size() 返回 底层数组的原始长度 大小

binary.read() 方法

	reader := bytes.NewReader(data) 
	var ls uint16
	binary.Read(reader, binary.LittleEndian, &ls) 

按照指定的字节顺序 读取reader 中数据解码到ls 中
读取之后 reader 结构中

i        int64 // current reading index

会发生变化

当下次调用的时候 读取会从这个index 位置处读取

不使用reader 的方法 比较简单

	data = data[2:]
	mtype := binary.LittleEndian.Uint16(data)

取出data 的前两个字符 然后转化为10进制形式 方会给返回
这种方式在 获取头信息的时候 需要每次都要用切片赋值给一个新值

func (r *Reader) Read(b []byte) (n int, err error) {
	if r.i >= int64(len(r.s)) {
		return 0, io.EOF
	}
	r.prevRune = -1
	n = copy(b, r.s[r.i:])
	r.i += int64(n)
	return
}

io.ReadFull

// 断言最少读
func io.ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
    ...
    n, err = r.Read(buf[:])
    ...
}
// 断言全量读
func io.ReadFull(r Reader, buf []byte) (n int, err error) {
    return ReadAtLeast(r, buf, len(buf))
}

将Reader 中未读取的数据读取到buf 中
还是围绕io.Reader.Read 方法进行的

注意这里面的不同的错误状态
这两个函在操作Reader对象读 的过程中 产生一个新的错误态:io.errUnexpectedEOF

  • io.ReadAtLeast 贪婪读,至少读 min 个即视为成功,尽可能的读 len(buf)
    当读取的内容字节数 n == 0 时,err = io.EOF
    当 0 < n < min 时,err = io.ErrUnexpectedEOF
    当 n >= min 时,err = nil

  • io.ReadFull 断言读,必须读 len(buf) 才视为成功
    当读取的内容字节数 n == 0 时,err = io.EOF
    当 0 < n < len(buf) 时,err = io.ErrUnexpectedEOF
    当 n == len(buf) 时,err = nil

在reader 中取出自己需要的字段之后取出后面所有的数据 给新的对象

d := make([]byte, reader.len())
io.ReadFull(reader, d)

copy 用法

用于两个slice 之间进行拷贝数据, 其拷贝数据的长度为len(src) len(dst) 之间的最小值

// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).

eg:

func copy(dst, src []Type) int

package main

import "fmt"

func main() {
	s := []int{1, 2, 3}
	fmt.Println(s) //[1 2 3]
	b := s
	//copy(s, []int{4, 5, 6,7,8})
	copy(s, []int{4})
	fmt.Println(s) //[4 2 3]
	copy(s, []int{})
	fmt.Println(s) //[4 2 3]
	fmt.Println(b) //[4 2 3]
}

可以理解
dst 就相当于一个桶一样 大小已经确定在那里 当复制过看来的数据长度 大于的dst 的时候才能全部覆盖, 不然就只有部分覆盖。

copy 中复制是值复制

package main

import "fmt"

func main() {
	a := []int{1, 2, 3}
	b := []int{4, 5, 6}
	copy(a, b) 
	fmt.Println(a) // [4 5 6]
	b[0] = 100
	fmt.Println(b) // [100 5 6]
	fmt.Println(a) // [4 5 6]
}

当修改b 中的数据时候a 中数据中没有改变

深拷贝
基于序列化和反序列化来实现对象的深拷贝
内部 基于反射reflect 来实现

func deepCopy(dst, src interface{}) error {
    var buf bytes.Buffer
    if err := gob.NewEncoder(&buf).Encode(src); err != nil {
        return err
    }
    return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}

注意:
需要深拷贝的变量必须首字母大写才可以被拷贝

bufio 包

https://www.cnblogs.com/golove/p/3282667.html

这个也是实现socket 中拆包 处理啊粘包问题的代码
(在每个包中加入包的大小size 就可以解决粘包的问你)
客户端代码

package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
	"time"
)

func main() {
	data := [] byte("我要")

	l := len(data)
	magicNum := make([]byte, 4)
	binary.BigEndian.PutUint32(magicNum, 0x123456) // 设置四位头
	lenNum := make([]byte, 2)
	binary.BigEndian.PutUint16(lenNum, uint16(l)) // 将这个数字转化为二进制数组 2位的保存
	// 这里也间接规定了 传输数据的上限多少
	putBuf := bytes.NewBuffer(magicNum)
	putBuf.Write(lenNum)
	putBuf.Write(data)
	// 以上是封装数据
	conn, err := net.DialTimeout("tcp", "localhost:4044", time.Second*30)
	if err != nil {
		fmt.Printf("connect failed err: %v \n", err.Error())
		return
	}
	//for i := 0; i < 10; i++ {
	//	_, err = conn.Write(putBuf.Bytes())
	//	if err != nil {
	//		fmt.Printf("write failed err : %v \n", err)
	//		break
	//	}
	//	time.Sleep(3 * time.Second)
	//}

	writer := bufio.NewWriter(conn)
	writer.Write(putBuf.Bytes())
	writer.Write(putBuf.Bytes())
	//writer.Write([]byte("hello world")) // 这种不按照约定头的 就直接给丢弃掉了 不符合规则的
	writer.Flush()


	time.Sleep(10 * time.Second)
}

//func ()  {
//
//}


服务端代码

package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
)

func main() {
	l, err := net.Listen("tcp", ":4044")
	if err != nil {
		panic(err)
	}
	fmt.Println("listen to 4044")
	for {
		conn, err := l.Accept()
		if err != nil {
			fmt.Println("conn err:", err)
		} else {
			go handleConn(conn)
		}
	}
}

func packageSlitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if !atEOF && len(data) > 6 && binary.BigEndian.Uint32(data[:4]) == 0x123456 {
		var l int16
		// 读出包中的实际长度 大小为0-2 的16次方
		binary.Read(bytes.NewReader(data[4:6]), binary.BigEndian, &l)
		p1 := int(l) + 6 // 加上了头信息的长度 总共的长度
		if p1 <= len(data) {
			return p1, data[:p1], nil
		}
	}
	return
}

func handleConn(conn net.Conn) {
	defer conn.Close()
	defer fmt.Println("关闭")
	fmt.Println("新链接:", conn.RemoteAddr())
	buf := make([]byte, 12)
	for {
		scanner := bufio.NewScanner(conn)
		scanner.Buffer(buf, 11)
		scanner.Split(packageSlitFunc)
		fmt.Println("哈哈哈 看看这里走了没有")
		for scanner.Scan() {
			fmt.Println("recv: ", string(scanner.Bytes()[6:]))
		}
		if err := scanner.Err(); err != nil {
			fmt.Println("invalid input", err)
			break
		}
	}

}

引用
https://www.jianshu.com/p/ec461b39bf43

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值