diff --git a/README.md b/README.md index 164615c..fb7aa53 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ client.on('close', function(){ }); // Write on the socket -client.write("Hello server!"); +client.write('Hello server!'); // Close socket client.destroy(); @@ -155,17 +155,20 @@ server.on('close', () => { * [`destroy()`](#destroy) #### `createConnection()` -`createConnection(options[, callback])` creates a TCP connection using the given [`options`](#options). -##### `options` -**Required**. Available options for creating a socket. It is an `object` with the following properties: - -| Property | Type | Description | -| --------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- | -| **`host`** | `` | **Required**. A valid server IP address in IPv4 format or `"localhost"`. | -| **`port`** | `` | **Required**. A valid server port. | -| `[localAddress]` | `` | A valid local IP address to bind the socket. If not specified, the OS will decide. It is **highly recommended** to specify a `localAddress` to prevent overload errors and improve performance. | -| `[localPort]` | `` | A valid local port to bind the socket. If not specified, the OS will decide. | -| `[interface]`| `` | The interface to bind the socket. If not specified, it will use the current active connection. The options are: `"wifi"`. | +`createConnection(options[, callback])` creates a TCP connection using the given [`options`](#createconnection-options). +##### `createConnection: options` +**Required**. Available options for creating a socket. It must be an `object` with the following properties: + +| Property | Type | iOS | Android |Description | +| --------------------- | ------ | :--: | :-----: |-------------------------------------------------------------------------------------------------- | +| **`port`** | `` | ✅ | ✅ | **Required**. Port the socket should connect to. | +| `host` | `` | ✅ | ✅ | Host the socket should connect to. IP address in IPv4 format or `'localhost'`. **Default**: `'localhost'`. | +| `localAddress` | `` | ✅ | ✅ | Local address the socket should connect from. If not specified, the OS will decide. It is **highly recommended** to specify a `localAddress` to prevent overload errors and improve performance. | +| `localPort` | `` | ✅ | ✅ | Local port the socket should connect from. If not specified, the OS will decide. | +| `interface`| `` | ❌ | ✅ | Interface the socket should connect from. If not specified, it will use the current active connection. The options are: `'wifi'`. | +| `reuseAddress`| `` | ❌ | ✅ | Enable/disable the reuseAddress socket option. **Default**: `true`. | + +**Note**: The platforms marked as ❌ use the default value. #### `write()` * `data`: ` | | ` @@ -177,11 +180,22 @@ server.on('close', () => { ### Server * **Methods:** * [`createServer(callback)`](#createserver) - * [`listen(port[, host])`](#listen) + * [`listen(options[, callback])`](#listen) * [`close()`](#close) #### `listen()` -`listen(port[, host])` creates a TCP server socket listening on the given port. If the host is not explicity selected, the socket will be bound to `'0.0.0.0'`. +`listen(options[, callback])` creates a TCP server socket using the given [`options`](#listen-options). + +##### `listen: options` +**Required**. Available options for creating a server socket. It must be an `object` with the following properties: + +| Property | Type | iOS | Android |Description | +| --------------------- | ------ | :--: | :-----: |-------------------------------------------------------------------------------------------------- | +| **`port`** | `` | ✅ | ✅ | **Required**. Port the socket should listen to. | +| `host` | `` | ✅ | ✅ | Host the socket should listen to. IP address in IPv4 format or `'localhost'`. **Default**: `'0.0.0.0'`. | +| `reuseAddress`| `` | ❌ | ✅ | Enable/disable the reuseAddress socket option. **Default**: `true`. | + +**Note**: The platforms marked as ❌ use the default value. ## Maintainers Looking for maintainers! diff --git a/android/build.gradle b/android/build.gradle index 6af83d6..456e1f1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -14,7 +14,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.5.3' } } } @@ -66,6 +66,7 @@ dependencies { } def configureReactNativePom(def pom) { + //noinspection UnnecessaryQualifiedReference def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) pom.project { diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5785b34..870f3a6 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Oct 05 12:20:33 CEST 2019 +#Sat Jan 11 13:40:56 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpReceiverTask.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpReceiverTask.java index 9239dfa..6b9b782 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpReceiverTask.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpReceiverTask.java @@ -18,8 +18,9 @@ public class TcpReceiverTask extends AsyncTask... params) { + protected final Void doInBackground(Pair... params) { if (params.length > 1) { throw new IllegalArgumentException("This task is only for a single socket/listener pair."); } @@ -52,6 +53,7 @@ protected Void doInBackground(Pair(this, receiverListener)); } - public TcpSocketClient(final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id, final Socket socket) { - this.id = id; + TcpSocketClient(final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id, final Socket socket) { + this(id); this.socket = socket; receiverTask = new TcpReceiverTask(); mReceiverListener = receiverListener; + //noinspection unchecked receiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Pair<>(this, receiverListener)); } diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java index de6f261..1e31014 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java @@ -1,6 +1,7 @@ package com.asterinet.react.tcpsocket; +import android.annotation.SuppressLint; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; @@ -35,7 +36,7 @@ public class TcpSocketModule extends ReactContextBaseJavaModule implements TcpRe private final SparseArray mNetworkMap = new SparseArray<>(); private Network mSelectedNetwork; - public static final String TAG = "TcpSockets"; + private static final String TAG = "TcpSockets"; public TcpSocketModule(ReactApplicationContext reactContext) { super(reactContext); @@ -108,6 +109,8 @@ public void onUnavailable() { * @param port socket port to be bound * @param options extra options */ + @SuppressLint("StaticFieldLeak") + @SuppressWarnings("unused") @ReactMethod public void connect(final Integer cId, final String host, final Integer port, final ReadableMap options) { new GuardedAsyncTask(mReactContext.getExceptionHandler()) { @@ -123,13 +126,12 @@ protected void doInBackgroundGuarded(Void... params) { onError(cId, TAG + "createSocket called twice with the same id."); return; } - String localAddress = options.getString("localAddress"); - String iface = options.getString("interface"); - int localPort = options.getInt("localPort"); try { // Get the network interface + String localAddress = options.getString("localAddress"); + String iface = options.getString("interface"); selectNetwork(iface, localAddress); - client = new TcpSocketClient(TcpSocketModule.this, cId, host, port, localAddress, localPort, mSelectedNetwork); + client = new TcpSocketClient(TcpSocketModule.this, cId, host, port, options, mSelectedNetwork); socketClients.put(cId, client); onConnect(cId, host, port); } catch (Exception e) { @@ -139,6 +141,8 @@ protected void doInBackgroundGuarded(Void... params) { }.execute(); } + @SuppressLint("StaticFieldLeak") + @SuppressWarnings("unused") @ReactMethod public void write(final Integer cId, final String base64String, final Callback callback) { new GuardedAsyncTask(mReactContext.getExceptionHandler()) { @@ -163,6 +167,8 @@ protected void doInBackgroundGuarded(Void... params) { }.execute(); } + @SuppressLint("StaticFieldLeak") + @SuppressWarnings("unused") @ReactMethod public void end(final Integer cId) { new GuardedAsyncTask(mReactContext.getExceptionHandler()) { @@ -178,19 +184,24 @@ protected void doInBackgroundGuarded(Void... params) { }.execute(); } + @SuppressWarnings("unused") @ReactMethod public void destroy(final Integer cId) { end(cId); } + @SuppressLint("StaticFieldLeak") + @SuppressWarnings("unused") @ReactMethod - public void listen(final Integer cId, final String host, final Integer port) { + public void listen(final Integer cId, final ReadableMap options) { new GuardedAsyncTask(mReactContext.getExceptionHandler()) { @Override protected void doInBackgroundGuarded(Void... params) { try { - TcpSocketServer server = new TcpSocketServer(socketClients, TcpSocketModule.this, cId, host, port); + TcpSocketServer server = new TcpSocketServer(socketClients, TcpSocketModule.this, cId, options); socketClients.put(cId, server); + int port = options.getInt("port"); + String host = options.getString("host"); onConnect(cId, host, port); } catch (Exception uhe) { onError(cId, uhe.getMessage()); diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketPackage.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketPackage.java index 83ae6cb..f66e679 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketPackage.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketPackage.java @@ -11,9 +11,11 @@ import androidx.annotation.NonNull; +@SuppressWarnings("unused") public class TcpSocketPackage implements ReactPackage { @Override public @NonNull List createNativeModules(@NonNull ReactApplicationContext reactContext) { + //noinspection ArraysAsListWithZeroOrOneArgument return Arrays.asList(new TcpSocketModule(reactContext)); } diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketServer.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketServer.java index e9e31a3..f56d82d 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketServer.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketServer.java @@ -1,8 +1,11 @@ package com.asterinet.react.tcpsocket; +import android.annotation.SuppressLint; import android.os.AsyncTask; import android.util.SparseArray; +import com.facebook.react.bridge.ReadableMap; + import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -13,9 +16,10 @@ public class TcpSocketServer extends TcpSocketClient { private ServerSocket serverSocket; private TcpReceiverTask.OnDataReceivedListener mReceiverListener; private int clientSocketIds; - private SparseArray socketClients; + private final SparseArray socketClients; private final SparseArray serverSocketClients = new SparseArray<>(); + @SuppressLint("StaticFieldLeak") private final AsyncTask listening = new AsyncTask() { @Override protected Void doInBackground(Object[] objects) { @@ -39,14 +43,25 @@ protected Void doInBackground(Object[] objects) { public TcpSocketServer(final SparseArray socketClients, final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id, - final String address, final Integer port) throws IOException { - this.id = id; + final ReadableMap options) throws IOException { + super(id); + // Get data from options + int port = options.getInt("port"); + String address = options.getString("host"); this.socketClients = socketClients; - clientSocketIds = (1 + this.id) * 1000; + clientSocketIds = (1 + getId()) * 1000; // Get the addresses InetAddress localInetAddress = InetAddress.getByName(address); // Create the socket serverSocket = new ServerSocket(port, 50, localInetAddress); + // setReuseAddress + try { + boolean reuseAddress = options.getBoolean("reuseAddress"); + serverSocket.setReuseAddress(reuseAddress); + } catch (Exception e) { + // Default to true + serverSocket.setReuseAddress(true); + } mReceiverListener = receiverListener; listen(); } @@ -61,6 +76,7 @@ private int getClientId() { } private void listen() { + //noinspection unchecked listening.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } diff --git a/examples/tcpsockets/App.js b/examples/tcpsockets/App.js index 11ef5f7..b269c1c 100644 --- a/examples/tcpsockets/App.js +++ b/examples/tcpsockets/App.js @@ -48,7 +48,7 @@ class App extends React.Component { socket.on('close', (error) => { this.updateChatter('server client closed ' + (error ? error : '')); }); - }).listen(serverPort, serverHost, (address) => { + }).listen({port: serverPort, host: serverHost, reuseAddress: true}, (address) => { this.updateChatter('opened server on ' + JSON.stringify(address)); }); @@ -64,6 +64,7 @@ class App extends React.Component { port: serverPort, host: serverHost, localAddress: "127.0.0.1", + reuseAddress: true, // localPort: 20000, // interface: "wifi" }, (address) => { diff --git a/ios/TcpSocketClient.h b/ios/TcpSocketClient.h index 4f74b78..4434d7f 100644 --- a/ios/TcpSocketClient.h +++ b/ios/TcpSocketClient.h @@ -69,11 +69,10 @@ typedef enum RCTTCPError RCTTCPError; /** * Starts listening on a local host and port * - * @param local ip address - * @param local port + * @param options NSDictionary which must have a @"port" and @"host" to specify where to listen * @return true if connected, false if there was an error */ -- (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error; +- (BOOL)listen:(NSDictionary *)options error:(NSError **)error; /** * Returns the address information diff --git a/ios/TcpSocketClient.m b/ios/TcpSocketClient.m index d08dcab..9f6d1c6 100644 --- a/ios/TcpSocketClient.m +++ b/ios/TcpSocketClient.m @@ -108,7 +108,7 @@ - (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)opti @"family": @"unkown" }; } -- (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error +- (BOOL)listen:(NSDictionary *)options error:(NSError **)error { if (_tcpSocket) { if (error) { @@ -121,6 +121,10 @@ - (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error _tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[self methodQueue]]; [_tcpSocket setUserData: _id]; + // Get the host and port + NSString *host = options[@"host"]; + int port = [options[@"port"] intValue]; + // GCDAsyncSocket doesn't recognize 0.0.0.0 if ([@"0.0.0.0" isEqualToString: host]) { host = nil; diff --git a/ios/TcpSockets.m b/ios/TcpSockets.m index 9d96cc0..1135f51 100644 --- a/ios/TcpSockets.m +++ b/ios/TcpSockets.m @@ -106,8 +106,7 @@ - (TcpSocketClient *)createSocket:(nonnull NSNumber*)cId } RCT_EXPORT_METHOD(listen:(nonnull NSNumber*)cId - host:(NSString *)host - port:(int)port) + withOptions:(nonnull NSDictionary *)options) { TcpSocketClient* client = _clients[cId]; if (!client) { @@ -115,7 +114,7 @@ - (TcpSocketClient *)createSocket:(nonnull NSNumber*)cId } NSError *error = nil; - if (![client listen:host port:port error:&error]) + if (![client listen:options error:&error]) { [self onError:client withError:error]; return; @@ -200,4 +199,4 @@ -(NSNumber*)getNextId { return @(_counter++ + COUNTER_OFFSET); } -@end \ No newline at end of file +@end diff --git a/src/TcpServer.js b/src/TcpServer.js index 4e13352..c568b2e 100644 --- a/src/TcpServer.js +++ b/src/TcpServer.js @@ -20,8 +20,21 @@ export default class TcpServer extends TcpSocket { callback(this._connections.length); } - listen(port, host, callback) { - host = host || '0.0.0.0'; + listen(options, callback) { + let gotOptions = {}; + // Normalize args + if (typeof arguments[0] === 'number') { + // Deprecated old version: listen(port[, host][, callback]) + console.warn( + 'TcpServer.listen(port[, host][, callback]) is deprecated and has been moved to TcpServer.listen(options[, callback]). It will be removed in react-native-tcp-socket@4.0.0' + ); + gotOptions.port = arguments[0]; + gotOptions.host = arguments[1]; + callback = arguments[2]; + } else { + gotOptions = options; + } + gotOptions.host = gotOptions.host || '0.0.0.0'; const connectListener = this._eventEmitter.addListener('connect', (ev) => { if (this._id !== ev.id) return; connectListener.remove(); @@ -32,7 +45,7 @@ export default class TcpServer extends TcpSocket { if (this._id !== ev.id) return; this._onConnection(ev.info); }); - Sockets.listen(this._id, host, port); + Sockets.listen(this._id, gotOptions); return this; }