对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 ORM框架SQLAlchemy学习笔记之关系映射实例
Jun 10 Python
python实现二分查找算法
Sep 21 Python
通过python+selenium3实现浏览器刷简书文章阅读量
Dec 26 Python
python中强大的format函数实例详解
Dec 05 Python
Django错误:TypeError at / 'bool' object is not callable解决
Aug 16 Python
django实现web接口 python3模拟Post请求方式
Nov 19 Python
Python 装饰器原理、定义与用法详解
Dec 07 Python
Python内置类型性能分析过程实例
Jan 29 Python
python实现人脸签到系统
Apr 13 Python
python实现从ftp上下载文件的实例方法
Jul 19 Python
python如何对链表操作
Oct 10 Python
python 列表推导和生成器表达式的使用
Feb 01 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
一些操作和快捷键的理解和讨论
2020/03/04 星际争霸
推荐十款免费 WordPress 插件
2015/03/24 PHP
php+curl 发送图片处理代码分享
2015/07/09 PHP
基于php(Thinkphp)+jquery 实现ajax多选反选不选删除数据功能
2017/02/24 PHP
jQuery Tools tab(幻灯片)
2012/07/14 Javascript
Array.prototype.concat不是通用方法反驳[译]
2012/09/20 Javascript
js 限制input只能输入数字、字母和汉字等等
2013/12/18 Javascript
jQuery应用之jQuery链用法实例
2015/01/19 Javascript
jQuery实现的超酷苹果风格图标滑出菜单效果代码
2015/09/16 Javascript
基于jquery fly插件实现加入购物车抛物线动画效果
2016/04/05 Javascript
AngularJS入门教程之 XMLHttpRequest实例讲解
2016/07/27 Javascript
用AngularJS来实现监察表单按钮的禁用效果
2016/11/02 Javascript
Jil,高效的json序列化和反序列化库
2017/02/15 Javascript
如何通过非数字与字符的方式实现PHP WebShell详解
2017/07/02 Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
2017/10/23 Javascript
微信小程序实现图片上传功能实例(前端+PHP后端)
2018/01/10 Javascript
angular第三方包开发整理(小结)
2018/04/19 Javascript
浅谈VueJS SSR 后端绘制内存泄漏的相关解决经验
2018/12/20 Javascript
Vue服务端渲染实践之Web应用首屏耗时最优化方案
2019/03/22 Javascript
浅谈JS的原型和继承
2019/05/08 Javascript
Python单体模式的几种常见实现方法详解
2017/07/28 Python
Python数据结构与算法之字典树实现方法示例
2017/12/13 Python
python程序变成软件的实操方法
2019/06/24 Python
Html5之webcoekt播放JPEG图片流
2020/09/22 HTML / CSS
美国知名的网上鞋类及相关服装零售商:Shoes.com
2017/05/06 全球购物
俄罗斯名牌服装网上商店:UNIQUE FABRIC
2019/07/25 全球购物
英国领先的餐饮折扣俱乐部:Gourmet Society
2020/07/26 全球购物
Exception类的常用方法
2012/06/16 面试题
高速铁道技术专业求职信
2014/08/09 职场文书
毕业生班级鉴定评语
2015/01/04 职场文书
开平碉楼导游词
2015/02/06 职场文书
教师求职自荐信
2015/03/26 职场文书
水知道答案观后感
2015/06/08 职场文书
小学生法制教育心得体会
2016/01/14 职场文书
高中美术教学反思
2016/02/17 职场文书
python中requests库+xpath+lxml简单使用
2021/04/29 Python