How to Transfer Files Between Android and iOS using TCP?

Hi Developers,

I am going to share the best way to transfer files between Android and iOS. I shared the full code for working on cross-platform file sharing (between Android and iOS) issues and solutions.

When I worked on an Android app project, I was restricted from using Lollipop OS due to Java 8 did not support it. So, you may or may not face the same issue depending on what you are trying to achieve.

First of all, Let’s define what jReto and SReto are.

  1. jReto: P2P Framework for real-time collaboration in Java.
  2. SReto: P2P Framework for real-time collaboration in Swift.

Reto is an extensible P2P network platform used in Java 8 and Swift. It delivers the APIs in both languages.

The features of this frame are:

  • Peer discovery
  • Putting connections between peers
  • Acting cancellable data transfers
  • Features assistance for routed connections (i.e., peers that Can’t directly communicate could Nevertheless Be found and utilize other peers to forward information)

This module allows peers to discover one another and discuss over the Web whether a RemoteP2P server is necessary.

  1. WLAN module: Enables peer discovery and connectivity in LAN (Local Area Networks) by using Bonjour for detection (locating) and regular TCP/IP connections for information transfers.
  2. RemoteP2PModule: Uses an internet server to facilitate communication between peers. This module enables peers to discover one another and discuss over the Web; Still, a RemoteP2P server is required.

Now! Let’s shorten it down in our example.

jReto (Android) /SReto ( iOS ) + Bonjour service: Finding devices and sending objects.

We used Bonjour Discovery to find out nearby running the same services to send and receive files for cross-platform file sharing. We can act as servers and clients by broadcasting and seeking service usage.

Here I prepared a code for it. So, you can use it directly in your project or application. i.e. android is a broadcasting ( advertising ) service, which can be discovered by the client.

Android Java Broadcaster:


// 1. Create the WlanModule
WlanModule wlanModule = new WlanModule("ExampleType");

// 2. Create the LocalPeer
LocalPeer localPeer = new LocalPeer(Arrays.asList(SatvaTutorial), Executors.newSingleThreadExecutor());

// 3. Starting the LocalPeer
localPeer.start(
    discoveredPeer -> System.out.println("Discovered peer: " + discoveredPeer),
    removedPeer -> System.out.println("Removed peer: " + removedPeer),
    (peer, incomingConnection) -> System.out.println("Received incoming connection: " + incomingConnection + " from peer: " + peer)
);

iOS Swift Broadcaster:


func startBroadcast() {
    socket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main)
    do {
        try socket.accept(onPort: 0)
        service = NSNetService(domain: "", type: "_test._tcp", name: "SatvaTutorial", port: Int32(socket.localPort))
    } catch {
        print("Error listening on port")
    }
    
    if let service = service {
        service.delegate = self
        service.publish()
    }
}

func netServiceDidPublish(sender: NSNetService) {
    guard let service = service else {
        return
    }
    print("Successfully published on port \(service.port) / domain: \(service.domain) / type: \(service.type) / name: \(service.name)")
}

  • Ensure that your class conforms to the required protocols for GCDAsyncSocketDelegate and NSNetServiceDelegate.
  • Make sure to import necessary frameworks at the top of your Swift file:

import CocoaAsyncSocket
import Foundation

  • Only the same name service can be discovered by the client to connect with.
  • Once we find a broadcasting service from the server we can go ahead and can connect to the server.

func netServiceBrowser (browser: NSNetServiceBrowser , didFindService service: NSNetService , moreComing: Bool ) {
services . addObject (service)
connect ()
}
func connect () {
service = services . firstObject ! ash ! NSNetService
service . delegate = self
service . resolveWithTimeout ( 30.0 )
}
  • i.e. Android broadcasted its service and iPhone ( which is seeking for service to get connected with ) found service on the same network to connect with.

func netServiceDidResolveAddress(sender: NSNetService) {
    if connectWithService(sender) {
        print("Did connect with service")
    } else {
        print("Error connecting with service")
    }
}
  • Once the connection was established, we tried to share files from one platform to another ( here iOS to Android ).

Receiving Data on Android:


someConnection.setOnTransfer((connection, transfer) -> {
    // 2. Configuring a transfer to let you handle data as it is received, instead of letting the transfer buffer all data
    transfer.setOnPartialData((transfer, data) -> {
        System.out.println("Received a chunk of data!");
    });

    // 3. Registering for progress updates
    transfer.setOnProgress(transfer -> {
        System.out.println("Current progress: " + transfer.progress + " of " + transfer.length);
    });
});

// 4. Sending a transfer example
let transfer = someConnection.send(someData.length, range -> {
    return somehowProvideDataForRange(range);
});

// 5. Registering for progress updates
transfer.setOnProgress(transfer -> {
    System.out.println("Current progress: " + transfer.progress + " of " + transfer.length);
});

Sending Data from iOS:


func connectWithService(service: NSNetService) -> Bool {
    var isConnected = false
    guard let addresses = service.addresses else { return false }

    if socket == nil || !socket.isConnected {
        socket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main)

        // Connect
        var count = 0
        while !isConnected && count < addresses.count {
            let address = addresses[count] as! NSData
            count += 1
            do {
                try socket.connect(toAddress: address as Data)
                isConnected = true
            } catch {
                print("Failed to connect")
            }
        }
    } else {
        isConnected = socket.isConnected
    }
    
    return isConnected
}

