无网络环境下多设备向单iPhone传文件的Socket服务器方案咨询
单台iPhone作为Socket接收端的跨设备文件传输方案
一、iOS端原生Socket服务器实现(基于GCDAsyncSocket)
GCDAsyncSocket是iOS/macOS生态下成熟的异步Socket库,适合快速搭建稳定的Socket服务:
- 集成方式:通过CocoaPods添加依赖,Podfile中写入
pod 'GCDAsyncSocket',执行pod install完成集成。 - 核心实现步骤:
- 初始化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() } } }
- 初始化Socket服务器并设置代理:
二、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服务,跨端兼容性更好:
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)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
相关产品推荐
相关产品推荐

