python 通信相关包_python学习之socket&黏包

7.4 socket

​【重要】

避免学习各层的接口,以及协议的使用, socket已经封装好了所有的接口,直接使用这些接口或者方法即可,方便快捷,提升开发效率。

socket在python中就是一个模块,通过使用学习模块提供的功能,建立两个进程之间的连接和通信(ip+port)。

流程图

​服务器端先初始化socket,然后绑定bind端口,对端口进行监听listen,调用accept夯住程序,等待客户端连接;客户端初始化socket,connect服务器,连接成功后,客户端向服务器端发送数据,服务器端接收后返回数据,客户端读取数据,请求关闭,一次交互结束。

socket模块

循环通信

# 服务器端

import socket

server = socket.socket() # 创建server服务端,可以不写,默认是socket.AF_INET,socket.SOCK_STREAM

server.bind(('127.0.0.1',8003)) # 绑定IP地址和端口

server.listen(5) # 设置最大连接数

print('listening')

conn,addr = server.accept() # 进入监听状态

while 1:

from_client_data = conn.recv(1024).decode('utf-8') # 设置最大字节数

if from_client_data.upper() == 'Q': #判断对方是否要求关闭连接

break

else:

print(f"来自{addr}的消息:\033[1;32m{from_client_data}\033[0m")

se = input('>>>').encode('utf-8')

conn.send(se)

conn.close() # 关闭连接

server.close() # 关闭服务端

# 客户端

import socket

client = socket.socket() # 可以不写,默认是socket.AF_INET,socket.SOCK_STREAM

client.connect(('127.0.0.1',8003)) # 与服务器建立连接,ip地址与端口号必须要与服务器端一致

while 1:

se = input('>>>')

if se.upper() == 'Q':

client.send('q'.encode('utf-8'))

break

client.send(se.encode('utf-8'))

from_server_data = client.recv(1024) # 设置最大字节数

print(f"来自服务器的消息:\033[1;32m {from_server_data.decode('utf-8')}\033[0m")

client.close()

服务器一直监听版【重要】

# 服务器端

import socket

server = socket.socket()

server.bind(('127.0.0.1',8003))

server.listen(5)

print('listening')

while 1:

conn,addr = server.accept()

while 1:

try :

from_client_data = conn.recv(1024).decode('utf-8')

if from_client_data.upper() == 'Q':

break

else:

print(f"来自{addr}的消息:\033[1;32m{from_client_data}\033[0m")

se = input('>>>').encode('utf-8')

conn.send(se)

except ConnectionResetError:

break

conn.close()

server.close()

# 客户端

import socket

client = socket.socket()

client.connect(('127.0.0.1',8003))

while 1:

se = input('>>>')

if se.upper() == 'Q':

client.send('q'.encode('utf-8'))

break

client.send(se.encode('utf-8'))

from_server_data = client.recv(1024).decode('utf-8')

print(f"来自服务器的消息:\033[1;32m{from_server_data}\033[0m")

client.close()

# 这里要注意多client同时访问server时,会按照顺序,断开一个连接后继续下一个连接

远程执行命令

subprocess应用,创建进程

import subprocess

