Rapsssito / react-native-tcp-socket

React Native TCP socket API for Android, iOS & macOS with SSL/TLS support.
MIT License
315 stars 80 forks source link

Connection error never emitted on iOS #53

Closed mi-mazouz closed 3 years ago

mi-mazouz commented 4 years ago

Description

Hello!!

I'm trying to create a tcp connection to an incorrect host (the ip is unreachable).

Here is my code:


...
private _device = null

private async openConnection(
        host,
        dataListenerCallback,
    ): Promise<void> {
        if (this._device) {
              this._device.destroy();
        }

        return new Promise((resolve, reject) => {
            this._device = net.createConnection({
                port: 53333,
                host,
                timeout: 1000,
            }, (socket) => {
                console.log('connected');
                resolve();
            });

            this._device.on('connect', () => {
                console.log('connect');
            });

            this._device.on('connection', () => {
                console.log('connnection');
            });

            this._device.on('error', (error) => {
                console.log('Error', error);
                reject();
            });

            this._device.on('data', dataListener);

            this._device.on('close', () => {
                console.log('Connection closed!');
            });
        });

Current behavior

this function never rejects or resolves.

Expected behavior

I expected to catch a connection error in the error event listener.

Relevant information

| react-native | 0.61.5 | | react-native-tcp-socket | 3.7.1 |

Rapsssito commented 4 years ago

@mi-mazouz, thanks for the feedback! Currently, there is not official timeout support. I am working on it. There is JS code that acts as a timeout listener/event, but it is not fully tested and might not work in all circumstances. In the meantime, this is a possible workaround:

function connect(options) {
       return new Promise((resolve, reject) => {
            const tcpTimeout = setTimeout(() => {
                     reject('Timeout');
            }, 1000);
            const tcpSocket = net.createConnection(options, () => {
                    clearTimeout(tcpTimeout);
                    resolve();
                }
            );
      });
}
mi-mazouz commented 4 years ago

ok for the timeout but what about the connection error? What should happen if the connection fails?

Rapsssito commented 4 years ago

@mi-mazouz, if you are setting an "incorrect" IP, you should get an 'error' event with a message along the lines of "Host unreachable".

mi-mazouz commented 4 years ago

That's what I expected but I don't get this error

Rapsssito commented 4 years ago

@mi-mazouz, can you reproduce the bug using the example code?

mi-mazouz commented 4 years ago

@Rapsssito Yes you should be able to reproduce it using my code

Rapsssito commented 4 years ago

@mi-mazouz, I meant if you could reproduce the issue using the example code provided in the repository. You can comment out all the server references and test the client with your "incorrect" IP.

mi-mazouz commented 4 years ago

same with this code, nothing happened:


import React from 'react';
import {
    ScrollView,
    StyleSheet,
    Text,
    View,
} from 'react-native';
import TcpSocket from 'react-native-tcp-socket';

class App extends React.Component {
    constructor(props) {
        super(props);

        this.updateChatter = this.updateChatter.bind(this);
        this.state = { chatter: [] };
    }

    updateChatter(msg) {
        this.setState({
            chatter: this.state.chatter.concat([msg]),
        });
    }

    componentDidMount() {
        const serverPort = 53333;
        const serverHost = '192.1.1.1';
        // let server;
        let client;

        // server = TcpSocket.createServer((socket) => {
        //     this.updateChatter(`server connected on ${JSON.stringify(socket.address())}`);

        //     socket.on('data', (data) => {
        //         this.updateChatter(`Server Received: ${data}`);
        //         socket.write('Echo server\r\n');
        //     });

        //     socket.on('error', (error) => {
        //         this.updateChatter(`server client error ${error}`);
        //     });

        //     socket.on('close', (error) => {
        //         this.updateChatter(`server client closed ${error || ''}`);
        //     });
        // }).listen({ port: serverPort, host: serverHost, reuseAddress: true }, (address) => {
        //     this.updateChatter(`opened server on ${JSON.stringify(address)}`);
        // });

        // server.on('error', (error) => {
        //     this.updateChatter(`Server error ${error}`);
        // });

        // server.on('close', () => {
        //     this.updateChatter('server close');
        // });

        client = TcpSocket.createConnection({
            port: serverPort,
            host: serverHost,
        }, (address) => {
            this.updateChatter(`opened client on ${JSON.stringify(address)}`);
            client.write('Hello, server! Love, Client.');
        });

        client.on('data', (data) => {
            this.updateChatter(`Client Received: ${data}`);
            this.client.destroy(); // kill client after server's response
        });

        client.on('error', (error) => {
            this.updateChatter(`client error ${error}`);
        });

        client.on('close', () => {
            this.updateChatter('client close');
        });

        this.client = client;
    }

    componentWillUnmount() {
        this.client = null;
    }

    render() {
        return (
            <View style={styles.container}>
                <ScrollView>
                    {this.state.chatter.map((msg, index) => (
                        <Text key={index} style={styles.welcome}>
                            {msg}
                        </Text>
                    ))}
                </ScrollView>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
    },
    instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5,
    },
});

