You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无网络环境下多设备向单iPhone传文件的Socket服务器方案咨询

单台iPhone作为Socket接收端的跨设备文件传输方案

一、iOS端原生Socket服务器实现(基于GCDAsyncSocket)

GCDAsyncSocket是iOS/macOS生态下成熟的异步Socket库,适合快速搭建稳定的Socket服务:

  1. 集成方式:通过CocoaPods添加依赖,Podfile中写入pod 'GCDAsyncSocket',执行pod install完成集成。
  2. 核心实现步骤:
    • 初始化Socket服务器并设置代理:
      import GCDAsyncSocket
      
      class SocketServer: NSObject, GCDAsyncSocketDelegate {
          private var serverSocket: GCDAsyncSocket!
          private var activeSockets = [GCDAsyncSocket]()
          private var fileHandle: FileHandle?
          private var expectedFileSize: Int64 = 0
          private var receivedBytes: Int64 = 0
      
          override init() {
              super.init()
              serverSocket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main)
          }
      
          func startListening(port: UInt16 = 8888) {
              do {
                  try serverSocket.accept(onPort: port)
                  print("Socket服务器已启动,监听端口\(port)")
              } catch {
                  print("启动失败:\(error.localizedDescription)")
              }
          }
      }
      
    • 处理客户端连接与文件接收:
      // 代理方法:新客户端连接回调
      func socket(_ sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) {
          activeSockets.append(newSocket)
          // 先读取文件头(格式:文件名|文件大小,以换行符结尾)
          newSocket.readData(to: "\n".data(using: .utf8)!, withTimeout: -1, tag: 0)
      }
      
      // 代理方法:接收数据回调
      func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
          if tag == 0 {
              // 解析文件头
              guard let header = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
                    let components = header.split(separator: "|").map({ String($0) }),
                    components.count == 2,
                    let fileSize = Int64(components[1]) else {
                  sock.disconnect()
                  return
              }
              expectedFileSize = fileSize
              receivedBytes = 0
              let fileName = components[0]
              // 创建文件存储路径
              let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
              let fileURL = documentsDir.appendingPathComponent(fileName)
              // 创建空文件
              FileManager.default.createFile(atPath: fileURL.path, contents: nil)
              fileHandle = FileHandle(forWritingAtPath: fileURL.path)
              // 开始接收文件内容(每次最多读取1MB)
              sock.readData(toLength: UInt(min(1024 * 1024, expectedFileSize)), withTimeout: -1, tag: 1)
          } else if tag == 1 {
              // 写入文件数据
              fileHandle?.write(data)
              receivedBytes += Int64(data.count)
              if receivedBytes < expectedFileSize {
                  // 继续接收剩余数据
                  let remaining = expectedFileSize - receivedBytes
                  sock.readData(toLength: UInt(min(1024 * 1024, remaining)), withTimeout: -1, tag: 1)
              } else {
                  // 文件接收完成
                  fileHandle?.closeFile()
                  print("文件接收完成:\(receivedBytes)字节")
                  sock.disconnect()
              }
          }
      }
      

二、Android端Socket客户端实现

Android端直接使用原生Socket API即可完成文件发送:

import java.io.*
import java.net.Socket

class SocketFileClient {
    fun sendFile(serverIp: String, port: Int, filePath: String) {
        Thread {
            try {
                val socket = Socket(serverIp, port)
                val file = File(filePath)
                val outputStream = socket.getOutputStream()
                val dataOutputStream = DataOutputStream(outputStream)

                // 发送文件头:文件名|文件大小(换行符结尾)
                val header = "${file.name}|${file.length()}\n"
                dataOutputStream.write(header.toByteArray())
                dataOutputStream.flush()

                // 分块发送文件内容(每次1MB)
                val buffer = ByteArray(1024 * 1024)
                val fileInputStream = FileInputStream(file)
                var bytesRead: Int
                while (fileInputStream.read(buffer).also { bytesRead = it } != -1) {
                    dataOutputStream.write(buffer, 0, bytesRead)
                    dataOutputStream.flush()
                }

                fileInputStream.close()
                dataOutputStream.close()
                socket.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }.start()
    }
}

三、简化方案:基于WebSocket复用现有HTTP服务

既然你已经用FlyingFox搭建了HTTP服务器,可以直接扩展WebSocket支持,无需单独维护Socket服务,跨端兼容性更好:

