No.29粘包

Posted elliottwave

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了No.29粘包相关的知识,希望对你有一定的参考价值。

No.29

今日概要

  • 粘包问题

内容回顾

OSI七层协议

  • 应用层
  • 传输层(理解port)
    • tcp
      • 可靠、慢、全双工、数据长度大
      • 三次握手:发了 syn/ack 信号
        • 三次握手把一个回复和请求连接的两条信息合并成一条了
      • 四次挥手:发了 fin/ack 信号
        • 由于一方断开连接后,可能另一方还有数据没有传递完,所以不能立即断开。
    • udp
      • 不可靠、快、数据长度小
    • 四层路由器、四层交换机
  • 网络层(理解ip)
    • ipv4 / ipv6
    • 路由器、三层交换机
  • 数据链路层(理解mac)
    • arp协议
    • 交换机、网卡
  • 物理层

TCP/UDP协议

TCP
# server                                  #client
import socket                             import socket
sk = socket.socket()                      sk = socket.socket()

sk.bind((‘127.0.0.1‘, 9000))
sk.listen()

conn, adr = sk.accept()                   sk = connect((‘127.0.0.1‘, 9000))

msg = conn.recv(1024)                     sk.send(bytes)
msg.decode(‘utf-8‘)                       
conn.send(bytes)                          msg = sk.recv(1024).decode(‘utf-8‘)

conn.close()                              sk.close()
sk.close()
UDP
# server                                       # client
import socket                                  import socket
sk = socket.socket(type=socket.SOCK_DGRAM)     sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind((‘127.0.0.1‘, 9000))

msg, adr = sk.recvfrom(1024)                   sk.sendto(bytes, (‘127.0.0.1‘, 9000))
msg.decode(‘utf-8‘)                           
sk.sendto(bytes, adr)                          msg = sk.recv(1024).decode(‘utf-8‘)        

sk.close()                                     sk.close()

编码

‘我是好人‘ → 字节

计算机上的存储/网线上的数据传输 → 二进制

1位 = 1bit

10101100 8位 = 8bit = 1个字节

s1编码 s2编码 utf-8 gbk
1 → 我 10 → 我
2 → 是 11 → 是
3 → 好 12 → 好
4 → 人 14 → 人

s1编码

  • ‘我是好人‘ → 1234 → 00000001 00000010 00000011 00000100

  • 将字符串按照指定编码编译后才能转成二进制数据进行存储和传输

s2编码

  • ‘我是好人‘ → 10121314 → 00001010 00001011 00001100 00001101

  • 将字符串按照指定编码编译后才能转成二进制数据进行存储和传输

send

  • str.encode(编码) → bytes

recv

  • bytes.decode(编码) → str

习题

基于TCP协议,实现 Server端 和 Client端 连接后,知道对面 Client端 是哪一个好友,且不同好友聊天显示不同颜色字体。

# Server.py
import socket
import json

sk = socket.socket()
sk.bind((‘127.0.0.1‘, 9000))
sk.listen()
color_dic = {
	     ‘123‘:{‘color‘:‘33[31m‘, ‘name‘:‘alex‘},
	     ‘456‘:{‘color‘:‘33[32m‘, ‘name‘:‘wusir‘},
             ‘789‘:{‘color‘:‘33[33m‘, ‘name‘:‘yuan‘}
            }
while True:
    conn, _ = sk.accept()
    while True:
        msg = conn.recv(1024).decode(‘utf-8‘)
        dic = json.loads(msg)
        user_id = dic[‘id‘]
        if dic[‘msg‘].upper() == ‘Q‘:
            print(‘%s已经下线‘%(color_dic[user_id][‘name‘],))
            break
        print(‘%s%s : %s33[0m‘%(color_dic[user_id][‘color‘],color_dic[user_id][‘name‘],dic[‘msg‘]))
        inp = input(‘>>>‘)
        conn.send(inp.encode(‘utf-8‘))
        if inp.upper() == ‘Q‘:
            print(‘您已经断开和%s的聊天‘%(color_dic[user_id][‘name‘],))
            break
	conn.close()
sk.close()
# Client.py
import socket
import json

sk = socket.socket()
user_id = ‘123‘
sk.connect((‘127.0.0.1‘, 9000))
while True:
    inp = input(‘>>>‘)
    dic = {‘msg‘:inp, ‘id‘:user_id}
    str_dic = json.dumps(dic)
    sk.send(str_dic.encode(‘utf-8‘))
    if inp.upper() == ‘Q‘:
        print(‘您已经断开和server的聊天‘)
        break
    msg = sk.recv(1024).decode(‘utf-8‘)
    if msg.upper() == ‘Q‘:
        break
    print(msg)
    
sk.close()

内容详细

什么是粘包现象?

  • 发生在发送端的粘包
    • 如果两条数据的发送间隔短 + 数据的长度小
    • 那么TCP协议的优化机制会将这两条数据合并成一条数据发送出去
    • 目的是为了减少TCP协议中“确认收到”(回执)的网络延迟时间
  • 发生在接收端的粘包
    • 由于TCP协议中所传输的数据无边界
    • 接收方来不及接收的多条数据会在内核(操作系统)的缓存端合并在一起
  • 本质:接收的边界不清晰

如何解决粘包问题?

  • 自定义协议
    • 先发送报头
      • 报头长度:4个字节
        • struct 模块
          • pack 方法能够把所有的数字都固定的转换成4个字节
      • 报头内容:即将发送报文的字节长度
    • 再发送报文
import struct

ret = struct.pack(‘i‘, 1)
print(ret)

ret = struct.pack(‘i‘, 6666)
print(ret)

ret = struct.pack(‘i‘, 7878)
print(ret)

ret = struct.pack(‘i‘, 100000)
print(ret)
print(struct.unpack(‘i‘,ret)) # unpack返回的是元组
import time
import struct
import socket

sk = socket.socket()
sk.bind((‘127.0.0.1‘, 9000))
sk.listen()

conn, _ = sk.accept()
time.sleep(0.1)
byte_len = conn.recv(4)
size = struct.unpack(‘i‘, byte_len)[0]  # unpack返回的是元组
msg1 = conn.recv(size)
print(msg1)

byte_len = conn.recv(4)
size = struct.unpack(‘i‘, byte_len)[0]
msg2 = conn.recv(size)
print(msg2)

conn.close()
import struct
import socket

sk = socket.socket()
sk.connect((‘127.0.0.1‘, 9000))

msg = b‘hellow‘
byte_len = struct.pack(‘i‘, len(msg))
sk.send(byte_len)
sk.send(msg)

msg = b‘world‘
byte_len = struct.pack(‘i‘, len(msg))
sk.send(byte_len)
sk.send(msg)

sk.close()

以上是关于No.29粘包的主要内容,如果未能解决你的问题,请参考以下文章

Netty进阶——粘包与半包(短链接方式解决粘包问题)

Netty进阶——粘包与半包(代码示例)

Netty进阶——粘包与半包(固定长度方式解决粘包问题)

Netty进阶——粘包与半包(固定长度方式解决粘包问题)

Netty进阶——粘包与半包(预设长度方式解决粘包问题)

Netty进阶——粘包与半包(预设长度方式解决粘包问题)