obj = subprocess.Popen('dir',

shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

print(obj.stdout.read().decode('gbk')) # 正确命令

print('error:',obj.stderr.read().decode('gbk')) # 错误命令

远程使用

import socket

import subprocess

server = socket.socket()

server.bind(('127.0.0.1',8004))

server.listen(5)

print('listening')

while 1:

conn,addr = server.accept()

while 1:

rec = conn.recv(1024).decode('utf-8')

if rec.upper() == 'Q':

break

else:

obj = subprocess.Popen(rec,

shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

se = obj.stdout.read().decode('gbk')+obj.stderr.read().decode('gbk')

conn.send(se.encode('utf-8'))

conn.close()

server.closed()

import socket

client = socket.socket()

client.connect(('127.0.0.1',8004))

while 1:

se = input('请输入命令')

if se.upper() == 'Q':

client.send('q'.encode('utf-8'))

break

else:

client.send(se.encode('utf-8'))

re = client.recv(1024).decode('utf-8')

print(re)

client.close()

# 如果返回的数据过多,会有黏包现象

7.5 黏包

产生原因

缓冲区

如果没有缓冲区,收发数据会受到网络波动的影响,影响数据的上传和下载

虽然缓冲区解决了上传下载的传输的效率问题,但是黏包问题

产生条件

1.recv会产生黏包。如果recv接受的数据 < 缓冲区的数据,缓冲区中接受不完的数据会与后边recv的数据黏在一起,流式数据

2.连续send少量的数据,先到缓存区。由于Nagle的算法,发送端发送的数据如果比较小,会暂存在缓冲区,再加上应用层给TCP传送数据很快的话,就会把两个应用层数据包黏在一起,TCP最后只发一个TCP数据包给接收端

解决方案

struct模块

按照指定的格式把一个python类型转换成固定长度的bytes字节流

格式

C语言类型

Python类型

标准尺寸

x

填充字节

没有值

c

char

string of length 1

1

b

signed

integer

1

B

unsigned char

integer

1

?

_Bool

bool

1

h

short

integer

2

H

unsigned short

integer

2

i

int

integer

4

I

unsigned int

integer

4

l

long

integer

4

L

unsigned long

long

4

q

long long

long

8

Q

unsigned long long

long

8

n

ssize_t

intter

N

size_t

intter

f

float

float

4

d

double

float

8

s

char[]

string

p

char[]

string

P

void *

long

import struct

# 将一个数字转化成等长度的bytes类型。

ret = struct.pack('q', 154365400024634546545646546)

print(ret, type(ret), len(ret))

# 通过unpack反解回来

ret1 = struct.unpack('i',ret)[0]

print(ret1)

方案一:

​1.在第二次向对方发送数据之前,先把缓冲区的数据全部取出

​2.如何限制循环次数?当接收数据的总字节数数等于发送数据的总字节数时,停止循环;

​3.服务端先send要发送数据的总字节数;

​4.发送端制作报头,固定头部长度,使用struct模块,pack转换成等长度4个字节bytes类型,发送到接收端,接收端再使用unpack将字节数转换回来;

代码实现

服务器端

import socket

import subprocess

import struct

server = socket.socket()

server.bind(('127.0.0.1',8004))

server.listen(5)

print('listening')

# 接收连接

while 1:

conn,addr = server.accept()

while 1:

rec = conn.recv(1024).decode('utf-8')

obj = subprocess.Popen(rec,

shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

se = obj.stdout.read().decode('gbk')+obj.stderr.read().decode('gbk')

# 制作报头

total_size = len(se)

# 将长度不固定的int类型报头,转成固定长度bytes4个字节

# 将一个数字转换成等长度的bytes类型

total_size_bytes = struct.pack('i',total_size)

# 发送报头

conn.send(total_size_bytes)

# 发送原始数据

conn.send(se.encode('gbk'))

conn.close()

server.closed()

客户端

import struct

import socket

client = socket.socket()

client.connect(('127.0.0.1',8004))

# 发送消息

while 1:

se = input('请输入命令')

client.send(se.encode('utf-8'))

# 1-接收报头

head_bytes = client.recv(4)

# 2-将报头反解回int类型

total_size = struct.unpack('i',head_bytes)[0] #unpack返回的一个元组,里边只有一个反解的元素

# 3-循环接收数据

total_data = b'' #设定一个初始值

while len(total_data.decode('gbk')) < total_size:

total_data += client.recv(1024)

print(total_data.decode('gbk'))

client.close()

存在的问题:

​数据量较大的数据,使用struct时会报错;

​报头信息不可能只含有数据的大小;

方案二:

为了解决以上问题,我们引入方案二:

自定义一种传输报文:

将传送数据的信息汇集成字典,字典中记录了传输数据的MD5值,filename以及filesize,使用json模块把字典转换成bytes类型,将其加到传输数据流的字典头;

记录字典bytes的长度,使用struct模块转成4字节的固定长度,然后把它作为长度头加到数据流的最开始;

将 [长度头+字典头+传输数据] 一起发送给对方

对方接到数据流之后,首先取4个字节,求出字典头的长度;取字典头长度的数据,进行格式转换,得到字典;

循环接收数据,将得到的数据流与字典中的MD5进行校验,校验通过,转码显示数据,校验不通过,则不显示;

服务器端

while 1:

conn, addr = server.accept()

while 1:

try :

re = conn.recv(1024).decode('gbk')

obj = subprocess.Popen(re,

shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

result = obj.stdout.read() + obj.stderr.read() # 获得的是gbk转码后的数据

size = len(result) #求原数据的大小

# 做一下文件校验的MD5序列

ret = hashlib.md5()

ret.update(result)

md5 = ret.hexdigest()

# 制作报头

head_dict = {

'md5':md5,

'filename':re,

'filesize':size

}

# 将报头字典转换成json序列

head_dict_json = json.dumps(head_dict)

# 将json字符串转换成bytes

head_dict_json_bytes = head_dict_json.encode('gbk')

# 获取报头的长度

head_len = len(head_dict_json_bytes)

# 将head_len转成固定长度

head_len_bytes = struct.pack('i',head_len)

# 发送固定的4个字节

conn.send(head_len_bytes)

# 发送字典报头

conn.send(head_dict_json_bytes)

# 发送文件

conn.send(result)

except Exception:

break

conn.close()

server.close()

客户端

import socket

import struct

import json

import hashlib

client = socket.socket()

client.connect(('127.0.0.1',8005))

while 1:

se = input('>>>').strip().encode('gbk')

client.send(se)

# 接受4个字节,获得头部的字典长度

head_bytes = client.recv(4)

# 将字典长度转成int类型

head_dic_len = struct.unpack('i',head_bytes)[0]

# 接收字典的数据流

head_dic_bytes = client.recv(head_dic_len).decode('gbk')

# 转换成字典模式

head_dict = json.loads(head_dic_bytes)

# 定义一个接收主内容的句柄

file = b''

while len(file) < head_dict['filesize']:

file += client.recv(1024)

# 计算一下file的MD5

ret = hashlib.md5()

ret.update(file)

md_5 = ret.hexdigest()

if md_5 == head_dict['md5']:

print('文件校验成功')

print(file.decode('gbk'))

else:

print('文件校验未通过')

break

client.close()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值