多点连接 - 在 Swift 5 中获取文件传输(互联网)速度和文件大小

2024-01-07

我正在点对点传输照片。一切正常,但我无法获得照片(文件)传输速度,例如互联网速度。与MB 一样,文件也被传输。其次我想获取该文件的大小。

我们使用数据格式传递照片MCSession

由于隐私原因,我无法在此处添加项目代码,但我将分享我关注的参考 github 项目。在项目中,我传递字符串,在我的例子中传递照片。所有的事情都是一样的。

我在 Stackoverflow 上查了一下,但没有找到任何准确的答案!

参考项目链接:https://github.com/YogeshPateliOS/MultipeerConnectivity-.git https://github.com/YogeshPateliOS/MultipeerConnectivity-.git

谢谢你!


TLDR:如果您不想阅读冗长的解释并直接进入代码,下面的所有想法都汇集在一起​​,可以通过下载我的进行测试公共存储库 https://github.com/shawn-frank/MultiPeer-Progress-iOS其中有评论来解释这一切。

所以这是我关于如何实现这一目标的建议

查看您的代码后,我发现您正在使用以下函数来发送数据

func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode)

这没有任何问题,您确实可以将 UIImage 转换为 Data 对象并以这种方式发送它,它会起作用。

但是,我认为您无法跟踪进度,并且 MultiPeer 不会为您提供任何委托来使用此方法跟踪进度。

相反,您还有另外两个选择。你可以使用

func session(_ session: MCSession, 
             didFinishReceivingResourceWithName resourceName: String, 
             fromPeer peerID: MCPeerID, 
             at localURL: URL?, 
             withError error: Error?)

或者你可以使用

func startStream(withName streamName: String, 
                 toPeer peerID: MCPeerID) throws -> OutputStream

我将使用第一个选项,因为它更简单,但我认为流选项会给你更好的结果。您可以在此处阅读这两个选项:

发送资源 https://developer.apple.com/documentation/multipeerconnectivity/mcsession/1407056-sendresource(我们将实现这一点)

流媒体 https://developer.apple.com/documentation/multipeerconnectivity/mcsession/1407071-startstream

Step 1

I made some UI updates to your original code by adding a UIImageView to show the transferred image to the advertiser(guest) and a UIButton to start the file transfer from the browser(host) MultiPeer Connectivity File Transfer Progress Sample Project Storyboard

UIImageView 有一个名为@IBOutlet weak var imageView: UIImageView!以及 UIButton 的操作@IBAction func sendImageAsResource(_ sender: Any)

我还在项目中添加了一个名为 image2.jpg 的图像,我们将从主机发送到访客。

Step 2

我还声明了一些额外的变量

// Progress variable that needs to store the progress of the file transfer
var fileTransferProgress: Progress?
    
// Timer that will be used to check the file transfer progress
var checkProgressTimer: Timer?

// Used by the host to track bytes to receive
var bytesExpectedToExchange = 0
    
// Used to track the time taken in transfer, this is for testing purposes.
// You might get more reliable results using Date to track time
var transferTimeElapsed = 0.0

Step 3

通过分别点击“访客”和“主机”按钮,正常设置主机和访客。之后,点击Send image as resource主机上的按钮,主机的操作执行如下:

// A new action added to send the image stored in the bundle
@IBAction func sendImageAsResource(_ sender: Any) 
{        
        // Call local function created
        sendImageAsResource()
}

func sendImageAsResource()
{
        // 1. Get the url of the image in the project bundle.
        // Change this if your image is hosted in your documents directory
        // or elsewhere.
        //
        // 2. Get all the connected peers. For testing purposes I am only
        // getting the first peer, you might need to loop through all your
        // connected peers and send the files individually.
        guard let imageURL = Bundle.main.url(forResource: "image2",
                                             withExtension: "jpg"),
              let guestPeerID = mcSession.connectedPeers.first else {
            return
        }
        
        // Retrieve the file size of the image
        if let fileSizeToTransfer = getFileSize(atURL: imageURL)
        {
            bytesExpectedToExchange = fileSizeToTransfer
            
            // Put the file size in a dictionary
            let fileTransferMeta = ["fileSize": bytesExpectedToExchange]
            
            // Convert the dictionary to a data object in order to send it via
            // MultiPeer
            let encoder = JSONEncoder()
            
            if let JSONData = try? encoder.encode(fileTransferMeta)
            {
                // Send the file size to the guest users
                try? mcSession.send(JSONData, toPeers: mcSession.connectedPeers,
                                    with: .reliable)
            }
        }
        
        // Ideally for best reliability, you will want to develop some logic
        // for the guest to respond that it has received the file size and then
        // you should initiate the transfer to that peer only after you receive
        // this confirmation. For now, I just add a delay so that I am highly
        // certain the guest has received this data for testing purposes
        DispatchQueue.main.asyncAfter(deadline: .now() + 1)
        { [weak self] in
            self?.initiateFileTransfer(ofImage: imageURL, to: guestPeerID)
        }
    }
    
