No.29粘包
Posted elliottwave
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了No.29粘包相关的知识,希望对你有一定的参考价值。
No.29
今日概要
- 粘包问题
内容回顾
OSI七层协议
- 应用层
- 传输层(理解port)
- tcp
- 可靠、慢、全双工、数据长度大
- 三次握手:发了 syn/ack 信号
- 三次握手把一个回复和请求连接的两条信息合并成一条了
- 四次挥手:发了 fin/ack 信号
- 由于一方断开连接后,可能另一方还有数据没有传递完,所以不能立即断开。
- udp
- 不可靠、快、数据长度小
- 四层路由器、四层交换机
- tcp
- 网络层(理解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 : %s 33[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个字节
- struct 模块
- 报头内容:即将发送报文的字节长度
- 报头长度: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粘包的主要内容,如果未能解决你的问题,请参考以下文章