mirror of
https://github.com/status-im/react-native-udp.git
synced 2025-02-27 07:50:30 +00:00
Initial commit
This commit is contained in:
commit
df9879dd5f
33
.flowconfig
Normal file
33
.flowconfig
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[ignore]
|
||||||
|
|
||||||
|
# We fork some components by platform.
|
||||||
|
.*/*.web.js
|
||||||
|
.*/*.android.js
|
||||||
|
|
||||||
|
# Some modules have their own node_modules with overlap
|
||||||
|
.*/node_modules/node-haste/.*
|
||||||
|
|
||||||
|
# Ignore react-tools where there are overlaps, but don't ignore anything that
|
||||||
|
# react-native relies on
|
||||||
|
.*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js
|
||||||
|
.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js
|
||||||
|
.*/node_modules/react-tools/src/browser/ui/React.js
|
||||||
|
.*/node_modules/react-tools/src/core/ReactInstanceHandles.js
|
||||||
|
.*/node_modules/react-tools/src/event/EventPropagators.js
|
||||||
|
|
||||||
|
# Ignore commoner tests
|
||||||
|
.*/node_modules/react-tools/node_modules/commoner/test/.*
|
||||||
|
|
||||||
|
# See https://github.com/facebook/flow/issues/442
|
||||||
|
.*/react-tools/node_modules/commoner/lib/reader.js
|
||||||
|
|
||||||
|
# Ignore jest
|
||||||
|
.*/react-native/node_modules/jest-cli/.*
|
||||||
|
|
||||||
|
[include]
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||||
|
|
||||||
|
[options]
|
||||||
|
module.system=haste
|
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
*.xcodeproj/project.xcworkspace/
|
||||||
|
*.xcodeproj/xcuserdata/
|
996
GCDAsyncSocket/GCDAsyncUdpSocket.h
Normal file
996
GCDAsyncSocket/GCDAsyncUdpSocket.h
Normal file
@ -0,0 +1,996 @@
|
|||||||
|
//
|
||||||
|
// GCDAsyncUdpSocket
|
||||||
|
//
|
||||||
|
// This class is in the public domain.
|
||||||
|
// Originally created by Robbie Hanson of Deusty LLC.
|
||||||
|
// Updated and maintained by Deusty LLC and the Apple development community.
|
||||||
|
//
|
||||||
|
// https://github.com/robbiehanson/CocoaAsyncSocket
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <dispatch/dispatch.h>
|
||||||
|
#import <TargetConditionals.h>
|
||||||
|
#import <Availability.h>
|
||||||
|
|
||||||
|
extern NSString *const GCDAsyncUdpSocketException;
|
||||||
|
extern NSString *const GCDAsyncUdpSocketErrorDomain;
|
||||||
|
|
||||||
|
extern NSString *const GCDAsyncUdpSocketQueueName;
|
||||||
|
extern NSString *const GCDAsyncUdpSocketThreadName;
|
||||||
|
|
||||||
|
enum GCDAsyncUdpSocketError
|
||||||
|
{
|
||||||
|
GCDAsyncUdpSocketNoError = 0, // Never used
|
||||||
|
GCDAsyncUdpSocketBadConfigError, // Invalid configuration
|
||||||
|
GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed
|
||||||
|
GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out
|
||||||
|
GCDAsyncUdpSocketClosedError, // The socket was closed
|
||||||
|
GCDAsyncUdpSocketOtherError, // Description provided in userInfo
|
||||||
|
};
|
||||||
|
typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may optionally set a receive filter for the socket.
|
||||||
|
* A filter can provide several useful features:
|
||||||
|
*
|
||||||
|
* 1. Many times udp packets need to be parsed.
|
||||||
|
* Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
|
||||||
|
* The end result is a parallel socket io, datagram parsing, and packet processing.
|
||||||
|
*
|
||||||
|
* 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
|
||||||
|
* The filter can prevent such packets from arriving at the delegate.
|
||||||
|
* And because the filter can run in its own independent queue, this doesn't slow down the delegate.
|
||||||
|
*
|
||||||
|
* - Since the udp protocol does not guarantee delivery, udp packets may be lost.
|
||||||
|
* Many protocols built atop udp thus provide various resend/re-request algorithms.
|
||||||
|
* This sometimes results in duplicate packets arriving.
|
||||||
|
* A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
|
||||||
|
*
|
||||||
|
* - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
|
||||||
|
* Such packets need to be ignored.
|
||||||
|
*
|
||||||
|
* 3. Sometimes traffic shapers are needed to simulate real world environments.
|
||||||
|
* A filter allows you to write custom code to simulate such environments.
|
||||||
|
* The ability to code this yourself is especially helpful when your simulated environment
|
||||||
|
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
|
||||||
|
* or the system tools to handle this aren't available (e.g. on a mobile device).
|
||||||
|
*
|
||||||
|
* @param data - The packet that was received.
|
||||||
|
* @param address - The address the data was received from.
|
||||||
|
* See utilities section for methods to extract info from address.
|
||||||
|
* @param context - Out parameter you may optionally set, which will then be passed to the delegate method.
|
||||||
|
* For example, filter block can parse the data and then,
|
||||||
|
* pass the parsed data to the delegate.
|
||||||
|
*
|
||||||
|
* @returns - YES if the received packet should be passed onto the delegate.
|
||||||
|
* NO if the received packet should be discarded, and not reported to the delegete.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
|
||||||
|
*
|
||||||
|
* MyProtocolMessage *msg = [MyProtocol parseMessage:data];
|
||||||
|
*
|
||||||
|
* *context = response;
|
||||||
|
* return (response != nil);
|
||||||
|
* };
|
||||||
|
* [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id *context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may optionally set a send filter for the socket.
|
||||||
|
* A filter can provide several interesting possibilities:
|
||||||
|
*
|
||||||
|
* 1. Optional caching of resolved addresses for domain names.
|
||||||
|
* The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
|
||||||
|
*
|
||||||
|
* 2. Reusable modules of code for bandwidth monitoring.
|
||||||
|
*
|
||||||
|
* 3. Sometimes traffic shapers are needed to simulate real world environments.
|
||||||
|
* A filter allows you to write custom code to simulate such environments.
|
||||||
|
* The ability to code this yourself is especially helpful when your simulated environment
|
||||||
|
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
|
||||||
|
* or the system tools to handle this aren't available (e.g. on a mobile device).
|
||||||
|
*
|
||||||
|
* @param data - The packet that was received.
|
||||||
|
* @param address - The address the data was received from.
|
||||||
|
* See utilities section for methods to extract info from address.
|
||||||
|
* @param tag - The tag that was passed in the send method.
|
||||||
|
*
|
||||||
|
* @returns - YES if the packet should actually be sent over the socket.
|
||||||
|
* NO if the packet should be silently dropped (not sent over the socket).
|
||||||
|
*
|
||||||
|
* Regardless of the return value, the delegate will be informed that the packet was successfully sent.
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag);
|
||||||
|
|
||||||
|
|
||||||
|
@interface GCDAsyncUdpSocket : NSObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GCDAsyncUdpSocket uses the standard delegate paradigm,
|
||||||
|
* but executes all delegate callbacks on a given delegate dispatch queue.
|
||||||
|
* This allows for maximum concurrency, while at the same time providing easy thread safety.
|
||||||
|
*
|
||||||
|
* You MUST set a delegate AND delegate dispatch queue before attempting to
|
||||||
|
* use the socket, or you will get an error.
|
||||||
|
*
|
||||||
|
* The socket queue is optional.
|
||||||
|
* If you pass NULL, GCDAsyncSocket will automatically create its own socket queue.
|
||||||
|
* If you choose to provide a socket queue, the socket queue must not be a concurrent queue,
|
||||||
|
* then please see the discussion for the method markSocketQueueTargetQueue.
|
||||||
|
*
|
||||||
|
* The delegate queue and socket queue can optionally be the same.
|
||||||
|
**/
|
||||||
|
- (id)init;
|
||||||
|
- (id)initWithSocketQueue:(dispatch_queue_t)sq;
|
||||||
|
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq;
|
||||||
|
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;
|
||||||
|
|
||||||
|
#pragma mark Configuration
|
||||||
|
|
||||||
|
- (id)delegate;
|
||||||
|
- (void)setDelegate:(id)delegate;
|
||||||
|
- (void)synchronouslySetDelegate:(id)delegate;
|
||||||
|
|
||||||
|
- (dispatch_queue_t)delegateQueue;
|
||||||
|
- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue;
|
||||||
|
- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue;
|
||||||
|
|
||||||
|
- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr;
|
||||||
|
- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||||
|
- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, both IPv4 and IPv6 are enabled.
|
||||||
|
*
|
||||||
|
* This means GCDAsyncUdpSocket automatically supports both protocols,
|
||||||
|
* and can send to IPv4 or IPv6 addresses,
|
||||||
|
* as well as receive over IPv4 and IPv6.
|
||||||
|
*
|
||||||
|
* For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6.
|
||||||
|
* If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4.
|
||||||
|
* If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6.
|
||||||
|
* If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference.
|
||||||
|
* If IPv4 is preferred, then IPv4 is used.
|
||||||
|
* If IPv6 is preferred, then IPv6 is used.
|
||||||
|
* If neutral, then the first IP version in the resolved array will be used.
|
||||||
|
*
|
||||||
|
* Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral.
|
||||||
|
* On prior systems the default IP preference is IPv4.
|
||||||
|
**/
|
||||||
|
- (BOOL)isIPv4Enabled;
|
||||||
|
- (void)setIPv4Enabled:(BOOL)flag;
|
||||||
|
|
||||||
|
- (BOOL)isIPv6Enabled;
|
||||||
|
- (void)setIPv6Enabled:(BOOL)flag;
|
||||||
|
|
||||||
|
- (BOOL)isIPv4Preferred;
|
||||||
|
- (BOOL)isIPv6Preferred;
|
||||||
|
- (BOOL)isIPVersionNeutral;
|
||||||
|
|
||||||
|
- (void)setPreferIPv4;
|
||||||
|
- (void)setPreferIPv6;
|
||||||
|
- (void)setIPVersionNeutral;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
|
||||||
|
* The default maximum size is 9216 bytes.
|
||||||
|
*
|
||||||
|
* The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
|
||||||
|
* The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
|
||||||
|
*
|
||||||
|
* Since the OS/GCD notifies us of the size of each received UDP packet,
|
||||||
|
* the actual allocated buffer size for each packet is exact.
|
||||||
|
* And in practice the size of UDP packets is generally much smaller than the max.
|
||||||
|
* Indeed most protocols will send and receive packets of only a few bytes,
|
||||||
|
* or will set a limit on the size of packets to prevent fragmentation in the IP layer.
|
||||||
|
*
|
||||||
|
* If you set the buffer size too small, the sockets API in the OS will silently discard
|
||||||
|
* any extra data, and you will not be notified of the error.
|
||||||
|
**/
|
||||||
|
- (uint16_t)maxReceiveIPv4BufferSize;
|
||||||
|
- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max;
|
||||||
|
|
||||||
|
- (uint32_t)maxReceiveIPv6BufferSize;
|
||||||
|
- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User data allows you to associate arbitrary information with the socket.
|
||||||
|
* This data is not used internally in any way.
|
||||||
|
**/
|
||||||
|
- (id)userData;
|
||||||
|
- (void)setUserData:(id)arbitraryUserData;
|
||||||
|
|
||||||
|
#pragma mark Diagnostics
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local address info for the socket.
|
||||||
|
*
|
||||||
|
* The localAddress method returns a sockaddr structure wrapped in a NSData object.
|
||||||
|
* The localHost method returns the human readable IP address as a string.
|
||||||
|
*
|
||||||
|
* Note: Address info may not be available until after the socket has been binded, connected
|
||||||
|
* or until after data has been sent.
|
||||||
|
**/
|
||||||
|
- (NSData *)localAddress;
|
||||||
|
- (NSString *)localHost;
|
||||||
|
- (uint16_t)localPort;
|
||||||
|
|
||||||
|
- (NSData *)localAddress_IPv4;
|
||||||
|
- (NSString *)localHost_IPv4;
|
||||||
|
- (uint16_t)localPort_IPv4;
|
||||||
|
|
||||||
|
- (NSData *)localAddress_IPv6;
|
||||||
|
- (NSString *)localHost_IPv6;
|
||||||
|
- (uint16_t)localPort_IPv6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the remote address info for the socket.
|
||||||
|
*
|
||||||
|
* The connectedAddress method returns a sockaddr structure wrapped in a NSData object.
|
||||||
|
* The connectedHost method returns the human readable IP address as a string.
|
||||||
|
*
|
||||||
|
* Note: Since UDP is connectionless by design, connected address info
|
||||||
|
* will not be available unless the socket is explicitly connected to a remote host/port.
|
||||||
|
* If the socket is not connected, these methods will return nil / 0.
|
||||||
|
**/
|
||||||
|
- (NSData *)connectedAddress;
|
||||||
|
- (NSString *)connectedHost;
|
||||||
|
- (uint16_t)connectedPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this socket has been connected to a single host.
|
||||||
|
* By design, UDP is a connectionless protocol, and connecting is not needed.
|
||||||
|
* If connected, the socket will only be able to send/receive data to/from the connected host.
|
||||||
|
**/
|
||||||
|
- (BOOL)isConnected;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this socket has been closed.
|
||||||
|
* The only way a socket can be closed is if you explicitly call one of the close methods.
|
||||||
|
**/
|
||||||
|
- (BOOL)isClosed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this socket is IPv4.
|
||||||
|
*
|
||||||
|
* By default this will be true, unless:
|
||||||
|
* - IPv4 is disabled (via setIPv4Enabled:)
|
||||||
|
* - The socket is explicitly bound to an IPv6 address
|
||||||
|
* - The socket is connected to an IPv6 address
|
||||||
|
**/
|
||||||
|
- (BOOL)isIPv4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this socket is IPv6.
|
||||||
|
*
|
||||||
|
* By default this will be true, unless:
|
||||||
|
* - IPv6 is disabled (via setIPv6Enabled:)
|
||||||
|
* - The socket is explicitly bound to an IPv4 address
|
||||||
|
* _ The socket is connected to an IPv4 address
|
||||||
|
*
|
||||||
|
* This method will also return false on platforms that do not support IPv6.
|
||||||
|
* Note: The iPhone does not currently support IPv6.
|
||||||
|
**/
|
||||||
|
- (BOOL)isIPv6;
|
||||||
|
|
||||||
|
#pragma mark Binding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the UDP socket to the given port.
|
||||||
|
* Binding should be done for server sockets that receive data prior to sending it.
|
||||||
|
* Client sockets can skip binding,
|
||||||
|
* as the OS will automatically assign the socket an available port when it starts sending data.
|
||||||
|
*
|
||||||
|
* You may optionally pass a port number of zero to immediately bind the socket,
|
||||||
|
* yet still allow the OS to automatically assign an available port.
|
||||||
|
*
|
||||||
|
* You cannot bind a socket after its been connected.
|
||||||
|
* You can only bind a socket once.
|
||||||
|
* You can still connect a socket (if desired) after binding.
|
||||||
|
*
|
||||||
|
* On success, returns YES.
|
||||||
|
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
|
||||||
|
**/
|
||||||
|
- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the UDP socket to the given port and optional interface.
|
||||||
|
* Binding should be done for server sockets that receive data prior to sending it.
|
||||||
|
* Client sockets can skip binding,
|
||||||
|
* as the OS will automatically assign the socket an available port when it starts sending data.
|
||||||
|
*
|
||||||
|
* You may optionally pass a port number of zero to immediately bind the socket,
|
||||||
|
* yet still allow the OS to automatically assign an available port.
|
||||||
|
*
|
||||||
|
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
|
||||||
|
* You may also use the special strings "localhost" or "loopback" to specify that
|
||||||
|
* the socket only accept packets from the local machine.
|
||||||
|
*
|
||||||
|
* You cannot bind a socket after its been connected.
|
||||||
|
* You can only bind a socket once.
|
||||||
|
* You can still connect a socket (if desired) after binding.
|
||||||
|
*
|
||||||
|
* On success, returns YES.
|
||||||
|
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
|
||||||
|
**/
|
||||||
|
- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
|
||||||
|
*
|
||||||
|
* If you have an existing struct sockaddr you can convert it to a NSData object like so:
|
||||||
|
* struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
|
||||||
|
* struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
|
||||||
|
*
|
||||||
|
* Binding should be done for server sockets that receive data prior to sending it.
|
||||||
|
* Client sockets can skip binding,
|
||||||
|
* as the OS will automatically assign the socket an available port when it starts sending data.
|
||||||
|
*
|
||||||
|
* You cannot bind a socket after its been connected.
|
||||||
|
* You can only bind a socket once.
|
||||||
|
* You can still connect a socket (if desired) after binding.
|
||||||
|
*
|
||||||
|
* On success, returns YES.
|
||||||
|
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
|
||||||
|
**/
|
||||||
|
- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
#pragma mark Connecting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects the UDP socket to the given host and port.
|
||||||
|
* By design, UDP is a connectionless protocol, and connecting is not needed.
|
||||||
|
*
|
||||||
|
* Choosing to connect to a specific host/port has the following effect:
|
||||||
|
* - You will only be able to send data to the connected host/port.
|
||||||
|
* - You will only be able to receive data from the connected host/port.
|
||||||
|
* - You will receive ICMP messages that come from the connected host/port, such as "connection refused".
|
||||||
|
*
|
||||||
|
* The actual process of connecting a UDP socket does not result in any communication on the socket.
|
||||||
|
* It simply changes the internal state of the socket.
|
||||||
|
*
|
||||||
|
* You cannot bind a socket after it has been connected.
|
||||||
|
* You can only connect a socket once.
|
||||||
|
*
|
||||||
|
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
|
||||||
|
*
|
||||||
|
* This method is asynchronous as it requires a DNS lookup to resolve the given host name.
|
||||||
|
* If an obvious error is detected, this method immediately returns NO and sets errPtr.
|
||||||
|
* If you don't care about the error, you can pass nil for errPtr.
|
||||||
|
* Otherwise, this method returns YES and begins the asynchronous connection process.
|
||||||
|
* The result of the asynchronous connection process will be reported via the delegate methods.
|
||||||
|
**/
|
||||||
|
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
|
||||||
|
*
|
||||||
|
* If you have an existing struct sockaddr you can convert it to a NSData object like so:
|
||||||
|
* struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
|
||||||
|
* struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
|
||||||
|
*
|
||||||
|
* By design, UDP is a connectionless protocol, and connecting is not needed.
|
||||||
|
*
|
||||||
|
* Choosing to connect to a specific address has the following effect:
|
||||||
|
* - You will only be able to send data to the connected address.
|
||||||
|
* - You will only be able to receive data from the connected address.
|
||||||
|
* - You will receive ICMP messages that come from the connected address, such as "connection refused".
|
||||||
|
*
|
||||||
|
* Connecting a UDP socket does not result in any communication on the socket.
|
||||||
|
* It simply changes the internal state of the socket.
|
||||||
|
*
|
||||||
|
* You cannot bind a socket after its been connected.
|
||||||
|
* You can only connect a socket once.
|
||||||
|
*
|
||||||
|
* On success, returns YES.
|
||||||
|
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
|
||||||
|
*
|
||||||
|
* Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup.
|
||||||
|
* Thus when this method returns, the connection has either failed or fully completed.
|
||||||
|
* In other words, this method is synchronous, unlike the asynchronous connectToHost::: method.
|
||||||
|
* However, for compatibility and simplification of delegate code, if this method returns YES
|
||||||
|
* then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked.
|
||||||
|
**/
|
||||||
|
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
#pragma mark Multicast
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join multicast group.
|
||||||
|
* Group should be an IP address (eg @"225.228.0.1").
|
||||||
|
*
|
||||||
|
* On success, returns YES.
|
||||||
|
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
|
||||||
|
**/
|
||||||
|
- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join multicast group.
|
||||||
|
* Group should be an IP address (eg @"225.228.0.1").
|
||||||
|
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
|
||||||
|
*
|
||||||
|
* On success, returns YES.
|
||||||
|
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
|
||||||
|
**/
|
||||||
|
- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr;
|
||||||
|
- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
#pragma mark Broadcast
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, the underlying socket in the OS will not allow you to send broadcast messages.
|
||||||
|
* In order to send broadcast messages, you need to enable this functionality in the socket.
|
||||||
|
*
|
||||||
|
* A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
|
||||||
|
* delivered to every host on the network.
|
||||||
|
* The reason this is generally disabled by default (by the OS) is to prevent
|
||||||
|
* accidental broadcast messages from flooding the network.
|
||||||
|
**/
|
||||||
|
- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
|
||||||
|
|
||||||
|
#pragma mark Sending
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously sends the given data, with the given timeout and tag.
|
||||||
|
*
|
||||||
|
* This method may only be used with a connected socket.
|
||||||
|
* Recall that connecting is optional for a UDP socket.
|
||||||
|
* For connected sockets, data can only be sent to the connected address.
|
||||||
|
* For non-connected sockets, the remote destination is specified for each packet.
|
||||||
|
* For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The data to send.
|
||||||
|
* If data is nil or zero-length, this method does nothing.
|
||||||
|
* If passing NSMutableData, please read the thread-safety notice below.
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* The timeout for the send opeartion.
|
||||||
|
* If the timeout value is negative, the send operation will not use a timeout.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* The tag is for your convenience.
|
||||||
|
* It is not sent or received over the socket in any manner what-so-ever.
|
||||||
|
* It is reported back as a parameter in the udpSocket:didSendDataWithTag:
|
||||||
|
* or udpSocket:didNotSendDataWithTag:dueToError: methods.
|
||||||
|
* You can use it as an array index, state id, type constant, etc.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Thread-Safety Note:
|
||||||
|
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
|
||||||
|
* the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
|
||||||
|
* udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
|
||||||
|
* that this particular send operation has completed.
|
||||||
|
* This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
|
||||||
|
* It simply retains it for performance reasons.
|
||||||
|
* Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
|
||||||
|
* Copying this data adds an unwanted/unneeded overhead.
|
||||||
|
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
|
||||||
|
* completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
|
||||||
|
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
|
||||||
|
**/
|
||||||
|
- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously sends the given data, with the given timeout and tag, to the given host and port.
|
||||||
|
*
|
||||||
|
* This method cannot be used with a connected socket.
|
||||||
|
* Recall that connecting is optional for a UDP socket.
|
||||||
|
* For connected sockets, data can only be sent to the connected address.
|
||||||
|
* For non-connected sockets, the remote destination is specified for each packet.
|
||||||
|
* For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The data to send.
|
||||||
|
* If data is nil or zero-length, this method does nothing.
|
||||||
|
* If passing NSMutableData, please read the thread-safety notice below.
|
||||||
|
*
|
||||||
|
* @param host
|
||||||
|
* The destination to send the udp packet to.
|
||||||
|
* May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
|
||||||
|
* You may also use the convenience strings of "loopback" or "localhost".
|
||||||
|
*
|
||||||
|
* @param port
|
||||||
|
* The port of the host to send to.
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* The timeout for the send opeartion.
|
||||||
|
* If the timeout value is negative, the send operation will not use a timeout.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* The tag is for your convenience.
|
||||||
|
* It is not sent or received over the socket in any manner what-so-ever.
|
||||||
|
* It is reported back as a parameter in the udpSocket:didSendDataWithTag:
|
||||||
|
* or udpSocket:didNotSendDataWithTag:dueToError: methods.
|
||||||
|
* You can use it as an array index, state id, type constant, etc.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Thread-Safety Note:
|
||||||
|
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
|
||||||
|
* the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
|
||||||
|
* udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
|
||||||
|
* that this particular send operation has completed.
|
||||||
|
* This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
|
||||||
|
* It simply retains it for performance reasons.
|
||||||
|
* Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
|
||||||
|
* Copying this data adds an unwanted/unneeded overhead.
|
||||||
|
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
|
||||||
|
* completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
|
||||||
|
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
|
||||||
|
**/
|
||||||
|
- (void)sendData:(NSData *)data
|
||||||
|
toHost:(NSString *)host
|
||||||
|
port:(uint16_t)port
|
||||||
|
withTimeout:(NSTimeInterval)timeout
|
||||||
|
tag:(long)tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously sends the given data, with the given timeout and tag, to the given address.
|
||||||
|
*
|
||||||
|
* This method cannot be used with a connected socket.
|
||||||
|
* Recall that connecting is optional for a UDP socket.
|
||||||
|
* For connected sockets, data can only be sent to the connected address.
|
||||||
|
* For non-connected sockets, the remote destination is specified for each packet.
|
||||||
|
* For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The data to send.
|
||||||
|
* If data is nil or zero-length, this method does nothing.
|
||||||
|
* If passing NSMutableData, please read the thread-safety notice below.
|
||||||
|
*
|
||||||
|
* @param remoteAddr
|
||||||
|
* The address to send the data to (specified as a sockaddr structure wrapped in a NSData object).
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* The timeout for the send opeartion.
|
||||||
|
* If the timeout value is negative, the send operation will not use a timeout.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* The tag is for your convenience.
|
||||||
|
* It is not sent or received over the socket in any manner what-so-ever.
|
||||||
|
* It is reported back as a parameter in the udpSocket:didSendDataWithTag:
|
||||||
|
* or udpSocket:didNotSendDataWithTag:dueToError: methods.
|
||||||
|
* You can use it as an array index, state id, type constant, etc.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Thread-Safety Note:
|
||||||
|
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
|
||||||
|
* the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
|
||||||
|
* udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
|
||||||
|
* that this particular send operation has completed.
|
||||||
|
* This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
|
||||||
|
* It simply retains it for performance reasons.
|
||||||
|
* Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
|
||||||
|
* Copying this data adds an unwanted/unneeded overhead.
|
||||||
|
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
|
||||||
|
* completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
|
||||||
|
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
|
||||||
|
**/
|
||||||
|
- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may optionally set a send filter for the socket.
|
||||||
|
* A filter can provide several interesting possibilities:
|
||||||
|
*
|
||||||
|
* 1. Optional caching of resolved addresses for domain names.
|
||||||
|
* The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
|
||||||
|
*
|
||||||
|
* 2. Reusable modules of code for bandwidth monitoring.
|
||||||
|
*
|
||||||
|
* 3. Sometimes traffic shapers are needed to simulate real world environments.
|
||||||
|
* A filter allows you to write custom code to simulate such environments.
|
||||||
|
* The ability to code this yourself is especially helpful when your simulated environment
|
||||||
|
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
|
||||||
|
* or the system tools to handle this aren't available (e.g. on a mobile device).
|
||||||
|
*
|
||||||
|
* For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef.
|
||||||
|
* To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
|
||||||
|
*
|
||||||
|
* Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below),
|
||||||
|
* passing YES for the isAsynchronous parameter.
|
||||||
|
**/
|
||||||
|
- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The receive filter can be run via dispatch_async or dispatch_sync.
|
||||||
|
* Most typical situations call for asynchronous operation.
|
||||||
|
*
|
||||||
|
* However, there are a few situations in which synchronous operation is preferred.
|
||||||
|
* Such is the case when the filter is extremely minimal and fast.
|
||||||
|
* This is because dispatch_sync is faster than dispatch_async.
|
||||||
|
*
|
||||||
|
* If you choose synchronous operation, be aware of possible deadlock conditions.
|
||||||
|
* Since the socket queue is executing your block via dispatch_sync,
|
||||||
|
* then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
|
||||||
|
* For example, you can't query properties on the socket.
|
||||||
|
**/
|
||||||
|
- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock
|
||||||
|
withQueue:(dispatch_queue_t)filterQueue
|
||||||
|
isAsynchronous:(BOOL)isAsynchronous;
|
||||||
|
|
||||||
|
#pragma mark Receiving
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are two modes of operation for receiving packets: one-at-a-time & continuous.
|
||||||
|
*
|
||||||
|
* In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
|
||||||
|
* Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
|
||||||
|
* where your state machine may not always be ready to process incoming packets.
|
||||||
|
*
|
||||||
|
* In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
|
||||||
|
* Receiving packets continuously is better suited to real-time streaming applications.
|
||||||
|
*
|
||||||
|
* You may switch back and forth between one-at-a-time mode and continuous mode.
|
||||||
|
* If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode.
|
||||||
|
*
|
||||||
|
* When a packet is received (and not filtered by the optional receive filter),
|
||||||
|
* the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
|
||||||
|
*
|
||||||
|
* If the socket is able to begin receiving packets, this method returns YES.
|
||||||
|
* Otherwise it returns NO, and sets the errPtr with appropriate error information.
|
||||||
|
*
|
||||||
|
* An example error:
|
||||||
|
* You created a udp socket to act as a server, and immediately called receive.
|
||||||
|
* You forgot to first bind the socket to a port number, and received a error with a message like:
|
||||||
|
* "Must bind socket before you can receive data."
|
||||||
|
**/
|
||||||
|
- (BOOL)receiveOnce:(NSError **)errPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are two modes of operation for receiving packets: one-at-a-time & continuous.
|
||||||
|
*
|
||||||
|
* In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
|
||||||
|
* Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
|
||||||
|
* where your state machine may not always be ready to process incoming packets.
|
||||||
|
*
|
||||||
|
* In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
|
||||||
|
* Receiving packets continuously is better suited to real-time streaming applications.
|
||||||
|
*
|
||||||
|
* You may switch back and forth between one-at-a-time mode and continuous mode.
|
||||||
|
* If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode.
|
||||||
|
*
|
||||||
|
* For every received packet (not filtered by the optional receive filter),
|
||||||
|
* the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
|
||||||
|
*
|
||||||
|
* If the socket is able to begin receiving packets, this method returns YES.
|
||||||
|
* Otherwise it returns NO, and sets the errPtr with appropriate error information.
|
||||||
|
*
|
||||||
|
* An example error:
|
||||||
|
* You created a udp socket to act as a server, and immediately called receive.
|
||||||
|
* You forgot to first bind the socket to a port number, and received a error with a message like:
|
||||||
|
* "Must bind socket before you can receive data."
|
||||||
|
**/
|
||||||
|
- (BOOL)beginReceiving:(NSError **)errPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving.
|
||||||
|
* That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again.
|
||||||
|
*
|
||||||
|
* Important Note:
|
||||||
|
* GCDAsyncUdpSocket may be running in parallel with your code.
|
||||||
|
* That is, your delegate is likely running on a separate thread/dispatch_queue.
|
||||||
|
* When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked.
|
||||||
|
* Thus, if those delegate methods have already been dispatch_async'd,
|
||||||
|
* your didReceive delegate method may still be invoked after this method has been called.
|
||||||
|
* You should be aware of this, and program defensively.
|
||||||
|
**/
|
||||||
|
- (void)pauseReceiving;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may optionally set a receive filter for the socket.
|
||||||
|
* This receive filter may be set to run in its own queue (independent of delegate queue).
|
||||||
|
*
|
||||||
|
* A filter can provide several useful features.
|
||||||
|
*
|
||||||
|
* 1. Many times udp packets need to be parsed.
|
||||||
|
* Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
|
||||||
|
* The end result is a parallel socket io, datagram parsing, and packet processing.
|
||||||
|
*
|
||||||
|
* 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
|
||||||
|
* The filter can prevent such packets from arriving at the delegate.
|
||||||
|
* And because the filter can run in its own independent queue, this doesn't slow down the delegate.
|
||||||
|
*
|
||||||
|
* - Since the udp protocol does not guarantee delivery, udp packets may be lost.
|
||||||
|
* Many protocols built atop udp thus provide various resend/re-request algorithms.
|
||||||
|
* This sometimes results in duplicate packets arriving.
|
||||||
|
* A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
|
||||||
|
*
|
||||||
|
* - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
|
||||||
|
* Such packets need to be ignored.
|
||||||
|
*
|
||||||
|
* 3. Sometimes traffic shapers are needed to simulate real world environments.
|
||||||
|
* A filter allows you to write custom code to simulate such environments.
|
||||||
|
* The ability to code this yourself is especially helpful when your simulated environment
|
||||||
|
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
|
||||||
|
* or the system tools to handle this aren't available (e.g. on a mobile device).
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
|
||||||
|
*
|
||||||
|
* MyProtocolMessage *msg = [MyProtocol parseMessage:data];
|
||||||
|
*
|
||||||
|
* *context = response;
|
||||||
|
* return (response != nil);
|
||||||
|
* };
|
||||||
|
* [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
|
||||||
|
*
|
||||||
|
* For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef.
|
||||||
|
* To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
|
||||||
|
*
|
||||||
|
* Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below),
|
||||||
|
* passing YES for the isAsynchronous parameter.
|
||||||
|
**/
|
||||||
|
- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The receive filter can be run via dispatch_async or dispatch_sync.
|
||||||
|
* Most typical situations call for asynchronous operation.
|
||||||
|
*
|
||||||
|
* However, there are a few situations in which synchronous operation is preferred.
|
||||||
|
* Such is the case when the filter is extremely minimal and fast.
|
||||||
|
* This is because dispatch_sync is faster than dispatch_async.
|
||||||
|
*
|
||||||
|
* If you choose synchronous operation, be aware of possible deadlock conditions.
|
||||||
|
* Since the socket queue is executing your block via dispatch_sync,
|
||||||
|
* then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
|
||||||
|
* For example, you can't query properties on the socket.
|
||||||
|
**/
|
||||||
|
- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
|
||||||
|
withQueue:(dispatch_queue_t)filterQueue
|
||||||
|
isAsynchronous:(BOOL)isAsynchronous;
|
||||||
|
|
||||||
|
#pragma mark Closing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately closes the underlying socket.
|
||||||
|
* Any pending send operations are discarded.
|
||||||
|
*
|
||||||
|
* The GCDAsyncUdpSocket instance may optionally be used again.
|
||||||
|
* (it will setup/configure/use another unnderlying BSD socket).
|
||||||
|
**/
|
||||||
|
- (void)close;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the underlying socket after all pending send operations have been sent.
|
||||||
|
*
|
||||||
|
* The GCDAsyncUdpSocket instance may optionally be used again.
|
||||||
|
* (it will setup/configure/use another unnderlying BSD socket).
|
||||||
|
**/
|
||||||
|
- (void)closeAfterSending;
|
||||||
|
|
||||||
|
#pragma mark Advanced
|
||||||
|
/**
|
||||||
|
* GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
|
||||||
|
* In most cases, the instance creates this queue itself.
|
||||||
|
* However, to allow for maximum flexibility, the internal queue may be passed in the init method.
|
||||||
|
* This allows for some advanced options such as controlling socket priority via target queues.
|
||||||
|
* However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
|
||||||
|
*
|
||||||
|
* For example, imagine there are 2 queues:
|
||||||
|
* dispatch_queue_t socketQueue;
|
||||||
|
* dispatch_queue_t socketTargetQueue;
|
||||||
|
*
|
||||||
|
* If you do this (pseudo-code):
|
||||||
|
* socketQueue.targetQueue = socketTargetQueue;
|
||||||
|
*
|
||||||
|
* Then all socketQueue operations will actually get run on the given socketTargetQueue.
|
||||||
|
* This is fine and works great in most situations.
|
||||||
|
* But if you run code directly from within the socketTargetQueue that accesses the socket,
|
||||||
|
* you could potentially get deadlock. Imagine the following code:
|
||||||
|
*
|
||||||
|
* - (BOOL)socketHasSomething
|
||||||
|
* {
|
||||||
|
* __block BOOL result = NO;
|
||||||
|
* dispatch_block_t block = ^{
|
||||||
|
* result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
|
||||||
|
* }
|
||||||
|
* if (is_executing_on_queue(socketQueue))
|
||||||
|
* block();
|
||||||
|
* else
|
||||||
|
* dispatch_sync(socketQueue, block);
|
||||||
|
*
|
||||||
|
* return result;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* What happens if you call this method from the socketTargetQueue? The result is deadlock.
|
||||||
|
* This is because the GCD API offers no mechanism to discover a queue's targetQueue.
|
||||||
|
* Thus we have no idea if our socketQueue is configured with a targetQueue.
|
||||||
|
* If we had this information, we could easily avoid deadlock.
|
||||||
|
* But, since these API's are missing or unfeasible, you'll have to explicitly set it.
|
||||||
|
*
|
||||||
|
* IF you pass a socketQueue via the init method,
|
||||||
|
* AND you've configured the passed socketQueue with a targetQueue,
|
||||||
|
* THEN you should pass the end queue in the target hierarchy.
|
||||||
|
*
|
||||||
|
* For example, consider the following queue hierarchy:
|
||||||
|
* socketQueue -> ipQueue -> moduleQueue
|
||||||
|
*
|
||||||
|
* This example demonstrates priority shaping within some server.
|
||||||
|
* All incoming client connections from the same IP address are executed on the same target queue.
|
||||||
|
* And all connections for a particular module are executed on the same target queue.
|
||||||
|
* Thus, the priority of all networking for the entire module can be changed on the fly.
|
||||||
|
* Additionally, networking traffic from a single IP cannot monopolize the module.
|
||||||
|
*
|
||||||
|
* Here's how you would accomplish something like that:
|
||||||
|
* - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
|
||||||
|
* {
|
||||||
|
* dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
|
||||||
|
* dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
|
||||||
|
*
|
||||||
|
* dispatch_set_target_queue(socketQueue, ipQueue);
|
||||||
|
* dispatch_set_target_queue(iqQueue, moduleQueue);
|
||||||
|
*
|
||||||
|
* return socketQueue;
|
||||||
|
* }
|
||||||
|
* - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
|
||||||
|
* {
|
||||||
|
* [clientConnections addObject:newSocket];
|
||||||
|
* [newSocket markSocketQueueTargetQueue:moduleQueue];
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
|
||||||
|
* This is often NOT the case, as such queues are used solely for execution shaping.
|
||||||
|
**/
|
||||||
|
- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
|
||||||
|
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's not thread-safe to access certain variables from outside the socket's internal queue.
|
||||||
|
*
|
||||||
|
* For example, the socket file descriptor.
|
||||||
|
* File descriptors are simply integers which reference an index in the per-process file table.
|
||||||
|
* However, when one requests a new file descriptor (by opening a file or socket),
|
||||||
|
* the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
|
||||||
|
* So if we're not careful, the following could be possible:
|
||||||
|
*
|
||||||
|
* - Thread A invokes a method which returns the socket's file descriptor.
|
||||||
|
* - The socket is closed via the socket's internal queue on thread B.
|
||||||
|
* - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
|
||||||
|
* - Thread A is now accessing/altering the file instead of the socket.
|
||||||
|
*
|
||||||
|
* In addition to this, other variables are not actually objects,
|
||||||
|
* and thus cannot be retained/released or even autoreleased.
|
||||||
|
* An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
|
||||||
|
*
|
||||||
|
* Although there are internal variables that make it difficult to maintain thread-safety,
|
||||||
|
* it is important to provide access to these variables
|
||||||
|
* to ensure this class can be used in a wide array of environments.
|
||||||
|
* This method helps to accomplish this by invoking the current block on the socket's internal queue.
|
||||||
|
* The methods below can be invoked from within the block to access
|
||||||
|
* those generally thread-unsafe internal variables in a thread-safe manner.
|
||||||
|
* The given block will be invoked synchronously on the socket's internal queue.
|
||||||
|
*
|
||||||
|
* If you save references to any protected variables and use them outside the block, you do so at your own peril.
|
||||||
|
**/
|
||||||
|
- (void)performBlock:(dispatch_block_t)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These methods are only available from within the context of a performBlock: invocation.
|
||||||
|
* See the documentation for the performBlock: method above.
|
||||||
|
*
|
||||||
|
* Provides access to the socket's file descriptor(s).
|
||||||
|
* If the socket isn't connected, or explicity bound to a particular interface,
|
||||||
|
* it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
|
||||||
|
**/
|
||||||
|
- (int)socketFD;
|
||||||
|
- (int)socket4FD;
|
||||||
|
- (int)socket6FD;
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These methods are only available from within the context of a performBlock: invocation.
|
||||||
|
* See the documentation for the performBlock: method above.
|
||||||
|
*
|
||||||
|
* Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket.
|
||||||
|
*
|
||||||
|
* Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.)
|
||||||
|
* However, if you need one for any reason,
|
||||||
|
* these methods are a convenient way to get access to a safe instance of one.
|
||||||
|
**/
|
||||||
|
- (CFReadStreamRef)readStream;
|
||||||
|
- (CFWriteStreamRef)writeStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is only available from within the context of a performBlock: invocation.
|
||||||
|
* See the documentation for the performBlock: method above.
|
||||||
|
*
|
||||||
|
* Configures the socket to allow it to operate when the iOS application has been backgrounded.
|
||||||
|
* In other words, this method creates a read & write stream, and invokes:
|
||||||
|
*
|
||||||
|
* CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
|
||||||
|
* CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
|
||||||
|
*
|
||||||
|
* Returns YES if successful, NO otherwise.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* [asyncUdpSocket performBlock:^{
|
||||||
|
* [asyncUdpSocket enableBackgroundingOnSocket];
|
||||||
|
* }];
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now).
|
||||||
|
**/
|
||||||
|
//- (BOOL)enableBackgroundingOnSockets;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma mark Utilities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracting host/port/family information from raw address data.
|
||||||
|
**/
|
||||||
|
|
||||||
|
+ (NSString *)hostFromAddress:(NSData *)address;
|
||||||
|
+ (uint16_t)portFromAddress:(NSData *)address;
|
||||||
|
+ (int)familyFromAddress:(NSData *)address;
|
||||||
|
|
||||||
|
+ (BOOL)isIPv4Address:(NSData *)address;
|
||||||
|
+ (BOOL)isIPv6Address:(NSData *)address;
|
||||||
|
|
||||||
|
+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address;
|
||||||
|
+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma mark -
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@protocol GCDAsyncUdpSocketDelegate
|
||||||
|
@optional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By design, UDP is a connectionless protocol, and connecting is not needed.
|
||||||
|
* However, you may optionally choose to connect to a particular host for reasons
|
||||||
|
* outlined in the documentation for the various connect methods listed above.
|
||||||
|
*
|
||||||
|
* This method is called if one of the connect methods are invoked, and the connection is successful.
|
||||||
|
**/
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By design, UDP is a connectionless protocol, and connecting is not needed.
|
||||||
|
* However, you may optionally choose to connect to a particular host for reasons
|
||||||
|
* outlined in the documentation for the various connect methods listed above.
|
||||||
|
*
|
||||||
|
* This method is called if one of the connect methods are invoked, and the connection fails.
|
||||||
|
* This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved.
|
||||||
|
**/
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the datagram with the given tag has been sent.
|
||||||
|
**/
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if an error occurs while trying to send a datagram.
|
||||||
|
* This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet.
|
||||||
|
**/
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the socket has received the requested datagram.
|
||||||
|
**/
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
|
||||||
|
fromAddress:(NSData *)address
|
||||||
|
withFilterContext:(id)filterContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the socket is closed.
|
||||||
|
**/
|
||||||
|
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
5367
GCDAsyncSocket/GCDAsyncUdpSocket.m
Normal file
5367
GCDAsyncSocket/GCDAsyncUdpSocket.m
Normal file
File diff suppressed because it is too large
Load Diff
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Tradle
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
1017
RCTUDP.xcodeproj/project.pbxproj
Normal file
1017
RCTUDP.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
255
RCTUDPSocket.js
Normal file
255
RCTUDPSocket.js
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
//
|
||||||
|
// react-native-udp
|
||||||
|
//
|
||||||
|
// Created by Mark Vayngrib on 05/10/15.
|
||||||
|
// Copyright (c) 2015 Tradle, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @providesModule RCTUDPSocket
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native')
|
||||||
|
var {
|
||||||
|
Component
|
||||||
|
} = React
|
||||||
|
|
||||||
|
var mixInEventEmitter = require('mixInEventEmitter')
|
||||||
|
var DeviceEventEmitter = require('RCTDeviceEventEmitter')
|
||||||
|
var NativeModules = require('NativeModules')
|
||||||
|
var sockets = NativeModules.UDP // just UDP for now
|
||||||
|
var noop = function () {}
|
||||||
|
var instances = 0
|
||||||
|
var STATE = {
|
||||||
|
UNBOUND: 0,
|
||||||
|
BINDING: 1,
|
||||||
|
BOUND: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
class RCTSocket extends Component {
|
||||||
|
id: String;
|
||||||
|
|
||||||
|
_state: STATE.UNBOUND;
|
||||||
|
|
||||||
|
_address: undefined;
|
||||||
|
|
||||||
|
_port: undefined;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.id = instances++
|
||||||
|
this.subscriptiom = DeviceEventEmitter.addListener(
|
||||||
|
'udp-' + this.id + '-data', this._onReceive.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ensure compatibility with node's EventEmitter
|
||||||
|
if (!this.on) this.on = this.addListener.bind(this)
|
||||||
|
|
||||||
|
sockets.createSocket(this.id, {
|
||||||
|
type: props.type || 'udp4'
|
||||||
|
}) // later
|
||||||
|
}
|
||||||
|
|
||||||
|
_debug() {
|
||||||
|
var args = [].slice.call(arguments)
|
||||||
|
args.unshift(this.id)
|
||||||
|
console.log.apply(console, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind(port, address, callback) {
|
||||||
|
var self = this
|
||||||
|
if (typeof address === 'function') {
|
||||||
|
callback = address
|
||||||
|
address = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// address = address || '0.0.0.0' //'127.0.0.1'
|
||||||
|
if (callback) this.once('listening', callback)
|
||||||
|
|
||||||
|
this._state = STATE.BINDING
|
||||||
|
this._debug('binding, address:', address, 'port:', port)
|
||||||
|
sockets.bind(this.id, port, address, function(err, addr) {
|
||||||
|
if (err) {
|
||||||
|
// questionable: may want to self-destruct and
|
||||||
|
// force user to create a new socket
|
||||||
|
self._state = STATE.UNBOUND
|
||||||
|
self._debug('failed to bind', err)
|
||||||
|
return self.emit('error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
self._address = addr.address
|
||||||
|
self._port = addr.port
|
||||||
|
self._state = STATE.BOUND
|
||||||
|
self.emit('listening')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.subscription.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onReceive(info) {
|
||||||
|
this._debug('received', info)
|
||||||
|
|
||||||
|
var buf = toByteArray(info.data)
|
||||||
|
var rinfo = {
|
||||||
|
address: info.address,
|
||||||
|
port: info.port,
|
||||||
|
family: 'IPv4', // not necessarily
|
||||||
|
size: buf.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Buffer !== 'undefined') buf = new Buffer(buf)
|
||||||
|
|
||||||
|
this.emit('message', buf, rinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* socket.send(buf, offset, length, port, address, [callback])
|
||||||
|
*
|
||||||
|
* For UDP sockets, the destination port and IP address must be
|
||||||
|
* specified. A string may be supplied for the address parameter, and it will
|
||||||
|
* be resolved with DNS. An optional callback may be specified to detect any
|
||||||
|
* DNS errors and when buf may be re-used. Note that DNS lookups will delay
|
||||||
|
* the time that a send takes place, at least until the next tick. The only
|
||||||
|
* way to know for sure that a send has taken place is to use the callback.
|
||||||
|
*
|
||||||
|
* If the socket has not been previously bound with a call to bind, it's
|
||||||
|
* assigned a random port number and bound to the "all interfaces" address
|
||||||
|
* (0.0.0.0 for udp4 sockets, ::0 for udp6 sockets).
|
||||||
|
*
|
||||||
|
* @param {Array|string} message to be sent
|
||||||
|
* @param {number} offset Offset in the buffer where the message starts.
|
||||||
|
* @param {number} length Number of bytes in the message.
|
||||||
|
* @param {number} port destination port
|
||||||
|
* @param {string} address destination IP
|
||||||
|
* @param {function} callback Callback when message is done being delivered.
|
||||||
|
* Optional.
|
||||||
|
*/
|
||||||
|
// Socket.prototype.send = function (buf, host, port, cb) {
|
||||||
|
send(buffer, offset, length, port, address, callback) {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
if (offset !== 0) throw new Error('Non-zero offset not supported yet')
|
||||||
|
|
||||||
|
if (this._state === STATE.UNBOUND) {
|
||||||
|
throw new Error('bind before sending, seriously dude')
|
||||||
|
}
|
||||||
|
else if (this._state === STATE.BINDING) {
|
||||||
|
// we're ok, GCDAsync(Udp)Socket handles queueing internally
|
||||||
|
}
|
||||||
|
|
||||||
|
callback = callback || noop
|
||||||
|
if (typeof buffer === 'string') {
|
||||||
|
buffer = toByteArray(buffer)
|
||||||
|
}
|
||||||
|
else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(buffer)) {
|
||||||
|
buffer = buffer.toJSON().data
|
||||||
|
}
|
||||||
|
|
||||||
|
self._debug('sending', buffer)
|
||||||
|
sockets.send(this.id, buffer, +port, address, function(err) {
|
||||||
|
if (err) {
|
||||||
|
self._debug('send failed', err)
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
self._debug('sent')
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
address() {
|
||||||
|
if (this._state !== STATE.BOUND) {
|
||||||
|
throw new Error('socket is not bound yet')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: this._address,
|
||||||
|
port: this._port,
|
||||||
|
family: 'IPv4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
var self = this
|
||||||
|
if (this._destroyed) return
|
||||||
|
|
||||||
|
this._destroyed = true
|
||||||
|
this._debug('closing')
|
||||||
|
sockets.close(this.id, function() {
|
||||||
|
self._debug('closed')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
setBroadcast(flag) {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
|
|
||||||
|
setTTL(ttl) {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
|
|
||||||
|
setMulticastTTL(ttl, callback) {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
|
|
||||||
|
setMulticastLoopback(flag, callback) {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
|
|
||||||
|
addMembership(multicastAddress, multicastInterface, callback) {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
|
|
||||||
|
dropMembership(multicastAddress, multicastInterface, callback) {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
|
|
||||||
|
ref() {
|
||||||
|
// anything?
|
||||||
|
}
|
||||||
|
|
||||||
|
unref() {
|
||||||
|
// anything?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixInEventEmitter(RCTSocket, {
|
||||||
|
'listening': true,
|
||||||
|
'message': true,
|
||||||
|
'close': true,
|
||||||
|
'error': true
|
||||||
|
})
|
||||||
|
|
||||||
|
function toByteArray(obj) {
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
var i = 0
|
||||||
|
var arr = []
|
||||||
|
while (true) {
|
||||||
|
if (!(i in obj)) break
|
||||||
|
|
||||||
|
arr.push(+obj[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(arr)
|
||||||
|
}
|
||||||
|
else if (typeof obj !== 'string') {
|
||||||
|
throw new Error('unsupported format')
|
||||||
|
}
|
||||||
|
|
||||||
|
var uint = new Uint8Array(obj.length);
|
||||||
|
for (var i = 0, l = obj.length; i < l; i++){
|
||||||
|
uint[i] = obj.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(uint);
|
||||||
|
}
|
||||||
|
module.exports = RCTSocket
|
68
README.md
Normal file
68
README.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# UDP in React Native
|
||||||
|
|
||||||
|
node's [dgram](https://nodejs.org/api/dgram.html) API in React Native
|
||||||
|
|
||||||
|
## This module is used by [Tradle](https://github.com/tradle)
|
||||||
|
|
||||||
|
PR's welcome!
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
* Create a new react-native project. [Check react-native getting started](http://facebook.github.io/react-native/docs/getting-started.html#content)
|
||||||
|
|
||||||
|
* in PROJECT_DIR/node_modules/react-native, execute:
|
||||||
|
```
|
||||||
|
npm install --save react-native-udp
|
||||||
|
```
|
||||||
|
|
||||||
|
* Drag RCTUDP.xcodeproj from node_modules/react-native-udp into your XCode project. Click on the project in XCode, go to Build Phases, then Link Binary With Libraries and add libReactUDP.a
|
||||||
|
|
||||||
|
Buckle up, Dorothy
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### package.json
|
||||||
|
|
||||||
|
_only if you want to write require('dgram') in your javascript_
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"browser": {
|
||||||
|
"dgram": "react-native-udp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JS
|
||||||
|
|
||||||
|
_see/run index.ios.js for a complete example, but basically it's just like dgram_
|
||||||
|
|
||||||
|
var dgram = require('dgram')
|
||||||
|
// OR, if not shimming via package.json "browser" field:
|
||||||
|
// var dgram = require('RCTUDP')
|
||||||
|
var socket = dgram.createSocket('udp4')
|
||||||
|
socket.bind(12345)
|
||||||
|
socket.once('listening', function() {
|
||||||
|
var buf = toByteArray('excellent!')
|
||||||
|
socket.send(buf, 0, buf.length, remotePort, remoteHost, function(err) {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
console.log('message was sent')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('message', function(msg, rinfo) {
|
||||||
|
console.log('message was received', msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
### Note
|
||||||
|
|
||||||
|
If you want to send and receive node Buffer objects, you'll have to "npm install buffer" and set it as a global for RCTUDP to pick it up:
|
||||||
|
|
||||||
|
```js
|
||||||
|
global.Buffer = global.Buffer || require('buffer').Buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
[Tradle, Inc.](https://github.com/tradle/about/wiki)
|
15
dgram.js
Normal file
15
dgram.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @providesModule RCTUDP
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
var RCTSocket = require('RCTUDPSocket')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createSocket: function(type) {
|
||||||
|
return new RCTSocket({
|
||||||
|
type: type
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
16
iOS/AppDelegate.h
Normal file
16
iOS/AppDelegate.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Tradle, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, strong) UIWindow *window;
|
||||||
|
|
||||||
|
@end
|
60
iOS/AppDelegate.m
Normal file
60
iOS/AppDelegate.m
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Tradle, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
#import "RCTRootView.h"
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||||
|
{
|
||||||
|
NSURL *jsCodeLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading JavaScript code - uncomment the one you want.
|
||||||
|
*
|
||||||
|
* OPTION 1
|
||||||
|
* Load from development server. Start the server from the repository root:
|
||||||
|
*
|
||||||
|
* $ npm start
|
||||||
|
*
|
||||||
|
* To run on device, change `localhost` to the IP address of your computer
|
||||||
|
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
||||||
|
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
||||||
|
* on the same Wi-Fi network.
|
||||||
|
*/
|
||||||
|
|
||||||
|
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTION 2
|
||||||
|
* Load from pre-bundled file on disk. To re-generate the static bundle
|
||||||
|
* from the root of your project directory, run
|
||||||
|
*
|
||||||
|
* $ react-native bundle --minify
|
||||||
|
*
|
||||||
|
* see http://facebook.github.io/react-native/docs/runningondevice.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||||
|
|
||||||
|
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||||
|
moduleName:@"react-native-udp"
|
||||||
|
launchOptions:launchOptions];
|
||||||
|
|
||||||
|
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||||
|
UIViewController *rootViewController = [[UIViewController alloc] init];
|
||||||
|
rootViewController.view = rootView;
|
||||||
|
self.window.rootViewController = rootViewController;
|
||||||
|
[self.window makeKeyAndVisible];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
42
iOS/Base.lproj/LaunchScreen.xib
Normal file
42
iOS/Base.lproj/LaunchScreen.xib
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
|
||||||
|
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||||
|
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="RCTUDP" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||||
|
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||||
|
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||||
|
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||||
|
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||||
|
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||||
|
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||||
|
</constraints>
|
||||||
|
<nil key="simulatedStatusBarMetrics"/>
|
||||||
|
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||||
|
<point key="canvasLocation" x="548" y="455"/>
|
||||||
|
</view>
|
||||||
|
</objects>
|
||||||
|
</document>
|
38
iOS/Images.xcassets/AppIcon.appiconset/Contents.json
Normal file
38
iOS/Images.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "60x60",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "60x60",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
42
iOS/Info.plist
Normal file
42
iOS/Info.plist
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>armv7</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
19
iOS/RCTUDP.h
Normal file
19
iOS/RCTUDP.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// RCTUDP.h
|
||||||
|
// react-native-udp
|
||||||
|
//
|
||||||
|
// Created by Mark Vayngrib on 5/8/15.
|
||||||
|
// Copyright (c) 2015 Tradle, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "RCTUDPClient.h"
|
||||||
|
#import "RCTBridgeModule.h"
|
||||||
|
#import "RCTBridge.h"
|
||||||
|
#import "RCTEventDispatcher.h"
|
||||||
|
|
||||||
|
@interface RCTUDP : NSObject<SocketClientDelegate, RCTBridgeModule>
|
||||||
|
|
||||||
|
@property(retain, nonatomic)NSMutableDictionary *clients;
|
||||||
|
|
||||||
|
@end
|
118
iOS/RCTUDP.m
Normal file
118
iOS/RCTUDP.m
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
//
|
||||||
|
// RCTUDP.m
|
||||||
|
// react-native-udp
|
||||||
|
//
|
||||||
|
// Created by Mark Vayngrib on 5/8/15.
|
||||||
|
// Copyright (c) 2015 Tradle, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "RCTAssert.h"
|
||||||
|
#import "RCTBridge.h"
|
||||||
|
#import "RCTConvert.h"
|
||||||
|
#import "RCTEventDispatcher.h"
|
||||||
|
#import "RCTLog.h"
|
||||||
|
#import "RCTUDP.h"
|
||||||
|
#import "RCTUDPClient.h"
|
||||||
|
|
||||||
|
@implementation RCTUDP
|
||||||
|
|
||||||
|
RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
|
@synthesize bridge = _bridge;
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(createSocket:(NSString*)cId withOptions:(NSDictionary*)options)
|
||||||
|
{
|
||||||
|
if (!_clients) _clients = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
|
if (!cId) {
|
||||||
|
RCTLogError(@"%@.createSocket called with nil id parameter.", [self class]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RCTUDPClient *client = [_clients objectForKey:cId];
|
||||||
|
if (client) {
|
||||||
|
RCTLogError(@"%@.createSocket called twice with the same id.", [self class]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = [RCTUDPClient socketClientWithConfig:self];
|
||||||
|
[_clients setObject:client forKey:cId];
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(bind:(NSString*)cId
|
||||||
|
port:(int)port
|
||||||
|
address:(NSString *)address
|
||||||
|
callback:(RCTResponseSenderBlock)callback)
|
||||||
|
{
|
||||||
|
RCTUDPClient* client = [self findClient:cId callback:callback];
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
if (![client bind:port address:address error:&error])
|
||||||
|
{
|
||||||
|
callback(@[error]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(@[[NSNull null], [client address]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(send:(NSString*)cId
|
||||||
|
data:(NSData*)data
|
||||||
|
port:(int)port
|
||||||
|
address:(NSString*)address
|
||||||
|
callback:(RCTResponseSenderBlock)callback) {
|
||||||
|
RCTUDPClient* client = [self findClient:cId callback:callback];
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
[client send:data remotePort:port remoteAddress:address callback:callback];
|
||||||
|
if (callback) callback(@[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(close:(NSString*)cId
|
||||||
|
callback:(RCTResponseSenderBlock)callback) {
|
||||||
|
RCTUDPClient* client = [self findClient:cId callback:callback];
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
[client close];
|
||||||
|
[_clients removeObjectForKey:cId];
|
||||||
|
|
||||||
|
if (callback) callback(@[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) onData:(RCTUDPClient*) client data:(NSData *)data host:(NSString *)host port:(uint16_t)port
|
||||||
|
{
|
||||||
|
NSString *clientID = [[_clients allKeysForObject:client] objectAtIndex:0];
|
||||||
|
NSPropertyListFormat format;
|
||||||
|
NSArray* arr = [NSPropertyListSerialization propertyListFromData:data
|
||||||
|
mutabilityOption:NSPropertyListMutableContainers
|
||||||
|
format:&format
|
||||||
|
errorDescription:NULL];
|
||||||
|
|
||||||
|
[self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"udp-%@-data", clientID]
|
||||||
|
body:@{
|
||||||
|
@"data": arr,
|
||||||
|
@"address": host,
|
||||||
|
@"port": [NSNumber numberWithInt:port]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(RCTUDPClient*)findClient:(NSString*)cId callback:(RCTResponseSenderBlock)callback
|
||||||
|
{
|
||||||
|
RCTUDPClient *client = [_clients objectForKey:cId];
|
||||||
|
if (!client) {
|
||||||
|
if (!callback) {
|
||||||
|
RCTLogError(@"%@.missing callback parameter.", [self class]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(@[[NSString stringWithFormat:@"no client found with id %@", cId]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
85
iOS/RCTUDPClient.h
Normal file
85
iOS/RCTUDPClient.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// RCTUDPClient.h
|
||||||
|
// react-native-udp
|
||||||
|
//
|
||||||
|
// Created by Mark Vayngrib on 5/9/15.
|
||||||
|
// Copyright (c) 2015 Tradle, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "RCTBridgeModule.h"
|
||||||
|
|
||||||
|
extern NSString *const RCTUDPErrorDomain;
|
||||||
|
|
||||||
|
enum RCTUDPError
|
||||||
|
{
|
||||||
|
RCTUDPNoError = 0, // Never used
|
||||||
|
RCTUDPInvalidInvocationError,// Invalid method invocation
|
||||||
|
RCTUDPBadConfigError, // Invalid configuration
|
||||||
|
RCTUDPBadParamError, // Invalid parameter was passed
|
||||||
|
RCTUDPSendTimeoutError, // A send operation timed out
|
||||||
|
RCTUDPSendFailedError, // A send operation failed
|
||||||
|
RCTUDPClosedError, // The socket was closed
|
||||||
|
RCTUDPOtherError, // Description provided in userInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum RCTUDPError RCTUDPError;
|
||||||
|
|
||||||
|
@class RCTUDPClient;
|
||||||
|
|
||||||
|
@protocol SocketClientDelegate <NSObject>
|
||||||
|
|
||||||
|
- (void)onData:(RCTUDPClient*) client data:(NSData *)data host:(NSString*) host port:(uint16_t) port;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface RCTUDPClient : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, retain) NSString* id;
|
||||||
|
@property (nonatomic, retain) NSString* host;
|
||||||
|
@property (nonatomic) u_int16_t port;
|
||||||
|
|
||||||
|
///---------------------------------------------------------------------------------------
|
||||||
|
/// @name Class Methods
|
||||||
|
///---------------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Initializes a new RCTUDPClient
|
||||||
|
*
|
||||||
|
* @param delegate The object holding the callbacks, usually 'self'.
|
||||||
|
*
|
||||||
|
* @return New RCTUDPClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
+ (id)socketClientWithConfig:(id<SocketClientDelegate>) delegate;
|
||||||
|
|
||||||
|
///---------------------------------------------------------------------------------------
|
||||||
|
/// @name Instance Methods
|
||||||
|
///---------------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Binds to a host and port
|
||||||
|
*
|
||||||
|
* @param port
|
||||||
|
* @param host ip address
|
||||||
|
* @return true if bound, false if there was an error
|
||||||
|
*/
|
||||||
|
- (BOOL)bind:(u_int16_t) port address:(NSString*) address error:(NSError**)error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* send data to another host and port
|
||||||
|
*
|
||||||
|
* @param port
|
||||||
|
* @param host ip address
|
||||||
|
*/
|
||||||
|
- (void)send:(NSData*) data remotePort:(u_int16_t) port remoteAddress:(NSString*) address callback:(RCTResponseSenderBlock) callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return { address: ip, port: port }
|
||||||
|
*/
|
||||||
|
- (NSDictionary *)address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close client
|
||||||
|
*/
|
||||||
|
- (void)close;
|
||||||
|
|
||||||
|
@end
|
174
iOS/RCTUDPClient.m
Normal file
174
iOS/RCTUDPClient.m
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
//
|
||||||
|
// RCTUDPClient.m
|
||||||
|
// react-native-udp
|
||||||
|
//
|
||||||
|
// Created by Mark Vayngrib on 5/9/15.
|
||||||
|
// Copyright (c) 2015 Tradle, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <netinet/in.h>
|
||||||
|
#import <arpa/inet.h>
|
||||||
|
#import "RCTUDPClient.h"
|
||||||
|
#import "RCTBridgeModule.h"
|
||||||
|
#import "GCDAsyncUdpSocket.h"
|
||||||
|
|
||||||
|
NSString *const RCTUDPErrorDomain = @"RCTUDPErrorDomain";
|
||||||
|
|
||||||
|
@interface RCTUDPClient()
|
||||||
|
{
|
||||||
|
@private
|
||||||
|
uint16_t _port;
|
||||||
|
NSString* _address;
|
||||||
|
GCDAsyncUdpSocket *_udpSocket;
|
||||||
|
id<SocketClientDelegate> _clientDelegate;
|
||||||
|
NSMutableDictionary* _pendingSends;
|
||||||
|
long tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithConfig:(id<SocketClientDelegate>) aDelegate;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation RCTUDPClient
|
||||||
|
|
||||||
|
+ (id)socketClientWithConfig:(id<SocketClientDelegate>)delegate
|
||||||
|
{
|
||||||
|
return [[[self class] alloc] initWithConfig:delegate];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithConfig:(id<SocketClientDelegate>) aDelegate
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_clientDelegate = aDelegate;
|
||||||
|
_pendingSends = [NSMutableDictionary dictionary];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) bind:(u_int16_t)port address:(NSString *)address error:(NSError **) error
|
||||||
|
{
|
||||||
|
|
||||||
|
if (_port) {
|
||||||
|
if (error) {
|
||||||
|
*error = [self badInvocationError:@"this client's socket is already bound"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_port = port;
|
||||||
|
_address = address;
|
||||||
|
|
||||||
|
_udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:[self methodQueue]];
|
||||||
|
BOOL result;
|
||||||
|
if (address) {
|
||||||
|
struct sockaddr_in ip;
|
||||||
|
ip.sin_family = AF_INET;
|
||||||
|
ip.sin_port = htons(6003);
|
||||||
|
inet_pton(AF_INET, [address cStringUsingEncoding:NSASCIIStringEncoding], &ip.sin_addr);
|
||||||
|
|
||||||
|
NSData * hostAndPort = [NSData dataWithBytes:&ip length:sizeof(ip)];
|
||||||
|
result = [_udpSocket bindToAddress:hostAndPort error:error];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = [_udpSocket bindToPort:port error:error];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result && [_udpSocket beginReceiving:error];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)msgTag
|
||||||
|
{
|
||||||
|
NSNumber* tagNum = [NSNumber numberWithLong:msgTag];
|
||||||
|
RCTResponseSenderBlock callback = [_pendingSends objectForKey:tagNum];
|
||||||
|
if (callback) {
|
||||||
|
callback(@[]);
|
||||||
|
[_pendingSends removeObjectForKey:tagNum];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)msgTag dueToError:(NSError *)error
|
||||||
|
{
|
||||||
|
// NSError* err = [self sendFailedError:[error description]];
|
||||||
|
NSNumber* tagNum = [NSNumber numberWithLong:msgTag];
|
||||||
|
RCTResponseSenderBlock callback = [_pendingSends objectForKey:tagNum];
|
||||||
|
if (callback) {
|
||||||
|
callback(@[error]);
|
||||||
|
[_pendingSends removeObjectForKey:tagNum];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) send:(NSData *)data
|
||||||
|
remotePort:(u_int16_t)port
|
||||||
|
remoteAddress:(NSString *)address
|
||||||
|
callback:(RCTResponseSenderBlock)callback
|
||||||
|
{
|
||||||
|
[_udpSocket sendData:data toHost:address port:port withTimeout:-1 tag:tag];
|
||||||
|
if (callback) {
|
||||||
|
[_pendingSends setObject:callback forKey:[NSNumber numberWithLong:tag]];
|
||||||
|
}
|
||||||
|
|
||||||
|
tag++;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDictionary* ) address
|
||||||
|
{
|
||||||
|
return @{
|
||||||
|
@"address": [_udpSocket localHost],
|
||||||
|
@"port": [NSNumber numberWithInt:[_udpSocket localPort]]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) close
|
||||||
|
{
|
||||||
|
[_udpSocket close];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
|
||||||
|
fromAddress:(NSData *)address
|
||||||
|
withFilterContext:(id)filterContext
|
||||||
|
{
|
||||||
|
if (!_clientDelegate) return;
|
||||||
|
|
||||||
|
NSString *host = nil;
|
||||||
|
uint16_t port = 0;
|
||||||
|
[GCDAsyncUdpSocket getHost:&host port:&port fromAddress:address];
|
||||||
|
[_clientDelegate onData:self data:data host:host port:port];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSError *)badParamError:(NSString *)errMsg
|
||||||
|
{
|
||||||
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
|
||||||
|
|
||||||
|
return [NSError errorWithDomain:RCTUDPErrorDomain
|
||||||
|
code:RCTUDPBadParamError
|
||||||
|
userInfo:userInfo];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSError *)badInvocationError:(NSString *)errMsg
|
||||||
|
{
|
||||||
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
|
||||||
|
|
||||||
|
return [NSError errorWithDomain:RCTUDPErrorDomain
|
||||||
|
code:RCTUDPInvalidInvocationError
|
||||||
|
userInfo:userInfo];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSError *)sendFailedError:(NSString *)errMsg
|
||||||
|
{
|
||||||
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
|
||||||
|
|
||||||
|
return [NSError errorWithDomain:RCTUDPErrorDomain
|
||||||
|
code:RCTUDPSendFailedError
|
||||||
|
userInfo:userInfo];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (dispatch_queue_t)methodQueue
|
||||||
|
{
|
||||||
|
// return dispatch_queue_create("com.facebook.React.UDPSocketsQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
return dispatch_get_main_queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
8
iOS/main.jsbundle
Normal file
8
iOS/main.jsbundle
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Offline JS
|
||||||
|
// To re-generate the offline bundle, run this from the root of your project:
|
||||||
|
//
|
||||||
|
// $ react-native bundle --minify
|
||||||
|
//
|
||||||
|
// See http://facebook.github.io/react-native/docs/runningondevice.html for more details.
|
||||||
|
|
||||||
|
throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions');
|
18
iOS/main.m
Normal file
18
iOS/main.m
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Tradle, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
@autoreleasepool {
|
||||||
|
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||||
|
}
|
||||||
|
}
|
128
index.ios.js
Normal file
128
index.ios.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* Sample React Native App
|
||||||
|
* https://github.com/facebook/react-native
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
AppRegistry,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
function randomPort() {
|
||||||
|
return Math.random() * 65536 | 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var dgram = require('RCTUDP')
|
||||||
|
var a = dgram.createSocket('udp4')
|
||||||
|
var aPort = randomPort()
|
||||||
|
a.bind(bPort, function(err) {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
console.log('address', a.address())
|
||||||
|
})
|
||||||
|
|
||||||
|
var b = dgram.createSocket('udp4')
|
||||||
|
var bPort = randomPort()
|
||||||
|
b.bind(bPort, function(err) {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
console.log('address', b.address())
|
||||||
|
})
|
||||||
|
|
||||||
|
a.on('message', function(data, rinfo) {
|
||||||
|
var str = String.fromCharCode.apply(null, new Uint8Array(data));
|
||||||
|
console.log('a received', str, rinfo)
|
||||||
|
a.close()
|
||||||
|
b.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
b.on('message', function(data, rinfo) {
|
||||||
|
var str = String.fromCharCode.apply(null, new Uint8Array(data));
|
||||||
|
console.log('b received', str, rinfo)
|
||||||
|
|
||||||
|
// echo back
|
||||||
|
b.send(data, 0, data.length, aPort, '127.0.0.1', function(err) {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
console.log('sent')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
b.once('listening', function() {
|
||||||
|
var msg = toByteArray('hello')
|
||||||
|
a.send(msg, 0, msg.length, bPort, '127.0.0.1', function(err) {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
console.log('sent')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
var rctsockets = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.welcome}>
|
||||||
|
Welcome to React Native!
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
To get started, edit index.ios.js
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
Press Cmd+R to reload,{'\n'}
|
||||||
|
Cmd+D or shake for dev menu
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function toByteArray(obj) {
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
var i = 0
|
||||||
|
var arr = []
|
||||||
|
while (true) {
|
||||||
|
if (!(i in obj)) break
|
||||||
|
|
||||||
|
arr.push(+obj[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(arr)
|
||||||
|
}
|
||||||
|
else if (typeof obj !== 'string') {
|
||||||
|
throw new Error('unsupported format')
|
||||||
|
}
|
||||||
|
|
||||||
|
var uint = new Uint8Array(obj.length);
|
||||||
|
for (var i = 0, l = obj.length; i < l; i++){
|
||||||
|
uint[i] = obj.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(uint);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppRegistry.registerComponent('react-native-udp', () => rctsockets);
|
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "react-native-udp",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "node's dgram API for react-native",
|
||||||
|
"main": "./dgram.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/tradle/react-native-udp.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"react-component",
|
||||||
|
"reactnative",
|
||||||
|
"react-native",
|
||||||
|
"dgram",
|
||||||
|
"udp",
|
||||||
|
"sockets",
|
||||||
|
"ios"
|
||||||
|
],
|
||||||
|
"author": "Mark Vayngrib <mark.vayngrib@lablz.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/tradle/react-native-udp/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/tradle/react-native-udp",
|
||||||
|
"dependencies": {
|
||||||
|
"react-native": "^0.4.2"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user