func initiateFileTransfer(ofImage imageURL: URL, to guestPeerID: MCPeerID)
{
        // Initialize and fire a timer to check the status of the file
        // transfer every 0.1 second
        checkProgressTimer = Timer.scheduledTimer(timeInterval: 0.1,
                                                  target: self,
                                                  selector: #selector(updateProgressStatus),
                                                  userInfo: nil,
                                                  repeats: true)
        
        // Call the sendResource function and send the image from the bundle
        // keeping hold of the returned progress object which we need to keep checking
        // using the timer
        fileTransferProgress = mcSession.sendResource(at: imageURL,
                                          withName: "image2.jpg",
                                          toPeer: guestPeerID,
                                          withCompletionHandler: { (error) in
                                            
                                            // Handle errors
                                            if let error = error as NSError?
                                            {
                                                print("Error: \(error.userInfo)")
                                                print("Error: \(error.localizedDescription)")
                                            }
                                            
                                          })
}

func getFileSize(atURL url: URL) -> Int?
{
        let urlResourceValue = try? url.resourceValues(forKeys: [.fileSizeKey])
        
        return urlResourceValue?.fileSize
}

Step 4

下一个功能由主机和访客使用。来宾会做出一些稍后有意义的事情,但是对于主机来说,在步骤 3 中,您在启动文件传输后存储了一个进度对象,并且启动了一个每 0.1 秒触发一次的计时器,因此现在实现计时器查询该进度对象,在UILabel中显示主机端的进度和数据传输状态

/// Function fired by the local checkProgressTimer object used to track the progress of the file transfer
/// Function fired by the local checkProgressTimer object used to track the progress of the file transfer
@objc
func updateProgressStatus()
{
        // Update the time elapsed. As mentioned earlier, a more reliable approach
        // might be to compare the time of a Date object from when the
        // transfer started to the time of a current Date object
        transferTimeElapsed += 0.1
        
        // Verify the progress variable is valid
        if let progress = fileTransferProgress
        {
            // Convert the progress into a percentage
            let percentCompleted = 100 * progress.fractionCompleted
            
            // Calculate the data exchanged sent in MegaBytes
            let dataExchangedInMB = (Double(bytesExpectedToExchange)
                                     * progress.fractionCompleted) / 1000000
            
            // We have exchanged 'dataExchangedInMB' MB of data in 'transferTimeElapsed'
            // seconds. So we have to calculate how much data will be exchanged in 1 second
            // using cross multiplication
            // For example:
            // 2 MB in 0.5s
            //  ?   in  1s
            // MB/s = (1 x 2) / 0.5 = 4 MB/s
            let megabytesPerSecond = (1 * dataExchangedInMB) / transferTimeElapsed
            
            // Convert dataExchangedInMB into a string rounded to 2 decimal places
            let dataExchangedInMBString = String(format: "%.2f", dataExchangedInMB)
            
            // Convert megabytesPerSecond into a string rounded to 2 decimal places
            let megabytesPerSecondString = String(format: "%.2f", megabytesPerSecond)
            
            // Update the progress an data exchanged on the UI
            numberLabel.text = "\(percentCompleted.rounded())% - \(dataExchangedInMBString) MB @ \(megabytesPerSecondString) MB/s"
            
            // This is mostly useful on the browser side to check if the file transfer
            // is complete so that we can safely deinit the timer, reset vars and update the UI
            if percentCompleted >= 100
            {
                numberLabel.text = "Transfer complete!"
                checkProgressTimer?.invalidate()
                checkProgressTimer = nil
                transferTimeElapsed = 0.0
            }
        }
}

Step 5

通过实现以下委托方法来处理接收者(来宾)端的文件接收