  1. iOS端扩展FlyingFox支持WebSocket:
    FlyingFox原生支持WebSocket,只需实现对应处理逻辑:

    import FlyingFox
    
    struct WebSocketFileHandler: WebSocketHandler {
        func onOpen(socket: WebSocket) {
            print("客户端WebSocket连接建立")
        }
    
        func onMessage(socket: WebSocket, message: WebSocketMessage) {
            static var fileHandle: FileHandle?
            static var expectedFileSize: Int64 = 0
            static var receivedBytes: Int64 = 0
    
            switch message {
            case .text(let header):
                // 解析文件头(同Socket方案格式)
                guard let components = header.split(separator: "|").map({ String($0) }),
                      components.count == 2,
                      let fileSize = Int64(components[1]) else {
                    socket.close(code: .invalidPayloadData, reason: "无效文件头")
                    return
                }
                expectedFileSize = fileSize
                receivedBytes = 0
                let fileName = components[0]
                // 创建文件路径并初始化文件句柄
                let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
                let fileURL = documentsDir.appendingPathComponent(fileName)
                FileManager.default.createFile(atPath: fileURL.path, contents: nil)
                fileHandle = FileHandle(forWritingAtPath: fileURL.path)
            case .binary(let data):
                // 写入文件数据
                fileHandle?.write(data)
                receivedBytes += Int64(data.count)
                if receivedBytes >= expectedFileSize {
                    fileHandle?.closeFile()
                    print("文件接收完成")
                    socket.close(code: .normalClosure, reason: "传输完成")
                }
            }
        }
    
        func onClose(socket: WebSocket, code: WebSocketCloseCode, reason: String?) {
            print("WebSocket连接关闭:\(reason ?? "无原因")")
        }
    }
    
    // 注册WebSocket路由
    let server = HTTPServer()
    server.register(handler: WebSocketFileHandler(), forPath: "/file-transfer")
    try server.start(port: 8080)
    
  2. Android端用OkHttp实现WebSocket客户端:

    import okhttp3.OkHttpClient
    import okhttp3.Request
    import okhttp3.WebSocket
    import okhttp3.WebSocketListener
    import java.io.FileInputStream
    
    class WebSocketFileSender {
        fun sendFile(serverUrl: String, filePath: String) {
            val client = OkHttpClient()
            val request = Request.Builder().url(serverUrl).build()
            val file = File(filePath)
            val listener = object : WebSocketListener() {
                override fun onOpen(webSocket: WebSocket, response: okhttp3.Response) {
                    // 发送文件头
                    val header = "${file.name}|${file.length()}"
                    webSocket.send(header)
                    // 分块发送二进制数据
                    val buffer = ByteArray(1024 * 1024)
                    val inputStream = FileInputStream(file)
                    var bytesRead: Int
                    while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                        webSocket.send(buffer, 0, bytesRead)
                    }
                    inputStream.close()
                    webSocket.close(1000, "文件发送完成")
                }
            }
            client.newWebSocket(request, listener)
        }
    }
    

四、关键优化与注意事项

  • 局域网服务发现:无需手动输入IP,iOS端用Bonjour发布服务,Android端用NSD(Network Service Discovery)搜索服务,自动获取服务器地址和端口。
  • 后台保活:iOS端需开启后台模式中的VOIP或远程通知权限,避免APP退到后台后Socket连接被系统断开;Android端需申请WAKE_LOCK权限,防止设备休眠中断传输。
  • 可靠性保障:添加MD5校验和,文件传输完成后两端校验哈希值,确保文件完整性;实现简单的重传机制,针对丢包的块重新发送。
  • 权限处理:iOS端需申请文件访问权限(如读取相册、Documents目录),Android端需申请READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE权限(Android 13+需READ_MEDIA_IMAGES等细分权限)。

内容的提问来源于stack exchange,提问作者BinoyDarvin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 14:40:33