12.粘包现象
客户端接收的信息指定了的字节,TCP协议没有丢失协议,只是只能接收指定的字节数,于是产生出了粘包现象
服务端接收命令只能接收1024字节,服务端执行命令结果以后传输给客户端,客户端再以1024个字节接收,但是如果结果超过1024个字节以后也不能再接收了,导致结果不可控了,没有接收的信息就会形成数据残留留到传输管道里,新的数据再发送的时候才会把老的数据发送过来,这样数据的传输会越来越不准确,这就是粘包的现象
粘包现象:多个包的数据粘到一起了,在管道里根本不区分数据,流式数据传输的特性,所有数据全部传到一起不做任何区分
服务端
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('starting')
while True:
conn, client_addr = phone.accept()
print(client_addr)
while True:
try:
# 收到命令
cmd = conn.recv(1024)
if not cmd:break
print('客户端接收的数据', cmd)
# 执行收到命令,拿到结果
f = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout = f.stdout.read()
stderr = f.stderr.read()
conn.send(stdout+stderr)
# 把命令结果返回
except ConnectionRefusedError:
break
conn.close()
phone.close()
客户端
import socket
 
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
# 发命令
cmd = input('>>>').strip()
if not cmd: continue
phone.send(cmd.encode('utf-8'))
# 拿到命令结果并打印
data = phone.recv(1024)
print(data.decode('gbk'))
phone.close()

13.粘包底层原理分析
运行一个程序(软件)和什么硬件有关?
硬盘>>内存>>Cpu
send和recv的实现原理
站在客户端的角度讲,执行一次send的操作是应用程序的代码,想要将自己的数据发出去,但是应用程序不能执行直接发送,需要给自己的操作系统转交发送,但是客户端的内存和服务端的内存是相互隔离的,操作系统遵循tcp协议之后,通过接收到的数据发送,send只是拷贝给自己的操作系统的内存,send对于应用程序来说不过是完成了给内存的传输
数据接收,recv实际分为两个过程,1.数据接收,放到服务端的缓存中 2.从缓存中调取数据,解析发给应用程序
粘包发生的原因:数据量比较小,而且发送时间短
粘包是tcp的底层的优化算法决定的,但是解决粘包的关键就是需要指定发送文件的长度就可以了
服务端(粘包)
import socket
 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
conn, addr = server.accept()
data = conn.recv(1024)
print(data)
data1 = conn.recv(1024)
print(data1)
客户端
import socket
 
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
client.send('hello'.encode('utf-8'))
client.send('world'.encode('utf-8'))

14.解决粘包问题-伪代码实现
解决粘包问题的就是告诉发送方,需要发送的数据大小
服务端
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('starting')
while True:
conn, client_addr = phone.accept()
print(client_addr)
while True:
try:
# 收到命令
cmd = conn.recv(1024)
if not cmd:break
print('客户端接收的数据', cmd)
# 执行收到命令,拿到结果
f = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout = f.stdout.read()
stderr = f.stderr.read()
# 1.把报头(固定长度)数据的长度发送给客户端
total_size = len(stdout+stderr)
conn.send(str(total_size).encode('gbk'))
# 2.再发送真实的数据
 
conn.send(stdout)
conn.send(stderr)
# 把命令结果返回
except ConnectionRefusedError:
break
conn.close()
phone.close()
客户端
import socket
 
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
# 发命令
cmd = input('>>>').strip()
if not cmd: continue
phone.send(cmd.encode('utf-8'))
# 第一步拿到数据长度
total_size = 1000000
# 第二步接收真实的数据
recv_size = 0
recv_data = b''
while recv_size<total_size:
res = phone.recv(1024)
recv_data += res
recv_size += len(res)
 
print(recv_data.decode('gbk'))
phone.close()