func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)
{
        // Check if the guest has received file transfer data
        if let fileTransferMeta = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Int],
           let fileSizeToReceive = fileTransferMeta["fileSize"]
        {
            // Store the bytes to be received in a variable
            bytesExpectedToExchange = fileSizeToReceive
            print("Bytes expected to receive: \(fileSizeToReceive)")
            return
        }
}

func session(_ session: MCSession,
             didStartReceivingResourceWithName resourceName: String,
             fromPeer peerID: MCPeerID,
             with progress: Progress) 
{
        
        // Store the progress object so that we can query it using the timer
        fileTransferProgress = progress
        
        // Launch the main thread
        DispatchQueue.main.async { [unowned self] in
            
            // Fire the timer to check the file transfer progress every 0.1 second
            self.checkProgressTimer = Timer.scheduledTimer(timeInterval: 0.1,
                                                           target: self,
                                                           selector: #selector(updateProgressStatus),
                                                           userInfo: nil,
                                                           repeats: true)
        }
}

func session(_ session: MCSession,
             didFinishReceivingResourceWithName resourceName: String,
             fromPeer peerID: MCPeerID,
             at localURL: URL?,
             withError error: Error?) 
{
        
        // Verify that we have a valid url. You should get a url to the file in
        // the tmp directory
        if let url = localURL
        {
            // Launch the main thread
            DispatchQueue.main.async { [weak self] in
                
                // Call a function to handle download completion
                self?.handleDownloadCompletion(withImageURL: url)
            }
        }
}

/// Handles the file transfer completion process on the advertiser/client side
    /// - Parameter url: URL of a file in the documents directory
    func handleDownloadCompletion(withImageURL url: URL) 
{ 
        // Debugging data
        print("Full URL: \(url.absoluteString)")
        
        // Invalidate the timer
        checkProgressTimer?.invalidate()
        checkProgressTimer = nil
        
        // Set the UIImageView with the downloaded image
        imageView.image = UIImage(contentsOfFile: url.path)
}

Step 6

运行代码并这是最终结果(上传到youtube) https://youtu.be/Ug4gJ67B6Vw在传输完成后,在来宾端显示进度和文件,并且在主机端也会显示相同的进度。

Step 7

我没有实现这个,但我相信这一点很简单:

  1. 文件大小可以从主机计算出来,并且可以作为关于预期大小的消息发送给访客

  2. 您可以计算已被删除的文件的大约百分比 通过将进度%乘以文件大小来下载

  3. 速度可以根据传输开始以来下载的数据量/经过的时间来计算

如果您觉得这些计算并不简单,我可以尝试添加此代码。

Update

我已经更新了上述代码示例、github 存储库和视频,以包含最后 3 个步骤,最终结果如下:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多点连接 - 在 Swift 5 中获取文件传输(互联网)速度和文件大小 的相关文章

