粘包现象

首先引入subprocess模块

import subprocess
res=subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)#这里相当于将所有的dir信息和所有的错误信息放到了subprocess.PIPE里面
print('stdout:',res.stdout.read().decode('gbk'))#注意windows操作系统默认的编码格式是gbk
print('stderr:',res.stderr.read().decode('gbk'))

继续进阶

使用TCP方式展现粘包现象

sever端

import socket
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()


conn,addr=sk.accept()
conn.send(b'dir,ls')#这里本来应该只有dir,但是这里错误的加入了,ls,是为了引出后面的stderr
ret=conn.recv(1024).decode('utf-8')
print(ret)

conn.close()
sk.close()

clent端口

import socket
import subprocess
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
cmd=sk.recv(1024).decode('gbk')
ret=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout='stdout:'+(ret.stdout.read()).decode('gbk')#注意这里(stdout.read())本身是一个gbk形式的字节码,那么后面加上decode(gbk)以后就能正常显示了
stderr='stderr:'+(ret.stderr.read()).decode('gbk')
print(stdout)
print(stderr)
sk.send(stdout.encode('utf-8'))#利用encode('utf-8')就能编码成byts类型
sk.send(stderr.encode('utf-8'))
sk.close()

结果

D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/clent1.py
stdout: 驱动器 F 中的卷是 新加卷
 卷的序列号是 0EEF-3DE3

 F:\python\python学习\人工智能\第一阶段day2 的目录


stderr:找不到文件


Process finished with exit code 0

结果显示stdout和stderr都显示出来了

加上循环:

sever端

import socket
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()
while 1:
    cmd=input('>>>>:')
    conn.send(cmd.encode('utf-8'))#这里本来应该只有dir,但是这里错误的加入了,ls,是为了引出后面的stderr
    ret=conn.recv(1024).decode('utf-8')
    print(ret)

conn.close()
sk.close()

clent端口

import socket
import subprocess
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
while 1:
    cmd=sk.recv(1024).decode('gbk')
    ret=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    stdout='stdout:'+(ret.stdout.read()).decode('gbk')#注意这里(stdout.read())本身是一个gbk形式的字节码,那么后面加上decode(gbk)以后就能正常显示了
    stderr='stderr:'+(ret.stderr.read()).decode('gbk')
    print(stdout)
    print(stderr)
    sk.send(stdout.encode('utf-8'))#利用encode('utf-8')就能编码成byts类型
    sk.send(stderr.encode('utf-8'))
sk.close()

执行结果如下:

D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/sever.py
>>>>:dir,ls
stdout: 驱动器 F 中的卷是 新加卷
 卷的序列号是 0EEF-3DE3

 F:\python\python学习\人工智能\第一阶段day2 的目录


>>>>:ipconfig
stderr:找不到文件

>>>>:

可以看到在输入dir,ls的时候,只输出了dir,而错误的结果stderr:找不到文件在执行下一个命令ipconfig才出现,这种执行结果不在当前出现的现象就叫做粘包现象

再来看下udp方式

sever端

import socket
sk=socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8080))
msg,addr=sk.recvfrom(10240)#注意这里因为当使用ipconfig的时候过长,使用1024就会报错,所以使用10240
while 1:
    cmd=input('>>>>:')
    if cmd=='q':
        break
    sk.sendto(cmd.encode('utf-8'),addr)
    msg,addr=sk.recvfrom(10240)#注意这里因为当使用ipconfig的时候过长,使用1024就会报错,所以使用10240
    print(msg.decode('utf-8'))
sk.close()

clent端口

