对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 相关文章推荐
python类继承用法实例分析
Oct 10 Python
python实现多线程网页下载器
Apr 15 Python
python脚本生成caffe train_list.txt的方法
Apr 27 Python
python处理数据,存进hive表的方法
Jul 04 Python
Python/ArcPy遍历指定目录中的MDB文件方法
Oct 27 Python
python事件驱动event实现详解
Nov 21 Python
Python去除字符串前后空格的几种方法
Mar 04 Python
一个可以套路别人的python小程序实例代码
Apr 09 Python
Python基于Opencv来快速实现人脸识别过程详解(完整版)
Jul 11 Python
python使用pyecharts库画地图数据可视化的实现
Mar 25 Python
Python实现多线程下载脚本的示例代码
Apr 03 Python
Python趣味挑战之用pygame实现简单的金币旋转效果
May 31 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获取网页标题的3种实现方法代码实例
2014/04/11 PHP
浅析Yii2缓存的使用
2016/05/10 PHP
php变量与JS变量实现不通过跳转直接交互的方法
2017/08/25 PHP
PHP给前端返回一个JSON对象的实例讲解
2018/05/31 PHP
关于PHP中interface的用处详解
2020/07/26 PHP
prototype 1.5 &amp; scriptaculous 1.6.1 学习笔记
2006/09/07 Javascript
url地址自动加#号问题说明
2010/08/21 Javascript
jQuery Jcrop插件实现图片选取功能
2011/11/23 Javascript
Javascript验证用户输入URL地址是否为空及格式是否正确
2014/10/09 Javascript
基于jquery fly插件实现加入购物车抛物线动画效果
2016/04/05 Javascript
Three.js学习之Lamber材质和Phong材质
2016/08/04 Javascript
AngularJS 使用ng-repeat报错 [ngRepeat:dupes]
2017/01/19 Javascript
基于 Bootstrap Datetimepicker 联动
2017/08/03 Javascript
基于Vue实现拖拽功能
2020/07/29 Javascript
基于Vue自定义指令实现按钮级权限控制思路详解
2018/05/23 Javascript
详解微信小程序-canvas绘制文字实现自动换行
2019/04/26 Javascript
electron 如何将任意资源打包的方法步骤
2020/04/16 Javascript
浅谈vue 多个变量同时赋相同值互相影响
2020/08/05 Javascript
使用python编写android截屏脚本双击运行即可
2014/07/21 Python
Python中使用asyncio 封装文件读写
2016/09/11 Python
详解Python的Lambda函数与排序
2016/10/25 Python
Python学习小技巧总结
2018/06/10 Python
Django 静态文件配置过程详解
2019/07/23 Python
numpy的Fancy Indexing和array比较详解
2020/06/11 Python
Python根据字典的值查询出对应的键的方法
2020/09/30 Python
详解matplotlib绘图样式(style)初探
2021/02/03 Python
HTML5之SVG 2D入门1—SVG(可缩放矢量图形)概述
2013/01/30 HTML / CSS
捷克领先的户外服装及配件市场零售商:ALPINE PRO
2018/01/09 全球购物
俄罗斯家居用品购物网站:Евродом
2020/11/21 全球购物
德国户外商店:eXXpozed
2020/07/25 全球购物
传统软件工程与面向对象的软件工程有什么区别
2012/05/31 面试题
中学生学习生活的自我评价
2013/10/26 职场文书
销售人员职业生涯规划范文
2014/03/01 职场文书
雷锋之歌观后感
2015/06/10 职场文书
导游词之上饶龟峰
2019/10/25 职场文书
使用CSS3实现按钮悬停闪烁动态特效代码
2021/08/30 HTML / CSS