[ python ] 网络编程(2)
黏包问题
这样一个实例
import socket
import subprocess sk_server = socket.socket() # 创建 socket对象
sk_server.bind(('localhost', 8080)) # 建立socket
sk_server.listen(5) # 开启监听
conn, addr = sk_server.accept() # 接收客户端信息
while True:
command = conn.recv(1024).decode()
cmd_res = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) # 执行命令
stdout = cmd_res.stdout.read()
stderr = cmd_res.stderr.read()
result = stdout if stdout else stderr
print('result:', result)
conn.sendall(result) # 发送命令结果
server.py
import socket sk_client = socket.socket()
sk_client.connect(('localhost', 8080))
while True:
cmd = input('>>>').strip()
if not cmd: continue
sk_client.sendall(cmd.encode())
result = sk_client.recv(1024).decode('gbk')
print(result)
client.py
运行起来,我们在客户端输入 tasklist (windows查看所有进程),然后在输入 dir(查看当前目录信息)
执行完 tasklist 后,再次执行 dir 时,发现输出结果是 tasklist 未显示出来的部分。这种情况,就称之为 黏包。
【注意:只有TCP有粘包现象,UDP永远不会粘包】
黏包成因
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时才会清除缓冲区内容。数据是可靠的,但是会粘包。
udp和tcp一次发送数据长度的限制 用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送) 用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
补充说明
会发生黏包的两种情况:
1. 发送方的缓存机制:发送端需要等待缓冲区满才发送出去,造成黏包
2. 接收方的缓存机制:接收方不能及时接收缓冲区的包,造成多个包接收
总结:
黏包现象只发生在tcp协议中:
1. 从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信特点;
2. 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
黏包的解决方案:
黏包的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决黏包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知道,然后接收端来一个死循环收完所有数据。
解决方案一:
import socket
import subprocess sk_server = socket.socket()
sk_server.bind(('localhost', 8080))
sk_server.listen(5)
conn, addr = sk_server.accept() while True:
command = conn.recv(1024).decode()
cmd_res = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout = cmd_res.stdout.read()
stderr = cmd_res.stderr.read()
result = stdout if stdout else stderr
res_size = len(result)
conn.sendall(str(res_size).encode())
response = conn.recv(1024)
conn.sendall(result)
server.py
import socket sk_client = socket.socket()
sk_client.connect(('localhost', 8080)) while True:
command = input('>>>').strip()
if not command: continue
sk_client.sendall(command.encode())
res_size = sk_client.recv(1024).decode()
sk_client.sendall(b'')
revice_size = 0
while revice_size != int(res_size):
data = sk_client.recv(1024)
revice_size += len(data)
print(data.decode('gbk'))
client.py
使用这种方式存在的问题:
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用 send 去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
解决方案二:
使用 struct模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接收的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
struct 模块
该模块可以把一个类型,如数字,转成固定长度的bytes
In [1]: import struct In [2]: s = struct.pack('i', 111111) # 使用 pack 方法将int类型转换为固定的 4 个字节 In [3]: s
Out[3]: b'\x07\xb2\x01\x00' In [4]: len(s) # 固定的 4 个字节
Out[4]: 4 In [5]: struct.unpack('i', s) # 使用 unpack 方法将 4 个字节还原为字符,类型为元组
Out[5]: (111111,)
使用 struct 解决黏包
借助 struct 模块,我们知道长度数字可以被转换成一个标准大小的 4 个字节数字。因此可以利用这个特点来预先发送数据长度。
import socket, struct
import subprocess sk_server = socket.socket()
sk_server.bind(('localhost', 8080))
sk_server.listen(5)
conn, addr = sk_server.accept()
while True:
command = conn.recv(1024).decode()
res_cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout = res_cmd.stdout.read()
stderr = res_cmd.stderr.read()
result = stdout if stdout else stderr
res_size = len(result)
conn.sendall(struct.pack('i', res_size))
conn.sendall(result)
server.py
import socket
import struct sk_client = socket.socket()
sk_client.connect(('localhost', 8080)) while True:
command = input('>>>').strip()
if not command: continue
sk_client.sendall(command.encode())
res = sk_client.recv(4)
res_size = struct.unpack('i', res)[0]
print(res_size)
revice_size = 0
while revice_size != res_size:
data = sk_client.recv(1024)
revice_size += len(data)
print(data.decode('gbk'))
client.py
这里还可以将报头做成字典字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用 struct 将序列化后的数据长度打包成4个字节.
这种方式用于需要初始化较多的信息
import socket, struct, json
import subprocess sk_server = socket.socket()
sk_server.bind(('localhost', 8080))
sk_server.listen(5) conn, addr = sk_server.accept()
while True:
command = conn.recv(1024).decode()
cmd_res = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout = cmd_res.stdout.read()
stderr = cmd_res.stderr.read()
result = stdout if stdout else stderr
headers = {'res_size': len(result)} # 将head信息组合成 字典类型
head_json = json.dumps(headers) # 转换为 json 类型
head_json_bytes = bytes(head_json, encoding='utf-8')
conn.send(struct.pack('i', len(head_json_bytes))) # 首先发送 head 信息的大小
conn.send(head_json_bytes) # 再次发送 head 信息
conn.send(result) # 最后 发送 执行命令的结果集合
server.py
import socket, struct, json sk_client = socket.socket()
sk_client.connect(('localhost', 8080))
while True:
cmd = input('>>>').strip()
if not cmd: continue
sk_client.send(cmd.encode())
res_size = struct.unpack('i', sk_client.recv(1024))[0] # 首先获取 head 大小
head_json = sk_client.recv(res_size).decode() # 通过 head 大小获取 head 信息
head_dict = json.loads(head_json) # 转为 字典 类型
data_len = head_dict['res_size'] # 取出 结果集 大小
revice_size = 0
while revice_size != data_len: # 循环接收 结果集
data = sk_client.recv(1024)
revice_size += len(data)
print(data.decode('gbk'))
client.py
FTP小作业:上传下载文件
import socket
import json
import struct
import os class MyServer: request_queue_size = 5 def __init__(self, ip_port, bind_activate=True):
self.socket = socket.socket()
self.ip_port = ip_port
if bind_activate:
try:
self.activate()
except:
self.server_close()
raise def activate(self):
self.socket.bind(self.ip_port)
self.socket.listen(self.request_queue_size) def get_resquest(self):
return self.socket.accept() def server_close(self):
self.socket.close() def run(self):
while True:
self.conn, self.client_addr = self.get_resquest()
print('from client:', self.client_addr)
while True:
try:
head_struct = self.conn.recv(4)
head_len = struct.unpack('i', head_struct)[0]
head_json = self.conn.recv(head_len).decode()
head_dict = json.loads(head_json)
command = head_dict['command']
if hasattr(self, command):
func = getattr(self, command)
func(head_dict)
except Exception:
break def put(self, args):
filename = args['filename']
file_size = args['file_size']
file_path = os.path.join('file_upload', filename)
recv_size = 0
with open(file_path, 'wb') as f:
while recv_size != file_size:
recv_data = self.conn.recv(1024)
recv_size += len(recv_data)
f.write(recv_data) if __name__ == '__main__':
ftp_server = MyServer(('localhost', 8080))
ftp_server.run()
server.py
import socket
import json, struct
import os, sys class MyClient:
def __init__(self, ip_port, connect=True):
self.socket = socket.socket()
self.ip_port = ip_port
if connect:
try:
self.connect()
except:
self.client_close()
raise def connect(self):
self.socket.connect(self.ip_port) def client_close(self):
self.socket.close() def run(self):
while True:
cmd = input('>>>').strip()
if not cmd: continue
cmd_str = cmd.split()[0]
if hasattr(self, cmd_str):
func = getattr(self, cmd_str)
func(cmd) def put(self, command):
if len(command) > 1:
filename = command.split()[1]
if os.path.isfile(filename):
cmd_str = command.split()[0]
file_size = os.path.getsize(filename)
head_dict = {'command': cmd_str, 'filename': filename, 'file_size': file_size}
head_json = json.dumps(head_dict)
head_json_bytes = bytes(head_json, encoding='utf-8')
head_json_strcut = struct.pack('i', len(head_json_bytes))
print(head_json_strcut)
self.socket.send(head_json_strcut)
self.socket.send(head_json_bytes)
with open(filename, 'rb') as f:
while True:
data = f.read(1024)
send_size = f.tell()
if not data:
print('upload successful.')
break
self.socket.send(data)
self.__progress(send_size, file_size, '上传中')
else:
print('\033[31;1m文件不存在.\033[0m') else:
print('\033[31;1m命令格式错误.\033[0m') def __progress(self, trans_size, file_size, mode):
bar_length = 100
percent = float(trans_size) / float(file_size)
hashes = '=' * int(percent * bar_length)
spaces = ' ' * int(bar_length - len(hashes))
sys.stdout.write('\r%s %.2fM/%.2fM %d%% [%s]'
%(mode, trans_size/1048576, file_size/1048576, percent*100, hashes+spaces)) if __name__ == '__main__':
ftp_client = MyClient(('localhost', 8080))
ftp_client.run()
client.py
socketserver 模块
主要类型
该模块有4个比较主要的类,其中常用的是 TCPServer 和 UDPServer
- TCPServer
- UDPServer
- UnixStreamServer: 类似于TCPServer提供面向数据流的套接字连接,但是旨在UNIX平台上可用;
- UnixDatagramServer: 类似于UDPServer提供面向数据报的套接字连接,但是旨在UNIX平台上可用;
这四个类型同步地处理请求,也就是说一个请求没有完成之前是不会处理下一个请求的,这种模式当然不适合生产环境,一个客户端连接就可能拖延所有的执行。所以这个模块还提供了两种支持异步处理的类:
- ForkingMixIn: 为每一个客户端请求派生一个新的进程专门处理;
- ThreadingMixIn: 为每一个客户端请求派生一个新的线程专门处理;
继承自这两个类型的服务端在处理新的客户端连接时不会阻塞,而是创建新的进/线程专门处理客户端请求。
编程框架
首先从高层面介绍一下使用SocketServer模块开发多进程/线程 异步服务器的流程:
- 根据需要选择一个合适的服务类型,如,面向TCP连接的多进程服务器: ForkingTCPServer ;
- 创建一个请求处理器(request handler)类型,这个类型的 handle()(类似于回调函数)方法中定义如何处理到达的客户端连接。
- 实例化服务器,传入服务器绑定的地址和第2步定义的请求处理器类;
- 调用服务器实例的 handle_request() 或 serve_forever() 方法,一次或多次处理客户请求。
使用 socketserver 实例:
import socketserver class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
self.data = self.request.recv(1024).decode()
print('from client:', self.client_address)
print(self.data)
self.request.send(self.data.upper().encode()) if __name__ == '__main__':
HOST, PORT = 'localhost', 8080
server = socketserver.ThreadingTCPServer((HOST, PORT), MyServer)
server.serve_forever()
server.py
import socket class MyClient:
def __init__(self, ip_port, connect=True):
self.client = socket.socket()
self.ip_port = ip_port
if connect:
try:
self.connect()
except:
self.client_close()
raise def connect(self):
self.client.connect(self.ip_port) def client_close(self):
self.client.close() def start(self):
while True:
cmd = input('>>>').strip()
if not cmd: continue
self.client.send(cmd.encode())
cmd_upper = self.client.recv(1024).decode()
print(cmd_upper) if __name__ == '__main__':
client = MyClient(('localhost', 8080))
client.start()
client.py
[ python ] 网络编程(2)的更多相关文章
- Python 网络编程(二)
Python 网络编程 上一篇博客介绍了socket的基本概念以及实现了简单的TCP和UDP的客户端.服务器程序,本篇博客主要对socket编程进行更深入的讲解 一.简化版ssh实现 这是一个极其简单 ...
- Python 网络编程(一)
Python 网络编程 socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. ...
- Python学习(22)python网络编程
Python 网络编程 Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的 ...
- Day07 - Python 网络编程 Socket
1. Python 网络编程 Python 提供了两个级别访问网络服务: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口 ...
- python网络编程-01
python网络编程 1.socket模块介绍 ①在网络编程中的一个基本组件就是套接字(socket),socket是两个程序之间的“信息通道”. ②套接字包括两个部分:服务器套接字.客户机套接字 ③ ...
- 《Python网络编程》学习笔记--使用谷歌地理编码API获取一个JSON文档
Foundations of Python Network Programing,Third Edition <python网络编程>,本书中的代码可在Github上搜索fopnp下载 本 ...
- Python网络编程基础pdf
Python网络编程基础(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1VGwGtMSZbE0bSZe-MBl6qA 提取码:mert 复制这段内容后打开百度网盘手 ...
- python 网络编程(Socket)
# from wsgiref.simple_server import make_server## def RunServer(environ,start_response):# start_resp ...
- python 网络编程 IO多路复用之epoll
python网络编程——IO多路复用之epoll 1.内核EPOLL模型讲解 此部分参考http://blog.csdn.net/mango_song/article/details/4264 ...
- 自学Python之路-Python网络编程
自学Python之路-Python网络编程 自学Python之路[第一回]:1.11.2 1.3
随机推荐
- QT样式表
QT样式表 一.QT样式表简介 1.QT样式表简介 QSS的主要功能是使界面的表现与界面的元素分离,使得设计皮肤与界面控件分离的软件成为可能. QT样式表是允许用户定制widgets组件外观的强大机制 ...
- JavaScript中“&&”和“||”操作符的意义,深入理解和使用场景
一.概念 与其他语言不同,在js中,逻辑运算符可以返回任何类型的数据,不仅仅是true和false. &&和||的返回值是两个操作数的其中一个.即a&&b或者a||b ...
- 【题解】CF#713 E-Sonya Partymaker
这题真的想了挺久的,然而到最后也还是没想到怎样处理环的情况……网上竟然也完全没有题解,无奈之下到 CF 的 AC 代码里面去找了一份膜拜了一下.感谢~ 由于觉得这题有一定的难度,自己看代码也看了比较久 ...
- 【刷题】BZOJ 3365 [Usaco2004 Feb]Distance Statistics 路程统计
Description 在得知了自己农场的完整地图后(地图形式如前三题所述),约翰又有了新的问题.他提供 一个整数K(1≤K≤109),希望你输出有多少对农场之间的距离是不超过K的. Input 第1 ...
- 我是一个CPU:这个世界慢!死!了!
最近小编看到一篇十分有意思的文章,多方位.无死角的讲解了CPU关于处理速度的理解,看完之后真是豁然开朗.IOT时代,随着科技的发展CPU芯片的处理能力越来越强,强大的程度已经超乎了我们的想象.今天就把 ...
- 洛谷 P3338 [ZJOI2014]力 解题报告
P3338 [ZJOI2014]力 题目描述 给出n个数qi,给出Fj的定义如下: \(F_j = \sum_{i<j}\frac{q_i q_j}{(i-j)^2 }-\sum_{i>j ...
- Hdu1542 Atlantis
Atlantis Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Su ...
- jquery validate ajax 验证重复的2种方法
转载自:http://blog.51yip.com/jsjquery/1484.html jquery validate 经过这种多年的改良,已经很完善了.它能满足80%的验证需要,如果validat ...
- Java设计模式の责任链模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...
- 2017 济南精英班 Day1
不管怎么掰都是n*m-1 #include<cstdio> using namespace std; int main() { freopen("bpmp.in",&q ...