Skip to content

TCP Socket Connection Works on iOS but Fails on Android #223

@9xcoder

Description

@9xcoder

TCP Socket Connection Works on iOS but Fails on Android

Environment

  • Platform: React Native (Expo)
  • Library: react-native-tcp-socket
  • iOS: ✅ Working
  • Android: ❌ Not working
  • Use Case: Scanning subnet for printers on port 9100

Description

I'm implementing a network scanner to discover ESC/POS printers by scanning all IPs in a subnet on port 9100. The implementation works perfectly on iOS but completely fails on Android, even with all necessary permissions configured.

Code Implementation

Function 1: Basic TCP Port Check

function tcpCheckPort(host: string, port: number, timeoutMs = 1500): Promise<boolean> {
    return new Promise((resolve) => {
        let finished = false;
        let timer: ReturnType<typeof setTimeout>;
        
        const done = (ok: boolean, reason: string) => {
            if (finished) return;
            finished = true;
            if (timer) clearTimeout(timer);
            try {
                socket.destroy();
            } catch (e) {
                // ignore destroy errors
            }
            if (ok) {
                console.log(`✅ ${host}:${port} - ${reason}`);
            }
            resolve(ok);
        };
        
        console.log(`[${Platform.OS}] Attempting connection to ${host}:${port}...`);
        
        const socket = TcpSocket.createConnection(
            { host, port },
            () => {
                // Connection established successfully
                done(true, 'connected');
            }
        );
        
        socket.on('error', (err: any) => {
            console.log(`[${Platform.OS}] Connection error to ${host}:${port}: ${err?.message || 'unknown'}`);
            done(false, `error: ${err?.message || 'unknown'}`);
        });
        
        socket.on('close', (hadError: boolean) => {
            if (!finished) {
                done(true, 'closed after accept');
            }
        });
        
        timer = setTimeout(() => {
            done(false, 'timeout');
        }, timeoutMs);
    });
}

Function 2: ESC/POS Command Test

async function testPrinterConnection(host: string, port: number = 9100, timeoutMs = 2000): Promise<boolean> {
    return new Promise((resolve) => {
        let finished = false;
        let timer: ReturnType<typeof setTimeout>;
        
        const done = (ok: boolean, reason: string) => {
            if (finished) return;
            finished = true;
            if (timer) clearTimeout(timer);
            try {
                socket.destroy();
            } catch (e) {
                // ignore destroy errors
            }
            if (ok) {
                console.log(`✅ ${host}:${port} - Printer detected: ${reason}`);
            }
            resolve(ok);
        };
        
        const socket = TcpSocket.createConnection(
            { host, port },
            () => {
                try {
                    // Send ESC/POS initialization command (0x1B 0x40)
                    const escInit = Buffer.from([0x1b, 0x40]); // ESC @
                    socket.write(escInit, 'binary', (err) => {
                        if (err) {
                            done(false, `write error: ${err?.message || 'unknown'}`);
                        } else {
                            done(true, 'ESC/POS command accepted');
                        }
                    });
                    
                    setTimeout(() => {
                        try {
                            socket.end();
                        } catch (e) {
                            // ignore
                        }
                    }, 200);
                } catch (err: any) {
                    done(false, `initialization error: ${err?.message || 'unknown'}`);
                }
            }
        );
        
        socket.on('error', (err: any) => {
            done(false, `error: ${err?.message || 'unknown'}`);
        });
        
        socket.on('close', (hadError: boolean) => {
            if (!finished) {
                done(false, 'closed before write');
            }
        });
        
        timer = setTimeout(() => {
            done(false, 'timeout');
        }, timeoutMs);
    });
}

Android Configuration

app.json

{
  "expo": {
    "android": {
      "usesCleartextTraffic": true,
      "permissions": [
        "INTERNET",
        "ACCESS_NETWORK_STATE",
        "ACCESS_WIFI_STATE",
        "CHANGE_WIFI_STATE"
      ]
    }
  }
}

AndroidManifest.xml (after prebuild)

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

<application
    android:usesCleartextTraffic="true"
    ...>

Observed Behavior

iOS (Working ✅)

  • Successfully scans all IPs in subnet
  • Correctly identifies printers on port 9100
  • Both tcpCheckPort and testPrinterConnection work
  • Connection callbacks fire properly
  • ESC/POS commands are delivered successfully

Android (Not Working ❌)

  • Connections appear to time out
  • No successful connections detected
  • No error messages in adb logcat
  • Both functions fail silently
  • Timeouts occur even with known working printer IPs

Questions

  1. Is there a known issue with TCP socket connections on Android?
  2. Are there additional Android-specific configurations needed beyond cleartext traffic?
  3. Could this be related to IPv4/IPv6 handling differences between iOS and Android?
  4. Should I be using different socket options or events for Android?
  5. Is there a better approach for network scanning on Android?

What I've Tried

  • ✅ Added all necessary permissions
  • ✅ Enabled usesCleartextTraffic
  • ✅ Tested with npx expo prebuild --clean and npx expo run:android
  • ✅ Verified the same printer works from iOS device on the same network
  • ✅ Checked adb logcat for errors (none found)
  • ✅ Tested with different timeout values

Expected Behavior

TCP socket connections should work consistently across both iOS and Android platforms, allowing subnet scanning for printer discovery.

Additional Context

  • Using Expo managed workflow with custom development build
  • Target printer: ESC/POS compatible printer on port 9100
  • Network: Local WiFi subnet (e.g., 192.168.1.0/24)
  • Both devices (iOS/Android) are on the same network

Any guidance on Android-specific requirements or better approaches would be greatly appreciated!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions