热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python网络编程:深入探讨TCP粘包问题及解决方案

本文详细探讨了TCP协议下的粘包现象及其产生的原因,并提供了通过自定义报头解决粘包问题的具体实现方案。同时,对比了TCP与UDP协议在数据传输上的不同特性。
### 1. 粘包现象的产生

TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。它允许应用程序发送任意长度的数据流,但这种灵活性导致了一个常见的问题——粘包现象。粘包现象指的是发送方发送的若干包数据,接收方按字节流顺序接收到,但无法区分这些数据包的边界。

#### 1.1 为什么会出现粘包

- **TCP协议的特性**:TCP协议是面向流的,这意味着发送方可以连续发送多个数据包,而接收方必须将这些数据包作为一个连续的字节流处理。如果发送方连续发送多个小数据包,TCP协议栈可能会将这些数据包合并成一个较大的数据包发送,以提高传输效率。同样,接收方也可能将多个数据包合并成一个大包接收,从而导致粘包现象。
- **发送方的行为**:如果发送方短时间内发送多个小数据包,TCP协议栈可能会根据Nagle算法将这些数据包合并,以减少网络拥塞和提高传输效率。

### 2. TCP与UDP的区别

- **TCP协议**:面向连接,提供可靠的数据传输服务。数据传输前需要建立连接,传输过程中会进行流量控制和拥塞控制,确保数据的完整性和顺序性。但正是这种可靠性导致了粘包现象。
- **UDP协议**:无连接,提供不可靠的数据传输服务。每个UDP数据包都是独立的,接收方可以按消息边界接收数据,不会出现粘包现象,但数据包可能会丢失或乱序。

### 3. 解决粘包问题的方法

解决TCP粘包问题的关键在于明确数据包的边界。以下是几种常见的解决方法:

#### 3.1 自定义报头

通过在每个数据包前面添加一个固定长度的报头,报头中包含数据包的实际长度,接收方可以根据报头中的长度信息来正确地分割数据包。具体步骤如下:

1. **发送端**:
- 将要发送的数据序列化为字节流。
- 创建一个包含数据长度的报头,通常使用固定长度(如4字节)表示数据长度。
- 先发送报头,再发送实际数据。

2. **接收端**:
- 先接收报头,解析出数据长度。
- 根据数据长度接收实际数据。

#### 3.2 示例代码

##### 服务端代码

```python
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import socket
import json
import struct
import os

class MyTcpServer(object):
sockt_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_addr = False
max_trans_size = 8192
coding = 'utf-8'
tcp_queue_size = 5
upload_dir_name = 'upload_file'

def __init__(self, server_address, bind_and_active=True):
self.server_address = server_address
self.socket = socket.socket(self.sockt_family, self.socket_type)
if bind_and_active:
try:
self.server_bind()
self.server_active()
except Exception:
self.server_close()
raise

def server_bind(self):
if self.allow_reuse_addr:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()

def server_active(self):
self.socket.listen(self.tcp_queue_size)

def server_close(self):
self.socket.close()

def get_request(self):
return self.socket.accept()

def close_request(self):
return self.socket.close()

def run(self):
while True:
self.conn, self.client_ip = self.get_request()
print self.client_ip
while True:
try:
head_struct = self.conn.recv(4)
if not head_struct:
break
head_len = struct.unpack('i', head_struct)[0]
head_json = self.conn.recv(head_len).decode(self.coding)
head_dict = json.loads(head_json)
print head_dict
cmd = head_dict['cmd']
if hasattr(self, cmd):
func = getattr(self, cmd)
func(head_dict)
except Exception:
break

def put(self, args):
upload_file_path = os.path.normpath(os.path.join(self.upload_dir_name, args['file_name']))
file_size = args['file_size']
recv_size = 0
print "--->" + upload_file_path
with open(upload_file_path, 'wb+') as f:
while recv_size recv_data = self.conn.recv(self.max_trans_size)
f.write(recv_data)
recv_size += len(recv_data)
print "recvsize:%s filesize:%s" % (recv_size, file_size)

tcp_server = MyTcpServer(('10.39.0.100', 8888))
tcp_server.run()
```

##### 客户端代码

