粘包现象:html
TCP属于长链接,当服务端与一个客户端进行了链接之后,其余客户端须要(排队)等待.若服务端想要链接另外一个客户端,必须首先断开与第一个客户端的链接。node
缓冲区:它是内存空间的一部分。也就是说,在内存空间中预留了必定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫作缓冲区,显然缓冲区是具备必定大小的。缓冲区根据其对应的是输入设备仍是输出设备,分为输入缓冲区和输出缓冲区python
为何引入缓冲区:高速设备与低速设备的不匹配,势必会让高速设备花时间等待低速设备,咱们能够在这二者之间设立一个缓冲区,也就是一个台阶,怕低速的跟不上程序员
缓冲区(buffer)的做用:web
区别于缓存区(cache):CPU的Cache,它中文名称是高速缓冲存储器,读写速度很快,几乎与CPU同样。因为CPU的运算速度太快,内存的数据存取速度没法跟上CPU的速度,因此在cpu与内存间设置了cache为cpu的数据快取区。当计算机执行程序时,数据与地址管理部件会预测可能要用到的数据和指令,并将这些数据和指令预先从内存中读出送到Cache。一旦须要时,先检查Cache,如有就从Cache中读取,若无再访问内存,如今的CPU还有一级cache,二级cache。算法
总结:也就是说缓冲区是内存中的对应的输入输出,而缓存区是cpu中的。shell
每一个socket(套接字)被建立后,都会分配两个缓冲区: 输入缓冲区和输出缓冲区。json
在windows复制路径时,从后往前复制路径会自动添加看不见的符号,从前日后就不会有问题windows
一、write()/send()并不当即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器. 一旦将数据写入到缓冲区,函数就已经完成任务能够成功返回了,而不用去考虑数据什么时候被发送到网络,也不用去考虑数据是否已经到达目标机器,由于这些后续操做都是TCP协议负责的事情.
二、TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,屡次写入的数据被一次性发送到网络,这取决于当时的网络状况,当前线程是否空闲等诸多因素,不禁程序员控制.
三、read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取
四、这些I/O缓冲区特性可整理以下:
1). I/O缓冲区在每一个TCP套接字中单独存在
2). I/O缓冲区在建立套接字时自动生成
3). 即便关闭套接字也会继续传送输出缓冲区中遗留的数据
4). 关闭套接字将丢失输入缓冲区中的数据
5). 输入/输出缓冲区的默认大小通常是8K(了解:能够经过getsockopt()函数获取)
粘包现象的缘由:(UDP不存在粘包)缓存
粘包现象的模拟:
发送方连续发送较小的数据,而且每次发送之间的时间间隔很短,此时,两个消息在输出缓冲区黏在一块儿了.缘由是TCP为了传输效率,作了一个优化算法(Nagle),减小连续的小包发送(由于每一个消息被包裹之后,都会有两个过程:组包和拆包,这两个过程是极其消耗时间的,优化算法Magle的目的就是为了减小传输时间)。
#服务端
import socket
server = socket.socket()
ip_port = ("192.168.15.28", 8001)
server.bind(ip_port)
server.listen()
conn, addr = server.accept()
# 连续接收两次消息
from_client_msg1 = conn.recv(1024).decode("utf-8")
print("第一次接收到的消息>>>", from_client_msg1)
from_client_msg2 = conn.recv(1024).decode("utf-8")
print("第二次接收到的消息>>>", from_client_msg2)
conn.close()
server.close()
#客户端
import socket
client = socket.socket()
server_ip_port = ("192.168.15.28", 8001)
client.connect(server_ip_port)
# 连续发送两次消息
client.send('Hello'.encode('utf-8'))
client.send('World'.encode('utf-8'))
client.close()
#结果
#第一次接收到的消息>>> HelloWorld
#第二次接收到的消息>>>
方案1、粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,因此解决粘包的方法就是围绕"如何让发送端在发送数据前,把本身将要发送的字节流总长度让接收端知晓"
xxxxxxxxxx
#解决步骤:
#a. 发送端把"数据长度"传输给接收端
#b. 接收端把"确认信息"传输给发送端
#c. 发送端把"所有数据"传输给接收端
#d. 接收端使用一个死循环接收完全部数据.
#服务端代码
import socket
import subprocess
server = socket.socket()
ip_port = ('192.168.15.28',8001)
server.bind(ip_port)
server.listen()
conn,addr = server.accept()
while 1:
from_client_cmd = conn.recv(1024).decode('utf-8') # a.接收来自客户端的cmd指令
sub_obj = subprocess.Popen(
from_client_cmd, # 客户端的指令
shell=True, # 使用shell,就至关于使用cmd窗口
stdout=subprocess.PIPE, # 标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到
stderr=subprocess.PIPE,
)
server_cmd_msg = sub_obj.stdout.read() # b.拿到cmd指令返回值 --> stdout接受到的返回值是bytes类型的,而且windows系统的默认编码为gbk
cmd_msg_len = str(len(server_cmd_msg)) # c.拿到返回值的长度
print("cmd返回的正确信息的长度>>>",cmd_msg_len)
conn.send(cmd_msg_len.encode('gbk')) # c.把"长度"传输给客户端
from_client_ack = conn.recv(1024).decode('utf-8') # d.拿到"确认信息"
if from_client_ack == "确认":
conn.send(server_cmd_msg) # e.把"cmd指令返回值"传输给客户端
else:
continue
#客户端
import socket
client = socket.socket()
server_ip_port = ('192.168.15.28',8001)
client.connect(server_ip_port)
while 1:
cmd = input('请输入要执行的指令>>>') # a.用户输入cmd指令
client.send(cmd.encode('utf-8')) # b.把"cmd指令"传输给服务端
from_server_msglen = int(client.recv(1024).decode('gbk')) # c.接收cmd指令返回值的"字节流长度"
print('接收到的信息长度是>>>', from_server_msglen)
client.send('确认'.encode('utf-8')) # d.把"确认信息"传输给服务端
from_server_stdout = client.recv(from_server_msglen).decode('gbk') # e.设置最大可接收数据量,同时接收"cmd指令返回值"
print('接收到的指令返回值是>>>', from_server_stdout)
方案2、经过struct模块将数据实体(要传输的数据)的长度打包成一个"4bytes字符串",并将其传输给接收端.接收端取出这个"4bytes字符串",对其进行解包,解包后的内容就是"数据实体的长度",接收端再经过这个长度来继续接收数据实体.(注意粘包的两个send要写一块儿,不然缓存区会溢出,关闭链接)
xxxxxxxxxx
#struct模块中最重要的两个函数是:
#pack() -- 具备"打包"功能,struct.pack(format, values) 将value打包成bytes的4个
#unpack() -- 具备"解包"功能 struct.unpack(format, bytes) 经过bytes反解处具体的value
#解决流程
#a.拿到数据实体的长度
#b.将长度打包成"4bytes字符串"
#c.将"4bytes字符串"发送给客户端
#d.发送数据实体
#服务端
import socket
import subprocess
import struct
server = socket.socket()
ip_port = ("127.0.0.1", 8001)
server.bind(ip_port)
server.listen()
conn, addr = server.accept()
while 1:
from_client_cmd = conn.recv(1024).decode("utf-8")
print("来自客户端的指令是>>>")
# 经过subprocess模块拿到指令的返回值
sub_obj = subprocess.Popen(
from_client_cmd, # 客户端的指令
shell=True,
stdout=subprocess.PIPE, # 标准输出:接收正确指令的执行结果
stderr=subprocess.PIPE, # 标准错误输出:接收错误指令的执行结果
)
# 经过stdout拿到正确指令的执行结果,即须要发送的"数据实体"
server_cmd_msg = sub_obj.stdout.read()
# a.拿到数据实体的长度
cmd_msg_len = len(server_cmd_msg)
# b.将长度打包成"4bytes字符串"
msg_len_stru = struct.pack('i',cmd_msg_len)
# c.将"4bytes字符串"发送给客户端
conn.send(msg_len_stru)
# d.发送数据实体 --> sendall() 循环发送数据,直到数据所有发送成功
conn.sendall(server_cmd_msg)
#客户端
import socket
import struct
client = socket.socket()
server_ip_port = ("127.0.0.1", 8001)
client.connect(server_ip_port)
while 1:
cmd = input("请输入要执行的指令>>>")
client.send(cmd.encode("utf-8"))
# a.接收打包后的"4bytes字符串"
from_server_msglen = client.recv(4)
# b.解包,拿到"数据实体的长度",即unpack_msglen
unpack_msglen = struct.unpack('i', from_server_msglen)[0]
# c.循环接收数据实体,经过"数据实体的长度"来肯定跳出循环的条件
recv_msg_len = 0 # 统计"数据长度"
all_msg = b'' # 统计"数据实体"
while recv_msg_len < unpack_msglen:
every_recv_data = client.recv(1024)
# 将每次接收到的"数据实体"进行拼接
all_msg += every_recv_data
# 将每次接收到的"数据实体的长度"进行累加
recv_msg_len += len(every_recv_data)
print(all_msg.decode("gbk"))
一、在每次文件传送中,若是每次发送的数据长度是大于1460bytes的就会出现最后的数据总量缺乏一部分,缘由是涉及到网络带宽,通常是1500bytes,可是因为系统一些报头文件也须要一些,因此只有1460bytes能够,解决方法是每次文件只减小len(content)
大文件的传输
xxxxxxxxxx
#服务端
import json
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
conn,addr = sk.accept()
len_bytes = conn.recv(4)
num = struct.unpack('i',len_bytes)[0]
str_dic = conn.recv(num).decode('utf-8')
dic = json.loads(str_dic)
with open(dic['filename'],'wb') as f:
while dic['filesize']:
content = conn.recv(2048)
f.write(content)
dic['filesize'] -= len(content)
#客户端
import os
import json
import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
file_path = input('>>>')
filename = os.path.basename(file_path)
filesize = os.path.getsize(file_path)
dic = {'filename':filename,'filesize':filesize}
bytes_dic = json.dumps(dic).encode('utf-8')
len_bytes = struct.pack('i',len(bytes_dic))
sk.send(len_bytes)
sk.send(bytes_dic)
with open(file_path,'rb') as f:
while filesize > 2048:
content = f.read(2048)
sk.send(content)
filesize -= 2048
else:
content = f.read()
sk.send(content)
sk.close()
文件的上传加认证
xxxxxxxxxx
#服务端
import json
import socket
import struct
import hashlib
def get_md5(usr,pwd):
md5 = hashlib.md5(usr.encode('utf-8'))
md5.update(pwd.encode('utf-8'))
return md5.hexdigest()
def login(conn):
msg = conn.recv(1024).decode('utf-8')
dic = json.loads(msg)
with open('userinfo', encoding='utf-8') as f:
for line in f:
username, password = line.strip().split('|')
if username == dic['user'] and password == get_md5(dic['user'], dic['passwd']):
res = json.dumps({'flag': True}).encode('utf-8')
conn.send(res)
return True
else:
res = json.dumps({'flag': False}).encode('utf-8')
conn.send(res)
return False
def upload(conn):
len_bytes = conn.recv(4)
num = struct.unpack('i', len_bytes)[0]
str_dic = conn.recv(num).decode('utf-8')
dic = json.loads(str_dic)
with open(dic['filename'], 'wb') as f:
while dic['filesize']:
content = conn.recv(2048)
f.write(content)
dic['filesize'] -= len(content)
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
try:
conn,addr = sk.accept()
ret = login(conn)
if ret:
upload(conn)
except Exception as e:
print(e)
finally:
conn.close()
sk.close()
#客户端
import os
import json
import socket
import struct
def upload(sk):
# 上传文件
file_path = input('>>>')
filename = os.path.basename(file_path)
filesize = os.path.getsize(file_path)
dic = {'filename': filename, 'filesize': filesize}
bytes_dic = json.dumps(dic).encode('utf-8')
len_bytes = struct.pack('i', len(bytes_dic))
sk.send(len_bytes)
sk.send(bytes_dic)
with open(file_path, 'rb') as f:
while filesize > 2048:
content = f.read(2048)
sk.send(content)
filesize -= 2048
else:
content = f.read()
sk.send(content)
usr = input('username :')
pwd = input('password :')
dic = {'operate':'login','user':usr,'passwd':pwd}
bytes_dic = json.dumps(dic).encode('utf-8')
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
sk.send(bytes_dic)
res = sk.recv(1024).decode('utf-8')
dic = json.loads(res)
if dic['flag']:
print('登陆成功')
upload(sk)
else:
print('登陆失败')
sk.close()
验证客户端连接合法性:当别人知道我ip,而且经过端口扫描知道个人相应的端口时,岂不是很危险,就必须进行验证,经过验证成功才能进来。经过 hmac模块加盐/也可使用hashlib,不过比较麻烦。
xxxxxxxxxx
#服务端
#其中os.urandom(n) 是一种bytes类型的随机生成n个字节字符串的方法,并且每次生成的值都不相同。再加上md5等加密的处理,就可以成内容不一样长度相同的字符串了。
from socket import *
import hmac,os
secret_key=b'Jedan has a big key!' #密钥
def conn_auth(conn):
print('开始验证新连接的合法性')
msg=os.urandom(32)#生成一个32字节的随机字符串
conn.sendall(msg)
h=hmac.new(secret_key,msg)
digest=h.digest()
respone=conn.recv(len(digest))
return hmac.compare_digest(respone,digest)
def data_handler(conn,bufsize=1024):
if not conn_auth(conn):
print('该连接不合法,关闭')
conn.close()
return
print('连接合法,开始通讯')
while True:
data=conn.recv(bufsize)
if not data:break
conn.sendall(data.upper())
def server_handler(ip_port,bufsize,backlog=5):
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(backlog)
while True:
conn,addr=tcp_socket_server.accept()
print('新链接[%s:%s]' %(addr[0],addr[1]))
data_handler(conn,bufsize)
if __name__ == '__main__':
ip_port=('127.0.0.1',9999)
bufsize=1024
server_handler(ip_port,bufsize)
#客服端
from socket import *
import hmac,os
secret_key=b'Jedan has a big key!'
def conn_auth(conn):
msg=conn.recv(32)
h=hmac.new(secret_key,msg)
digest=h.digest()
conn.sendall(digest)
def client_handler(ip_port,bufsize=1024):
tcp_socket_client=socket(AF_INET,SOCK_STREAM)
tcp_socket_client.connect(ip_port)
conn_auth(tcp_socket_client)
while True:
data=input('>>: ').strip()
if not data:continue
if data == 'quit':break
tcp_socket_client.sendall(data.encode('utf-8'))
respone=tcp_socket_client.recv(bufsize)
print(respone.decode('utf-8'))
tcp_socket_client.close()
if __name__ == '__main__':
ip_port=('127.0.0.1',9999)
bufsize=1024
client_handler(ip_port,bufsize)