0,0 → 1,511 |
// |
// AsyncSocket.h |
// |
// This class is in the public domain. |
// Originally created by Dustin Voss on Wed Jan 29 2003. |
// Updated and maintained by Deusty Designs and the Mac development community. |
// |
// http://code.google.com/p/cocoaasyncsocket/ |
// |
|
#import <Foundation/Foundation.h> |
|
@class AsyncSocket; |
@class AsyncReadPacket; |
@class AsyncWritePacket; |
|
extern NSString *const AsyncSocketException; |
extern NSString *const AsyncSocketErrorDomain; |
|
enum AsyncSocketError |
{ |
AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum. |
AsyncSocketNoError = 0, // Never used. |
AsyncSocketCanceledError, // onSocketWillConnect: returned NO. |
AsyncSocketConnectTimeoutError, |
AsyncSocketReadMaxedOutError, // Reached set maxLength without completing |
AsyncSocketReadTimeoutError, |
AsyncSocketWriteTimeoutError |
}; |
typedef enum AsyncSocketError AsyncSocketError; |
|
@interface NSObject (AsyncSocketDelegate) |
|
/** |
* In the event of an error, the socket is closed. |
* You may call "unreadData" during this call-back to get the last bit of data off the socket. |
* When connecting, this delegate method may be called |
* before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:". |
**/ |
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; |
|
/** |
* Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, |
* do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". |
* |
* If you call the disconnect method, and the socket wasn't already disconnected, |
* this delegate method will be called before the disconnect method returns. |
**/ |
- (void)onSocketDidDisconnect:(AsyncSocket *)sock; |
|
/** |
* Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have |
* the same delegate and will call "onSocket:didConnectToHost:port:". |
**/ |
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; |
|
/** |
* Called when a new socket is spawned to handle a connection. This method should return the run-loop of the |
* thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. |
**/ |
- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket; |
|
/** |
* Called when a socket is about to connect. This method should return YES to continue, or NO to abort. |
* If aborted, will result in AsyncSocketCanceledError. |
* |
* If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the |
* CFReadStream and CFWriteStream as desired prior to connection. |
* |
* If the connectToAddress:error: method was called, the delegate will be able to access and configure the |
* CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and |
* configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method. |
**/ |
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock; |
|
/** |
* Called when a socket connects and is ready for reading and writing. |
* The host parameter will be an IP address, not a DNS name. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; |
|
/** |
* Called when a socket has completed reading the requested data into memory. |
* Not called if there is an error. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; |
|
/** |
* Called when a socket has read in data, but has not yet completed the read. |
* This would occur if using readToData: or readToLength: methods. |
* It may be used to for things such as updating progress bars. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(CFIndex)partialLength tag:(long)tag; |
|
/** |
* Called when a socket has completed writing the requested data. Not called if there is an error. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; |
|
/** |
* Called when a socket has written some data, but has not yet completed the entire write. |
* It may be used to for things such as updating progress bars. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(CFIndex)partialLength tag:(long)tag; |
|
/** |
* Called if a read operation has reached its timeout without completing. |
* This method allows you to optionally extend the timeout. |
* If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. |
* If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. |
* |
* The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. |
* The length parameter is the number of bytes that have been read so far for the read operation. |
* |
* Note that this method may be called multiple times for a single read if you return positive numbers. |
**/ |
- (NSTimeInterval)onSocket:(AsyncSocket *)sock |
shouldTimeoutReadWithTag:(long)tag |
elapsed:(NSTimeInterval)elapsed |
bytesDone:(CFIndex)length; |
|
/** |
* Called if a write operation has reached its timeout without completing. |
* This method allows you to optionally extend the timeout. |
* If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. |
* If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. |
* |
* The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. |
* The length parameter is the number of bytes that have been written so far for the write operation. |
* |
* Note that this method may be called multiple times for a single write if you return positive numbers. |
**/ |
- (NSTimeInterval)onSocket:(AsyncSocket *)sock |
shouldTimeoutWriteWithTag:(long)tag |
elapsed:(NSTimeInterval)elapsed |
bytesDone:(CFIndex)length; |
|
/** |
* Called after the socket has successfully completed SSL/TLS negotiation. |
* This method is not called unless you use the provided startTLS method. |
* |
* If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, |
* and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code. |
**/ |
- (void)onSocketDidSecure:(AsyncSocket *)sock; |
|
@end |
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
@interface AsyncSocket : NSObject |
{ |
CFSocketRef theSocket4; // IPv4 accept or connect socket |
CFSocketRef theSocket6; // IPv6 accept or connect socket |
CFReadStreamRef theReadStream; |
CFWriteStreamRef theWriteStream; |
|
CFRunLoopSourceRef theSource4; // For theSocket4 |
CFRunLoopSourceRef theSource6; // For theSocket6 |
CFRunLoopRef theRunLoop; |
CFSocketContext theContext; |
NSArray *theRunLoopModes; |
|
NSTimer *theConnectTimer; |
|
NSMutableArray *theReadQueue; |
AsyncReadPacket *theCurrentRead; |
NSTimer *theReadTimer; |
NSMutableData *partialReadBuffer; |
|
NSMutableArray *theWriteQueue; |
AsyncWritePacket *theCurrentWrite; |
NSTimer *theWriteTimer; |
|
id theDelegate; |
UInt16 theFlags; |
|
long theUserData; |
} |
|
- (id)init; |
- (id)initWithDelegate:(id)delegate; |
- (id)initWithDelegate:(id)delegate userData:(long)userData; |
|
/* String representation is long but has no "\n". */ |
- (NSString *)description; |
|
/** |
* Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate |
* before changing it. It is, of course, safe to change the delegate before connecting or accepting connections. |
**/ |
- (id)delegate; |
- (BOOL)canSafelySetDelegate; |
- (void)setDelegate:(id)delegate; |
|
/* User data can be a long, or an id or void * cast to a long. */ |
- (long)userData; |
- (void)setUserData:(long)userData; |
|
/* Don't use these to read or write. And don't close them either! */ |
- (CFSocketRef)getCFSocket; |
- (CFReadStreamRef)getCFReadStream; |
- (CFWriteStreamRef)getCFWriteStream; |
|
// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in |
// and the other accept/connect methods can't be called without disconnecting the socket first. |
// If the attempt fails or times out, these methods either return NO or |
// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". |
|
// When an incoming connection is accepted, AsyncSocket invokes several delegate methods. |
// These methods are (in chronological order): |
// 1. onSocket:didAcceptNewSocket: |
// 2. onSocket:wantsRunLoopForNewSocket: |
// 3. onSocketWillConnect: |
// |
// Your server code will need to retain the accepted socket (if you want to accept it). |
// The best place to do this is probably in the onSocket:didAcceptNewSocket: method. |
// |
// After the read and write streams have been setup for the newly accepted socket, |
// the onSocket:didConnectToHost:port: method will be called on the proper run loop. |
// |
// Multithreading Note: If you're going to be moving the newly accepted socket to another run |
// loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the |
// onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods. |
// Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue. |
|
/** |
* Tells the socket to begin listening and accepting connections on the given port. |
* When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above). |
* The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) |
**/ |
- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; |
|
/** |
* This method is the same as acceptOnPort:error: with the additional option |
* of specifying which interface to listen on. So, for example, if you were writing code for a server that |
* has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it |
* to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi. |
* You may also use the special strings "localhost" or "loopback" to specify that |
* the socket only accept connections from the local machine. |
* |
* To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. |
**/ |
- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr; |
|
/** |
* Connects to the given host and port. |
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2") |
**/ |
- (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; |
|
/** |
* This method is the same as connectToHost:onPort:error: with an additional timeout option. |
* To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method. |
**/ |
- (BOOL)connectToHost:(NSString *)hostname |
onPort:(UInt16)port |
withTimeout:(NSTimeInterval)timeout |
error:(NSError **)errPtr; |
|
/** |
* Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. |
* For example, a NSData object returned from NSNetservice's addresses method. |
* |
* 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]; |
**/ |
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; |
|
/** |
* This method is the same as connectToAddress:error: with an additional timeout option. |
* To not time out use a negative time interval, or simply use the connectToAddress:error: method. |
**/ |
- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; |
|
/** |
* Disconnects immediately. Any pending reads or writes are dropped. |
* If the socket is not already disconnected, the onSocketDidDisconnect delegate method |
* will be called immediately, before this method returns. |
* |
* Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method) |
* [asyncSocket setDelegate:nil]; |
* [asyncSocket disconnect]; |
* [asyncSocket release]; |
**/ |
- (void)disconnect; |
|
/** |
* Disconnects after all pending reads have completed. |
* After calling this, the read and write methods will do nothing. |
* The socket will disconnect even if there are still pending writes. |
**/ |
- (void)disconnectAfterReading; |
|
/** |
* Disconnects after all pending writes have completed. |
* After calling this, the read and write methods will do nothing. |
* The socket will disconnect even if there are still pending reads. |
**/ |
- (void)disconnectAfterWriting; |
|
/** |
* Disconnects after all pending reads and writes have completed. |
* After calling this, the read and write methods will do nothing. |
**/ |
- (void)disconnectAfterReadingAndWriting; |
|
/* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */ |
- (BOOL)isConnected; |
|
/** |
* Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. |
* The host will be an IP address. |
**/ |
- (NSString *)connectedHost; |
- (UInt16)connectedPort; |
|
- (NSString *)localHost; |
- (UInt16)localPort; |
|
/** |
* Returns the local or remote address to which this socket is connected, |
* specified as a sockaddr structure wrapped in a NSData object. |
* |
* See also the connectedHost, connectedPort, localHost and localPort methods. |
**/ |
- (NSData *)connectedAddress; |
- (NSData *)localAddress; |
|
/** |
* Returns whether the socket is IPv4 or IPv6. |
* An accepting socket may be both. |
**/ |
- (BOOL)isIPv4; |
- (BOOL)isIPv6; |
|
// The readData and writeData methods won't block. |
// |
// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) |
// If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method |
// is called to optionally allow you to extend the timeout. |
// Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect". |
// |
// The tag is for your convenience. |
// You can use it as an array index, step number, state id, pointer, etc., just like the socket's user data. |
|
/** |
* This will read a certain number of bytes into memory, and call the delegate method when those bytes have been read. |
* |
* If the length is 0, this method does nothing and the delegate is not called. |
* If the timeout value is negative, the read operation will not use a timeout. |
**/ |
- (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; |
|
/** |
* This reads bytes until (and including) the passed "data" parameter, which acts as a separator. |
* The bytes and the separator are returned by the delegate method. |
* |
* If you pass nil or zero-length data as the "data" parameter, |
* the method will do nothing, and the delegate will not be called. |
* If the timeout value is negative, the read operation will not use a timeout. |
* |
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. |
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for |
* a character, the read will prematurely end. |
**/ |
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; |
|
/** |
* Same as readDataToData:withTimeout:tag, with the additional restriction that the amount of data read |
* may not surpass the given maxLength (specified in bytes). |
* |
* If you pass a maxLength parameter that is less than the length of the data parameter, |
* the method will do nothing, and the delegate will not be called. |
* |
* If the max length is surpassed, it is treated the same as a timeout - the socket is closed. |
* |
* Pass -1 as maxLength if no length restriction is desired, or simply use the readDataToData:withTimeout:tag method. |
**/ |
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag; |
|
/** |
* Reads the first available bytes that become available on the socket. |
* |
* If the timeout value is negative, the read operation will not use a timeout. |
**/ |
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; |
|
/** |
* Writes data to the socket, and calls the delegate when finished. |
* |
* If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. |
* If the timeout value is negative, the write operation will not use a timeout. |
**/ |
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; |
|
/** |
* Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check). |
* "tag", "done" and "total" will be filled in if they aren't NULL. |
**/ |
- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total; |
- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total; |
|
/** |
* Secures the connection using SSL/TLS. |
* |
* This method may be called at any time, and the TLS handshake will occur after all pending reads and writes |
* are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing |
* the upgrade to TLS at the same time, without having to wait for the write to finish. |
* Any reads or writes scheduled after this method is called will occur over the secured connection. |
* |
* The possible keys and values for the TLS settings are well documented. |
* Some possible keys are: |
* - kCFStreamSSLLevel |
* - kCFStreamSSLAllowsExpiredCertificates |
* - kCFStreamSSLAllowsExpiredRoots |
* - kCFStreamSSLAllowsAnyRoot |
* - kCFStreamSSLValidatesCertificateChain |
* - kCFStreamSSLPeerName |
* - kCFStreamSSLCertificates |
* - kCFStreamSSLIsServer |
* |
* Please refer to Apple's documentation for associated values, as well as other possible keys. |
* |
* If you pass in nil or an empty dictionary, the default settings will be used. |
* |
* The default settings will check to make sure the remote party's certificate is signed by a |
* trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. |
* However it will not verify the name on the certificate unless you |
* give it a name to verify against via the kCFStreamSSLPeerName key. |
* The security implications of this are important to understand. |
* Imagine you are attempting to create a secure connection to MySecureServer.com, |
* but your socket gets directed to MaliciousServer.com because of a hacked DNS server. |
* If you simply use the default settings, and MaliciousServer.com has a valid certificate, |
* the default settings will not detect any problems since the certificate is valid. |
* To properly secure your connection in this particular scenario you |
* should set the kCFStreamSSLPeerName property to "MySecureServer.com". |
* If you do not know the peer name of the remote host in advance (for example, you're not sure |
* if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the |
* certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. |
* The X509Certificate class is part of the CocoaAsyncSocket open source project. |
**/ |
- (void)startTLS:(NSDictionary *)tlsSettings; |
|
/** |
* For handling readDataToData requests, data is necessarily read from the socket in small increments. |
* The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and |
* store any overflow in a small internal buffer. |
* This is termed pre-buffering, as some data may be read for you before you ask for it. |
* If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone. |
* |
* The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition. |
* It is highly recommended one leave this set to YES. |
* |
* This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason. |
* In that case, this method exists to allow one to easily enable pre-buffering when ready. |
**/ |
- (void)enablePreBuffering; |
|
/** |
* When you create an AsyncSocket, it is added to the runloop of the current thread. |
* So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it. |
* |
* If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to |
* allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design. |
* |
* If, however, you need to move the socket to a separate thread at a later time, this |
* method may be used to accomplish the task. |
* |
* This method must be called from the thread/runloop the socket is currently running on. |
* |
* Note: After calling this method, all further method calls to this object should be done from the given runloop. |
* Also, all delegate calls will be sent on the given runloop. |
**/ |
- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; |
|
/** |
* Allows you to configure which run loop modes the socket uses. |
* The default set of run loop modes is NSDefaultRunLoopMode. |
* |
* If you'd like your socket to continue operation during other modes, you may want to add modes such as |
* NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. |
* |
* Accepted sockets will automatically inherit the same run loop modes as the listening socket. |
* |
* Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. |
**/ |
- (BOOL)setRunLoopModes:(NSArray *)runLoopModes; |
|
/** |
* Returns the current run loop modes the AsyncSocket instance is operating in. |
* The default set of run loop modes is NSDefaultRunLoopMode. |
**/ |
- (NSArray *)runLoopModes; |
|
/** |
* In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read |
* any data that's left on the socket. |
**/ |
- (NSData *)unreadData; |
|
/* A few common line separators, for use with the readDataToData:... methods. */ |
+ (NSData *)CRLFData; // 0x0D0A |
+ (NSData *)CRData; // 0x0D |
+ (NSData *)LFData; // 0x0A |
+ (NSData *)ZeroData; // 0x00 |
|
@end |