服务端代码如下

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. )
  7. //处理函数
  8. func process(conn net.Conn) {
  9. defer conn.Close() //关闭连接
  10. for {
  11. //获取输入流
  12. reader := bufio.NewReader(conn)
  13. //每次读取的大小
  14. var buf [1024]byte
  15. n, err := reader.Read(buf[:]) //读取数据 从头到尾读取
  16. if err != nil {
  17. fmt.Println("read from client failed,err", err)
  18. break
  19. }
  20. recvStr := string(buf[:n])
  21. fmt.Println("收到client端发来的数据:", recvStr)
  22. conn.Write([]byte(recvStr))
  23. }
  24. }
  25. func main() {
  26. listen, err := net.Listen("tcp", "127.0.0.1:30000")
  27. if err != nil {
  28. fmt.Println("listen failed,err", err)
  29. return
  30. }
  31. for {
  32. conn, err := listen.Accept()
  33. if err != nil {
  34. fmt.Println("accept failed,err", err)
  35. continue
  36. }
  37. go process(conn) //开启启程去处理读取数据
  38. }
  39. }

客户端

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. )
  6. func main() {
  7. conn, err := net.Dial("tcp", "127.0.0.1:30000")
  8. if err != nil {
  9. fmt.Println("diall error:", err)
  10. return
  11. }
  12. defer conn.Close()
  13. for i := 0; i < 20; i++ {
  14. msg := `Hello, Hello. How are you?`
  15. conn.Write([]byte(msg))
  16. }
  17. }

result:

Golang网络编程TCP粘包 - 图1
通过以上截图可以看出客户端写了19次的消息到服务端。但是服务端接受的数据把19条数据整合成了一条信息。把多条信息都粘在了一起。

为什么会出现粘包?
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。”粘包”可以发送在发送端,也可发生在接收端:

  • 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
  • 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

解决办法
粘包主要的问题出现在接收方不确定要传输的数据包的大小。因此我们可以把数据包进行封包和拆包的操作
封包:封包就是给一段数据加上包头,这样以来数据包就分为包头和包体两部分内容了。包头部分的长度是固定的。并且存储了包体的长度,根据包头长度固定以及包含的包体长度 就能正确拆分出一个完整的数据包。

  1. // Encode 将消息编码
  2. func Encode(message string) ([]byte, error) {
  3. // 读取消息的长度,转换成int32类型(占4个字节)
  4. //长度是int32 为4个字节 也就是占包的前4位
  5. var length = int64(len(message))
  6. var pkg = new(bytes.Buffer)
  7. // 写入消息头
  8. //intDataSize write方法中根据第三个参数 data获取字节长度 int32 长度位4字节 int64的话就为8个字节可自行参考
  9. //不一定非要定义4个字节
  10. err := binary.Write(pkg, binary.LittleEndian, length)
  11. if err != nil {
  12. return nil, err
  13. }
  14. // 写入消息实体
  15. err = binary.Write(pkg, binary.LittleEndian, []byte(message))
  16. if err != nil {
  17. return nil, err
  18. }
  19. return pkg.Bytes(), nil
  20. }
  21. // Decode 解码消息
  22. func Decode(reader *bufio.Reader) (string, error) {
  23. // 读取消息的长度
  24. //可能在这边大家不是特别理解为什么指定读取8个字节
  25. //可参考上面ecode的write当中的intDataSize 方法。上面我才用了int64 占byte字节8字节。如果采用int32那么就是占4字节
  26. lengthByte, _ := reader.Peek(8) // 读取前8个字节
  27. lengthBuff := bytes.NewBuffer(lengthByte)
  28. var length int64
  29. err := binary.Read(lengthBuff, binary.LittleEndian, &length)
  30. if err != nil {
  31. return "", err
  32. }
  33. // Buffered返回缓冲中现有的可读取的字节数。
  34. if int64(reader.Buffered()) < length+8 {
  35. return "", err
  36. }
  37. // 读取真正的消息数据
  38. pack := make([]byte, int(8+length))
  39. _, err = reader.Read(pack)
  40. if err != nil {
  41. return "", err
  42. }
  43. return string(pack[8:]), nil
  44. }
  45. //服务端改造如下
  46. func process(conn net.Conn) {
  47. defer conn.Close()
  48. reader := bufio.NewReader(conn)
  49. for {
  50. msg, err := proto.Decode(reader)
  51. if err == io.EOF {
  52. return
  53. }
  54. if err != nil {
  55. fmt.Println("decode msg failed, err:", err)
  56. return
  57. }
  58. fmt.Println("收到client发来的数据:", msg)
  59. }
  60. }
  61. func main() {
  62. listen, err := net.Listen("tcp", "127.0.0.1:30000")
  63. if err != nil {
  64. fmt.Println("listen failed, err:", err)
  65. return
  66. }
  67. defer listen.Close()
  68. for {
  69. conn, err := listen.Accept()
  70. if err != nil {
  71. fmt.Println("accept failed, err:", err)
  72. continue
  73. }
  74. go process(conn)
  75. }
  76. }
  77. //客户端改造如下
  78. func main() {
  79. conn, err := net.Dial("tcp", "127.0.0.1:30000")
  80. if err != nil {
  81. fmt.Println("dial failed, err", err)
  82. return
  83. }
  84. defer conn.Close()
  85. for i := 0; i < 20; i++ {
  86. msg := `Hello, Hello. How are you?`
  87. data, err := proto.Encode(msg)
  88. if err != nil {
  89. fmt.Println("encode msg failed, err:", err)
  90. return
  91. }
  92. conn.Write(data)
  93. }
  94. }

Golang网络编程TCP粘包 - 图2