15.解决粘包问题-简单版本
使用struck模块打包数据报头
服务端
import socket
import subprocess
import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('starting')
while True:
conn, client_addr = phone.accept()
print(client_addr)
while True:
try:
# 收到命令
cmd = conn.recv(1024)
if not cmd:break
print('客户端接收的数据', cmd)
# 执行收到命令,拿到结果
f = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout = f.stdout.read()
stderr = f.stderr.read()
# 1.把报头(固定长度)数据的长度发送给客户端
 
total_size = len(stdout+stderr)
header = struct.pack('i', total_size)
conn.send(header)
conn.send(str(total_size).encode('gbk'))
# 2.再发送真实的数据
 
conn.send(stdout)
conn.send(stderr)
# 把命令结果返回
except ConnectionRefusedError:
break
conn.close()
phone.close()
客户端
import socket
import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
# 发命令
cmd = input('>>>').strip()
if not cmd: continue
phone.send(cmd.encode('utf-8'))
# 第一步拿到数据长度
total_size = struct.unpack('i', phone.recv(4))[0]
# 第二步接收真实的数据
recv_size = 0
recv_data = b''
while recv_size<total_size:
res = phone.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode('gbk'))
phone.close()

16.解决粘包问题-终极版本
基于struck的打包会超过它的打包的长度,struck的模块有两种类型,i模式和l模式,对于大文件都有可能不够使用
import struct
res = struct.pack('i',12879999777)
print(res,type(res),len(res))
res1 = struct.unpack('i',res)
print(res1)

客户端
import socket
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
# 发命令
cmd = input('>>>').strip()
if not cmd: continue
phone.send(cmd.encode('utf-8'))
# 第一步拿到数据长度
header_size = struct.unpack('i', phone.recv(4))[0]
# 接收报头内容
header_bytes = phone.recv(header_size)
# 从报头中解析真实数据的信息
header_json = header_bytes.decode('gbk')
header_dict = json.loads(header_json)
print(header_dict)
 
