粘包现象
首先引入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协议不需要购买加密证书,不需要花钱。
粘包问题的本质:不知道服务器端发送过来数据的长度。