import socket
import subprocess
sk=socket.socket(type=socket.SOCK_DGRAM)
addr=('127.0.0.1',8080)
sk.sendto('Hello'.encode('utf-8'),addr)
while 1:
    cmd,addr=sk.recvfrom(10240)#注意这里因为当使用ipconfig的时候过长,使用1024就会报错,所以使用10240
    ret=subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    stdout='stdout:'+(ret.stdout.read()).decode('gbk')#注意这里(stdout.read())本身是一个gbk形式的字节码,那么后面加上decode(gbk)以后就能正常显示了
    stderr='stderr:'+(ret.stderr.read()).decode('gbk')
    print(stdout)
    print(stderr)
    sk.sendto(stdout.encode('utf-8'),addr)#利用encode('utf-8')就能编码成byts类型
    sk.sendto(stderr.encode('utf-8'),addr)
sk.close()

自己运行代码后发现了在udp形式下也有粘包现象,但是老师说没有,不理解。

老师的结论:

udp不会粘包,但是udp但是会丢包,
tcp会粘包,但是不会丢包

黏包成因

TCP协议中的数据传递

tcp协议的拆包机制

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 
MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。

面向流的通信特点和Nagle算法

复制代码
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。 
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。 
可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
复制代码

 例子:

sever端口

import socket
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()

conn,addr=sk.accept()
ret=conn.recv(1024)
print(ret)

conn.close()
sk.close()

client端口

import socket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))

sk.send(b'hello')
sk.send(b'egg')
sk.close()

执行结果如下:

D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/sever.py
b'helloegg'

Process finished with exit code 0

通过执行结果可见,client端虽然发送了两条信息,但是sever端实际上只接收到了一条信息。这是以为client端发送的两条信息都比较小,Nagle算法就将这两条小信息合并起来一起发送了。当然接受到的数据的长度受sever端口中ret=conn.recv(1024)中的数字控制,这里是1024字节,可以将client端口中发送的两条信息合并起来一起接受,如果只有8字节,那么就不能将两条信息合并起来了。即就是小数据包会被合并。

多个send小数据包连在一起,会发生粘包现象,这是tcp协议内部的优化算法造成的。

再继续深化

sever端口

import socket
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()

conn,addr=sk.accept()
ret1=conn.recv(1024)
ret2=conn.recv(1024)
print(ret1,ret2)

conn.close()
sk.close()

client端口

import socket
import time
sk=socket.socket()
sk.connect(('127.0.0.1',8080))

sk.send(b'hello')
time.sleep(0.2)
sk.send(b'egg')
sk.close()

可以看出在client端口中的两个send之间加上了时间延迟后,sever端接收到的消息就是分开的,而不是合并起来的。

基于tcp协议特点的黏包现象成因 

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据。
也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。
怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

 

粘包问题出现的原因:当socket服务器端将要发送的内容首先是传输到本应用的操作系统,然后本应用的操作系统将数据传输给对方应用的操作系统,对方应用的操作系统再将内容传输给本应用。

以迅雷看看下载电影为例:使用的是TCP协议传输

首先电影网站的服务器将电影的数据传输到其自身的操作系统,服务器操作系统将数据传输给本电脑的操作系统,本电脑的操作系统将数据给迅雷看看服务器 。但是电影文件很大,本电脑操作系统接收到数据以后首先存在操作系统缓存中,慢慢得将数据传输给迅雷看看,这样在实际中看到就是迅雷看看中的进度条上有一部分有内容,有一部分没有内容。这就是粘包的具体体现。但是不会丢失数据,也就是说会完整下载完所有的电影,不会只是下载了一部分。

 

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

UDP不会发生黏包

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。 
不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。 
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。 
不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

参考:http://www.cnblogs.com/Eva-J/articles/8244551.html#_label5

总结:

几个重要的协议:tcp,udp,ip,arp
应用层协议:http(https)网页、ftp文件传输、smtp邮件相关的协议
https:与http协议相比,需要使用加密证书,所以需要花钱,可以输入密码等,比较安全,一般大公司网站会使用。
http协议不需要购买加密证书,不需要花钱。
粘包问题的本质:不知道服务器端发送过来数据的长度。

posted @ 2019-03-29 00:20  舒畅123  阅读(129)  评论(0编辑  收藏  举报