# 第二步接收真实的数据
total_size = header_dict['total_size']
recv_size = 0
recv_data = b''
while recv_size< total_size:
res = phone.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode('gbk'))
phone.close()
服务端
import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('starting')
while True:
conn, client_addr = phone.accept()
print(client_addr)
while True:
try:
# 收到命令
cmd = conn.recv(1024)
if not cmd:break
print('客户端接收的数据', cmd)
# 执行收到命令,拿到结果
f = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout = f.stdout.read()
stderr = f.stderr.read()
# 1.制作固定长度的报头
header_dic = {
'filename': 'a.txt',
'md5':'xxxxxxdxxx',
'total_size': len(stdout)+len(stderr)
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('gbk')
# 发送报头的长度
conn.send(struct.pack('i',len(header_bytes)))
# 再发报头
conn.send(header_bytes)
# 2.再发送真实的数据
total_size = len(stdout + stderr)
header = struct.pack('i', total_size)
conn.send(header)
conn.send(str(total_size).encode('gbk'))
conn.send(stdout)
conn.send(stderr)
# 把命令结果返回
except ConnectionRefusedError:
break
conn.close()
phone.close()

17.文件传输功能实现
什么是文件上传下载功能呢?如何实现呢?
首先是客户端提交命令,指定下载的文件名,是软件级别自己的定义的命令

18.文件传输功能-函数版
客户端
import socket
import struct
import json
 
download_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/client/download'
 
def get(phone,cmds):
# 2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件
# 第一步:先收报头的长度
obj = phone.recv(4)
header_size = struct.unpack('i', obj)[0]
 
# 第二步:再收报头
header_bytes = phone.recv(header_size)
 
# 第三步:从报头中解析出对真实数据的描述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
'''
header_dic={
'filename': filename, #'filename':'1.mp4'
'md5':'xxdxxx',
'file_size': os.path.getsize(filename)
}
'''
print(header_dic)
total_size = header_dic['file_size']
filename = header_dic['filename']
 
# 第四步:接收真实的数据
with open('%s/%s' % (download_dir, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = phone.recv(1024) # 1024是一个坑
f.write(line)
recv_size += len(line)
print('总大小:%s 已下载大小:%s' % (total_size, recv_size))
 
def put(phone,cmds):
pass
 
def run():
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 
phone.connect(('127.0.0.1',8912))
 
while True:
#1、发命令
inp=input('>>: ').strip() #get a.txt
if not inp:continue
phone.send(inp.encode('utf-8'))
 
cmds=inp.split() #['get','a.txt']
if cmds[0] == 'get':
get(phone,cmds)
elif cmds[0] == 'put':
put(phone,cmds)
 
phone.close()
 
 
 
if __name__ == '__main__':
run()
服务端
import socket
import subprocess
import struct
import json
import os
 
share_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/server/share'
 
def get(conn,cmds):
filename = cmds[1]
 
# 3、以读的方式打开文件,读取文件内容发送给客户端
# 第一步:制作固定长度的报头
header_dic = {
'filename': filename, # 'filename':'1.mp4'
'md5': 'xxdxxx',
'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
# os.path.getsize(r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/server/share/1.mp4')
}
 
header_json = json.dumps(header_dic)
 
header_bytes = header_json.encode('utf-8')
 
# 第二步:先发送报头的长度
conn.send(struct.pack('i', len(header_bytes)))
 
# 第三步:再发报头
conn.send(header_bytes)
 
# 第四步:再发送真实的数据
with open('%s/%s' % (share_dir, filename), 'rb') as f:
# conn.send(f.read())
for line in f:
conn.send(line)
 
def put(conn,cmds):
pass
 
def run():
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8912)) #0-65535:0-1024给操作系统使用
phone.listen(5)
 
print('starting...')
while True: # 链接循环
conn,client_addr=phone.accept()
print(client_addr)
 
while True: #通信循环
try:
#1、收命令
res=conn.recv(8096) # b'put 1.mp4'
if not res:break #适用于linux操作系统
 
#2、解析命令,提取相应命令参数
cmds=res.decode('utf-8').split() #['put','1.mp4']
if cmds[0] == 'get':
get(conn,cmds)
elif cmds[0] == 'put':
input(conn,cmds)
 
 
except ConnectionResetError: #适用于windows操作系统
break
conn.close()
 
phone.close()
 
 
if __name__ == '__main__':
run()

19.文件传输功能-面向对象版
 

20.基于udp协议的套接字介绍
UDP协议不会粘包
客户端
from socket import *
 
client = socket(AF_INET, SOCK_DGRAM)
 
 
while True:
msg=input('>>: ').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
 
data,server_addr=client.recvfrom(1024)
print(data,server_addr)
client.close()
服务端
from socket import *
 
server=socket(AF_INET,SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
 
 
while True:
data,client_addr=server.recvfrom(1024)
print(data)
 
server.sendto(data.upper(),client_addr)
 
server.close()
 

路飞学城Python-Day24的更多相关文章

  1. 路飞学城—Python爬虫实战密训班 第三章

    路飞学城—Python爬虫实战密训班 第三章 一.scrapy-redis插件实现简单分布式爬虫 scrapy-redis插件用于将scrapy和redis结合实现简单分布式爬虫: - 定义调度器 - ...

  2. 路飞学城—Python爬虫实战密训班 第二章

    路飞学城—Python爬虫实战密训班 第二章 一.Selenium基础 Selenium是一个第三方模块,可以完全模拟用户在浏览器上操作(相当于在浏览器上点点点). 1.安装 - pip instal ...

  3. 路飞学城Python爬虫课第一章笔记

    前言 原创文章,转载引用务必注明链接.水平有限,如有疏漏,欢迎指正. 之前看阮一峰的博客文章,介绍到路飞学城爬虫课程限免,看了眼内容还不错,就兴冲冲报了名,99块钱满足以下条件会返还并送书送视频. 缴 ...

  4. 路飞学城-Python开发集训-第3章

    学习心得: 通过这一章的作业,使我对正则表达式的使用直接提升了一个level,虽然作业完成的不怎么样,重复代码有点多,但是收获还是非常大的,有点找到写代码的感觉了,遗憾的是,这次作业交过,这次集训就结 ...

  5. 路飞学城-Python开发集训-第1章

    学习体会: 在参加这次集训之前我自己学过一段时间的Python,看过老男孩的免费视频,自我感觉还行,老师写的代码基本上都能看懂,但是实际呢?....今天是集训第一次交作业的时间,突然发现看似简单升级需 ...

  6. 路飞学城-Python开发集训-第4章

    学习心得: 学习笔记: 在python中一个py文件就是一个模块 模块好处: 1.提高可维护性 2.可重用 3.避免函数名和变量名冲突 模块分为三种: 1.内置标准模块(标准库),查看所有自带和第三方 ...

  7. 路飞学城-Python开发集训-第2章

    学习心得: 这章对编码的讲解超级赞,现在对于编码终于有一点认知了,但还没有大彻大悟,还需要更加细心的琢磨一下Alex博客和视频,以前真的是被编码折磨死了,因为编码的问题而浪费的时间很多很多,现在终于感 ...

  8. 路飞学城-Python开发-第二章

    ''' 数据结构: menu = { '北京':{ '海淀':{ '五道口':{ 'soho':{}, '网易':{}, 'google':{} }, '中关村':{ '爱奇艺':{}, '汽车之家' ...

  9. 路飞学城-Python开发-第三章

    # 数据结构: # goods = [ # {"name": "电脑", "price": 1999}, # {"name&quo ...

  10. 路飞学城-Python开发-第一章

    # 基础需求: # 让用户输入用户名密码 # 认证成功后显示欢迎信息 # 输错三次后退出程序 username = 'pandaboy' password = ' def Login(username ...

随机推荐

  1. 洛谷P2296 寻找道路_简单BFS

    Code: #include<cstdio> #include<queue> #include<algorithm> using namespace std; co ...

  2. css+div 绘制多边形

    /*1.正方形*/ <div id="square"></div> #square { width: 100px; height: 100px; backg ...

  3. [tyvj-1071]LCIS 动态规划

    LCIS模板 #include <cstdio> #include <cstring> #include <iostream> using namespace st ...

  4. JAVA基础知识复习小结

    集合 Set集合 Set集合的基本特征是元素不允许重复.HashSet不保存元素顺序,LinkedHashSet用链表保持元素的插入顺序,TreeSet可定制排序规则. HashSet的底层是用Has ...

  5. 对Java线程安全与不安全的理解

    当我们查看JDK API的时候,总会发现一些类说明写着,线程安全或者线程不安全,比如说到StringBuilder中,有这么一句,"将StringBuilder 的实例用于多个线程是不安全的 ...

  6. [BZOJ 3884][欧拉定理]上帝与集合的正确使用方法

    看看我们机房某畸形写的题解:http://blog.csdn.net/sinat_27410769/article/details/46754209 此题为popoQQQ神犇所出,在此orz #inc ...

  7. 杭电1018-Big Number(大数)

    Big Number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total ...

  8. C++实现位数组

    当我们遇到大量整数排序时候为了节省内存空间我们能够考虑使用bit数组来实现,缺点是其仅仅适用于正整数. 思想: 在32位系统一个int类型占4个字节,按位来计算一个int类型能够记录32个数.因此,採 ...

  9. HDU 5654 xiaoxin and his watermelon candy 离线树状数组

    xiaoxin and his watermelon candy Problem Description During his six grade summer vacation, xiaoxin g ...

  10. 【为小白菜打call】

    作为本校的竞赛生,我必须为我大OJ打call caioj,小白菜oj,顾名思义,就是为刚踏进OI的“小白菜”们准备的网站,里面包含了许多专题内容,各种模版和讲解视频 而且对于刚学习C++的同学,更有帮 ...