随机推荐

  • 谷歌应用程序引擎 JDO 3

    Google 刚刚为 Google App Engine 推出了 JDO 3 0 它使用 DataNucleus 2 0 我想使用它 因为它可以方便地支持无主关系 我已经尝试了好几天了 但我不知道如何将它与 Google Eclipse 插
  • Swift:UIControls 的 UIStackView 具有不触发的选择器方法

    介绍 我正在创建一个使用自定义视图的应用程序 其中我有一个 UIStackView 来整理 5 个 UIControl 当用户点击其中一个 UIControl 时 下划线会变为动画 并在点击的 UIControl 下方滑动 但是 由于某种原
  • 在圆弧中心绘制位图?

    在我的自定义视图中 我绘制了多个填充的Arc像这样 canvas drawArc oval startAngle sweepAngle true sectorPaint 现在 我想在圆弧的中心绘制一个图标 我是这样开始的 Bitmap bi
  • Media.getduration 返回 -1

    我正在尝试获取音频文件的持续时间 为此 我使用以下代码 fntReproducir function obtenemos una instancia del elemento que contiene la info de cancion
  • Pythonic 方法来计算列表中特定邻居的数量

    我有一个清单 例如 0 0 1 0 0 1 0 我想知道最有效的计算方法是什么1 gt 0过渡 例如 在本例中 答案为 2 在 2 3 和 5 6 位置 我尝试了以下方法 stat 0 0 1 0 0 1 0 pair1 stat 1 pa
  • 如何分析我的代码?

    我想知道如何分析我的代码 我已经阅读了文档 但由于没有给出示例 我无法从中得到任何信息 我有一个很大的代码 并且花费了很多时间 因此我想分析并提高其速度 我还没有在方法中编写我的代码 中间有一些代码 但不完全 我的代码中没有任何 main
  • 当违反 Swing 的线程策略时会发生什么?

    在过去的几年里 我主要在 Eclipse 中完成 UI 开发 Eclipse 在线程访问方面非常保守 任何从 UI 线程外部更改 UI 小部件上的属性 例如颜色 文本 的尝试都会引发异常 我现在正在查看 Swing 中的一个现有程序 该程序
  • 为什么在向根工作区 package.json 添加依赖项时,yarn 会发出警告

    每当我将依赖项添加到工作区项目的根目录时 e g yarn add assets webpack plugin D 我收到以下错误 运行此命令会将依赖项添加到工作区根目录而不是工作区本身 这可能不是您想要的 如果您真的这么想 请通过使用 W
  • 如何删除无关紧要的分类交互项 Python StatsModel

    在统计模型中 添加交互项很容易 然而 并非所有相互作用都很重要 我的问题是如何去掉那些无关紧要的东西 例如库特尼机场 coding utf 8 import pandas as pd import statsmodels formula a
  • 如何在Java中以线程安全的方式使用mkdirs?

    在经历了 mkdirs 的问题并浏览了互联网之后 我得到的印象是 mkdirs 存在线程安全问题 当多个线程可能尝试创建类似的文件结构时 有没有办法确保正确创建目录 Thanks 就我而言 我将在 Android 上使用它 我不确定 And
  • 字典键在列表上匹配;获取键/值对

    在Python中 我有一个元素列表 my list 和一个字典 my dict 其中一些键与 my list 匹配 我想搜索字典并检索与 my list 元素匹配的键的键 值对 我试过这个 if any x in my dict for x
  • Swift - 如何获取具有初始值设定项的数组的索引?

    我正在制作一个 IOS 应用程序 我使用 swift 我有一个数组 需要它才能从 CoreData 数据库中检索数据 var myList Array
  • 如何解析带有特殊字符的xml?专门用于 & 符号

    我从文本框中获取数据并将其更改为 xml 格式并将其存储在数据库中 为了允许特殊字符 我编写了 javascript 函数 用其 html 实体替换特殊字符 quot amp lt lt gt gt 对于 引号 小于 大于 其工作正常 对于
  • 如何在 Kivy 中将 android.bluetooth.socket 输入流转换为 python 字符串?

    我正在开发 Kivy 应用程序 由于我想从蓝牙适配器获取数据 因此我使用了下面的代码 from kivy app import App from kivy uix label import Label from kivy uix scatt
  • 隐藏或加密文件的 URL?

    大家好 感谢您抽出时间 我只想说 尽管我不是 php 新手 但我还不知道所有事情 而且我仍然缺乏一些知识来解决其中一些问题 我目前的困境是 我有一个包含用户制作的歌曲的数据库 其中包含各种信息 包括所述歌曲的位置 我让它工作的方式是我有一个
  • 如何让 Symfony2 与 SQL Azure 连接?

    有人已经在这个主题上取得了成功吗 我已经尝试编写一个自定义 Doctrine 平台 但它并不像我想象的那么容易 顺便说一句 这是标有 sql azure symfony 2 0 的第一个问题 check https github com b
  • 为什么将 respond_with 从 Rails 4.2 中删除到它自己的 gem 中?

    In 导轨 4 2 http weblog rubyonrails org 2014 8 20 Rails 4 2 beta1 respond with 已从核心移出到responders gem 中 测试版发行说明 respond wit
  • 从另一个视图调用我的服务器上的 API

    所以 这里的情况有点奇怪 我有一个 Django 项目 使用 TastyPie 来支持其 API 和一些视图 模板 这些视图 模板将用于为各个站点的插件提供支持 我没有将此插件构建为标准 Django 模板 而是被要求使用我们的 API 来
  • 使用三元运算符来初始化引用变量?

    抛开所有可维护性和阅读问题不谈 这些代码行会产生未定义的行为吗 float a 0 b 0 float x some condition a b x 5 cout lt lt a lt lt lt lt b 不 没关系 它不会在此代码中创建
  • 多点连接 - 在 Swift 5 中获取文件传输(互联网)速度和文件大小

    我正在点对点传输照片 一切正常 但我无法获得照片 文件 传输速度 例如互联网速度 与MB 一样 文件也被传输 其次我想获取该文件的大小 我们使用数据格式传递照片MCSession 由于隐私原因 我无法在此处添加项目代码 但我将分享我关注的参