react-native-tcp-socket icon indicating copy to clipboard operation
react-native-tcp-socket copied to clipboard

TCP Socket Connection Works on iOS but Fails on Android

Open 9xcoder opened this issue 1 week ago • 0 comments

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!

9xcoder avatar Dec 03 '25 09:12 9xcoder