Skip to content

Commit 9074c91

Browse files
authored
feat: reuseAddress on socket creation (#17)
* fixes: #13
1 parent 4608d9c commit 9074c91

File tree

13 files changed

+129
-54
lines changed

13 files changed

+129
-54
lines changed

README.md

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ client.on('close', function(){
117117
});
118118

119119
// Write on the socket
120-
client.write("Hello server!");
120+
client.write('Hello server!');
121121

122122
// Close socket
123123
client.destroy();
@@ -155,17 +155,20 @@ server.on('close', () => {
155155
* [`destroy()`](#destroy)
156156

157157
#### `createConnection()`
158-
`createConnection(options[, callback])` creates a TCP connection using the given [`options`](#options).
159-
##### `options`
160-
**Required**. Available options for creating a socket. It is an `object` with the following properties:
161-
162-
| Property | Type | Description |
163-
| --------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- |
164-
| **`host`** | `<string>` | **Required**. A valid server IP address in IPv4 format or `"localhost"`. |
165-
| **`port`** | `<number>` | **Required**. A valid server port. |
166-
| `[localAddress]` | `<string>` | 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. |
167-
| `[localPort]` | `<number>` | A valid local port to bind the socket. If not specified, the OS will decide. |
168-
| `[interface]`| `<string>` | The interface to bind the socket. If not specified, it will use the current active connection. The options are: `"wifi"`. |
158+
`createConnection(options[, callback])` creates a TCP connection using the given [`options`](#createconnection-options).
159+
##### `createConnection: options`
160+
**Required**. Available options for creating a socket. It must be an `object` with the following properties:
161+
162+
| Property | Type | iOS | Android |Description |
163+
| --------------------- | ------ | :--: | :-----: |-------------------------------------------------------------------------------------------------- |
164+
| **`port`** | `<number>` ||| **Required**. Port the socket should connect to. |
165+
| `host` | `<string>` ||| Host the socket should connect to. IP address in IPv4 format or `'localhost'`. **Default**: `'localhost'`. |
166+
| `localAddress` | `<string>` ||| 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. |
167+
| `localPort` | `<number>` ||| Local port the socket should connect from. If not specified, the OS will decide. |
168+
| `interface`| `<string>` ||| Interface the socket should connect from. If not specified, it will use the current active connection. The options are: `'wifi'`. |
169+
| `reuseAddress`| `<boolean>` ||| Enable/disable the reuseAddress socket option. **Default**: `true`. |
170+
171+
**Note**: The platforms marked as ❌ use the default value.
169172

170173
#### `write()`
171174
* `data`: `<string> | <Buffer> | <Uint8Array>`
@@ -177,11 +180,22 @@ server.on('close', () => {
177180
### Server
178181
* **Methods:**
179182
* [`createServer(callback)`](#createserver)
180-
* [`listen(port[, host])`](#listen)
183+
* [`listen(options[, callback])`](#listen)
181184
* [`close()`](#close)
182185

183186
#### `listen()`
184-
`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'`.
187+
`listen(options[, callback])` creates a TCP server socket using the given [`options`](#listen-options).
188+
189+
##### `listen: options`
190+
**Required**. Available options for creating a server socket. It must be an `object` with the following properties:
191+
192+
| Property | Type | iOS | Android |Description |
193+
| --------------------- | ------ | :--: | :-----: |-------------------------------------------------------------------------------------------------- |
194+
| **`port`** | `<number>` ||| **Required**. Port the socket should listen to. |
195+
| `host` | `<string>` ||| Host the socket should listen to. IP address in IPv4 format or `'localhost'`. **Default**: `'0.0.0.0'`. |
196+
| `reuseAddress`| `<boolean>` ||| Enable/disable the reuseAddress socket option. **Default**: `true`. |
197+
198+
**Note**: The platforms marked as ❌ use the default value.
185199

186200
## Maintainers
187201
Looking for maintainers!

android/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ buildscript {
1414
jcenter()
1515
}
1616
dependencies {
17-
classpath 'com.android.tools.build:gradle:3.4.1'
17+
classpath 'com.android.tools.build:gradle:3.5.3'
1818
}
1919
}
2020
}
@@ -66,6 +66,7 @@ dependencies {
6666
}
6767

6868
def configureReactNativePom(def pom) {
69+
//noinspection UnnecessaryQualifiedReference
6970
def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text)
7071

7172
pom.project {
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#Sat Oct 05 12:20:33 CEST 2019
1+
#Sat Jan 11 13:40:56 CET 2020
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

android/src/main/java/com/asterinet/react/tcpsocket/TcpReceiverTask.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ public class TcpReceiverTask extends AsyncTask<Pair<TcpSocketClient, TcpReceiver
1818
/**
1919
* An infinite loop to block and read data from the socket.
2020
*/
21+
@SafeVarargs
2122
@Override
22-
protected Void doInBackground(Pair<TcpSocketClient, TcpReceiverTask.OnDataReceivedListener>... params) {
23+
protected final Void doInBackground(Pair<TcpSocketClient, TcpReceiverTask.OnDataReceivedListener>... params) {
2324
if (params.length > 1) {
2425
throw new IllegalArgumentException("This task is only for a single socket/listener pair.");
2526
}
@@ -52,6 +53,7 @@ protected Void doInBackground(Pair<TcpSocketClient, TcpReceiverTask.OnDataReceiv
5253
/**
5354
* Listener interface for receive events.
5455
*/
56+
@SuppressWarnings("WeakerAccess")
5557
public interface OnDataReceivedListener {
5658
void onConnection(Integer serverId, Integer clientId, InetSocketAddress socketAddress);
5759

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketClient.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,68 @@
44
import android.os.AsyncTask;
55
import android.util.Pair;
66

7+
import com.facebook.react.bridge.ReadableMap;
8+
79
import java.io.OutputStream;
810
import java.io.IOException;
911
import java.net.InetAddress;
1012
import java.net.InetSocketAddress;
1113
import java.net.Socket;
1214

13-
public class TcpSocketClient {
15+
class TcpSocketClient {
1416
private TcpReceiverTask receiverTask;
1517
private Socket socket;
1618
private TcpReceiverTask.OnDataReceivedListener mReceiverListener;
1719

18-
protected Integer id;
19-
20-
public TcpSocketClient() {
20+
private final int id;
2121

22+
TcpSocketClient(final int id) {
23+
this.id = id;
2224
}
2325

2426
/**
2527
* TcpSocketClient constructor
2628
*
27-
* @param address server address
28-
* @param port server port
29-
* @param localAddress local address to bound to
30-
* @param localPort local port to bound to
29+
* @param address server address
30+
* @param port server port
31+
* @param options extra options
3132
*/
3233
public TcpSocketClient(final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id,
33-
final String address, final Integer port, final String localAddress, final int localPort, final Network network)
34+
final String address, final Integer port, final ReadableMap options, final Network network)
3435
throws IOException {
35-
this.id = id;
36+
this(id);
3637
// Get the addresses
38+
String localAddress = options.getString("localAddress");
3739
InetAddress localInetAddress = InetAddress.getByName(localAddress);
3840
InetAddress remoteInetAddress = InetAddress.getByName(address);
3941
// Create the socket
4042
socket = new Socket();
4143
if (network != null)
4244
network.bindSocket(socket);
43-
socket.setReuseAddress(true);
45+
// setReuseAddress
46+
try {
47+
boolean reuseAddress = options.getBoolean("reuseAddress");
48+
socket.setReuseAddress(reuseAddress);
49+
} catch (Exception e) {
50+
// Default to true
51+
socket.setReuseAddress(true);
52+
}
53+
// bind
54+
int localPort = options.getInt("localPort");
4455
socket.bind(new InetSocketAddress(localInetAddress, localPort));
4556
socket.connect(new InetSocketAddress(remoteInetAddress, port));
4657
receiverTask = new TcpReceiverTask();
4758
mReceiverListener = receiverListener;
59+
//noinspection unchecked
4860
receiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Pair<>(this, receiverListener));
4961
}
5062

51-
public TcpSocketClient(final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id, final Socket socket) {
52-
this.id = id;
63+
TcpSocketClient(final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id, final Socket socket) {
64+
this(id);
5365
this.socket = socket;
5466
receiverTask = new TcpReceiverTask();
5567
mReceiverListener = receiverListener;
68+
//noinspection unchecked
5669
receiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Pair<>(this, receiverListener));
5770
}
5871

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.asterinet.react.tcpsocket;
22

33

4+
import android.annotation.SuppressLint;
45
import android.content.Context;
56
import android.net.ConnectivityManager;
67
import android.net.NetworkCapabilities;
@@ -35,7 +36,7 @@ public class TcpSocketModule extends ReactContextBaseJavaModule implements TcpRe
3536
private final SparseArray<Network> mNetworkMap = new SparseArray<>();
3637
private Network mSelectedNetwork;
3738

38-
public static final String TAG = "TcpSockets";
39+
private static final String TAG = "TcpSockets";
3940

4041
public TcpSocketModule(ReactApplicationContext reactContext) {
4142
super(reactContext);
@@ -108,6 +109,8 @@ public void onUnavailable() {
108109
* @param port socket port to be bound
109110
* @param options extra options
110111
*/
112+
@SuppressLint("StaticFieldLeak")
113+
@SuppressWarnings("unused")
111114
@ReactMethod
112115
public void connect(final Integer cId, final String host, final Integer port, final ReadableMap options) {
113116
new GuardedAsyncTask<Void, Void>(mReactContext.getExceptionHandler()) {
@@ -123,13 +126,12 @@ protected void doInBackgroundGuarded(Void... params) {
123126
onError(cId, TAG + "createSocket called twice with the same id.");
124127
return;
125128
}
126-
String localAddress = options.getString("localAddress");
127-
String iface = options.getString("interface");
128-
int localPort = options.getInt("localPort");
129129
try {
130130
// Get the network interface
131+
String localAddress = options.getString("localAddress");
132+
String iface = options.getString("interface");
131133
selectNetwork(iface, localAddress);
132-
client = new TcpSocketClient(TcpSocketModule.this, cId, host, port, localAddress, localPort, mSelectedNetwork);
134+
client = new TcpSocketClient(TcpSocketModule.this, cId, host, port, options, mSelectedNetwork);
133135
socketClients.put(cId, client);
134136
onConnect(cId, host, port);
135137
} catch (Exception e) {
@@ -139,6 +141,8 @@ protected void doInBackgroundGuarded(Void... params) {
139141
}.execute();
140142
}
141143

144+
@SuppressLint("StaticFieldLeak")
145+
@SuppressWarnings("unused")
142146
@ReactMethod
143147
public void write(final Integer cId, final String base64String, final Callback callback) {
144148
new GuardedAsyncTask<Void, Void>(mReactContext.getExceptionHandler()) {
@@ -163,6 +167,8 @@ protected void doInBackgroundGuarded(Void... params) {
163167
}.execute();
164168
}
165169

170+
@SuppressLint("StaticFieldLeak")
171+
@SuppressWarnings("unused")
166172
@ReactMethod
167173
public void end(final Integer cId) {
168174
new GuardedAsyncTask<Void, Void>(mReactContext.getExceptionHandler()) {
@@ -178,19 +184,24 @@ protected void doInBackgroundGuarded(Void... params) {
178184
}.execute();
179185
}
180186

187+
@SuppressWarnings("unused")
181188
@ReactMethod
182189
public void destroy(final Integer cId) {
183190
end(cId);
184191
}
185192

193+
@SuppressLint("StaticFieldLeak")
194+
@SuppressWarnings("unused")
186195
@ReactMethod
187-
public void listen(final Integer cId, final String host, final Integer port) {
196+
public void listen(final Integer cId, final ReadableMap options) {
188197
new GuardedAsyncTask<Void, Void>(mReactContext.getExceptionHandler()) {
189198
@Override
190199
protected void doInBackgroundGuarded(Void... params) {
191200
try {
192-
TcpSocketServer server = new TcpSocketServer(socketClients, TcpSocketModule.this, cId, host, port);
201+
TcpSocketServer server = new TcpSocketServer(socketClients, TcpSocketModule.this, cId, options);
193202
socketClients.put(cId, server);
203+
int port = options.getInt("port");
204+
String host = options.getString("host");
194205
onConnect(cId, host, port);
195206
} catch (Exception uhe) {
196207
onError(cId, uhe.getMessage());

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketPackage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
import androidx.annotation.NonNull;
1313

14+
@SuppressWarnings("unused")
1415
public class TcpSocketPackage implements ReactPackage {
1516
@Override
1617
public @NonNull List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
18+
//noinspection ArraysAsListWithZeroOrOneArgument
1719
return Arrays.<NativeModule>asList(new TcpSocketModule(reactContext));
1820
}
1921

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketServer.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.asterinet.react.tcpsocket;
22

3+
import android.annotation.SuppressLint;
34
import android.os.AsyncTask;
45
import android.util.SparseArray;
56

7+
import com.facebook.react.bridge.ReadableMap;
8+
69
import java.io.IOException;
710
import java.net.InetAddress;
811
import java.net.InetSocketAddress;
@@ -13,9 +16,10 @@ public class TcpSocketServer extends TcpSocketClient {
1316
private ServerSocket serverSocket;
1417
private TcpReceiverTask.OnDataReceivedListener mReceiverListener;
1518
private int clientSocketIds;
16-
private SparseArray<TcpSocketClient> socketClients;
19+
private final SparseArray<TcpSocketClient> socketClients;
1720
private final SparseArray<TcpSocketClient> serverSocketClients = new SparseArray<>();
1821

22+
@SuppressLint("StaticFieldLeak")
1923
private final AsyncTask listening = new AsyncTask() {
2024
@Override
2125
protected Void doInBackground(Object[] objects) {
@@ -39,14 +43,25 @@ protected Void doInBackground(Object[] objects) {
3943

4044

4145
public TcpSocketServer(final SparseArray<TcpSocketClient> socketClients, final TcpReceiverTask.OnDataReceivedListener receiverListener, final Integer id,
42-
final String address, final Integer port) throws IOException {
43-
this.id = id;
46+
final ReadableMap options) throws IOException {
47+
super(id);
48+
// Get data from options
49+
int port = options.getInt("port");
50+
String address = options.getString("host");
4451
this.socketClients = socketClients;
45-
clientSocketIds = (1 + this.id) * 1000;
52+
clientSocketIds = (1 + getId()) * 1000;
4653
// Get the addresses
4754
InetAddress localInetAddress = InetAddress.getByName(address);
4855
// Create the socket
4956
serverSocket = new ServerSocket(port, 50, localInetAddress);
57+
// setReuseAddress
58+
try {
59+
boolean reuseAddress = options.getBoolean("reuseAddress");
60+
serverSocket.setReuseAddress(reuseAddress);
61+
} catch (Exception e) {
62+
// Default to true
63+
serverSocket.setReuseAddress(true);
64+
}
5065
mReceiverListener = receiverListener;
5166
listen();
5267
}
@@ -61,6 +76,7 @@ private int getClientId() {
6176
}
6277

6378
private void listen() {
79+
//noinspection unchecked
6480
listening.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
6581
}
6682

examples/tcpsockets/App.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class App extends React.Component {
4848
socket.on('close', (error) => {
4949
this.updateChatter('server client closed ' + (error ? error : ''));
5050
});
51-
}).listen(serverPort, serverHost, (address) => {
51+
}).listen({port: serverPort, host: serverHost, reuseAddress: true}, (address) => {
5252
this.updateChatter('opened server on ' + JSON.stringify(address));
5353
});
5454

@@ -64,6 +64,7 @@ class App extends React.Component {
6464
port: serverPort,
6565
host: serverHost,
6666
localAddress: "127.0.0.1",
67+
reuseAddress: true,
6768
// localPort: 20000,
6869
// interface: "wifi"
6970
}, (address) => {

ios/TcpSocketClient.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,10 @@ typedef enum RCTTCPError RCTTCPError;
6969
/**
7070
* Starts listening on a local host and port
7171
*
72-
* @param local ip address
73-
* @param local port
72+
* @param options NSDictionary which must have a @"port" and @"host" to specify where to listen
7473
* @return true if connected, false if there was an error
7574
*/
76-
- (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error;
75+
- (BOOL)listen:(NSDictionary *)options error:(NSError **)error;
7776

7877
/**
7978
* Returns the address information

0 commit comments

Comments
 (0)