export default App;

anyway, the setTimeout does the job!

Rapsssito commented 4 years ago

@mi-mazouz, glad it works with setTimeout()! I am not able to reproduce the issue, the device on your 192.168.1.1 may be accepting the TCP connection and keeping it on hold forever (that's why you don't get any error).

I will be releasing a setTimeout() update soon (#56), it should work in all scenarios.

mi-mazouz commented 4 years ago

same when I change the ip and I don't have any device on 192.168.1.1

Rapsssito commented 4 years ago

@mi-mazouz, what device are you using?

mi-mazouz commented 4 years ago

what do you mean? We use our own device but it does not change anything because the problem persists even if my device is off.

Rapsssito commented 4 years ago

@mi-mazouz, I mean what device are you using to test the app: iPhone, Huawei, Pixel...

mi-mazouz commented 4 years ago

I'm testing with ios simulator

Rapsssito commented 4 years ago

@mi-mazouz, I am able to reproduce the bug on iOS. Working on it.

mi-mazouz commented 4 years ago

hey @Rapsssito, do you have any news about this issue?

Rapsssito commented 4 years ago

@mi-mazouz, I have not been able to figure it out yet. I have came into the conclusion that it might be related to GCDAsyncSocket inner workings. I am still working on it. If you find anything, please let me know.

ewc003 commented 3 years ago

@Rapsssito Hi, I am running into a similar issue. Here is a snippet of my code. I followed your setTimeout example but my application crashes and runs into Error: Unhandled Error from my Samsung device. The function above is being called in a setInterval function. Update: App is not crashing anymore but after many calls from the setInterval, the socket gets stuck on a null error. Looks like the socket is not issuing a close/destroy.


function getTCPData() {
  return new Promise((resolve, reject) => {
    const tcpTimeout = setTimeout(() => {
      reject('Timeout');
    }, 1000);
    const socket = TcpSocket.createConnection(
      options,
      () => {
        clearTimeout(tcpTimeout);
        // Write on the socket
        socket.write('Ready for image');
      },
      socket.on('data', async function(data) {
        // get json string
      });

      socket.on('error', function(error) {
        socket.destroy();
      });

      socket.on('close', function() {
        // if data received is valid json resolve, else reject
      });
    );
  });
}
Rapsssito commented 3 years ago

@ewc003, could you create a new issue with your specific problem?

itzsankar commented 3 years ago

@Rapsssito I'm facing similar issue, but not in all the devices only iOS 14 with iphone 7,8,XR , In iPhone 11 with iOS 14 working fine.

Issue is occurring at first time ,when you back and do create connection, connection created successfully.

Rapsssito commented 3 years ago

@itzsankar I am currently working on a fix.

Santhosh-kumark commented 3 years ago

@Rapsssito Is there any update on this issue ?

Rapsssito commented 3 years ago

@Santhosh-kumark, yes! I found the problem, but I am really busy at the moment. The fix might take a while.

Rapsssito commented 3 years ago

@Santhosh-kumark @mi-mazouz, could you check if the issue still persists in the last version? I am testing on the iOS simulator without any timeout and after 1 minutes and 15 seconds I get an error in the error handler.

itzsankar commented 3 years ago

@Rapsssito I'm facing similar issue, but not in all the devices only iOS 14 with iphone 7,8,XR , In iPhone 11 with iOS 14 working fine.

Issue is occurring at first time ,when you back and do create connection, connection created successfully.

@Rapsssito Have you fixed this in the latest version?

Santhosh-kumark commented 3 years ago

@Rapsssito Mine was similar issue to https://github.com/Rapsssito/react-native-tcp-socket/issues/53#issuecomment-808131790 . Seems like this is not fixed in new version as well.

Rapsssito commented 3 years ago

@itzsankar @Santhosh-kumark, could you check if you get an error response after 2 minutes?

mi-mazouz commented 3 years ago

I updated the package to the latest version and tested this code:

import React from 'react';
import {
    StyleSheet,
    Text,
    View,
} from 'react-native';
import TcpSocket from 'react-native-tcp-socket';

class App extends React.Component {
    constructor(props) {
        super(props);

        this.updateChatter = this.updateChatter.bind(this);
        this.state = { chatter: ['hello'] };
    }

    updateChatter(msg) {
        this.setState({
            chatter: this.state.chatter.concat([msg]),
        });
    }

    componentDidMount() {
        const serverPort = 53333;
        const serverHost = '192.1.1.3';
        let client;

        client = TcpSocket.createConnection({
            port: serverPort,
            host: serverHost,
        }, (address) => {
            this.updateChatter(`opened client on ${JSON.stringify(address)}`);
            client.write('Hello, server! Love, Client.');
        });

        client.on('connect', () => {
            this.updateChatter('Client connect');
            console.log('CONNECT');
        });

        client.on('timeout', () => {
            this.updateChatter('Client timeout');
            console.log('TIMEOUT');
        });

        client.on('data', (data) => {
            console.log('DATA');
            this.updateChatter(`Client Received: ${data}`);
        });

        client.on('error', (error) => {
            console.log('ERROR');
            this.updateChatter(`client error ${error}`);
        });

        client.on('close', () => {
            console.log('CLOSE');
            this.updateChatter('client close');
        });

        this.client = client;
    }

    componentWillUnmount() {
        this.client = null;
    }

    render() {
        return (
            <View style={styles.container}>
                {this.state.chatter.map((msg, index) => (
                    <Text key={index} style={styles.welcome}>
                        {msg}
                    </Text>
                ))}
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: 'red',
    },
    welcome: {
        fontSize: 20,
        textAlign: 'center',
        color: 'black',
        margin: 10,
    },
});

export default App;

and after 1 min and 15secs the error and close events were triggered so that's a good new.

itzsankar commented 3 years ago

@itzsankar @Santhosh-kumark, could you check if you get an error response after 2 minutes?

@Rapsssito , my problem is only in iPhone version other than 11 ,iOS 14 I'm not getting any response for first time, if I quit the app and connect it again, it is connecting perfectly. But in iPhone 11 with iOS 14 it is connecting for the first time itself without any issues. Lower iOS versions no problem.

mi-mazouz commented 3 years ago

@Rapsssito I think timeout is not working well, with the following code timeout and createConnection callback are triggered:

 this._device = net.createConnection({
                port: TCP_PORT,
                host: deviceIp,
                timeout: 1500,
            }, (address) => {
                console.log('CONNECTED', address);
                resolve();
            });

            this._device.on('error', (error) => {
                console.log('ERROR', error);
            });

            this._device.on('timeout', (error) => {
                console.log('TIMEOUT', error);
            });

            this._device.on('data', dataListener);

if createConnection callback is triggered when the connection was successful timeout should not be triggered right?

mi-mazouz commented 3 years ago

my bad this_.device.removeListener('timeout') in createConnection callback did the job!

Rapsssito commented 3 years ago

@mi-mazouz, I am glad it worked! If you agree, I think we can close this issue now. It has been a difficult problem to tackle.

@itzsankar, this issue has +30 comments right now. Could you please create a new issue with your problem, the devices with issues and a reproducible code?

itzsankar commented 3 years ago

@Rapsssito sure, I have created a new issue #106 we can track from there , let me know if you need any details