对python中基于tcp协议的通信(数据传输)实例讲解


Posted in Python onJuly 22, 2019

阅读目录

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的流式协议的通信粘包问题。

这个问题的更形象过程可以见下图:

对python中基于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)
          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解码一下。

以上这篇对python中基于tcp协议的通信(数据传输)实例讲解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
NumPy 如何生成多维数组的方法
Feb 05 Python
python pandas消除空值和空格以及 Nan数据替换方法
Oct 30 Python
解决python 未发现数据源名称并且未指定默认驱动程序的问题
Dec 07 Python
pyspark操作MongoDB的方法步骤
Jan 04 Python
python根据文章标题内容自动生成摘要的实例
Feb 21 Python
python实现可变变量名方法详解
Jul 01 Python
python manage.py runserver流程解析
Nov 08 Python
python dataframe NaN处理方式
Dec 26 Python
python logging.basicConfig不生效的原因及解决
Feb 20 Python
python 链接sqlserver 写接口实例
Mar 11 Python
详解python模块pychartdir安装及导入问题
Oct 22 Python
Python可视化学习之seaborn绘制矩阵图详解
Feb 24 Python
Django使用中间键实现csrf认证详解
Jul 22 #Python
python Tcp协议发送和接收信息的例子
Jul 22 #Python
利用Python实现手机短信监控通知的方法
Jul 22 #Python
如何使用django的MTV开发模式返回一个网页
Jul 22 #Python
python3.7 sys模块的具体使用
Jul 22 #Python
使用 Python 处理 JSON 格式的数据
Jul 22 #Python
用python写一个定时提醒程序的实现代码
Jul 22 #Python
You might like
PHP中ini_set和ini_get函数的用法小结
2014/02/18 PHP
制作安全性高的PHP网站的几个实用要点
2014/12/30 PHP
PHP根据session与cookie用户登录状态操作类的代码
2016/05/13 PHP
php常用字符串String函数实例总结【转换,替换,计算,截取,加密】
2016/12/07 PHP
Mac下php 5升级到php 7的步骤详解
2017/04/26 PHP
php数据库的增删改查 php与javascript之间的交互
2017/08/31 PHP
jquery ui bootstrap 实现自定义风格
2014/11/14 Javascript
JavaScript动态修改弹出窗口大小的方法
2015/04/06 Javascript
JavaScript操作HTML元素和样式的方法详解
2015/10/21 Javascript
JavaScript编程中布尔对象的基本使用
2015/10/25 Javascript
适用于手机端的jQuery图片滑块动画
2016/12/09 Javascript
footer定位页面底部(代码分享)
2017/03/07 Javascript
JS实现图片转换成base64的各种应用场景实例分析
2018/06/22 Javascript
angular4自定义组件非input元素实现ngModel双向数据绑定的方法
2018/12/28 Javascript
vue element-ui之怎么封装一个自己的组件的详解
2019/05/20 Javascript
javascript实现切割轮播效果
2019/11/28 Javascript
微信小程序跨页面传递data数据方法解析
2019/12/13 Javascript
JS对象属性的检测与获取操作实例分析
2020/03/17 Javascript
Vue页面渲染中key的应用实例教程
2021/01/12 Vue.js
python实现k均值算法示例(k均值聚类算法)
2014/03/16 Python
python多线程编程中的join函数使用心得
2014/09/02 Python
仅利用30行Python代码来展示X算法
2015/04/01 Python
Python实现的求解最大公约数算法示例
2018/05/03 Python
Django开发中的日志输出的方法
2018/07/02 Python
python批量处理txt文件的实例代码
2020/01/13 Python
使用Python发现隐藏的wifi
2020/03/04 Python
美国珠宝网上商店:Jeulia
2016/09/01 全球购物
施华洛世奇韩国官网:SWAROVSKI韩国
2018/06/05 全球购物
英国电视和家用电器购物网站:rlrdistribution.co.uk
2018/11/20 全球购物
巴西宠物商店:Cobasi
2019/04/19 全球购物
巴西独家产品和现场演示购物网站:Shoptime
2019/07/11 全球购物
J2EE是技术还是平台还是框架
2016/08/14 面试题
幼儿师范毕业生自荐信
2013/11/09 职场文书
基层工作经验证明样本
2014/11/16 职场文书
JS如何实现基于websocket的多端桥接平台
2021/05/14 Javascript
python用海龟绘图写贪吃蛇游戏
2021/06/18 Python