```python
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import socket
import json
import struct
import os

class MyTcpClient(object):
sockt_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_addr = False
max_trans_size = 8192
coding = 'utf-8'
tcp_queue_size = 5

def __init__(self, server_address, cOnnect=True):
self.server_address = server_address
self.socket = socket.socket(self.sockt_family, self.socket_type)
if connect:
try:
self.client_connect()
except:
self.client_close()
raise

def client_connect(self):
self.socket.connect(self.server_address)

def client_close(self):
self.socket.close()

def run(self):
while True:
user_input = raw_input(">>").strip()
if not user_input:
continue
cmd = user_input.split()[0]
if hasattr(self, cmd):
func = getattr(self, cmd)
func(user_input.split())

def put(self, args):
cmd = args[0]
filename = args[1]
if not os.path.isfile(filename):
print "file is not exists!!"
return None
else:
filesize = os.path.getsize(filename)
head_dic = {'cmd': cmd, 'file_name': os.path.basename(filename), 'file_size': filesize}
print head_dic
head_json = json.dumps(head_dic).encode("utf-8")
head_struct_size = struct.pack('i', len(head_json))
self.socket.send(head_struct_size)
self.socket.send(head_json)
send_size = 0
with open(filename, 'rb') as f:
for line in f:
self.socket.send(line)
send_size += len(line)
print send_size
else:
print "file upload success!!"

client = MyTcpClient(('10.39.0.100', 8888))
client.run()
```

### 4. 总结

TCP粘包问题是网络编程中常见的问题,通过自定义报头的方式可以有效地解决这一问题。理解TCP和UDP协议的特性,选择合适的协议,对于开发高效、可靠的网络应用至关重要。
推荐阅读
  • 本文档介绍了如何使用ESP32开发板在STA模式下实现与TCP服务器的通信,包括环境搭建、代码解析及实验步骤。 ... [详细]
  • 利用Node.js实现PSD文件的高效切图
    本文介绍了如何通过Node.js及其psd2json模块,快速实现PSD文件的自动化切图过程,以适应项目中频繁的界面更新需求。此方法不仅提高了工作效率,还简化了从设计稿到实际应用的转换流程。 ... [详细]
  • 首部|接口类型_OSI 7层模型 & TCP/IP协议首部封装格式解析
    首部|接口类型_OSI 7层模型 & TCP/IP协议首部封装格式解析 ... [详细]
  • DirectShow Filter 开发指南
    本文总结了 DirectShow Filter 的开发经验,重点介绍了 Source Filter、In-Place Transform Filter 和 Render Filter 的实现方法。通过使用 DirectShow 提供的类,可以简化 Filter 的开发过程。 ... [详细]
  • C/C++ 应用程序的安装与卸载解决方案
    本文介绍了如何使用Inno Setup来创建C/C++应用程序的安装程序,包括自动检测并安装所需的运行库,确保应用能够顺利安装和卸载。 ... [详细]
  • 本报告记录了嵌入式软件设计课程中的第二次实验,主要探讨了使用KEIL V5开发环境和ST固件库进行GPIO控制及按键响应编程的方法。通过实际操作,加深了对嵌入式系统硬件接口编程的理解。 ... [详细]
  • 本文分享了作者在使用LaTeX过程中的几点心得,涵盖了从文档编辑、代码高亮、图形绘制到3D模型展示等多个方面的内容。适合希望深入了解LaTeX高级功能的用户。 ... [详细]
  • LeetCode 102 - 二叉树层次遍历详解
    本文详细解析了LeetCode第102题——二叉树的层次遍历问题,提供了C++语言的实现代码,并对算法的核心思想和具体步骤进行了深入讲解。 ... [详细]
  • 本文将详细介绍Fuel CMS如何基于CodeIgniter框架构建,包括其单入口模式的实现方式及关键配置文件的作用。通过分析本地环境中的index.php和.htaccess文件,我们将更好地理解Fuel CMS的核心架构。 ... [详细]
  • 本文介绍了Tomcat的基本操作,包括启动、关闭及首次访问的方法,并详细讲解了如何在IDEA中创建Web项目,配置Servlet及其映射,以及如何将项目部署到Tomcat。 ... [详细]
  • 本文探讨了使用Python实现监控信息收集的方法,涵盖从基础的日志记录到复杂的系统运维解决方案,旨在帮助开发者和运维人员提升工作效率。 ... [详细]
  • spring boot使用jetty无法启动 ... [详细]
  • 本文探讨了在一个物理隔离的环境中构建数据交换平台所面临的挑战,包括但不限于数据加密、传输监控及确保文件交换的安全性和可靠性。同时,作者结合自身项目经验,分享了项目规划、实施过程中的关键决策及其背后的思考。 ... [详细]
  • 本文介绍了如何将Spring属性占位符与Jersey的@Path和@ApplicationPath注解结合使用,以便在资源路径中动态解析属性值。 ... [详细]
  • Python学习day3网络基础之网络协议篇
    一、互联网协议连接两台计算机之间的Internet实际上就是一系列统一的标准,这些标准称之为互联网协议,互联网的本质就是一系列网络协议。二、为什么要有互联网协议互联网协议就相当于计 ... [详细]
author-avatar
海天豆浆
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有