python中基于tcp协议与udp的通信
python中基于tcp协议与udp的通信(数据传输)
一、TCP协议介绍
流式协议(以数据流的形式通信传输)
安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信)
tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层、数据链路层、物理层。可以说很多安全数据的传输通信都是基于tcp协议进行的。
为了让tcp通信更加方便需要引入一个socket模块(将网络层、数据链路层、物理层封装的模块),我们只要调用模块中的相关接口就能实现传输层下面的繁琐操作。
简单的tcp协议通信模板(需要一个服务端和一个客户端)
服务端: from socket import *
# 确定服务端传输协议↓↓↓↓↓↓↓
server = socket(AF_INET, SOCK_STREAM) # 这里的SOCK_STREAM代表的就是流式协议TCP,如果是SOCK_DGRAM就代表UDP协议
# 固定服务端IP和PORT,让客户端能够通过IP和端口访问服务端↓↓↓↓↓↓↓
server.bind(('127.0.0.1', 8080)) # ('127.0.0.1', 8080)这里必须用元组形式传入IP和PORT,本地访问本地IP默认为'127.0.0.1'
# 设置半连接池数量(一般为5)
server.listen(5) # 半连接池:客户端连接请求个数的容器,当前已连接的客户端信息收发未完成前,会有最大5个客户端连接请求进入排队状态,
# 等待上一个通信完毕后,就可以连接进入开始通信。 # 双向通道建立成功,可以进行下一步数据的通信了↓↓↓↓↓↓↓
conn, client_addr = server.accept()
# 进行一次信息的收与发
data = conn.recv(1024) # 每次最大接收1024字节,收到的数据为二进制Bytes类型 conn.send(data.upper()) # 将收到的数据进行处理,返回新的数据,反馈给客户端(给客户端发数据),发的数据类型也必须是Bytes类型 # 一轮信息收发完毕,关闭已经建立的双向通道
conn.close() 客户端:
from socket import *
# 确定客户端传输协议↓↓↓↓↓↓↓(服务端和客户端服务协议一样才能进行有效的通信)
client = socket(AF_INET, SOCK_STREAM) # 这里的SOCK_STREAM代表的就是流式协议TCP,如果是SOCK_DGRAM就代表UDP协议
# 开始连接服务端IP和PORT,建立双向链接
client.connect(('127.0.0.1', 8080)) # 通过服务端IP和PORT进行连接 # 走到这一步就已经建立连接完毕,接下来开始数据通信:
client.send('hello,server'.encode('utf-8')) # 将发送的信息转码成Bytes类型数据 data = client.recv(1024) # 每次最大收数据大小为1024字节(1kb) print(data.decode('utf-8')) # 将b类型数据转换成字符串格式 # 一次传输完毕
client.close() # 关闭客户端连接 启动服务端(服务端开始监听客户端的连接请求)
启动客户端(客户端给服务端发送连接请求)
建立双向链接完成
客户端给服务端发送信息 hello,server
服务端收到hello,server,将其转换成大写,返回给客户端(此时服务端一轮通信完毕)
客户端收到服务端的反馈信息,打印出HELLO,SERVER(此时客户端一轮通信完毕)
以上是最基本的一次基于tcp协议通信的过程客户端发,服务端收,服务端处理数据然后发,客户端收到服务端发了的反馈数据。
TCP协议的通信粘包问题
但是由于tcp协议是一种流式协议,流式协议就会有一个特点:数据的传输像一涓涓水流的形式传输,我们在收数据的时候默认最大收数据大小为1024字节,当发送的数据小于1024字节时候当然不会有问题,一次性全部收完,但是但是但是当发送的数据大于1024字节的时候,我们这边又不知道发送的数据大小是多少,只能默认的1024字节的时候,数据一次性就不可能收完,只能在这次收1024字节,那1024字节以外的数据呢?由于数据的传输是流式协议,所以没有收完的数据会依次排队在门外等着,等待你下次收数据时候再次收取,这样如果每次传的数据大小不确认,收的时候数据也不知道该收多少的时候,就会导致每次收数据的时候收不完,收不完的数据就会在缓存中排队,等待下次收,收不完的数据就好像粘粘在一起(zhan nian)。这就叫tcp的流式协议的通信粘包问题。
这个问题的更形象过程可以见下图:

知道这粘包的大致过程,就能够找到方法对症下药了:
粘包问题的解决分析:
粘包问题归根到底是数据接收不彻底导致,那么要解决这个问题最直接的方法就是每次都彻底地收完数据。
要想达到这个目的就需要每次在收数据之前事先知道我要收数据的文件大小,知道了文件大小我们就能有的放矢,准确的把数据收完不遗留。
解决方法:先发个包含待发送文件大小长度的报头文件>>>>再发送原始文件
引入模块struct
具体看代码:
服务端:
import socket
import struct server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_addr = server.accept()
print('客户端已连接')
while True:
try:
head = conn.recv(4)
size = struct.unpack('i', head)[0]
data = conn.recv(size)
print('已收到客户端信息:', data.decode('utf-8'))
except ConnectionResetError:
print('客户端已中断连接')
conn.close()
break 客户端:
import socket
import struct
while True:
try:
client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('已连接到服务端')
while True:
try:
msg = 'abcdefghijklmnopqrstuvwxyz1234567890'.encode('utf-8')
head = struct.pack('i', len(msg))
client.send(head)
client.send(msg) except ConnectionResetError:
print('服务端已中断连接')
client.close()
break except ConnectionRefusedError:
print('无法连接到服务器')
以上方法只是为了试验解决粘包问题,真正应用场景可以是上传或者下载一个大文件的时候,这时就必须要提前知道接收的文件实际大小,做到100%精确的接收每一个数据,这时就需要收数据前获取即将收到的文件大小,然后对症下药,做到精确接收,但实现方法不一定非要用struct模块,struct模块只是解决粘包问题中的一个官方正式的方法,自己还可以有自己的想法,比如先直接把要发送文件的大小已字符串的格式发送过去,然后再发送这个文件,目的只有一个,知道我接收的文件的大小,精准接收文件。
下面写一个客户端从服务端下载文件的实例,供大家参考:(假设下载文件在服务端文件同一级)
下载服务端: import socket
import time
import struct
import json # 计算当前文件夹下文件的md5值、大小
import os, hashlib def get_info(file_name):
file_info = {}
base_dir = os.path.dirname(__file__)
file_dir = os.path.join(base_dir, file_name)
if os.path.exists(file_dir):
# md5计算时文件数据是放在内存中的,当我们计算一个大文件时,可以用update方法进行分步计算,
# 每次添加部分文件数据进行计算,减少内存占用。
with open(file_dir, 'rb') as f:
le = 0
d5 = hashlib.md5()
for line in f:
le += len(line)
d5.update(line)
file_info['lenth'] = le # 将文件长度加入报头字典
file_md5 = d5.hexdigest()
file_info['md5'] = file_md5 # 将文件md5加入报头字典
file_size = os.path.getsize(file_dir) / float(1024 * 1024)
file_info['size(MB)'] = round(file_size, 2) # 将文件大小加入报头字典
return file_info
else:
return file_info server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_addr = server.accept()
print('%s >:客户端(%s)已连接' % (time.strftime('%Y-%m-%d %H:%M:%S'), client_addr))
while True:
try:
download_filename = conn.recv(1024).decode('utf-8')
download_file_info_dic = get_info(download_filename)
j_head = json.dumps(download_file_info_dic) # 将文件信息字典转成json字符串格式
head = struct.pack('i', len(j_head))
conn.send(head)
conn.send(j_head.encode('utf-8'))
if not download_file_info_dic:
continue
with open(download_filename, 'rb') as f:
while True:
data=f.read(1024)
if not data:
break
conn.send(data)
# for line in f:
# conn.send(line) except ConnectionResetError:
print('%s >:客户端(%s)已断开' % (time.strftime('%Y-%m-%d %H:%M:%S'), client_addr))
conn.close()
break
下载客户端: import socket
import time
import struct
import json # 进度条显示
def progress(percent,width=30):
text=('\r[%%-%ds]'%width)%('x'*int(percent*width))
text=text+'%3s%%'
text=text%(round(percent*100))
print(text,end='') while True:
try:
client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('%s >:已连接到服务端' % time.strftime('%Y-%m-%d %H:%M:%S'))
while True:
try:
file_name = input('请输入下载文件名称:')
client.send(file_name.encode('utf-8')) head = client.recv(4) # 收报头
j_dic_lenth = struct.unpack('i', head)[0] # 解压报头,获取json格式的文件信息字典的长度
j_head = client.recv(j_dic_lenth) # 收json格式的信息字典
file_info_dic = json.loads(j_head) # 反序列化json字典,得到文件信息字典
if not file_info_dic:
print('文件不存在')
continue
file_lenth = file_info_dic.get('lenth')
file_size = file_info_dic.get('size(MB)')
file_md5 = file_info_dic.get('md5')
rec_len = 0
with open('cpoy_'+file_name, 'wb') as f:
while rec_len < file_lenth:
data = client.recv(1024)
f.write(data)
rec_len += len(data)
per=rec_len/file_lenth
progress(per)
print()
# print('下载比例:%6s %%'%)
if not rec_len:
print('文件不存在')
else: print('文件[%s]下载成功: 大小:%s MB|md5值:[%s]' % (file_name, file_size, file_md5)) except ConnectionResetError:
print('%s >:服务端已终止' % time.strftime('%Y-%m-%d %H:%M:%S'))
client.close()
break except ConnectionRefusedError:
print('%s >:无法连接到服务器' % time.strftime('%Y-%m-%d %H:%M:%S'))
文件上传同理,只是换成客户端给服务端发送文件,服务端接收。
TCP协议下通信利用socketserver模块实现多客户端并发通信
服务端:
import socketserver
import time class MyTcpHandler(socketserver.BaseRequestHandler):
# 到这里表示服务端已监听到一个客户端的连接请求,将通信交给一个handle方法实现,自己再去监听客户连接请求
def handle(self):
# 建立双向通道,进行通信
print('%s|客户端%s已连接' % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address))
while True:
try:
data = self.request.recv(1024)
msg = '我已收到您的请求[%s],感谢您的关注!' % data.decode('utf-8')
self.request.send(msg.encode('utf-8'))
except ConnectionResetError:
print('%s|客户端%s已断开连接' % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address))
break if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler) # 绑定服务端IP和PORT,并产生并发方法对象
print('等待连接请求中...')
server.serve_forever() # 服务端一直开启
客户端:
from socket import *
import time
server_addr = ('127.0.0.1', 8080)
count = 1
while True:
if count > 10:
time.sleep(1)
print('%s|连接%s超时' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))
break
try:
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
count = 1
print('%s|服务端%s连接成功' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))
while True:
try:
client.send('北鼻'.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
time.sleep(0.5)
except ConnectionResetError:
print('%s|服务端%s已中断' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))
client.close()
break
except ConnectionRefusedError:
print('无法连接到服务端')
count += 1
同时再添加客户端2、客户端3,将发送数据稍微修改一下,实现多客户端并发通信服务端。
通过subprocess模块,实现远程shell命令行命令
服务端
import socketserver
import struct
import subprocess class MyTcpHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
print('客户端<%s,%s>已连接' % self.client_address)
try:
cmd = self.request.recv(1024).decode('utf-8')
res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = res.stdout.read()
stderr = res.stderr.read()
head = struct.pack('i', len(stdout + stderr))
self.request.send(head)
self.request.send(stdout)
self.request.send(stderr)
except ConnectionResetError:
print('客户端<%s,%s>已中断连接' % self.client_address)
self.request.close()
break if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler)
server.serve_forever()
客户端
from socket import *
import struct while True:
try:
client = socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
try:
cmd = input('>>>>>>>:').strip().encode('utf-8')
client.send(cmd)
head = client.recv(4)
size = struct.unpack('i', head)[0]
cur_size = 0
result = b''
while cur_size < size:
data = client.recv(1024)
cur_size += len(data)
result += data
print(result.decode('gbk')) # windows系统默认编码是gbk,解码肯定也要用gbk
except ConnectionResetError:
print('服务端已中断')
client.close()
break except ConnectionRefusedError:
print('无法连接服务端')
通过客户端输入命令,在服务端执行shell命令,通过服务端执行subprocess模块达到远程shell命令操作,此过程主要需要考虑2个难点,①解决命令产生结果数据的发送粘包问题,②注意返回结果的shell命令结果是gbk编码,接收后需要用gbk解码一下。
二、UDP协议介绍
UDP叫数据报协议,意味着发消息都带有数据报头
udp的server不需要就行监听也不需要建立连接
在启动服务之后只能被动的等待客户端发送消息过来,客户端发送消息的时候,要带上服务端的地址
服务端在回复消息的时候,也需要带上客户端的地址
1.udp协议客户端允许发空
2.udp协议不会粘包
3.udp协议服务端不存在的情况下,客户端照样不会报错
4.udp协议支持并发
简单的UDP协议数据传输
# 服务端
import socket server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080)) msg, addr = server.recvfrom(1024)
print(msg.decode('utf-8'))
server.sendto(b'hello', addr) server.close() #客户端
import socket client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8080) client.sendto(b'hello server baby!', server_addr)
msg, addr = client.recvfrom(1024)
print(msg, addr)
tips:
udp特点 >>> 无链接,类似于发短信,发了就行对方爱回不回,没有任何关系
将服务端关了,客户端起起来照样能够发数据。因为不需要考虑服务端能不能收到
验证udp协议有无粘包问题
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
print(server.recvfrom(1024))
print(server.recvfrom(1024))
print(server.recvfrom(1024)) import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1',8080)
client.sendto(b'hello',server_addr)
client.sendto(b'hello',server_addr)
client.sendto(b'hello',server_addr)
小知识点补充:
windows电脑和max电脑的时间同步功能,其实就是基于udp朝windows,max服务器发送请求获取标准时间
UDP协议下通信利用socketserver模块实现多客户端并发通信
# 服务端
import socketserver class MyUdpHandler(socketserver.BaseRequestHandler):
def handle(self):
data, sock = self.request
sock.sendto(data.upper(), self.client_address) if __name__ == '__main__':
server = socketserver.ThreadingUDPServer(('127.0.0.1', 8081), MyUdpHandler)
server.serve_forever() # 客户端1、2、3、4...
from _socket import * client = socket(AF_INET, SOCK_DGRAM) while True:
client.sendto(b'ddddddd', ('127.0.0.1', 8081))
data, addr = client.recvfrom(1024)
print(data.decode('utf-8'))
原文地址https://www.cnblogs.com/suguangti/p/10814737.html#863522415
python中基于tcp协议与udp的通信的更多相关文章
- python中基于tcp协议的通信(数据传输)
tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据 ...
- Python中的Tcp协议的应用之Tcp服务端程序开发
TCP通信协议是面向连接的可靠的网络通信协议. 网络间想要进行数据传输必须要用到socket,socket翻译过来叫做套接字,其主要作用是不同设备或同一台设备之间的进程通信工具. Python中的Tc ...
- 基于TCP 协议的socket 简单通信
DNS 服务器:域名解析 socket 套接字 : socket 是处于应用层与传输层之间的抽象层,也是一组操作起来非常简单的接口(接受数据),此接口接受数据之后,交由操作系统 为什么存在 soc ...
- 基于Tcp协议的简单Socket通信实例(JAVA)
好久没写博客了,前段时间忙于做项目,耽误了些时间,今天开始继续写起~ 今天来讲下关于Socket通信的简单应用,关于什么是Socket以及一些网络编程的基础,这里就不提了,只记录最简单易懂实用的东西. ...
- Python中的Tcp协议应用之TCP服务端-协程版(推荐)
利用gevent第三方库,实现协程. 通过协程实现一个服务端服务多个客户端需求. 使用协程的好处是协程比线程更加节省内存资源. gevent安装命令: pip3 install gevent 注意:在 ...
- Python中的Tcp协议应用之TCP服务端-线程版
利用线程实现,一个服务端同时服务多个客户端的需求. TCP服务端-线程版代码实现: import socket import threading def handle_client_socket(ne ...
- python 30 基于TCP协议的socket通信
目录 1. 单对单循环通信 2. 循环连接通信:可连接多个客户端 3. 执行远程命令 4. 粘包现象 4.1 socket缓冲区 4.2 出现粘包的情况: 4.3 解决粘包现象 bytes 1. 单对 ...
- python中的tcp示例详解
python中的tcp示例详解 目录 TCP简介 TCP介绍 TCP特点 TCP与UDP的不同点 udp通信模型 tcp客户端 tcp服务器 tcp注意点 TCP简介 TCP介绍 TCP协议 ...
- Python中的端口协议之基于UDP协议的通信传输
UDP协议: 1.python中基于udp协议的客户端与服务端通信简单过程实现 2.udp协议的一些特点(与tcp协议的比较) 3.利用socketserver模块实现udp传输协议的并 ...
- 网络编程----socket介绍、基于tcp协议的套接字实现、基于udp协议的套接字实现
一.客户端/服务器架构(C/S架构) 即C/S架构,包括: 1.硬件C/S架构(打印机) 2.软件C/S架 ...
随机推荐
- NOIP模拟55
T1 Skip 解题思路 正解给的是线段树维护单调栈,但是我不会.. CDQ 维护斜率可做!!! 先得出一个朴素的 DP 方程:设 \(f_i\) 表示最后一场是 i 的最优解. 转移方程就是 \(f ...
- c# winfrom DataGridView 动态UI下载功能(内含GIF图) || 循环可变化的集合 数组 datatable 等
Gif演示 分解步骤 1,使用组件DataGridView 2,使用DataSource来控制表格展示的数据来源(注意:来源需要是DataTable类型) 3,需要用到异步线程.如果是不控制数据源的话 ...
- 纯css+html+js模仿elementui组件
文件下载链接地址https://files.cnblogs.com/files/ht955/UIcomponents.zip?t=1717405975&download=true
- K8S部署ECK采集日志
1. 部署nfs 1. 安装nfs #所有节点安装 yum install -y nfs-utils 在master节点创建nfs共享目录 mkdir -pv /data/kubernetes 编写配 ...
- leetcode | 103. 二叉树的锯齿形层序遍历 | JavaScript实现
题目 给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 .(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行). 思路 按照正常的层序遍历,然后再对下标为奇数的数组进 ...
- Elasticsearch之Nested Query nestedQuery查询数组
es是通过符合条件的json记录找出来,本身并不是将数据中的记录filter过滤.es nestedQuery不是过滤的结果,是匹配的这条es记录,所以数组中的其他的记录也会查询出来1.方法1:可以在 ...
- mongodb插入文档,更新文档和检索文档
import com.mongodb.client.*; import com.mongodb.client.MongoClient; import com.mongodb.client.model. ...
- svn服务端安装和使用
首先去官网下载安装包 点我下载 下载完了以后选择安装路径然后一直next就可以了 安装完了以后在开始菜单里面找到svn 打开 如何使用? 这里是创建代码管理的存储库 点击 repositories ...
- Mybatis.xml文件中 大于小于等于
Mybatis中 大于小于等于的转义写法第一种写法:符号 转义字符< <<= <=> >>= >=& &am ...
- 未能加载文件或程序集“netstandard,Version=2.0.0.0, Culture=neutral,PublicKeyToken=cc7b13ffcd2ddd51”或它的某一个依赖项 解决
未能加载文件或程序集"netstandard,Version=2.0.0.0, Culture=neutral,PublicKeyToken=cc7b13ffcd2ddd51"或它 ...