func sendPacket(packet: Packet) {
    let packetData = NSKeyedArchiver.archivedData(withRootObject: packet)
    let packetDataLength = packetData.count
    var buffer = NSMutableData(bytes: &packetDataLength, length: MemoryLayout.size)
    buffer.append(packetData)
    socket.write(buffer as Data, withTimeout: -1, tag: 0)
}
  • We observed that files that are being transferred on a TCP connection are chunked into small bytes which created an issue on the Android side to read ( due to a heap memory issue ).

someConnection.setOnTransfer(
(connection, transfer) ->
// 2. Configuring a transfer to let you handle data as it is received, instead of letting the transfer buffer all data
transfer.setOnPartialData((transfer, data) -> System.out.println(“Received a chunk of data!”));
// 3. Registering for progress updates
transfer.setOnProgress(transfer -> System.out.println(“Current progress: “+transfer.progress+” of “+transfer.length));
);
// 4. Sending a transfer example
let transfer = someConnection.send(someData.length, range -> somehowProvideDataForRange(range));
// 5. Registering for progress updates
transfer.setOnProgress(transfer -> System.out.println(“Current progress: “+transfer.progress+” of “+transfer.length));

func socket(sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) {
    print("Socket accepted")
    socket = newSocket
    socket.delegate = self
    socket.readData(toLength: UInt(MemoryLayout.size), withTimeout: -1, tag: 1)
}

func socket(sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
    if tag == 1 {
        var bodyLength: Int16 = 0
        data.copyBytes(to: &bodyLength, count: MemoryLayout.size)
        print("Header received with body length: \(bodyLength)")
        socket.readData(toLength: UInt(bodyLength), withTimeout: -1, tag: 2)
    } else if tag == 2 {
        if let packet = NSKeyedUnarchiver.unarchiveObject(with: data) as? Packet {
            PacketHandler.handlePacket(packet)
        }
        socket.readData(toLength: UInt(MemoryLayout.size), withTimeout: -1, tag: 1)
    }
}
  • Which did not help share a large file between two devices.

Final Workaround:

  • After facing the above issue, we tried many ways including the web socket as well, but due to its file transfer speed issue wasn’t that handy technique to implement.
  • We decided to simply create an observer and listener with the same connection name.
  • After that, we tried to connect and shared a file using a fast socket from iOS to Android.

func connectWithService(service: NSNetService) -> Bool {
    var isConnected = false
    guard let addresses = service.addresses else {
        return isConnected
    }
    
    if socket == nil || !socket.isConnected {
        socket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main)
        
        // Connect
        for address in addresses {
            do {
                try socket.connect(toAddress: address as Data)
                isConnected = true
                break // Exit the loop if successfully connected
            } catch {
                print("Failed to connect: \(error.localizedDescription)")
            }
        }
    } else {
        isConnected = socket.isConnected
    }
    return isConnected
}

func sendPacket(packet: Packet) {
    let packetData = NSKeyedArchiver.archivedData(withRootObject: packet)
    let packetDataLength = packetData.count
    var buffer = Data(capacity: MemoryLayout.size + packetDataLength)
    var length = Int16(packetDataLength)
    
    // Append the length of the packet data
    buffer.append(Data(bytes: &length, count: MemoryLayout.size))
    // Append the actual packet data
    buffer.append(packetData)
    
    socket.write(buffer, withTimeout: -1, tag: 0)
}

@IBAction func btnSendImage(_ sender: Any) {
    do {
        let client = FastSocket(host: "192.168.43.1", andPort: "8080")
        
        // Attempt to connect to the server
        if client.connect() {
            print("Server connected")
            
            // Load the image and convert it to PNG data
            if let imageData = UIImage(named: "samplefile.png")?.pngData() {
                // Send image data as bytes
                imageData.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
                    if let baseAddress = bytes.baseAddress {
                        let sentBytes = client.sendBytes(baseAddress.assumingMemoryBound(to: UInt8.self), count: imageData.count)
                        print("Bytes sent: \(sentBytes)")
                    }
                }
            } else {
                print("Failed to load image data.")
            }
        } else {
            print("Failed to connect to the server.")
        }
    } catch {
        print("Error occurred: \(error.localizedDescription)")
    }
}
  • Which turned out to be helpful.
  • The only issue we found was that we could not pass the header file along with the file to identify the file type.

Guys, I hope you like my article. I intend to save you time and help other developers to solve the same issues during mobile application development.

Let’s have a meeting with our mobile app developers and work together on your problems.

Article by

Chintan Prajapati

Chintan Prajapati, a seasoned computer engineer with over 20 years in the software industry, is the Founder and CEO of Satva Solutions. His expertise lies in Accounting & ERP Integrations, RPA, and developing technology solutions around leading ERP and accounting software, focusing on using Responsible AI and ML in fintech solutions. Chintan holds a BE in Computer Engineering and is a Microsoft Certified Professional, Microsoft Certified Technology Specialist, Certified Azure Solution Developer, Certified Intuit Developer, and Xero Developer.Throughout his career, Chintan has significantly impacted the accounting industry by consulting and delivering integrations and automation solutions that have saved thousands of man-hours. He aims to provide readers with insightful, practical advice on leveraging technology for business efficiency.Outside of his professional work, Chintan enjoys trekking and bird-watching. Guided by the philosophy, "Deliver the highest value to clients". Chintan continues to drive innovation and excellence in digital transformation strategies from his base in Ahmedabad, India.