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

阅读目录

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)
          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协议的通信(数据传输)实例讲解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

时间: 2019-07-22

Python socket实现的简单通信功能示例

本文实例讲述了Python socket实现的简单通信功能.分享给大家供大家参考,具体如下: 套接字(socket)是计算机网络数据结构,在任何类型的通信开始之前,网络应用程序必须创建套接字,可以将其比作电话的插孔,没有它将无法进行通信 常用的地址家族 AF_UNIX:基于文件,实现同一主机不同进程之间的通信 AF_INET:基于网络,适用于IPv4 AF_INET6:基于网络,使用于IPv6 常见的连接类型 SOCK_STREAM:即TCP/IP.面向连接的套接字,通信之前必须建立可靠的连接.

python3实现TCP协议的简单服务器和客户端案例(分享)

利用python3来实现TCP协议,和UDP类似.UDP应用于及时通信,而TCP协议用来传送文件.命令等操作,因为这些数据不允许丢失,否则会造成文件错误或命令混乱.下面代码就是模拟客户端通过命令行操作服务器.客户端输入命令,服务器执行并且返回结果. TCP(Transmission Control Protocol 传输控制协议):是一种面向连接的.可靠的.基于字节流的传输层通信协议,由IETF的RFC 793定义. TCP客户端 from socket import * host = '192

python 基于TCP协议的套接字编程详解

基于TCP协议的套接字编程 实现电话沟通为例,这里传递的是字符,可以自己尝试去发送一个文件 # 服务端 import socket # 1. 符合TCP协议的手机 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # TCP # 2. 绑定手机号 一个服务器,我们自己的电脑作为服务器的话,用自己的IP地址 server.bind(('127.0.0.1',8000)) # 127.0.0.1 代表本地 # server.bind(

详解python中TCP协议中的粘包问题

TCP协议中的粘包问题 1.粘包现象 基于TCP实现一个简易远程cmd功能 #服务端 import socket import subprocess sever = socket.socket() sever.bind(('127.0.0.1', 33521)) sever.listen() while True: client, address = sever.accept() while True: try: cmd = client.recv(1024).decode('utf-8') p

Python两台电脑实现TCP通信的方法示例

为了实现Nao机器人与电脑端的TCP通信,于是研究了一下Python实现TCP通信,在网上也看到了很多例子,但大多都是在一台机器上验证.在两台机器上使用,出了一些小故障. 注意:若两台电脑通信出了问题,若能ping通!大部分是防火墙的问题.一开始A做服务器,B做客户端能实现:B做服务器,A做客户端,A就不能连接到B.我换了一台电脑A就能实现通信了.应该是A的防火墙需要设置.但是A的防火墙全关了也不能实现.真是很让人搞不懂. 首先是服务器端代码: # -*- encoding: utf-8 -*-

python中的tcp示例详解

TCP简介 TCP介绍 TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的.可靠的.基于字节流的传输层通信协议,由IETF的RFC 793定义. TCP通信需要经过创建连接.数据传送.终止连接三个步骤. TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话"" TCP特点 1. 面向连接 通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统

python3.5基于TCP实现文件传输

本文实例为大家分享了python3.5基于TCP实现文件传输的具体代码,供大家参考,具体内容如下 服务器代码 # _*_ coding:utf-8 _*_ from socket import * import _thread def tcplink(skt,addr): print(skt) print(addr,"已经连接上...") print('开始发送文件') with open('./ww.jpg', 'rb') as f: for data in f: print(dat

Java基于Socket的文件传输实现方法

本文实例讲述了Java基于Socket的文件传输实现方法.分享给大家供大家参考,具体如下: 1. Java代码如下: package sterning; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.net.Ser

浅析Java基于Socket的文件传输案例

本文实例介绍了Java基于Socket的文件传输案例,分享给大家供大家参考,具体内容如下 1.Java代码 package com.wf.demo.socket.socketfile; import java.net.*; import java.io.*; /** * 2.socket的Util辅助类 * * @author willson * */ public class ClientSocket { private String ip; private int port; private

python基于FTP实现文件传输相关功能代码实例

这篇文章主要介绍了python基于FTP实现文件传输相关功能代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本实例有文件传输相关功能,包括:文件校验.进度条打印.断点续传 客户端示例: import socket import json import os import hashlib CODE = { '1001':'重新上传文件' } def file_md5(file_path): obj = open(file_path,'rb

基于python3实现socket文件传输和校验

基于socket的文件传输并进行MD5值校验,供大家参考,具体内容如下 文件传输分为两个类,一个是服务端,一个是客户端. 客户端发起发送文件或接收文件的请求,服务端收到请求后接收或发送文件,最后进行MD5值的校验 socket数据通过struct模块打包 需要发送文件到服务端时,调用sendFile函数,struct包内包含文件信息.文件大小.文件MD5等信息,服务端接收到文件后进行MD5值校验,校验成功后则返回成功 需要从服务器下载文件时,调用recvFile函数,收到文件后进行MD5校验 c

Linux网络编程之基于UDP实现可靠的文件传输示例

了解网络传输协议的人都知道,采用TCP实现文件传输很简单.相对于TCP,由于UDP是面向无连接.不可靠的传输协议,所以我们需要考虑丢包和后发先至(包的顺序)的问题,所以我们想要实现UDP传输文件,则需要解决这两个问题.方法就是给数据包编号,按照包的顺序接收并存储,接收端接收到数据包后发送确认信息给发送端,发送端接收确认数据以后再继续发送下一个包,如果接收端收到的数据包的编号不是期望的编号,则要求发送端重新发送. 下面展示的是基于linux下C语言实现的一个示例程序,该程序定义一个包的结构体,其中

Java基于TCP方式的二进制文件传输

一个基于Java Socket协议之上文件传输的完整示例,基于TCP通信完成. 除了基于TCP的二进制文件传输,还演示了JAVA Swing的一些编程技巧,Demo程序 实现主要功能有以下几点: 1.基于Java Socket的二进制文件传输(包括图片,二进制文件,各种文档work,PDF) 2.SwingWorker集合JProgressBar显示实时传输/接受完成的百分比 3.其它一些Swing多线程编程技巧 首先来看一下整个Dome的Class之间的关系图: 下面按照上图来详细解释各个类的

python3基于TCP实现CS架构文件传输

本文实例为大家分享了python3实现CS架构文件传输的具体代码,供大家参考,具体内容如下 1.目标: 基于tcp实现CS架构的文件传输 指令列表:(1)get:从服务器端下载文件 (2)put:向服务器端上传文件 (3)list:获得服务器端的目录 2.socket模块函数: (1)send和sendall:send的作用是发送TCP数据,返回发送的数据大小.send函数不保证将所有数据全部发送,因此可能需要重复多次才能完成所有数据的发送.sendall的作用是发送完整的TCP数据,成功时返回

Python实现基于HTTP文件传输实例

本文实例讲述了Python实现基于HTTP文件传输的方法.分享给大家供大家参考.具体实现方法如下: 一.问题: 因为需要最近看了一下通过POST请求传输文件的内容 并且自己写了Server和Client实现了一个简单的机遇HTTP的文件传输工具 二.实现代码: Server端: 复制代码 代码如下: #coding=utf-8 from BaseHTTPServer import BaseHTTPRequestHandler import cgi class   PostHandler(Base

基于socket和javaFX简单文件传输工具

本文实例介绍了基于socket和javaFX简单文件传输工具,分享给大家供大家参考,具体内容如下 package application; import java.io.File; import org.james.component.ButtonBox; import org.james.component.FileReceiverGrid; import org.james.component.FileSenderGrid; import javafx.application.Applica