From e749bbdf2b4a3b67c9845a964744cd920cfe6e6d Mon Sep 17 00:00:00 2001 From: Mathewka Date: Fri, 19 Feb 2016 14:59:29 +0900 Subject: [PATCH] Update CocoaAsyncSocket 4.3.3 --- .../AsyncSocket Documentation.html | 1811 ++++++++++++++++++++ .../CocoaAsyncSocket-4.3.3/AsyncSocket.h | 223 +++ .../CocoaAsyncSocket-4.3.3/AsyncSocket.m | 1741 +++++++++++++++++++ .../AsyncSocketSample.xcodeproj/project.pbxproj | 425 +++++ .../AsyncSocketSample/EchoMain.m | 352 ++++ .../AsyncSocketSample/EchoServerMain.m | 215 +++ .../CocoaAsyncSocket-4.3.3/changes.txt | 52 + 7 files changed, 4819 insertions(+) create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket Documentation.html create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.h create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.m create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/AsyncSocketSample.xcodeproj/project.pbxproj create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoMain.m create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoServerMain.m create mode 100644 CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/changes.txt diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket Documentation.html b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket Documentation.html new file mode 100644 index 0000000..f384161 --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket Documentation.html @@ -0,0 +1,1811 @@ + + + + + + AsyncSocket Documentation + + + +

AsyncSocket 4.3

+ +

AsyncSocket is a TCP/IP socket networking library, designed to efficiently handle packet data. The library is in two files and one public Cocoa class.

+

The library is public domain, written and maintained by Dustin Voss. For support, e-mail d.j.v.@mac.com.

+ +

Contents

+
    +
  1. Introduction + +
  2. +
  3. Socket Basics + +
  4. +
  5. Using AsyncSocket + +
  6. +
  7. AsyncSocket Reference + +
  8. +
  9. API Changes
  10. +
  11. API Index
  12. +
+ + +

About AsyncSocket

+

The Cocoa API provides a handful of networking classes, suitable for downloading and uploading images and files. These classes support HTML, FTP, and other protocols, but cannot be used with application-specific protocols. Without low-level socket classes supporting application-specific protocols, developers must custom-code networking solutions using BSD or Carbon functions in conjunction with + NSFileHandle, + NSInputStream, and + NSOutputStream. These functions and classes are not optimized for TCP/IP networking in a real Cocoa application, and can be difficult to integrate. +

+

AsyncSocket provides easy-to-integrate “fire and forget” networking that makes it easy for your application to support networking. Features include:

+ + + +

About This Document

+

This document assumes the reader already has the general public’s understanding of networking, and a developer’s understanding of Cocoa and Objective-C programming.

+ + +

Socket Basics

+

In networking parlance, a computer is a host for a number of sockets. A socket is one end of a communication channel called a network connection; the other end is another socket. From its own point of view, any socket is the local socket, and the socket at the other end of the connection is the remote socket.

+

To establish the connection, one of the two sockets, the connect socket, must contact the other socket, the listen socket, and the listen socket must accept the connection. To contact the listen socket, the connect socket must know its socket address. Every socket has a socket address. The address consists of two parts: the host address and the port number. The host address is the IP address of the computer, and the port number uniquely identifies each socket hosted on the computer.

+

A computer can have multiple host addresses. It will have a pair of addresses for each possible connection method (e.g., an Ethernet card, a modem, an AirPort card, a VPN connection) and a pair for connecting to itself (called “localhost”). One address of each pair is an IPv4 address such as “192.168.3.1,” and the other is an IPv6 address such as “fe80::230:65ff:fe29:aa9d.”

+

An address such as “www.google.com” corresponds to a host address, but it is not a host address itself. It is a DNS address or DNS name, which is converted to a host address by a DNS look-up operation. A URL such as “http://www.google.com:80” is likewise not a host address. URLs can include a DNS name, host address, port number, and other information.

+

Applications running on different hosts, or even on the same host, can use sockets to communicate with each other. Looking at it another way, each socket provides a communication service to its client application. The applications send and receive data to and from each other, which they can interpret and act upon. The data is composed of bytes, arranged into groups called packets and sent and received in accordance with a protocol followed by both applications.

+

A protocol establishes the structure of each packet, the circumstances under which any particular packet should be sent, and rules to handle exceptional circumstances. It also establishes roles for each client application. In a client-server architecture, some applications (the servers) provide a service used by other applications (the clients). In a peer-to-peer architecture, some applications (the peers) act as clients and servers at the same time.

+ + +

Socket Limitations

+

In some ways, a socket is like a file. Both contain data with a beginning and an end. Both can be written to or read from. But in other ways they differ, and these differences drive the design of a protocol.

+

First, while a file is typically for either reading or writing, a socket is interactive. Applications must be able to interrupt and alert each other, changing each others’ behavior. For this reason, data is divided up into packets, and this division leaves an opening for interruptions.

+

Second, while a file has a known size, a socket does not have a size. An application cannot know how much data is left in the current packet, unless the protocol itself specifies this. Thus, all packets include implicit or explicit size information or markers to indicate when the packet is finished.

+

Third, while a file is reliable, a socket is not reliable. When you read from a socket, the data arrives in chunks, with possibly large delays between each chunk, and there is no way to know whether a particular delay is because of high traffic or because of an unexpected disconnection. So applications are forced to treat a long delay as if it was a disconnection, and protocols define time-outs and retry messages to regulate this.

+

The AsyncSocket library was designed to make these protocol considerations easy to deal with.

+ + +

Packet Structure

+

Network communication protocols employ certain basic elements common to all protocols. These are:

+ +

Parsing Packets” describes how to use AsyncSocket methods to read these different elements.

+ + +

Using AsyncSocket

+

The AsyncSocket library is composed of one class, also called AsyncSocket. An instance of AsyncSocket represents one socket, which may be a listen socket or a connect socket. An instance may also be disconnected, in which case it does not represent a functioning socket. Throughout this document, the terms “socket” and “instance of AsyncSocket” are used almost interchangeably.

+

To use the AsyncSocket class in your project, add AsyncSocket.m and /System/Library/Frameworks/CoreServices.framework to the project, and import AsyncSocket.h into each file that needs it.

+

This version of AsyncSocket requires Mac OS X 10.4 or later. If you must support Mac OS X 10.3, you may use AsyncSocket version 3.13.

+ + +

Socket Ownership

+

In a client-server architecture, an application acting as a client usually employs one connect socket to communicate with a server, and an application acting as a server usually employs one listen socket to accept connections from clients and several connect sockets to communicate with the connected clients. In a peer-to-peer architecture, an application usually employs one listen socket and several connect sockets as if it were a server.

+

Each socket should be managed by a connection controller class. This class should be responsible for:

+ +

A collection of connected sockets should be managed by a connection array controller. This class should be responsible for creating and destroying individual connection controllers as needed. Each managed connection controller should keep the connection array controller apprised of its status.

+

If an application has a listen socket, it should be owned and managed by the connection array controller. When the listen socket accepts a connection, the connection array controller should construct a new connection controller responsible for managing the new connection.

+ + +

Delegate Methods

+

An instance of AsyncSocket sends messages to the its delegate object upon completing certain operations or encountering certain errors. All instances of AsyncSocket should have a delegate that responds to these messages appropriately. The delegate object should be the socket’s connection controller or connection array controller.

+

The delegate object should implement the following delegate methods according to the socket’s purpose (see “AsyncSocket Reference” below for detailed descriptions):

+ +

You will seldom need to change a socket’s delegate, but should the need arise, be careful. If a socket completes any read or write operations initiated under the old delegate, the new delegate will be notified, not the old delegate. You can check for pending read or write operations by sending -canSafelySetDelegate: to the socket. See “Reading and Writing” below for a discussion of pending read or write operations.

+

Several instances of AsyncSocket can safely share one delegate object. Each instance passes itself as the first argument of any delegate message it sends. This allows the delegate object to distinguish between AsyncSocket instances.

+ + +

Accepting, Connecting, and Disconnecting

+

To initiate a connection to a remote socket at a given socket address, send -connectToHost:onPort:error: to the socket, passing the host address and port as arguments. The host address can be an IP address or DNS name, including “localhost.” A DNS name does not include a port number.

+

To set up a listen socket for a given port, send -acceptOnPort:error: to the socket. The socket will listen on all available host addresses. To set up a socket that listens on only one address, send -acceptOnAddress:port:error:. To optionally direct an incoming connection to a particular thread, implement -onSocket:wantsRunLoopForNewSocket: in the socket’s delegate.

+

To alter the properties of the socket’s underlying CFReadStream or CFWriteStream objects (for example, to support SSL connections) implement -onSocketWillConnect: in the socket’s delegate.

+

To disconnect the socket cleanly, send -disconnectAfterWriting to the socket. This method will complete all write operations before disconnecting. To disconnect immediately, send -disconnect. In either case, the socket will send -onSocketDidDisconnect: to the delegate after it finishes disconnecting.

+

If the remote socket cleanly disconnects from the local socket, the local socket will send -onSocket:willDisconnectWithError: to the delegate. The message’s error argument will be nil (see “Error Handling” below). The local socket will then send -onSocketDidDisconnect: to the delegate.

+ + +

Reading and Writing

+

AsyncSocket handles reading and writing operations itself. You specify the operation you desire, and AsyncSocket carries it out as soon as possible and sends a message to the delegate when the operation is complete.

+

AsyncSocket supports three reading operations (“read-to-length,” “read-to-data,” and “read-any”) and one write operation (“write-data”).

+ +

Invoking a read or write method will perform the requested operation immediately, if possible. But if the operation cannot be completed immediately, it will be placed in a read or write queue. AsyncSocket will continue to execute the queued operations in sequential order in the background.

+

To check the progress of the currently-executing “read-to-length” or “write-data” operation, you may send -progressOfReadReturningTag:bytesDone:total: or -progressOfWriteReturningTag:bytesDone:total: to the socket. These methods return a decimal number between 0.0 and 1.0, and you may also get the number of bytes read or written of the total. +

The read and write operations support optional time-outs. The time-out value is a decimal number of seconds, for example, 1.5. The time-out value specifies how long the operation can take once begun. A queued operation does not start running out of time until it begins execution. AsyncSocket will use the system-defined time-out value if you send a negative time-out argument.

+

If an operation runs out of time before it is completed, the socket will disconnect. If your protocol supports a mechanism to recover from a long-delayed transmission, you can supply your own “soft” time-out by using NSTimer objects instead of or in addition to AsyncSocket’s own time-out system.

+

When an operation has completed within the allotted time, the socket will send a message to its delegate (either -onSocket:didReadData:withTag: or -onSocket:didWriteDataWithTag:). The delegate object should respond appropriately, sending another read or write message to the socket as necessary.

+

To help the delegate object respond appropriately, you can supply a tag value as an argument of a read or write message. A tag value can be an integer, pointer, or object id. Tag values are not transmitted to the remote socket, and the remote socket does not transmit tag values to the local socket. But the message sent by the local socket to its delegate upon completing the operation includes the tag value you initially supplied. The tag value can be used to “remember” the circumstances under which the operation was initiated. Tag values can mark a type of operation, or specify a step of a multi-operation process.

+ + +

Parsing Packets

+

To parse a packet, or to read a data stream, line of text, or data stream header, consider how to break it down into a series of simple read operations.

+

Here are some techniques you can use to read and parse the elements of a protocol:

+ + + +

Error Handling

+

If a socket encounters an input/output error, or if a read or write operation times out, AsyncSocket assumes the connection has been broken and must be re-established. The socket will proceed to disconnect itself. The remote socket will typically be disconnected by its own client application.

+

Before disconnecting the local socket, an AsyncSocket instance will send -onSocket:willDisconnectWithError: to its delegate object. The second argument of this message is an NSError object. This object may contain a POSIX, Mac OS, or CFStream error code, as indicated by the object’s -domain method. It may also contain an AsyncSocket-specific error code. See “Errors” below for details.

+

During the execution of its -onSocket:willDisconnectWithError: method, the delegate object may retrieve all unreceived data (including data from any partially-completed read operations) by sending -readDataWithTimeout:tag: to the socket. The socket will invoke the delegate object’s -onSocket:didReadData:withTag: method before returning from -readDataWithTimeout:tag:.

+

After the delegate object’s -onSocket:willDisconnectWithError: method returns, the socket will be disconnected. It will then send -onSocketDidDisconnect: to the delegate.

+ + +

Threading and Run-Loops

+

An AsyncSocket instance must be used in conjunction with a run-loop. Its run-loop activity occurs in the NSDefaultRunLoopMode mode.

+

An AsyncSocket instance may be used from a worker thread or the main thread. However, each instance should only be used from one thread, so that the instance does not invoke delegate methods from within the wrong thread context.

+

To create a connect or listen socket in a particular thread, simply create the socket in the context of that thread.

+

A listen socket creates a new socket when it accepts a connection. To ensure this new socket is created in a particular thread, return the thread’s run-loop from the listen socket’s -onSocket:wantsRunLoopForNewSocket: delegate method.

+ + +

Customizing AsyncSocket

+

AsyncSocket is not intended to be sub-classed. However, since AsyncSocket is in the public domain, you may alter it as much as you like for your own projects. The source code was written to be understandable.

+

AsyncSocket uses CFSocket, CFReadStream, and CFWriteStream internally. You may access these directly and set whatever properties or behaviors you may need. The -onSocketWillConnect: delegate method is designed to facilitate this.

+ + +

AsyncSocket Reference

+

See “API Index” below for an alphabetical list of messages, methods, types, and constants.

+ + +

Initialization and Deallocation Messages

+

AsyncSocket does not provide an auto-released convenience initializer.

+

-init -initWithDelegate: -initWithDelegate:userData: -dealloc

+ + +

-init

+

This message initializes the receiver, without setting a delegate.

+ + + + + + + + + + + + + + + + + +

Syntax

-(id)init

Arguments

None.

Return Value

An instance of AsyncSocket.

Errors

None.

+ + +

-initWithDelegate:

+

This message initializes the receiver, setting the delegate at the same time.

+ + + + + + + + + + + + + + + + + +

Syntax

-(id)initWithDelegate:(id)delegate

Arguments

delegate

+

An object that will act as the delegate for the receiver. The delegate should implement the necessary AsyncSocketDelegate methods.

+

May be nil.

+

Return Value

+
+

An instance of AsyncSocket.

+

Errors

None.

+ + +

-initWithDelegate:userData:

+

This message initializes the receiver, setting the delegate and user data at the same time. This method is the designated initializer of an AsyncSocket instance.

+ + + + + + + + + + + + + + + + + + + + + +

Syntax

-(id)initWithDelegate:(id)delegate userData:(long)userData

Arguments

delegate

+

An object that will act as the delegate for the receiver. The delegate should implement the necessary AsyncSocketDelegate methods.

+

May be nil.

+

userData

+

A value that will be associated with the receiver. It may be retrieved later through a -userData: message.

+

Return Value

+
+

An instance of AsyncSocket.

+

Errors

None.

+ + +

-dealloc

+

This message will deallocate the receiver, disconnecting if necessary.

+ + + + + + + + + + + + + + + + + +

Syntax

-(void)dealloc

Arguments

None.

Return Value

None.

Errors

None.

+ + +

User Data Messages

+

-userData -setUserData:

+ + +

-userData

+

This message returns the receiver’s current user data, an arbitary value associated with the receiver.

+ + + + + + + + + + + + + + + + + +

Syntax

-(long)userData

Arguments

None.

Return Value

The current user data.

Errors

None.

+ + +

-setUserData:

+

This message sets the receiver’s user data, an arbitary value associated with the receiver.

+ + + + + + + + + + + + + + + + + + +

Syntax

-(void)setUserData:(long)userData

Arguments

userData

+

A value that will be associated with the receiver.

+

Return Value

None.

Errors

None.

+ + +

Delegation Messages

+

-delegate -setDelegate: -canSafelySetDelegate

+ + +

-delegate

+

This message returns the receiver’s current delegate object.

+ + + + + + + + + + + + + + + + + +

Syntax

-(id)delegate

Arguments

None.

Return Value

The current delegate object, or nil.

Errors

None.

+ + +

-setDelegate:

+

This message sets the receiver’s delegate object. The delegate object is not retained.

+ + + + + + + + + + + + + + + + + + +

Syntax

-(void)setDelegate:(id)delegate

Arguments

delegate

+

An instance of a class that will act as the delegate for the receiver. Should implement the necessary AsyncSocketDelegate methods.

+

May be nil.

+

Return Value

None.

Errors

None.

+

If the delegate object is changed, the old delegate object will no longer receive any messages that it may be expecting as a result of pending read or write operations that it initiated. To ensure there are no pending read or write operations, the delegate object can invoke -canSafelySetDelegate:.

+ + +

-canSafelySetDelegate

+

This message can be sent to determine whether a new delegate object needs to be made aware of pending read or write operations.

+ + + + + + + + + + + + + + + + + +

Syntax

-(BOOL)canSafelySetDelegate

Arguments

None.

Return Value

+

YES, if the receiver has any pending read or write operations.

+

NO, if the receiver has no pending read or write operations.

+

Errors

None.

+ + +

Connection Messages

+

-connectToHost:onPort:error: -acceptOnPort:error: -acceptOnAddress:port:error: -isConnected -disconnect -disconnectAfterWriting -connectedHost -connectedPort -localHost -localPort

+ +

-connectToHost:onPort:error:

+

This message establishes an outgoing connection from the receiver.

+ + + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)err

Arguments

hostname

+

A DNS name or IP address to which the receiver should connect. Both IPv4 and IPv6 addresses are supported.

+

port

+
+

A port number to which the receiver should connect.

+

err

+

The address of an NSError object pointer. In the event of an error, the pointer will be set to the NSError object describing the error.

+

The sender may pass nil if it does not wish to retrieve any NSError object.

+

Return Value

+

Returns YES if the connection is successful.

+

This does not indicate that the socket is ready for use. The socket is only ready when the -onSocket:didConnectToHost:port: delegate method is called.

+

Errors

+

Returns NO and an NSError object under the following conditions:

+
    +
  • hostname is not a valid address, or there is no such address.
  • +
  • The socket cannot be created, or cannot connect to the address.
  • +
+

Returns YES and calls -onSocket:willDisconnectWithError: under the following conditions:

+
    +
  • The read and write streams could not be attached or opened.
  • +
+

Raises an AsyncSocketException if the socket is already connected or accepting connections, or if no delegate has been set.

+
+

If the receiver returns YES, it will continue to establish a connection. When the connection is successfully established, or fails to be established, the receiver will send an appropriate message to its delegate object.

+

Read and write operations may be queued before the connection is successfully established. They will be executed after the connection is complete.

+ + +

-acceptOnPort:error:

+

This message establishes the receiver as a listen socket that will accept incoming connections.

+ + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(BOOL)acceptOnPort:(UInt16)port error:(NSError **)err

Arguments

port

+

A port number at which the receiver should accept connections.

+

err

+

The address of an NSError object pointer. In the event of an error, the pointer will be set to the NSError object describing the error.

+

The sender may pass nil if it does not wish to retrieve any NSError object.

+

Return Value

+

Returns YES if the receiver is successfully accepting connections at the specified port.

+

Errors

+

Returns NO and an NSError object if the socket cannot be created, or cannot accept connections on the specified port.

+

Raises an AsyncSocketException if the socket is already connected or accepting connections, or if no delegate has been set.

+
+

The receiver establishes a listen socket with the SO_REUSEADDR option set.

+

In the event of a connection from a remote socket, the receiver will create a new AsyncSocket instance. The new instance will have the same delegate object as the receiver, and will attempt to complete the connection to the remote socket.

+

There are three possible outcomes of the new instance’s attempt. First, the attempt could succeed. Second, the attempt could fail because a local socket could not be created. Third, the attempt could fail because of another issue.

+

If successful, the receiver will send -onSocket:didAcceptNewSocket: and -onSocket:wantsRunLoopForNewSocket: to its delegate object. At this point the delegate object can change the new instance’s delegate object or assign a run-loop. After the delegate methods return, the new instance will send -onSocket:didConnectToHost:port: to its delegate object.

+

If unsuccessful because a local socket could not be created, the new instance will be silently destroyed, and the receiver will continue to accept connections.

+

If unsuccessful for some other reason, the receiver will send -onSocket:didAcceptNewSocket: and -onSocket:wantsRunLoopForNewSocket: to its delegate object. After the delegate method returns, the new instance will send -onSocket:willDisconnectWithError: to its delegate with details about the failure condition.

+ + +

-acceptOnAddress:port:error:

+

This message establishes the receiver as a listen socket that will accept incoming connections on a particular host address and port.

+ + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(BOOL)acceptOnAddress:(NSString *)hostaddr port:(UInt16)port error:(NSError **)err

Arguments

hostaddr

+

A host address at which the receiver should accept connections. The address should be an IPv4 or IPv6 address, such as “192.168.3.1” or “fe80::230:65ff:fe29:aa9d.”

+

If nil or an empty string, the effect is the same as -acceptOnPort:error: message. +

port

+

A port number at which the receiver should accept connections.

+

err

+

The address of an NSError object pointer. In the event of an error, the pointer will be set to the NSError object describing the error.

+

The sender may pass nil if it does not wish to retrieve any NSError object.

+

Return Value

+

Returns YES if the receiver is successfully accepting connections at the specified port.

+

Errors

+

Returns NO and an NSError object if the socket cannot be created, or cannot accept connections on the specified address or port.

+

Raises an AsyncSocketException if the socket is already connected or accepting connections, or if no delegate has been set.

+
+

See -acceptOnPort:error: for more information.

+ + +

-isConnected

+

This message may be sent to determine whether the receiver is connected and capable of reading and writing.

+ + + + + + + + + + + + + + + + + +

Syntax

-(BOOL)isConnected

Arguments

None.

Return Value

+

YES, if the receiver is connected and able to send and receive data.

+

NO, if the receiver is not connected, accepting connections, or not able to send and receive data.

+

Errors

None.

+

If the input or output streams have reached EOF, the receiver returns NO. If the input or output streams are open, but in some other error state, the receiver returns YES.

+

If the receiver is accepting incoming connections, it always returns NO.

+ + +

-disconnect

+

This message immediately disconnects the receiver.

+ + + + + + + + + + + + + + + + + +

Syntax

-(void)disconnect

Arguments

None.

Return Value

None.

Errors

None.

+

If the receiver was accepting incoming connections, it will no stop doing so. Any pending read or write operations are dropped.

+

After this method returns, the client application may send a -connectToHost:onPort:error:, -acceptOnPort:error:, or -acceptOnAddress:port:error: messages again.

+ + +

-disconnectAfterWriting

+

This message will disconnect the receiver after all pending write operations are completed. Pending read operations will not prevent the receiver from disconnecting.

+ + + + + + + + + + + + + + + + + +

Syntax

-(void)disconnectAfterWriting

Arguments

None.

Return Value

None.

Errors

None.

+

While the pending write operations are completing, the receiver will ignore any further read or write messages. Other messages may be sent as usual.

+ + +

-connectedHost

+

This message returns the IP address of the connected remote socket as a string.

+ + + + + + + + + + + + + + + + + +

Syntax

-(NSString *)connectedHost

Arguments

None.

Return Value

+

If the receiver is connected, an IP address.

+

If the receiver is not connected, nil.

+

Errors

None.

+ + +

-connectedPort

+

This message returns the port number of the connected remote socket.

+ + + + + + + + + + + + + + + + + +

Syntax

-(UInt16)connectedPort

Arguments

None.

Return Value

+

If the receiver is connected, a port number.

+

If the receiver is not connected, 0.

+

Errors

None.

+ + +

-localHost

+

This method returns the local IP address of the receiver as a string.

+ + + + + + + + + + + + + + + + + +

Syntax

-(NSString *)localHost

Arguments

None.

Return Value

+

If the receiver is connected, an IP address.

+

If the receiver is not connected, nil.

+

Errors

None.

+

If the computer has more than one IP address, the one in use by the receiver will be returned. If the computer is behind a NAT, the returned IP address will be a LAN address, not useable outside the LAN.

+ + +

-localPort

+

This method returns the port number of the receiver.

+ + + + + + + + + + + + + + + + + +

Syntax

-(UInt16)localPort

Arguments

None.

Return Value

+

If the receiver is connected, a port number.

+

If the receiver is not connected, 0.

+

Errors

None.

+

If the computer is behind a NAT, the returned port number will be a LAN address, not accurate outside the LAN.

+ + +

Read and Write Messages

+

-readDataToLength:withTimeout:tag: -readDataToData:withTimeout:tag: -readDataWithTimeout:tag: -writeData:withTimeout:tag: -progressOfReadReturningTag:bytesDone:total: -progressOfWriteReturningTag:bytesDone:total:

+ + +

-readDataToLength:withTimeout:tag:

+

This message queues a read operation. The receiver will read a certain number of bytes from the socket.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag

Arguments

length

+

Number of bytes that the receiver should read.

+

If 0, the receiver does nothing, and does not send -onSocket:didReadData:withTag: to its delegate.

+

timeout

+

The number of seconds from the start of the read operation in which the operation must complete. If the operation takes longer than this interval, the operation times out.

+

If negative, the read operation will not time out.

+

tag

+

An application-defined integer or pointer that will be sent as an argument to the -onSocket:didReadData:withTag: message sent to the delegate.

+

Return Value

+

The receiver will send -onSocket:didReadData:withTag: to the delegate object when the read operation has completed. The received data will be passed as an argument of that message.

+

Errors

+

The receiver will send -onSocket:willDisconnectWithError: to the delegate object under the following conditions:

+
    +
  • The read operation times out.
  • +
  • The receiver is disconnected from the remote socket.
  • +
  • Some other i/o error occurs.
  • +
+

The -onSocket:willDisconnectWithError: method may retrieve partially received data by sending -readDataWithTimeout:tag: to the socket.

+
+

When the bytes have been successfully received, the receiver will send -onSocket:didReadData:withTag: to its delegate object.

+

The read operation will be performed immediately if possible. If so, the receiver will send the message before returning from this method.

+ + +

-readDataToData:withTimeout:tag:

+

This message queues a read operation. The receiver will read bytes until (and including) a sequence of bytes passed in the data argument. That sequence acts as a separator or delimiter.

+ + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag

Arguments

data

+

A sequence of bytes that mark the end of the read operation.

+

If nil or empty, the receiver does nothing, and does not send -onSocket:didReadData:withTag: to its delegate.

+

timeout

+
+

The number of seconds from the start of the read operation in which the operation must complete. If the operation takes longer than this interval, the operation times out.

+

If negative, the read operation will not time out.

+

tag

+
+

An application-defined integer or pointer that will be sent as an argument to the -onSocket:didReadData:withTag: message sent to the delegate.

+

Return Value

+

The receiver will send -onSocket:didReadData:withTag: to the delegate object when the read operation has completed. The received data will be passed as an argument of that message. It will include the delimiter.

+

Errors

+

The receiver will send -onSocket:willDisconnectWithError: to the delegate object under the following conditions:

+
    +
  • The read operation times out.
  • +
  • The receiver is disconnected from the remote socket.
  • +
  • Some other i/o error occurs.
  • +
+

The -onSocket:willDisconnectWithError: method may retrieve partially received data by sending -readDataWithTimeout:tag: to the socket.

+
+

When the bytes have been successfully received, the receiver will send -onSocket:didReadData:withTag: to its delegate object, passing all the received bytes as a parameter, including the delimiting sequence.

+

The read operation will be performed immediately if possible. If so, the receiver will send the message before returning from this method.

+

Note that this method is not character-set aware. If a character should happen to be encoded to a sequence of bytes that matches the delimiting sequence, the read operation can prematurely end.

+ + +

-readDataWithTimeout:tag:

+

This message queues a read operation. The receiver will retrieve the first available bytes.

+ + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag

Arguments

timeout

+

The number of seconds from the start of the read operation in which the operation must complete. If the operation takes longer than this interval, the operation times out.

+

If negative, the read operation will not time out.

+

If this message is being sent from within the delegate’s -onSocket:willDisconnectWithError: method to recover unread data, this argument is irrelevant.

+

tag

+

An application-defined integer or pointer that will be sent as an argument to the -onSocket:didReadData:withTag: message sent to the delegate.

+

Return Value

+

The receiver will send -onSocket:didReadData:withTag: to its delegate object when the read operation has completed. The received data, if any, will be passed as an argument.

+

Errors

+

An error will occur under these conditions:

+
    +
  • The read operation times out.
  • +
  • The receiver is disconnected from the remote socket.
  • +
  • Some other i/o error occurs.
  • +
+
+

When the bytes have been successfully retrieved, the receiver will then send -onSocket:didReadData:withTag: to its delegate object, passing all the received bytes as an argument.

+

The read operation will be performed immediately if possible. If so, the receiver will send the message before returning from this method.

+

The delegate object may send this message while responding to a -onSocket:willDisconnectWithError: message. This allows the delegate to retrieve partially-received and unreceived bytes.

+ + +

-writeData:withTimeout:tag:

+

This message queues a write operation. The receiver will write an NSData object to the socket.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag

Arguments

data

+

The data that should be written to the remote socket.

+

If nil, the receiver does nothing, and does not send -onSocket:didWriteDataWithTag: to its delegate.

+

timeout

+

The number of seconds from the start of the write operation in which the operation must complete. If the operation takes longer than this interval, the operation times out.

+

If negative, the write operation will not time out.

+

tag

+

An application-defined integer or pointer that will be sent as an argument to the -onSocket:didWriteDataWithTag: message sent to the delegate.

+

Return Value

+

None. The receiver will send -onSocket:didWriteDataWithTag: to the delegate object when the write operation has completed. +

Errors

+

The receiver will send -onSocket:willDisconnectWithError: to the delegate object under the following conditions:

+
    +
  • The write operation times out.
  • +
  • The receiver is disconnected from the remote socket.
  • +
  • Some other i/o error occurs.
  • +
+
+

When the bytes have been successfully sent, the receiver will send -onSocket:didWriteData:withTag: to its delegate object.

+

The write operation will be performed immediately if possible. If so, the receiver will send the message before returning from this method.

+ + +

-progressOfReadReturningTag:bytesDone:total:

+

This message can be sent to determine the progress of the current read operation.

+ + + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total

Arguments

tag

+

The address of a variable. When this method returns, the variable will contain the tag of the current read operation. If there is no current read operation, the variable will not be changed.

+

The sender may pass NULL if it does not wish to retrieve the tag.

+

done

+

The address of a variable. When this method returns, the variable will contain the number of bytes that the current read operation has read. If there is no current read operation, the variable will not be changed.

+

The sender may pass NULL if it does not wish to retrieve the number of bytes read.

+

total

+

The address of a variable. When this method returns, the variable will contain the total number of bytes that the current read operation expects to read. This value is only meaningful if the current read operation is a “read-to-length” operation. If there is no current read operation, the variable will not be changed.

+

The sender may pass NULL if it does not wish to retrieve the total number of bytes being read.

+

Return Value

+

A decimal value.

+
    +
  • NaN indicates that there is no read operation currently being executed. Use the isnan() function to test for this return value.
  • +
  • 0.0 indicates that no bytes have been read of the total.
  • +
  • 1.0 indicates that all bytes have been read of the total, or the total number of bytes that must be read is not known.
  • +
+

Errors

None.

+ + +

-progressOfWriteReturningTag:bytesDone:total:

+

This message can be sent to determine the progress of the current write operation.

+ + + + + + + + + + + + + + + + + + + + + + + + + +

Syntax

-(float)progressOfWriteReturningTag(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total

Arguments

tag

+

The address of a variable. When this method returns, the variable will contain the tag of the current read operation. If there is no current read operation, the variable will not be changed.

+

The sender may pass NULL if it does not wish to retrieve the tag.

+

done

+

The address of a variable. When this method returns, the variable will contain the number of bytes that have been sent. If there is no current read operation, the variable will not be changed.

+

The sender may pass NULL if it does not wish to retrieve the number of bytes sent.

+

total

+

The address of a variable. When this method returns, the variable will contain the total number of bytes that the current write operation is sending. If there is no current read operation, the variable will not be changed.

+

The sender may pass NULL if it does not wish to retrieve the total number of bytes being sent.

+

Return Value

+

A decimal value.

+
    +
  • NaN indicates that there is no write operation currently being executed. Use the isnan() function to test for this return value.
  • +
  • 0.0 indicates that no bytes have been sent of the total.
  • +
  • 1.0 indicates that all bytes have been sent of the total.
  • +
+

Errors

None.

+ + +

Delimiter Messages

+

The results of these messages are commonly-used delimiters that can be passed as the data argument of a -readDataToData:withTimeout:tag: message.

+

+CRLFData +CRData +LFData +ZeroData

+ + +

+CRLFData

+

This method returns the CRLF byte sequence, the line separator for DOS and Windows.

+ + + + + + + + + + + + + + + + + +

Syntax

+(NSData *)CRLFData

Arguments

None.

Return Value

+

An instance of NSData containing the bytes 0D0A.

+

Errors

None.

+ + +

+CRData

+

This method returns the CR byte sequence, the line separator for Macintosh operating systems before Mac OS X.

+ + + + + + + + + + + + + + + + + +

Syntax

+(NSData *)CRData

Arguments

None.

Return Value

+

An instance of NSData containing the byte 0D.

+

Errors

None.

+ + +

+LFData

+

This method returns the LF byte sequence, the line separator for most Unix operating systems and Mac OS X.

+ + + + + + + + + + + + + + + + + +

Syntax

+(NSData *)LFData

Arguments

None.

Return Value

+

An instance of NSData containing the byte 0A.

+

Errors

None.

+ + +

+ZeroData

+

This method returns a zero byte, the delimiter for C strings.

+ + + + + + + + + + + + + + + + + +

Syntax

+(NSData *)ZeroData

Arguments

None.

Return Value

+

An instance of NSData containing the byte 00.

+

Errors

None.

+ + +

Debugging and Customization Messages

+

-description -getCFSocket -getCFReadStream -getCFWriteStream

+ + +

-description

+

This message returns a description of the receiver suitable for debugging purposes.

+ + + + + + + + + + + + + + + + + +

Syntax

-(NSString *)description

Arguments

None.

Return Value

An string describing the receiver and its status.

Errors

None.

+

The returned description does not have any newline characters and includes:

+ + + +

-getCFSocket

+

This message returns the internal CFSocket instance being using by the receiver, enabling access to the underlying Unix socket.

+ + + + + + + + + + + + + + + + + +

Syntax

-(CFSocketRef)getCFSocket

Arguments

None.

Return Value

+

The receiver’s CFSocket reference number, or NULL if not connected or accepting connections.

+

Errors

None.

+

Do not close, read from, or write to the underlying socket.

+ + +

-getCFReadStream

+

This method returns the internal CFReadStream instance being using by the receiver, enabling access to the underlying Carbon stream.

+ + + + + + + + + + + + + + + + + +

Syntax

-(CFReadStreamRef)getCFReadStream

Arguments

None.

Return Value

+

The receiver’s CFReadStream reference number, or NULL if not connected or accepting connections.

+

Errors

None.

+

Do not close, read from, or write to the underlying stream.

+ + +

-getCFWriteStream

+

This method returns the internal CFWriteStream instance being used by the receiver, enabling access to the underlying Carbon stream.

+ + + + + + + + + + + + + + + + + +

Syntax

-(CFWriteStreamRef)getCFWriteStream

Arguments

None.

Return Value

+

The receiver’s CFWriteStream reference number, or NULL if not connected or accepting connections.

+

Errors

None.

+

Do not close, read from, or write to the underlying stream.

+ + +

AsyncSocketDelegate Methods

+

Delegate objects may implement these methods to respond to AsyncSocket messages.

+

-onSocket:willDisconnectWithError: -onSocketDidDisconnect: -onSocket:didAcceptNewSocket: -onSocket:wantsRunLoopForNewSocket: -onSocketWillConnect: -onSocket:didConnectToHost:port: -onSocket:didReadData:withTag: -onSocket:didWriteDataWithTag:

+ + +

-onSocket:willDisconnectWithError:

+

In the event of an error, the socket is disconnected. The socket sends this message before disconnection.

+ + + + + + + + + + + + + + + + + + +

Prototype

-(void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err

Arguments

sock

+

The sending AsyncSocket instance.

+

err

+

The error causing the socket to disconnect.

+

If nil, then there is no error. This typically means the socket was cleanly closed by the remote client application.

+

Condition

+

This message is sent:

+
    +
  • if there is an connection, time out, or other i/o error.
  • +
  • if the remote socket cleanly disconnects.
  • +
  • before the local socket is disconnected.
  • +
+
+

This message is primarily intended to give the receiver a chance to retrieve any remaining buffered data from the connection. The receiver may do this by sending -readDataWithTimeout:tag: to the sender.

+ + +

-onSocketDidDisconnect:

+

The socket sends this message is called after it has disconnected.

+ + + + + + + + + + + + + + +

Prototype

-(void)onSocketDidDisconnect:(AsyncSocket *)sock

Arguments

sock

+

The sending AsyncSocket instance.

+

Condition

This message is sent after the sender disconnects for any reason.

+

The receiver may safely release the sender in this method.

+ + +

-onSocket:didAcceptNewSocket:

+

The socket sends this message to provide the receiver with a chance to save a new socket in an appropriate place.

+ + + + + + + + + + + + + + + + + + +

Prototype

-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket

Arguments

sock

+

The sending AsyncSocket instance. This instance will be accepting connections.

+

newSocket

+

A new instance of AsyncSocket.

+

Condition

+

This method is sent after the sender accepts an incoming connection and creates a new instance of AsyncSocket to handle it.

+
+

When this message is sent, newSocket is not fully connected to the remote socket.

+

The receiver should assign and retain newSocket, and may also set a more appropriate delegate.

+ + +

-onSocket:wantsRunLoopForNewSocket:

+

The socket sends this message to determine which thread and run-loop the new socket and its delegate’s methods should operate on. Defaults to the current run-loop.

+ + + + + + + + + + + + + + + + + + + + + + +

Prototype

-(NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket

Arguments

sock

+

The sending AsyncSocket instance. This instance will be accepting connections.

+

newSocket

+

An instance of AsyncSocket.

+

Return Value

+

The instance of NSRunLoop associated with a target thread.

+

Condition

+

This method is sent after the sender accepts an incoming connection and sends the -onSocket:didAcceptNewSocket: message.

+
+

When this message is sent, newSocket is not fully connected to the remote socket.

+

The receiver should return the instance of NSRunLoop associated with a target thread. All delegate messages from newSocket will be sent in the context of that thread to that run-loop, all timers will run on that run-loop, and all processing will occur on that thread.

+

If the receiver does not implement this method, the sender will use the current thread and run-loop.

+ + +

-onSocketWillConnect:

+

The socket sends this message when it is about to connect to a remote socket.

+ + + + + + + + + + + + + + + + + +

Prototype

-(BOOL)onSocketWillConnect:(AsyncSocket *)sock

Argument

sock

The sender.

Return Value

+

YES if the socket should continue to connect to the remote socket. This is the default if this method is not implemented by the receiver.

+

NO to cancel the connection.

+

Condition

+

This message is sent before the sender attempts to connect to:

+ +
+

This message is primarily intended to give the receiver a chance to configure properties of the internal CFReadStream or CFWriteStream instances. The receiver should return YES or NO, indicating whether the sender should continue connecting.

+

If the receiver returns NO and the connection attempt was initiated by -connectToHost:onPort:error:, that method will return NO to its sender, along with an AsyncSocketCanceledError error object.

+

If the receiver returns NO and the connection attempt was initiated by a remote socket, no error will be reported.

+ + +

-onSocket:didConnectToHost:port:

+

The socket sends this message when it is connected and ready for reading or writing.

+ + + + + + + + + + + + + + + + + + + + + +

Prototype

-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port

Arguments

sock

The sender.

host

+

The remote socket’s IPv4 or IPv6 address.

+

This may differ from the IP address or DNS name sent to the sender as the hostname argument of -connectToHost:onPort:error:.

+

port

+
+

The remote socket’s port.

+

Condition

+

This message is sent after the sender has successfully connected to:

+ +
+

The receiver may choose to disconnect the sender, or to queue a read or write operation.

+

The socket will perform any previously-queued read or write operations after the receiver returns from this method.

+ + +

-onSocket:didReadData:withTag:

+

The sender sends this message when it successfully completes a read operation. It may send this message before the invoked read method returns, or later.

+ + + + + + + + + + + + + + + + + + + + + + +

Prototype

-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag

Arguments

sock

The sender.

data

+

The received data.

+

tag

+

The tag argument passed in the read message.

+

Condition

+

This message is sent after the sender has successfully received the requested data.

+

If a read operation is unsuccessful, the sender will send a -onSocket:willDisconnectWithError: message instead.

+

The receiver should process the data and queue a read or write operation or disconnect as needed.

+ + +

-onSocket:didWriteDataWithTag:

+

The sender sends this message when a write operation has successfully completed. It may send this message before the invoked write method returns, or later.

+ + + + + + + + + + + + + + + + + + +

Prototype

-(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag

Arguments

sock

The sender.

tag

+

The tag argument passed in the write message.

+

Condition

+

This message is sent after the sender has successfully sent the provided data.

+
+

If a write operation is unsuccessful, the sender will send a -onSocket:willDisconnectWithError: message instead.

+

The receiver should queue a read or write operation or disconnect as needed.

+ + +

Errors

+ + +

AsyncSocketException

+

An instance of AsyncSocket raises this exception when it receives an -acceptOnPort:error:, -acceptOnAddress:port:error:, or -connectToHost:onPort:error: message, and:

+ + + +

AsyncSocketErrorDomain

+

This NSError domain includes the following AsyncSocketError codes:

+ +

You may provide localized error messages for these codes in an AsyncSocket.strings strings file in the main bundle, using as keys “AsyncSocketNoError,” etc. Default English strings are provided.

+ + +

API Changes

+

Changes since AsyncSocket 4.2:

+ +

Changes since AsyncSocket 4.0:

+ + +

Changes since AsyncSocket 3.13:

+ + + +

API Index

+ + +
+
Author: Dustin Voss
+ + diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.h b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.h new file mode 100644 index 0000000..4f79257 --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.h @@ -0,0 +1,223 @@ +// +// AsyncSocket.h +// +// Created by Dustin Voss on Wed Jan 29 2003. +// This class is in the public domain. +// If used, I'd appreciate it if you credit me. +// +// E-Mail: d-j-v@earthlink.net +// + +/* + * Make sure to include /System/Library/Frameworks/CoreServices.framework in the project. + */ + +#import + +@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. + AsyncSocketReadTimeoutError, + AsyncSocketWriteTimeoutError +}; +typedef enum AsyncSocketError AsyncSocketError; + +@interface NSObject ( AsyncSocketDelegate ) + +/** + * In the event of an error, the socket is closed. You may call "readDataWithTimeout:tag:" 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:". +**/ +- (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. +**/ +- (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. 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; + +@end + +@interface AsyncSocket : NSObject +{ + CFSocketRef theSocket; // IPv4/IPv6 accept or connect socket. + CFSocketRef theSocket6; // IPv6 accept socket. + CFReadStreamRef theReadStream; + CFWriteStreamRef theWriteStream; + + CFRunLoopSourceRef theSource; // For theSocket. + CFRunLoopSourceRef theSource6; // For theSocket6. + CFRunLoopRef theRunLoop; + CFSocketContext theContext; + + NSMutableArray *theReadQueue; + AsyncReadPacket *theCurrentRead; + NSTimer *theReadTimer; + NSData *partialReadBuffer; + + NSMutableArray *theWriteQueue; + AsyncWritePacket *theCurrentWrite; + NSTimer *theWriteTimer; + + id theDelegate; + Byte theFlags; + + long theUserData; +} + +- (id) init; +- (id) initWithDelegate:(id)delegate; +- (id) initWithDelegate:(id)delegate userData:(long)userData; +- (void) dealloc; + +/* 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 these methods is called, the AsyncSocket instance is locked in, and the rest can't be called without + * disconnecting the socket first. If the attempt times out or fails, these methods either return NO or + * call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". +**/ +- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; +- (BOOL)acceptOnAddress:(NSString *)hostaddr port:(UInt16)port error:(NSError **)errPtr; +- (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr; + +/** + * Disconnects immediately. Any pending reads or writes are dropped. +**/ +- (void)disconnect; + +/** + * Disconnects after all pending writes have completed. + * After calling this, the read and write methods (including "readDataWithTimeout:tag:") will do nothing. + * The socket will disconnect even if there are still pending reads. +**/ +- (void)disconnectAfterWriting; + +/* 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; + +/** + * The following methods won't block. To not time out, use a negative time interval. + * If they time out, "onSocket:disconnectWithError:" is called. 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, and call the delegate method when those bytes have been read. + * If there is an error, partially read data is lost. If the length is 0, this method does nothing and the delegate is not called. +**/ +- (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 0-length data as the "data" parameter, the method will do nothing, and the delegate will not be called. + * 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; + +/* This reads the first available bytes. */ +- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/* Writes data. If you pass in nil or 0-length data, this method does nothing and the delegate will not be called. */ +- (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; + +/* A few common line separators, for use with "readDataToData:withTimeout:tag:". */ ++ (NSData *) CRLFData; // 0x0D0A ++ (NSData *) CRData; // 0x0D ++ (NSData *) LFData; // 0x0A ++ (NSData *) ZeroData; // 0x00 + +@end diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.m b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.m new file mode 100644 index 0000000..3789c3f --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocket.m @@ -0,0 +1,1741 @@ +// +// AsyncSocket.m +// +// Created by Dustin Voss on Wed Jan 29 2003. +// This class is in the public domain. +// If used, I'd appreciate it if you credit me. +// +// E-Mail: d-j-v@earthlink.net +// + +#import "AsyncSocket.h" +#import +#import +#import +#import + +#pragma mark Declarations + +#define READQUEUE_CAPACITY 5 /* Initial capacity. */ +#define WRITEQUEUE_CAPACITY 5 /* Initial capacity. */ +#define READALL_CHUNKSIZE 256 /* Incremental increase in buffer size. */ +#define WRITE_CHUNKSIZE (4*1024) /* Limit on size of each write pass. */ + +NSString *const AsyncSocketException = @"AsyncSocketException"; +NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain"; + +// This is a mutex lock used by all instances of AsyncSocket, to protect getaddrinfo. +// The man page says it is not thread-safe. +NSString *getaddrinfoLock = @"lock"; + +enum AsyncSocketFlags +{ + kDidCallConnectDeleg = 0x01, // If set, connect delegate has been called. + kDidPassConnectMethod = 0x02, // If set, disconnection results in delegate call. + kForbidReadsWrites = 0x04, // If set, no new reads or writes are allowed. + kDisconnectSoon = 0x08 // If set, disconnect as soon as nothing is queued. +}; + +@interface AsyncSocket (Private) + +// Socket Implementation +- (CFSocketRef) createAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr; +- (BOOL) attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; +- (void) doAcceptWithSocket:(CFSocketNativeHandle)newSocket; + +// Stream Implementation +- (BOOL) createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr; +- (BOOL) createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; +- (BOOL) attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; +- (BOOL) configureStreamsAndReturnError:(NSError **)errPtr; +- (BOOL) openStreamsAndReturnError:(NSError **)errPtr; +- (void) doStreamOpen; +- (BOOL) setSocketFromStreamsAndReturnError:(NSError **)errPtr; + +// Disconnect Implementation +- (void) closeWithError:(NSError *)err; +- (void) recoverUnreadData; +- (void) emptyQueues; +- (void) close; + +// Errors +- (NSError *) getAbortError; +- (NSError *) getStreamError; +- (NSError *) getSocketError; +- (NSError *) getReadTimeoutError; +- (NSError *) getWriteTimeoutError; +- (NSError *) errorFromCFStreamError:(CFStreamError)err; + +// Diagnostics +- (BOOL) isSocketConnected; +- (BOOL) areStreamsConnected; +- (NSString *) connectedHost: (CFSocketRef)socket; +- (UInt16) connectedPort: (CFSocketRef)socket; +- (NSString *) localHost: (CFSocketRef)socket; +- (UInt16) localPort: (CFSocketRef)socket; +- (NSString *) addressHost: (CFDataRef)cfaddr; +- (UInt16) addressPort: (CFDataRef)cfaddr; + +// Reading +- (void) doBytesAvailable; +- (void) completeCurrentRead; +- (void) endCurrentRead; +- (void) scheduleDequeueRead; +- (void) maybeDequeueRead; +- (void) doReadTimeout:(NSTimer *)timer; + +// Writing +- (void) doSendBytes; +- (void) completeCurrentWrite; +- (void) endCurrentWrite; +- (void) scheduleDequeueWrite; +- (void) maybeDequeueWrite; +- (void) maybeScheduleDisconnect; +- (void) doWriteTimeout:(NSTimer *)timer; + +// Callbacks +- (void) doCFCallback:(CFSocketCallBackType)type forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData; +- (void) doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream; +- (void) doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream; + +// Utilities +- (NSData *) sockaddrFromString:(NSString *)addrStr port:(UInt16)port error:(NSError **)errPtr; + + +@end + +static void MyCFSocketCallback (CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); +static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo); +static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The AsyncReadPacket encompasses the instructions for a current read. + * The content of a read packet allows the code to determine if we're: + * reading to a certain length, reading to a certain separator, or simply reading the first chunk of data. +**/ +@interface AsyncReadPacket : NSObject +{ +@public + NSMutableData *buffer; + CFIndex bytesDone; + NSTimeInterval timeout; + long tag; + NSData *term; + BOOL readAllAvailableData; +} +- (id)initWithData:(NSMutableData *)d timeout:(NSTimeInterval)t tag:(long)i readAllAvailable:(BOOL)a terminator:(NSData *)e bufferOffset:(CFIndex)b; +- (void)dealloc; +@end + +@implementation AsyncReadPacket +- (id)initWithData:(NSMutableData *)d timeout:(NSTimeInterval)t tag:(long)i readAllAvailable:(BOOL)a terminator:(NSData *)e bufferOffset:(CFIndex)b +{ + if(self = [super init]) + { + buffer = [d retain]; + timeout = t; + tag = i; + term = [e copy]; + bytesDone = b; + readAllAvailableData = a; + } + return self; +} +- (void)dealloc +{ + [buffer release]; + [term release]; + [super dealloc]; +} +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface AsyncWritePacket : NSObject +{ +@public + NSData *buffer; + CFIndex bytesDone; + long tag; + NSTimeInterval timeout; +} +- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; +- (void)dealloc; +@end + +@implementation AsyncWritePacket +- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; +{ + if(self = [super init]) + { + buffer = [d retain]; + timeout = t; + tag = i; + bytesDone = 0; + } + return self; +} +- (void)dealloc +{ + [buffer release]; + [super dealloc]; +} +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation AsyncSocket + +- (id) init +{ + return [self initWithDelegate:nil userData:0]; +} + +- (id) initWithDelegate:(id)delegate +{ + return [self initWithDelegate:delegate userData:0]; +} + +// Designated initializer. +- (id) initWithDelegate:(id)delegate userData:(long)userData +{ + self = [super init]; + + theFlags = 0x00; + theDelegate = delegate; + theUserData = userData; + + theSocket = NULL; + theSource = NULL; + theSocket6 = NULL; + theSource6 = NULL; + theRunLoop = NULL; + theReadStream = NULL; + theWriteStream = NULL; + + theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY]; + theCurrentRead = nil; + theReadTimer = nil; + + partialReadBuffer = nil; + + theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY]; + theCurrentWrite = nil; + theWriteTimer = nil; + + // Socket context + NSAssert (sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext and CFStreamClientContext aren't the same size anymore. Contact the developer."); + theContext.version = 0; + theContext.info = self; + theContext.retain = nil; + theContext.release = nil; + theContext.copyDescription = nil; + + return self; +} + +// The socket may been initialized in a connected state and auto-released, so this should close it down cleanly. +- (void) dealloc +{ + [self close]; + [theReadQueue release]; + [theWriteQueue release]; + [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onSocketDidDisconnect:) object:self]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + [super dealloc]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Accessors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (long) userData +{ + return theUserData; +} + +- (void) setUserData:(long)userData +{ + theUserData = userData; +} + +- (id) delegate +{ + return theDelegate; +} + +- (void) setDelegate:(id)delegate +{ + theDelegate = delegate; +} + +- (BOOL) canSafelySetDelegate +{ + return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil); +} + +- (CFSocketRef) getCFSocket +{ + return theSocket; +} + +- (CFReadStreamRef) getCFReadStream +{ + return theReadStream; +} + +- (CFWriteStreamRef) getCFWriteStream +{ + return theWriteStream; +} + +- (float) progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total +{ + // Check to make sure we're actually reading something right now + if (!theCurrentRead) return NAN; + + // It's only possible to know the progress of our read if we're reading to a certain length + // If we're reading to data, we of course have no idea when the data will arrive + // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. + BOOL hasTotal = (theCurrentRead->readAllAvailableData == NO && theCurrentRead->term == nil); + + CFIndex d = theCurrentRead->bytesDone; + CFIndex t = hasTotal ? [theCurrentRead->buffer length] : 0; + if (tag != NULL) *tag = theCurrentRead->tag; + if (done != NULL) *done = d; + if (total != NULL) *total = t; + float ratio = (float)d/(float)t; + return isnan(ratio) ? 1.0 : ratio; // 0 of 0 bytes is 100% done. +} + +- (float) progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total +{ + if (!theCurrentWrite) return NAN; + CFIndex d = theCurrentWrite->bytesDone; + CFIndex t = [theCurrentWrite->buffer length]; + if (tag != NULL) *tag = theCurrentWrite->tag; + if (done != NULL) *done = d; + if (total != NULL) *total = t; + return (float)d/(float)t; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Class Methods +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Return line separators. ++ (NSData *) CRLFData +{ return [NSData dataWithBytes:"\x0D\x0A" length:2]; } + ++ (NSData *) CRData +{ return [NSData dataWithBytes:"\x0D" length:1]; } + ++ (NSData *) LFData +{ return [NSData dataWithBytes:"\x0A" length:1]; } + ++ (NSData *) ZeroData +{ return [NSData dataWithBytes:"" length:1]; } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Connection +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr +{ + return [self acceptOnAddress:nil port:port error:errPtr]; +} + +// Setting up IPv4 and IPv6 accepting sockets. +- (BOOL)acceptOnAddress:(NSString *)hostaddr port:(UInt16)port error:(NSError **)errPtr +{ + if (theDelegate == NULL) + [NSException raise:AsyncSocketException format:@"Attempting to accept without a delegate. Set a delegate first."]; + + if (theSocket != NULL || theSocket6 != NULL) + [NSException raise:AsyncSocketException format:@"Attempting to accept while connected or accepting connections. Disconnect first."]; + + // Set up the listen sockaddr structs if needed. + + NSData *address = nil, *address6 = nil; + if(hostaddr && ([hostaddr length] != 0)) + { + address = [self sockaddrFromString:hostaddr port:port error:errPtr]; + if (!address) return NO; + } + else + { + // Set up the addresses. + struct sockaddr_in nativeAddr; + nativeAddr.sin_len = sizeof(struct sockaddr_in); + nativeAddr.sin_family = AF_INET; + nativeAddr.sin_port = htons(port); + nativeAddr.sin_addr.s_addr = htonl(INADDR_ANY); + memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero)); + + struct sockaddr_in6 nativeAddr6; + nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); + nativeAddr6.sin6_family = AF_INET6; + nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_flowinfo = 0; + nativeAddr6.sin6_addr = in6addr_any; + nativeAddr6.sin6_scope_id = 0; + + // Wrap the native address structures for CFSocketSetAddress. + address = [NSData dataWithBytesNoCopy:&nativeAddr length:sizeof(nativeAddr) freeWhenDone:NO]; + address6 = [NSData dataWithBytesNoCopy:&nativeAddr6 length:sizeof(nativeAddr6) freeWhenDone:NO]; + } + + // Create the sockets. + + if (address) + { + theSocket = [self createAcceptSocketForAddress:address error:errPtr]; + if (theSocket == NULL) goto Failed; + } + + if (address6) + { + theSocket6 = [self createAcceptSocketForAddress:address6 error:errPtr]; + if (theSocket6 == NULL) goto Failed; + } + + // Attach the sockets to the run loop so that callback methods work + + [self attachSocketsToRunLoop:nil error:nil]; + + // Set the SO_REUSEADDR flags. + + int reuseOn = 1; + if (theSocket) setsockopt(CFSocketGetNative(theSocket), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + + // Set the local bindings which causes the sockets to start listening. + + CFSocketError err; + if (theSocket) + { + err = CFSocketSetAddress (theSocket, (CFDataRef)address); + if (err != kCFSocketSuccess) goto Failed; + + //NSLog(@"theSocket4: %hu", [self localPort:theSocket]); + } + + if(port == 0 && theSocket && theSocket6) + { + // The user has passed in port 0, which means he wants to allow the kernel to choose the port for them + // However, the kernel will choose a different port for both theSocket and theSocket6 + // So we grab the port the kernel choose for theSocket, and set it as the port for theSocket6 + UInt16 chosenPort = [self localPort:theSocket]; + + struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes]; + pSockAddr6->sin6_port = chosenPort; + } + + if (theSocket6) + { + err = CFSocketSetAddress (theSocket6, (CFDataRef)address6); + if (err != kCFSocketSuccess) goto Failed; + + //NSLog(@"theSocket6: %hu", [self localPort:theSocket6]); + } + + theFlags |= kDidPassConnectMethod; + return YES; + +Failed:; + if (errPtr) *errPtr = [self getSocketError]; + return NO; +} + +/** + * This method creates an initial CFReadStream and CFWriteStream to the given host on the given port. + * The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds. + * + * Thus the delegate will only have access to the CFReadStream and CFWriteStream prior to connection, + * specifically in the onSocketWillConnect: method. +**/ +- (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr +{ + if(theDelegate == NULL) + { + NSString *message = @"Attempting to connect without a delegate. Set a delegate first."; + [NSException raise:AsyncSocketException format:message]; + } + + if(theSocket != NULL || theSocket6 != NULL) + { + NSString *message = @"Attempting to connect while connected or accepting connections. Disconnect first."; + [NSException raise:AsyncSocketException format:message]; + } + + BOOL pass = YES; + + if(pass && ![self createStreamsToHost:hostname onPort:port error:errPtr]) pass = NO; + if(pass && ![self attachStreamsToRunLoop:nil error:errPtr]) pass = NO; + if(pass && ![self configureStreamsAndReturnError:errPtr]) pass = NO; + if(pass && ![self openStreamsAndReturnError:errPtr]) pass = NO; + + if(pass) + theFlags |= kDidPassConnectMethod; + else + [self close]; + + return pass; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Socket Implementation: +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Creates the accept sockets. + * Returns true if either IPv4 or IPv6 is created. + * If either is missing, an error is returned (even though the method may return true). +**/ +- (CFSocketRef)createAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr +{ + struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes]; + int addressFamily = pSockAddr->sa_family; + + CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, + addressFamily, + SOCK_STREAM, + 0, + kCFSocketAcceptCallBack, // Callback flags + (CFSocketCallBack)&MyCFSocketCallback, // Callback method + &theContext); + + if (socket == NULL && errPtr) + *errPtr = [self getSocketError]; + + return socket; +} + +/** + * Adds the CFSocket's to the run-loop so that callbacks will work properly. +**/ +- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr +{ + // Get the CFRunLoop to which the socket should be attached. + theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; + + if(theSocket) + { + theSource = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket, 0); + CFRunLoopAddSource (theRunLoop, theSource, kCFRunLoopDefaultMode); + } + + if(theSocket6) + { + theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0); + CFRunLoopAddSource (theRunLoop, theSource6, kCFRunLoopDefaultMode); + } + + return YES; +} + +/** + * Attempt to make the new socket. + * If an error occurs, ignore this event. +**/ +- (void)doAcceptWithSocket:(CFSocketNativeHandle)newNative +{ + AsyncSocket *newSocket = [[[AsyncSocket alloc] initWithDelegate:theDelegate] autorelease]; + if(newSocket) + { + NSRunLoop *runLoop = nil; + if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)]) + [theDelegate onSocket:self didAcceptNewSocket:newSocket]; + + if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)]) + runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket]; + + BOOL pass = YES; + + if(pass && ![newSocket createStreamsFromNative:newNative error:nil]) pass = NO; + if(pass && ![newSocket attachStreamsToRunLoop:runLoop error:nil]) pass = NO; + if(pass && ![newSocket configureStreamsAndReturnError:nil]) pass = NO; + if(pass && ![newSocket openStreamsAndReturnError:nil]) pass = NO; + + if(pass) + newSocket->theFlags |= kDidPassConnectMethod; + else { + // No NSError, but errors will still get logged from the above functions. + [newSocket close]; + } + + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Stream Implementation: +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Creates the CFReadStream and CFWriteStream from the given native socket. + * The CFSocket may be extracted from either stream after the streams have been opened. + * + * Note: The given native socket must already be connected! +**/ +- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr +{ + // Create the socket & streams. + CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream); + if (theReadStream == NULL || theWriteStream == NULL) + { + NSError *err = [self getStreamError]; + NSLog (@"AsyncSocket %p couldn't create streams from accepted socket, %@", self, err); + if (errPtr) *errPtr = err; + return NO; + } + + // Ensure the CF & BSD socket is closed when the streams are closed. + CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + + return YES; +} + +/** + * Creates the CFReadStream and CFWriteStream from the given hostname and port number. + * The CFSocket may be extracted from either stream after the streams have been opened. +**/ +- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr +{ + // Create the socket & streams. + CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)hostname, port, &theReadStream, &theWriteStream); + if (theReadStream == NULL || theWriteStream == NULL) + { + if (errPtr) *errPtr = [self getStreamError]; + return NO; + } + + // Ensure the CF & BSD socket is closed when the streams are closed. + CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + + return YES; +} + +- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr +{ + // Get the CFRunLoop to which the socket should be attached. + theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; + + // Make read stream non-blocking. + if (!CFReadStreamSetClient (theReadStream, + kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered | kCFStreamEventOpenCompleted, + (CFReadStreamClientCallBack)&MyCFReadStreamCallback, + (CFStreamClientContext *)(&theContext) )) + { + NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self); + goto Failed; + } + CFReadStreamScheduleWithRunLoop (theReadStream, theRunLoop, kCFRunLoopDefaultMode); + + // Make write stream non-blocking. + if (!CFWriteStreamSetClient (theWriteStream, + kCFStreamEventCanAcceptBytes | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered | kCFStreamEventOpenCompleted, + (CFWriteStreamClientCallBack)&MyCFWriteStreamCallback, + (CFStreamClientContext *)(&theContext) )) + { + NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self); + goto Failed; + } + CFWriteStreamScheduleWithRunLoop (theWriteStream, theRunLoop, kCFRunLoopDefaultMode); + + return YES; + +Failed:; + NSError *err = [self getStreamError]; + NSLog (@"%@", err); + if (errPtr) *errPtr = err; + return NO; +} + +/** + * Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect. + * Note that the CFSocket and CFNativeSocket will not be available until after the connection is opened. +**/ +- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr +{ + // Call the delegate method for further configuration. + if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) + { + if([theDelegate onSocketWillConnect:self] == NO) + { + NSError *err = [self getAbortError]; + if (errPtr) *errPtr = err; + return NO; + } + } + return YES; +} + +- (BOOL)openStreamsAndReturnError:(NSError **)errPtr +{ + BOOL pass = YES; + + if(pass && !CFReadStreamOpen (theReadStream)) + { + NSLog (@"AsyncSocket %p couldn't open read stream,", self); + pass = NO; + } + + if(pass && !CFWriteStreamOpen (theWriteStream)) + { + NSLog (@"AsyncSocket %p couldn't open write stream,", self); + pass = NO; + } + + if(!pass) + { + NSError *err = [self getStreamError]; + NSLog (@"%@", err); + if (errPtr) *errPtr = err; + } + + return pass; +} + +/** + * Called when read or write streams open. + * When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready. +**/ +- (void)doStreamOpen +{ + NSError *err = nil; + if ([self areStreamsConnected] && !(theFlags & kDidCallConnectDeleg)) + { + // Get the socket. + if (![self setSocketFromStreamsAndReturnError: &err]) + { + NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err); + [self closeWithError:err]; + } + + // Call the delegate. + theFlags |= kDidCallConnectDeleg; + if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)]) + { + [theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]]; + } + + // Immediately deal with any already-queued requests. + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } +} + +- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr +{ + CFSocketNativeHandle native; + CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle); + if(nativeProp == NULL) + { + if (errPtr) *errPtr = [self getStreamError]; + return NO; + } + + CFDataGetBytes(nativeProp, CFRangeMake(0, CFDataGetLength(nativeProp)), (UInt8 *)&native); + CFRelease(nativeProp); + + theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL); + if(theSocket == NULL) + { + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Disconnect Implementation: +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Sends error message and disconnects. +- (void) closeWithError:(NSError *)err +{ + if (theFlags & kDidPassConnectMethod) + { + // Try to salvage what data we can. + [self recoverUnreadData]; + + // Let the delegate know, so it can try to recover if it likes. + if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)]) + [theDelegate onSocket:self willDisconnectWithError:err]; + } + [self close]; +} + +// Prepare partially read data for recovery. +- (void) recoverUnreadData +{ + if (theCurrentRead) [theCurrentRead->buffer setLength: theCurrentRead->bytesDone]; + partialReadBuffer = (theCurrentRead ? [theCurrentRead->buffer copy] : nil); + [self emptyQueues]; +} + +- (void) emptyQueues +{ + if (theCurrentRead != nil) [self endCurrentRead]; + if (theCurrentWrite != nil) [self endCurrentWrite]; + [theReadQueue removeAllObjects]; + [theWriteQueue removeAllObjects]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil]; +} + +// Disconnects. This is called for both error and clean disconnections. +- (void) close +{ + // Empty queues. + [self emptyQueues]; + [partialReadBuffer release]; + partialReadBuffer = nil; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; + + // Close streams. + if (theReadStream != NULL) + { + CFReadStreamUnscheduleFromRunLoop (theReadStream, theRunLoop, kCFRunLoopDefaultMode); + CFReadStreamClose (theReadStream); + CFRelease (theReadStream); + theReadStream = NULL; + } + if (theWriteStream != NULL) + { + CFWriteStreamUnscheduleFromRunLoop (theWriteStream, theRunLoop, kCFRunLoopDefaultMode); + CFWriteStreamClose (theWriteStream); + CFRelease (theWriteStream); + theWriteStream = NULL; + } + + // Close sockets. + if (theSocket != NULL) + { + CFSocketInvalidate (theSocket); + CFRelease (theSocket); + theSocket = NULL; + } + if (theSocket6 != NULL) + { + CFSocketInvalidate (theSocket6); + CFRelease (theSocket6); + theSocket6 = NULL; + } + if (theSource != NULL) + { + CFRunLoopRemoveSource (theRunLoop, theSource, kCFRunLoopDefaultMode); + CFRelease (theSource); + theSource = NULL; + } + if (theSource6 != NULL) + { + CFRunLoopRemoveSource (theRunLoop, theSource6, kCFRunLoopDefaultMode); + CFRelease (theSource6); + theSource6 = NULL; + } + theRunLoop = NULL; + + // If the client has passed the connect/accept method, then the connection has at least begun. + // Notify delegate that it is now ending. + if (theFlags & kDidPassConnectMethod) + { + // Delay notification to give him freedom to release without returning here and core-dumping. + if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) + [theDelegate performSelector:@selector(onSocketDidDisconnect:) withObject:self afterDelay:0]; + } + + // Clear flags. + theFlags = 0x00; +} + +/** + * Disconnects immediately. Any pending reads or writes are dropped. +**/ +- (void) disconnect +{ + [self close]; +} + +/** + * Disconnects after all pending writes have completed. + * After calling this, the read and write methods (including "readDataWithTimeout:tag:") will do nothing. + * The socket will disconnect even if there are still pending reads. +**/ +- (void) disconnectAfterWriting +{ + theFlags |= kForbidReadsWrites; + theFlags |= kDisconnectSoon; + [self maybeScheduleDisconnect]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns a standard error message for a CFSocket error. + * Unfortunately, CFSocket offers no feedback on its errors. +**/ +- (NSError *)getSocketError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError", + @"AsyncSocket", [NSBundle mainBundle], + @"General CFSocket error", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; +} + +- (NSError *) getStreamError +{ + CFStreamError err; + if (theReadStream != NULL) + { + err = CFReadStreamGetError (theReadStream); + if (err.error != 0) return [self errorFromCFStreamError: err]; + } + + if (theWriteStream != NULL) + { + err = CFWriteStreamGetError (theWriteStream); + if (err.error != 0) return [self errorFromCFStreamError: err]; + } + + return nil; +} + +/** + * Returns a standard AsyncSocket abort error. +**/ +- (NSError *)getAbortError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError", + @"AsyncSocket", [NSBundle mainBundle], + @"Connection canceled", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket read timeout error. +**/ +- (NSError *)getReadTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError", + @"AsyncSocket", [NSBundle mainBundle], + @"Read operation timed out", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket write timeout error. +**/ +- (NSError *)getWriteTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError", + @"AsyncSocket", [NSBundle mainBundle], + @"Write operation timed out", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info]; +} + +- (NSError *)errorFromCFStreamError:(CFStreamError)err +{ + if (err.domain == 0 && err.error == 0) return nil; + + // Can't use switch; these constants aren't int literals. + NSString *domain = @"CFStreamError (unlisted domain)"; + NSString *message = nil; + + if(err.domain == kCFStreamErrorDomainPOSIX) { + domain = NSPOSIXErrorDomain; + } + else if(err.domain == kCFStreamErrorDomainMacOSStatus) { + domain = NSOSStatusErrorDomain; + } + else if(err.domain == kCFStreamErrorDomainMach) { + domain = NSMachErrorDomain; + } + else if(err.domain == kCFStreamErrorDomainNetDB) + { + domain = @"kCFStreamErrorDomainNetDB"; + message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding]; + } + else if(err.domain == kCFStreamErrorDomainNetServices) { + domain = @"kCFStreamErrorDomainNetServices"; + } + else if(err.domain == kCFStreamErrorDomainSOCKS) { + domain = @"kCFStreamErrorDomainSOCKS"; + } + else if(err.domain == kCFStreamErrorDomainSystemConfiguration) { + domain = @"kCFStreamErrorDomainSystemConfiguration"; + } + else if(err.domain == kCFStreamErrorDomainSSL) { + domain = @"kCFStreamErrorDomainSSL"; + } + + NSDictionary *info = nil; + if(message != nil) + { + info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; + } + return [NSError errorWithDomain:domain code:err.error userInfo:info]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Diagnostics +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL) isConnected +{ + return [self isSocketConnected] && [self areStreamsConnected]; +} + +- (NSString *) connectedHost +{ + if(theSocket) + return [self connectedHost:theSocket]; + else + return [self connectedHost:theSocket6]; +} + +- (UInt16) connectedPort +{ + if(theSocket) + return [self connectedPort:theSocket]; + else + return [self connectedPort:theSocket6]; +} + +- (NSString *)localHost +{ + if(theSocket) + return [self localHost:theSocket]; + else + return [self localHost:theSocket6]; +} + +- (UInt16)localPort +{ + if(theSocket) + return [self localPort:theSocket]; + else + return [self localPort:theSocket6]; +} + +- (NSString *)connectedHost:(CFSocketRef)socket +{ + if (socket == NULL) return nil; + CFDataRef peeraddr; + NSString *peerstr = nil; + + if(socket && (peeraddr = CFSocketCopyPeerAddress(socket))) + { + peerstr = [self addressHost:peeraddr]; + CFRelease (peeraddr); + } + + return peerstr; +} + +- (UInt16)connectedPort:(CFSocketRef)socket +{ + if (socket == NULL) return 0; + CFDataRef peeraddr; + UInt16 peerport = 0; + + if(socket && (peeraddr = CFSocketCopyPeerAddress(socket))) + { + peerport = [self addressPort:peeraddr]; + CFRelease (peeraddr); + } + + return peerport; +} + +- (NSString *)localHost:(CFSocketRef)socket +{ + if (socket == NULL) return nil; + CFDataRef selfaddr; + NSString *selfstr = nil; + + if(socket && (selfaddr = CFSocketCopyAddress(socket))) + { + selfstr = [self addressHost:selfaddr]; + CFRelease (selfaddr); + } + + return selfstr; +} + +- (UInt16)localPort:(CFSocketRef)socket +{ + if (socket == NULL) return 0; + CFDataRef selfaddr; + UInt16 selfport = 0; + + if (socket && (selfaddr = CFSocketCopyAddress(socket))) + { + selfport = [self addressPort:selfaddr]; + CFRelease (selfaddr); + } + + return selfport; +} + +- (BOOL)isSocketConnected +{ + if (theSocket == NULL && theSocket6 == NULL) return NO; + return CFSocketIsValid(theSocket) || CFSocketIsValid(theSocket6); +} + +- (BOOL) areStreamsConnected +{ + CFStreamStatus s; + + if (theReadStream != NULL) + { + s = CFReadStreamGetStatus (theReadStream); + if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) ) + return NO; + } + else return NO; + + if (theWriteStream != NULL) + { + s = CFWriteStreamGetStatus (theWriteStream); + if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) ) + return NO; + } + else return NO; + + return YES; +} + +- (NSString *) addressHost: (CFDataRef)cfaddr +{ + if (cfaddr == NULL) return nil; + char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; + struct sockaddr *pSockAddr = (struct sockaddr *) CFDataGetBytePtr (cfaddr); + struct sockaddr_in *pSockAddrV4 = (struct sockaddr_in *) pSockAddr; + struct sockaddr_in6 *pSockAddrV6 = (struct sockaddr_in6 *)pSockAddr; + + const void *pAddr = (pSockAddr->sa_family == AF_INET) ? + (void *)(&(pSockAddrV4->sin_addr)) : + (void *)(&(pSockAddrV6->sin6_addr)); + + const char *pStr = inet_ntop (pSockAddr->sa_family, pAddr, addrBuf, sizeof(addrBuf)); + if (pStr == NULL) [NSException raise: NSInternalInconsistencyException + format: @"Cannot convert address to string."]; + + return [NSString stringWithCString:pStr encoding:NSASCIIStringEncoding]; +} + +- (UInt16)addressPort:(CFDataRef)cfaddr +{ + if (cfaddr == NULL) return 0; + struct sockaddr_in *pAddr = (struct sockaddr_in *) CFDataGetBytePtr (cfaddr); + return ntohs (pAddr->sin_port); +} + +- (NSString *) description +{ + static const char *statstr[] = { "not open", "opening", "open", "reading", "writing", "at end", "closed", "has error" }; + CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus (theReadStream) : 0; + CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus (theWriteStream) : 0; + NSString *peerstr, *selfstr; + CFDataRef peeraddr, selfaddr = NULL, selfaddr6 = NULL; + + if (theSocket && (peeraddr = CFSocketCopyPeerAddress (theSocket))) + { + peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr], [self addressPort:peeraddr]]; + CFRelease (peeraddr); + peeraddr = NULL; + } + else peerstr = @"nowhere"; + + if (theSocket) selfaddr = CFSocketCopyAddress (theSocket); + if (theSocket6) selfaddr6 = CFSocketCopyAddress (theSocket6); + if (theSocket || theSocket6) + { + if (theSocket6) + { + selfstr = [NSString stringWithFormat: @"%@/%@ %u", [self addressHost:selfaddr], [self addressHost:selfaddr6], [self addressPort:selfaddr]]; + } + else + { + selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr], [self addressPort:selfaddr]]; + } + + if (selfaddr) CFRelease (selfaddr); + if (selfaddr6) CFRelease (selfaddr6); + selfaddr = NULL; + selfaddr6 = NULL; + } + else selfstr = @"nowhere"; + + NSMutableString *ms = [[NSMutableString alloc] init]; + [ms appendString: [NSString stringWithFormat:@"buffer length] != 0) + percentDone = (float)theCurrentRead->bytesDone / + (float)[theCurrentRead->buffer length] * 100.0; + else + percentDone = 100; + + [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ", + [theCurrentRead->buffer length], + theCurrentRead->bytesDone ? percentDone : 0]]; + } + + if (theCurrentWrite == nil) + [ms appendString: @"no current write, "]; + else + { + int percentDone; + if ([theCurrentWrite->buffer length] != 0) + percentDone = (float)theCurrentWrite->bytesDone / + (float)[theCurrentWrite->buffer length] * 100.0; + else + percentDone = 100; + + [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ", + [theCurrentWrite->buffer length], + theCurrentWrite->bytesDone ? percentDone : 0]]; + } + + [ms appendString: [NSString stringWithFormat:@"read stream %p %s, write stream %p %s", theReadStream, statstr [rs], theWriteStream, statstr [ws] ]]; + if (theFlags & kDisconnectSoon) [ms appendString: @", will disconnect soon"]; + if (![self isConnected]) [ms appendString: @", not connected"]; + + [ms appendString: @">"]; + + return [ms autorelease]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reading +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; +{ + if (length == 0) return; + if (theFlags & kForbidReadsWrites) return; + + NSMutableData *buffer = [[NSMutableData alloc] initWithLength:length]; + AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer + timeout:timeout + tag:tag + readAllAvailable:NO + terminator:nil + bufferOffset:0]; + + [theReadQueue addObject:packet]; + [self maybeDequeueRead]; + + [packet release]; + [buffer release]; +} + +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + if (data == nil || [data length] == 0) return; + if (theFlags & kForbidReadsWrites) return; + + NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0]; + AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer + timeout:timeout + tag:tag + readAllAvailable:NO + terminator:data + bufferOffset:0]; + + [theReadQueue addObject:packet]; + [self maybeDequeueRead]; + + [packet release]; + [buffer release]; +} + +- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + if (theFlags & kForbidReadsWrites) return; + + // The partialReadBuffer is used when recovering data from a broken connection. + NSMutableData *buffer; + if(partialReadBuffer) { + buffer = [partialReadBuffer mutableCopy]; + } + else { + buffer = [[NSMutableData alloc] initWithLength:0]; + } + + AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer + timeout:timeout + tag:tag + readAllAvailable:YES + terminator:nil + bufferOffset:[buffer length]]; + + [theReadQueue addObject:packet]; + [self maybeDequeueRead]; + + [packet release]; + [buffer release]; +} + +/** + * Puts a maybeDequeueRead on the run loop. + * An assumption here is that selectors will be performed consecutively within their priority. +**/ +- (void)scheduleDequeueRead +{ + [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0]; +} + +/** + * This method starts a new read, if needed. + * It is called when a user requests a read, + * or when a stream opens that may have requested reads sitting in the queue, etc. +**/ +- (void)maybeDequeueRead +{ + // If we're not currently processing a read AND + // we have read requests sitting in the queue AND we have actually have a read stream + if(theCurrentRead == nil && [theReadQueue count] != 0 && theReadStream != NULL) + { + // Get new current read AsyncReadPacket. + AsyncReadPacket *newPacket = [theReadQueue objectAtIndex:0]; + theCurrentRead = [newPacket retain]; + [theReadQueue removeObjectAtIndex:0]; + + // Start time-out timer. + if(theCurrentRead->timeout >= 0.0) + { + theReadTimer = [NSTimer scheduledTimerWithTimeInterval:theCurrentRead->timeout + target:self + selector:@selector(doReadTimeout:) + userInfo:nil + repeats:NO]; + } + + // Immediately read, if possible. + [self doBytesAvailable]; + } +} + +/** + * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. +**/ +- (void)doBytesAvailable +{ + // If data is available on the stream, but there is no read request, then we don't need to process the data yet. + // Also, if there is a read request, but no read stream setup yet, we can't process any data yet. + if(theCurrentRead != nil && theReadStream != NULL) + { + CFIndex totalBytesRead = 0; + BOOL error = NO, done = NO; + while(!done && !error && CFReadStreamHasBytesAvailable(theReadStream)) + { + // If reading all available data, make sure there's room in the packet buffer. + if(theCurrentRead->readAllAvailableData == YES) + [theCurrentRead->buffer increaseLengthBy:READALL_CHUNKSIZE]; + + // If reading until data, just do one byte. + if(theCurrentRead->term != nil) + [theCurrentRead->buffer increaseLengthBy:1]; + + // Number of bytes to read is space left in packet buffer. + CFIndex bytesToRead = [theCurrentRead->buffer length] - theCurrentRead->bytesDone; + + // Read stuff into start of unfilled packet buffer space. + UInt8 *packetbuf = (UInt8 *)( [theCurrentRead->buffer mutableBytes] + theCurrentRead->bytesDone ); + CFIndex bytesRead = CFReadStreamRead (theReadStream, packetbuf, bytesToRead); + totalBytesRead += bytesRead; + + // Check results. + if(bytesRead < 0) + { + bytesRead = 0; + error = YES; + } + + // Is packet done? + theCurrentRead->bytesDone += bytesRead; + if(theCurrentRead->readAllAvailableData != YES) + { + if(theCurrentRead->term != nil) + { + // Search for the terminating sequence in the buffer. + int termlen = [theCurrentRead->term length]; + if(theCurrentRead->bytesDone >= termlen) + { + const void *buf = [theCurrentRead->buffer bytes] + (theCurrentRead->bytesDone - termlen); + const void *seq = [theCurrentRead->term bytes]; + done = (memcmp (buf, seq, termlen) == 0); + } + } + else + { + // Done when (sized) buffer is full. + done = ([theCurrentRead->buffer length] == theCurrentRead->bytesDone); + } + } + // else readAllAvailable doesn't end until all readable is read. + } + + if (theCurrentRead->readAllAvailableData && theCurrentRead->bytesDone > 0) + done = YES; // Ran out of bytes, so the "read-all-data" type packet is done. + + if(done) + { + [self completeCurrentRead]; + if (!error) [self scheduleDequeueRead]; + } + else if(theCurrentRead->readAllAvailableData == NO) + { + // We're not done with the readToLength or readToData yet, but we have read in some bytes + if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)]) + { + [theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag]; + } + } + + if(error) + { + CFStreamError err = CFReadStreamGetError (theReadStream); + [self closeWithError: [self errorFromCFStreamError:err]]; + return; + } + } +} + +// Ends current read and calls delegate. +- (void) completeCurrentRead +{ + NSAssert (theCurrentRead, @"Trying to complete current read when there is no current read."); + [theCurrentRead->buffer setLength:theCurrentRead->bytesDone]; + if ([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)]) + { + [theDelegate onSocket:self didReadData:theCurrentRead->buffer withTag:theCurrentRead->tag]; + } + if (theCurrentRead != nil) [self endCurrentRead]; // Caller may have disconnected. +} + +// Ends current read. +- (void) endCurrentRead +{ + NSAssert (theCurrentRead, @"Trying to end current read when there is no current read."); + [theReadTimer invalidate]; + theReadTimer = nil; + [theCurrentRead release]; + theCurrentRead = nil; +} + +- (void) doReadTimeout:(NSTimer *)timer +{ + if (timer != theReadTimer) return; // Old timer. Ignore it. + if (theCurrentRead != nil) + { + // Send what we got. + [self endCurrentRead]; + } + [self closeWithError: [self getReadTimeoutError]]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Writing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void) writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; +{ + if (data == nil || [data length] == 0) return; + if (theFlags & kForbidReadsWrites) return; + + AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; + + [theWriteQueue addObject:packet]; + [self maybeDequeueWrite]; + + [packet release]; +} + +- (void) scheduleDequeueWrite +{ + [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0]; +} + +// Start a new write. +- (void) maybeDequeueWrite +{ + if (theCurrentWrite == nil && [theWriteQueue count] != 0 && theWriteStream != NULL) + { + // Get new current write AsyncWritePacket. + AsyncWritePacket *newPacket = [theWriteQueue objectAtIndex:0]; + theCurrentWrite = [newPacket retain]; + [theWriteQueue removeObjectAtIndex:0]; + + // Start time-out timer. + if (theCurrentWrite->timeout >= 0.0) + { + theWriteTimer = [NSTimer scheduledTimerWithTimeInterval:theCurrentWrite->timeout + target:self + selector:@selector(doWriteTimeout:) + userInfo:nil + repeats:NO]; + } + + // Immediately write, if possible. + [self doSendBytes]; + } +} + +- (void) doSendBytes +{ + if (theCurrentWrite != nil && theWriteStream != NULL) + { + BOOL done = NO, error = NO; + while (!done && !error && CFWriteStreamCanAcceptBytes (theWriteStream)) + { + // Figure out what to write. + CFIndex bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone; + CFIndex bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE; + UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone); + + // Write. + CFIndex bytesWritten = CFWriteStreamWrite (theWriteStream, writestart, bytesToWrite); + + // Check results. + if (bytesWritten < 0) + { + bytesWritten = 0; + error = YES; + } + + // Is packet done? + theCurrentWrite->bytesDone += bytesWritten; + done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone); + } + + if (done) + { + [self completeCurrentWrite]; + if (!error) [self scheduleDequeueWrite]; + } + + if (error) + { + CFStreamError err = CFWriteStreamGetError (theWriteStream); + [self closeWithError: [self errorFromCFStreamError:err]]; + return; + } + } +} + +// Ends current write and calls delegate. +- (void) completeCurrentWrite +{ + NSAssert (theCurrentWrite, @"Trying to complete current write when there is no current write."); + if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)]) + { + [theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag]; + } + if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected. +} + +// Ends current write. +- (void) endCurrentWrite +{ + NSAssert (theCurrentWrite, @"Trying to complete current write when there is no current write."); + [theWriteTimer invalidate]; + theWriteTimer = nil; + [theCurrentWrite release]; + theCurrentWrite = nil; + [self maybeScheduleDisconnect]; +} + +// Checks to see if all writes have been completed for disconnectAfterWriting. +- (void) maybeScheduleDisconnect +{ + if (theFlags & kDisconnectSoon) + if ([theWriteQueue count] == 0 && theCurrentWrite == nil) + [self performSelector:@selector(disconnect) withObject:nil afterDelay:0]; +} + +- (void) doWriteTimeout:(NSTimer *)timer +{ + if (timer != theWriteTimer) return; // Old timer. Ignore it. + if (theCurrentWrite != nil) + { + // Send what we got. + [self completeCurrentWrite]; + } + [self closeWithError: [self getWriteTimeoutError]]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark CF Callbacks +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)doCFSocketCallback:(CFSocketCallBackType)type forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData +{ + NSParameterAssert ((sock == theSocket) || (sock == theSocket6)); + switch (type) + { + case kCFSocketAcceptCallBack: + [self doAcceptWithSocket: *((CFSocketNativeHandle *)pData)]; + break; + default: + NSLog (@"AsyncSocket %p received unexpected CFSocketCallBackType %d.", self, type); + break; + } +} + +- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream +{ + CFStreamError err; + switch (type) + { + case kCFStreamEventOpenCompleted: + [self doStreamOpen]; + break; + case kCFStreamEventHasBytesAvailable: + [self doBytesAvailable]; + break; + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + err = CFReadStreamGetError (theReadStream); + [self closeWithError: [self errorFromCFStreamError:err]]; + break; + default: + NSLog (@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %d.", self, type); + } +} + +- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream +{ + CFStreamError err; + switch (type) + { + case kCFStreamEventOpenCompleted: + [self doStreamOpen]; + break; + case kCFStreamEventCanAcceptBytes: + [self doSendBytes]; + break; + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + err = CFWriteStreamGetError (theWriteStream); + [self closeWithError: [self errorFromCFStreamError:err]]; + break; + default: + NSLog (@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %d.", self, type); + } +} + +/** + * This is the callback we set up for CFSocket. + * This method does nothing but forward the call to it's Objective-C counterpart +**/ +static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo) +{ + AsyncSocket *socket = (AsyncSocket *)pInfo; + [socket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData]; +} + +/** + * This is the callback we set up for CFReadStream. + * This method does nothing but forward the call to it's Objective-C counterpart +**/ +static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +{ + AsyncSocket *socket = (AsyncSocket *)pInfo; + [socket doCFReadStreamCallback:type forStream:stream]; +} + +/** + * This is the callback we set up for CFWriteStream. + * This method does nothing but forward the call to it's Objective-C counterpart +**/ +static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) +{ + AsyncSocket *socket = (AsyncSocket *)pInfo; + [socket doCFWriteStreamCallback:type forStream:stream]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSData *) sockaddrFromString:(NSString *)addrStr port:(UInt16)port error:(NSError **)errPtr +{ + NSData *resultData = nil; + + struct addrinfo hints = {0}, *result; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE; + + @synchronized (getaddrinfoLock) + { + NSData *addrStrData = [addrStr dataUsingEncoding:NSASCIIStringEncoding + allowLossyConversion:YES]; + + char portStr[] = "65535"; // Reserve space for max port number. + snprintf(portStr, sizeof(portStr), "%u", port); + + int err = getaddrinfo ([addrStrData bytes], portStr, + (const struct addrinfo *)&hints, + (struct addrinfo **)&result); + if (!err) + { + resultData = [NSData dataWithBytes:result->ai_addr + length:result->ai_addrlen]; + freeaddrinfo (result); + } + else if (errPtr) + { + NSString *errMsg = [NSString stringWithCString: gai_strerror(err) + encoding: NSASCIIStringEncoding]; + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: + errMsg, NSLocalizedDescriptionKey, nil]; + + *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" + code:err + userInfo:info]; + } + } + + return resultData; +} + +@end diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/AsyncSocketSample.xcodeproj/project.pbxproj b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/AsyncSocketSample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..bd3b486 --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/AsyncSocketSample.xcodeproj/project.pbxproj @@ -0,0 +1,425 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 08FB77A2FE84155DC02AAC07 /* EchoMain.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* EchoMain.m */; settings = {ATTRIBUTES = (); }; }; + 08FB77A4FE84155DC02AAC07 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; + B1182B1C0451F3E600A87BA4 /* EchoServerMain.m in Sources */ = {isa = PBXBuildFile; fileRef = B1182B1B0451F3E600A87BA4 /* EchoServerMain.m */; }; + B1182B200451F4F900A87BA4 /* AsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = B1182B1E0451F4F900A87BA4 /* AsyncSocket.h */; }; + B1182B210451F4F900A87BA4 /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = B1182B1F0451F4F900A87BA4 /* AsyncSocket.m */; }; + B1182B220451F4F900A87BA4 /* AsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = B1182B1E0451F4F900A87BA4 /* AsyncSocket.h */; }; + B1182B230451F4F900A87BA4 /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = B1182B1F0451F4F900A87BA4 /* AsyncSocket.m */; }; + B1182B2C045220FE00A87BA4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; + B1D647B008E5F5A300CE4127 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1D647A108E5F5A300CE4127 /* CoreServices.framework */; }; + B1D647B108E5F5A300CE4127 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1D647A108E5F5A300CE4127 /* CoreServices.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 034768E6FF38A76511DB9C8B /* Echo */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = Echo; sourceTree = BUILT_PRODUCTS_DIR; }; + 08FB7796FE84155DC02AAC07 /* EchoMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EchoMain.m; sourceTree = ""; }; + 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + B109F4A508163F93000A6E39 /* AsyncSocket Documentation.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = "AsyncSocket Documentation.html"; path = "../AsyncSocket Documentation.html"; sourceTree = SOURCE_ROOT; }; + B1182B180451F2C000A87BA4 /* EchoServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; path = EchoServer; sourceTree = BUILT_PRODUCTS_DIR; }; + B1182B1B0451F3E600A87BA4 /* EchoServerMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EchoServerMain.m; sourceTree = ""; }; + B1182B1E0451F4F900A87BA4 /* AsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AsyncSocket.h; path = ../AsyncSocket.h; sourceTree = SOURCE_ROOT; }; + B1182B1F0451F4F900A87BA4 /* AsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = AsyncSocket.m; path = ../AsyncSocket.m; sourceTree = SOURCE_ROOT; }; + B1D647A108E5F5A300CE4127 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 08FB77A3FE84155DC02AAC07 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 08FB77A4FE84155DC02AAC07 /* Foundation.framework in Frameworks */, + B1D647B108E5F5A300CE4127 /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B1182B150451F2C000A87BA4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B1182B2C045220FE00A87BA4 /* Foundation.framework in Frameworks */, + B1D647B008E5F5A300CE4127 /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* AsyncSocketSample */ = { + isa = PBXGroup; + children = ( + B1DDB1D40476CB9500A87BA4 /* AsyncSocket */, + 08FB7795FE84155DC02AAC07 /* Source */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = AsyncSocketSample; + sourceTree = ""; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 08FB7796FE84155DC02AAC07 /* EchoMain.m */, + B1182B1B0451F3E600A87BA4 /* EchoServerMain.m */, + ); + name = Source; + sourceTree = ""; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + B1D647A108E5F5A300CE4127 /* CoreServices.framework */, + 08FB779EFE84155DC02AAC07 /* Foundation.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 034768E6FF38A76511DB9C8B /* Echo */, + B1182B180451F2C000A87BA4 /* EchoServer */, + ); + name = Products; + sourceTree = ""; + }; + B1DDB1D40476CB9500A87BA4 /* AsyncSocket */ = { + isa = PBXGroup; + children = ( + B109F4A508163F93000A6E39 /* AsyncSocket Documentation.html */, + B1182B1E0451F4F900A87BA4 /* AsyncSocket.h */, + B1182B1F0451F4F900A87BA4 /* AsyncSocket.m */, + ); + name = AsyncSocket; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 08FB77A0FE84155DC02AAC07 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B1182B220451F4F900A87BA4 /* AsyncSocket.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B1182B130451F2C000A87BA4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B1182B200451F4F900A87BA4 /* AsyncSocket.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = B14C468B0859613500F68F74 /* Build configuration list for PBXProject "AsyncSocketSample" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* AsyncSocketSample */; + projectDirPath = ""; + targets = ( + 08FB779FFE84155DC02AAC07 /* Echo */, + B1182B170451F2C000A87BA4 /* EchoServer */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXRezBuildPhase section */ + 08FB77A5FE84155DC02AAC07 /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B1182B160451F2C000A87BA4 /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXRezBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 08FB77A1FE84155DC02AAC07 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 08FB77A2FE84155DC02AAC07 /* EchoMain.m in Sources */, + B1182B230451F4F900A87BA4 /* AsyncSocket.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B1182B140451F2C000A87BA4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B1182B1C0451F3E600A87BA4 /* EchoServerMain.m in Sources */, + B1182B210451F4F900A87BA4 /* AsyncSocket.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXToolTarget section */ + 08FB779FFE84155DC02AAC07 /* Echo */ = { + isa = PBXToolTarget; + buildConfigurationList = B14C46830859613500F68F74 /* Build configuration list for PBXToolTarget "Echo" */; + buildPhases = ( + 08FB77A0FE84155DC02AAC07 /* Headers */, + 08FB77A1FE84155DC02AAC07 /* Sources */, + 08FB77A3FE84155DC02AAC07 /* Frameworks */, + 08FB77A5FE84155DC02AAC07 /* Rez */, + ); + dependencies = ( + ); + name = Echo; + productInstallPath = "$(HOME)/bin"; + productName = AsyncSocketSample; + productReference = 034768E6FF38A76511DB9C8B /* Echo */; + }; + B1182B170451F2C000A87BA4 /* EchoServer */ = { + isa = PBXToolTarget; + buildConfigurationList = B14C46870859613500F68F74 /* Build configuration list for PBXToolTarget "EchoServer" */; + buildPhases = ( + B1182B130451F2C000A87BA4 /* Headers */, + B1182B140451F2C000A87BA4 /* Sources */, + B1182B150451F2C000A87BA4 /* Frameworks */, + B1182B160451F2C000A87BA4 /* Rez */, + ); + dependencies = ( + ); + name = EchoServer; + productInstallPath = "$(HOME)/bin"; + productName = EchoServer; + productReference = B1182B180451F2C000A87BA4 /* EchoServer */; + }; +/* End PBXToolTarget section */ + +/* Begin XCBuildConfiguration section */ + B14C46840859613500F68F74 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + CC = "/usr/bin/gcc-3.3"; + COPY_PHASE_STRIP = NO; + CPLUSPLUS = "/usr/bin/g++-3.3"; + FRAMEWORK_SEARCH_PATHS = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Versions/A/Frameworks\""; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_VERSION = 3.x; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = Echo; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = YES; + }; + name = Development; + }; + B14C46850859613500F68F74 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + CC = "/usr/bin/gcc-3.3"; + COPY_PHASE_STRIP = YES; + CPLUSPLUS = "/usr/bin/g++-3.3"; + FRAMEWORK_SEARCH_PATHS = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Versions/A/Frameworks\""; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_VERSION = 3.x; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = Echo; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = NO; + }; + name = Deployment; + }; + B14C46860859613500F68F74 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + CC = "/usr/bin/gcc-3.3"; + CPLUSPLUS = "/usr/bin/g++-3.3"; + FRAMEWORK_SEARCH_PATHS = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Versions/A/Frameworks\""; + GCC_VERSION = 3.x; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = Echo; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Default; + }; + B14C46880859613500F68F74 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + CC = "/usr/bin/gcc-3.3"; + COPY_PHASE_STRIP = NO; + CPLUSPLUS = "/usr/bin/g++-3.3"; + FRAMEWORK_SEARCH_PATHS = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Versions/A/Frameworks\""; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_VERSION = 3.x; + INSTALL_PATH = "$(HOME)/bin"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = EchoServer; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = YES; + }; + name = Development; + }; + B14C46890859613500F68F74 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + CC = "/usr/bin/gcc-3.3"; + COPY_PHASE_STRIP = YES; + CPLUSPLUS = "/usr/bin/g++-3.3"; + FRAMEWORK_SEARCH_PATHS = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Versions/A/Frameworks\""; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_VERSION = 3.x; + INSTALL_PATH = "$(HOME)/bin"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = EchoServer; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = NO; + }; + name = Deployment; + }; + B14C468A0859613500F68F74 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + CC = "/usr/bin/gcc-3.3"; + CPLUSPLUS = "/usr/bin/g++-3.3"; + FRAMEWORK_SEARCH_PATHS = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Versions/A/Frameworks\""; + GCC_VERSION = 3.x; + INSTALL_PATH = "$(HOME)/bin"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = EchoServer; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Default; + }; + B14C468C0859613500F68F74 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + OTHER_CFLAGS = "-fobjc-exceptions"; + }; + name = Development; + }; + B14C468D0859613500F68F74 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + OTHER_CFLAGS = "-fobjc-exceptions"; + }; + name = Deployment; + }; + B14C468E0859613500F68F74 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + OTHER_CFLAGS = "-fobjc-exceptions"; + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B14C46830859613500F68F74 /* Build configuration list for PBXToolTarget "Echo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B14C46840859613500F68F74 /* Development */, + B14C46850859613500F68F74 /* Deployment */, + B14C46860859613500F68F74 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + B14C46870859613500F68F74 /* Build configuration list for PBXToolTarget "EchoServer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B14C46880859613500F68F74 /* Development */, + B14C46890859613500F68F74 /* Deployment */, + B14C468A0859613500F68F74 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + B14C468B0859613500F68F74 /* Build configuration list for PBXProject "AsyncSocketSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B14C468C0859613500F68F74 /* Development */, + B14C468D0859613500F68F74 /* Deployment */, + B14C468E0859613500F68F74 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoMain.m b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoMain.m new file mode 100644 index 0000000..76d4d36 --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoMain.m @@ -0,0 +1,352 @@ +#import +#import +#import "../AsyncSocket.h" +#include +#include + +#pragma mark Declarations + +@interface Echo : NSObject +{ + BOOL shouldExitLoop; + AsyncSocket *socket; + NSMutableString *text; +} +-(id)init; +-(void)dealloc; +-(void)runLoop; +-(void)stopRunLoop; +-(void)readFromServer; +-(void)readFromStdIn; +-(void)doTextCommand; +-(void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; +-(void)onSocketDidDisconnect:(AsyncSocket *)sock; +-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; +-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)t; +@end + +void showHelp(); + +#pragma mark - +#pragma mark Implementation + +@implementation Echo : NSObject + + +/* + This method creates the socket. Echo reuses this one socket throughout its life. + Echo also sets up the input. While a command-line app is waiting for input, it + is usually blocked; I make the input non-blocking so that the run-loop remains + active. +*/ +- (id)init +{ + self = [super init]; + + // Create socket. + NSLog (@"Creating socket."); + socket = [[AsyncSocket alloc] initWithDelegate:self]; + + // Create command buffer. + text = [[NSMutableString alloc] init]; + + // Set up stdin for non-blocking. + if (fcntl (STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1) + { + NSLog (@"Can't make STDIN non-blocking."); + exit(1); + } + + return self; +} + + +/* + I release allocated resources here, including the socket. The socket will close + any connection before releasing itself, but it will not need to. I explicitly + close any connections in the "quit" command handler. +*/ +- (void)dealloc +{ + [socket release]; + [text release]; + [super dealloc]; +} + + +/* + Echo spends one second handling any run-loop activity (i.e. socket activity) + and then comes up for air to check if any new commands have been entered and, + if so, executing them. Wash, rinse, repeat. + + Note the use of the shouldExitLoop flag to control when the run-loop ends and + the app quits. I could have just called exit(), but this allows the app to clean + up after itself properly. You should use a similar technique if you create a + thread for socket activity and processing. +*/ +- (void)runLoop +{ + shouldExitLoop = NO; + while (!shouldExitLoop) + { + [self readFromStdIn]; + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + } +} + + +/* This method just abstracts the stop-run-loop operation. */ +- (void)stopRunLoop +{ + shouldExitLoop = YES; +} + + +/* + This method simply abstracts the read-from-server operation. It is called + from -onSocket:didReadData:withTag: to set up another read operation. If it did + not set up another read operation, AsyncSocket would not do anything with any + further packets from Echo Server. + + You should not use "\n" as a packet delimiter in your own code. I explain why + in EchoServerMain.c. +*/ +- (void)readFromServer +{ + NSData *newline = [@"\n" dataUsingEncoding:NSASCIIStringEncoding]; + [socket readDataToData:newline withTimeout:-1 tag:0]; +} + + +/* + This method queues up a message that will be sent immediately upon connecting + to Echo Server. Note that the message consists of two write requests. They will + be sent consecutively, though, and will appear as one packet to Echo Server, + because Echo Server looks for the "\n" to determine when the packet ends. +*/ +- (void)prepareHello +{ + // Yes, you can call methods on NSString constants. + + NSData *message = + [@"Hello, Echo Server! " dataUsingEncoding:NSASCIIStringEncoding]; + + NSData *newline = + [@"I am a new client.\n" dataUsingEncoding:NSASCIIStringEncoding]; + + [socket writeData:message withTimeout:-1 tag:0]; + [socket writeData:newline withTimeout:-1 tag:0]; +}; + + +/* + I only runs the run-loop for a second at a time, because I need to receive and + act on input from the user (which won't result in run-loop activity). + That happens here. +*/ +- (void)readFromStdIn +{ + Byte c; + while (read (STDIN_FILENO, &c, 1) == 1) + { + [text appendString: [NSString stringWithFormat:@"%c", c]]; + if (c == '\n') [self doTextCommand]; + } +} + + +/* + This method sends text to Echo Server, or executes a command. +*/ +- (void)doTextCommand +{ + NSArray *params = [text componentsSeparatedByString:@" "]; + if ([text hasPrefix: @"quit"] || [text hasPrefix: @"exit"]) + { + // I don't technically need to call -disconnect here. When the app quits, + // the socket will disconnect itself if needed. But it is easier to keep + // track of here. Note that -onSocket:willDisconnectWithError: will NOT + // be called here, though -onSocketDidDisconnect: will be. + + // I stop the run loop like a gentleman instead of simply calling exit() + // so that the app exits cleanly. This is also technically unnecessary; + // the OS will destroy the socket connection when it cleans up after the + // process. + + NSLog (@"Disconnecting & exiting."); + [socket disconnect]; + [self stopRunLoop]; + } + else if ([text hasPrefix: @"disconnect"]) + { + NSLog (@"Disconnecting."); + [socket disconnect]; + } + else if ([text hasPrefix: @"connect"]) + { + UInt16 port; + NSString *host; + if ([params count] == 3) + { + port = [[params objectAtIndex:2] intValue]; + host = [params objectAtIndex:1]; + } + else + { + port = [[params objectAtIndex:1] intValue]; + host = @"localhost"; + } + + // This starts to establish a connection to the server. + // The connection will not be finished until later, when + // -onSocket:didConnectToPort:host: is called. But even so, you can + // immediately queue a read or write operation here. It will be + // performed when the connection is established. + // + // Note that I enclose the connect method call in a @try block. An + // exception will be thrown if the socket is already connected. Usually, + // if an exception was thrown, that indicates programmer error. In this + // case, an exception would indicate that I did not forsee a user trying + // to make a new connection when one already exists. I could and should + // have used -isConnected to check for that case in advance, but I wanted + // to demonstrate the exception-handling. + // + // Note also, that I call -prepareHello (which writes to the socket) + // before the socket is actually connected. The write request will be + // queued up and transmitted as soon as the connection is complete. + + @try + { + NSError *err; + + if ([socket connectToHost:host onPort:port error:&err]) + { + NSLog (@"Connecting to %@ port %u.", host, port); + [self prepareHello]; + } + else + { + NSLog (@"Couldn't connect to %@ port %u (%@).", host, port, err); + } + } + @catch (NSException *exception) + { + NSLog ([exception reason]); + } + } + else if ([text hasPrefix: @"dump"]) + { + // This demonstrates AsyncSocket's -description method. + NSLog (@"%@", socket); + } + else if ([text hasPrefix: @"help"]) + { + showHelp(); + } + else + { + // Anything other than a command is sent to Echo Server. Note that data + // will include the final press of the Return key, which is "\n". That + // is why I use "\n" verbatim as the packet delimiter, instead of + // specifying CRLF or something. + + NSData *data = [text dataUsingEncoding:NSASCIIStringEncoding]; + [socket writeData:data withTimeout:-1 tag:0]; + } + [text setString:@""]; +} + + +#pragma mark - +#pragma mark AsyncSocket Delegate Methods + + +/* + This will be called whenever AsyncSocket is about to disconnect. In Echo Server, + it does not do anything other than report what went wrong (this delegate method + is the only place to get that information), but in a more serious app, this is + a good place to do disaster-recovery by getting partially-read data. This is + not, however, a good place to do cleanup. The socket must still exist when this + method returns. + */ +-(void) onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err +{ + if (err != nil) + NSLog (@"Socket will disconnect. Error domain %@, code %d (%@).", + [err domain], [err code], [err localizedDescription]); + else + NSLog (@"Socket will disconnect. No error."); +} + + +/* + Normally, this is the place to release the socket and perform the appropriate + housekeeping and notification. But I intend to re-use this same socket for + other connections, so I do nothing. +*/ +-(void) onSocketDidDisconnect:(AsyncSocket *)sock +{ + NSLog (@"Disconnected."); +} + + +/* + This method is called when Echo has connected to Echo Server. I immediately + wait for incoming data from the server, but I already have two write requests + queued up (from -prepareHello), and will also be sending data when + the user gives me some to send. +*/ +-(void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; +{ + NSLog (@"Connected to %@ %u.", host, port); + [self readFromServer]; +} + + +/* + This method is called when Echo has finished reading a packet from Echo Server. + It prints it out and immediately calls -readFromServer, which will queue up a + read operation, waiting for the next packet. + + You'll note that I do not implement -onSocket:didWriteDataWithTag:. That is + because Echo does not care about the data once it is transmitted. AsyncSocket + will still send the data, but will not notify Echo when that it done. +*/ +-(void) onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)t +{ + NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + printf ([str cString]); + fflush (stdout); + [str release]; + [self readFromServer]; +} + + +@end + +#pragma mark - +#pragma mark C Functions + +void showHelp() +{ + printf ("ECHO by Dustin Voss copyright 2003. Sample code for using AsyncSocket."); + printf ("\nReads and writes text to an ECHO SERVER. The following commands are available:"); + printf ("\n\tquit, exit -- exit the program"); + printf ("\n\thelp -- display this message"); + printf ("\n\tconnect host port -- connects to the server on the given host and port"); + printf ("\n\tdisconnect -- disconnects from the current server"); + printf ("\n\tdump -- displays the socket's status"); + printf ("\nAnything else gets transmitted to the server. Begin!\n"); + fflush (stdout); +} + +int main (int argc, const char * argv[]) +{ + showHelp(); + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + Echo *e = [[Echo alloc] init]; + [e runLoop]; + [e release]; + [pool release]; + return 0; +} diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoServerMain.m b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoServerMain.m new file mode 100644 index 0000000..7abf203 --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/AsyncSocketSample/EchoServerMain.m @@ -0,0 +1,215 @@ +#import +#import "../AsyncSocket.h" + +#pragma mark Interface + +@interface EchoServer : NSObject +{ + NSMutableArray *sockets; +} +-(id) init; +-(void) dealloc; +-(void) acceptOnPortString:(NSString *)str; +-(void) onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; +-(void) onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; +-(void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; +-(void) onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag; +-(void) onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; +@end + +#pragma mark - +#pragma mark Implementation + +@implementation EchoServer + + +/* + This method sets up the accept socket, but does not actually start it. + Once started, the accept socket accepts incoming connections and creates new + instances of AsyncSocket to handle them. + Echo Server keeps the accept socket in index 0 of the sockets array and adds + incoming connections at indices 1 and up. +*/ +-(id) init +{ + self = [super init]; + sockets = [[NSMutableArray alloc] initWithCapacity:2]; + + AsyncSocket *acceptor = [[AsyncSocket alloc] initWithDelegate:self]; + [sockets addObject:acceptor]; + [acceptor release]; + return self; +} + + +/* + This method will never get called, because you'll be using Ctrl-C to exit the + app. +*/ +-(void) dealloc +{ + // Releasing a socket will close it if it is connected or listening. + [sockets release]; + [super dealloc]; +} + + +/* + This method actually starts the accept socket. It is the first thing called by + the run-loop. +*/ +- (void) acceptOnPortString:(NSString *)str +{ + // AsyncSocket requires a run-loop. + NSAssert ([[NSRunLoop currentRunLoop] currentMode] != nil, @"Run loop is not running"); + + UInt16 port = [str intValue]; + AsyncSocket *acceptor = (AsyncSocket *)[sockets objectAtIndex:0]; + + NSError *err = nil; + if ([acceptor acceptOnPort:port error:&err]) + NSLog (@"Waiting for connections on port %u.", port); + else + { + // If you get a generic CFSocket error, you probably tried to use a port + // number reserved by the operating system. + + NSLog (@"Cannot accept connections on port %u. Error domain %@ code %d (%@). Exiting.", port, [err domain], [err code], [err localizedDescription]); + exit(1); + } +} + + +/* + This will be called whenever AsyncSocket is about to disconnect. In Echo Server, + it does not do anything other than report what went wrong (this delegate method + is the only place to get that information), but in a more serious app, this is + a good place to do disaster-recovery by getting partially-read data. This is + not, however, a good place to do cleanup. The socket must still exist when this + method returns. + + I do not implement -onSocketDidDisconnect:. Normally, that is where you would + release the disconnected socket and perform housekeeping, but I am satisfied + to leave the disconnected socket instances alone until Echo Server quits. +*/ +-(void) onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err +{ + if (err != nil) + NSLog (@"Socket will disconnect. Error domain %@, code %d (%@).", + [err domain], [err code], [err localizedDescription]); + else + NSLog (@"Socket will disconnect. No error."); +} + + +/* + This method is called when a connection is accepted and a new socket is created. + This is a good place to perform housekeeping and re-assignment -- assigning an + controller for the new socket, or retaining it. Here, I add it to the array of + sockets. However, the new socket has not yet connected and no information is + available about the remote socket, so this is not a good place to screen incoming + connections. Use onSocket:didConnectToHost:port: for that. +*/ +-(void) onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket +{ + NSLog (@"Socket %d accepting connection.", [sockets count]); + [sockets addObject:newSocket]; +} + + +/* + At this point, the new socket is ready to use. This is where you can screen the + remote socket or find its DNS name (the host parameter is just an IP address). + This is also where you should set up your initial read or write request, unless + you have a particular reason for delaying it. +*/ +-(void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port +{ + NSLog (@"Socket %d successfully accepted connection from %@ %u.", [sockets indexOfObject:sock], host, port); + NSData *newline = [@"\n" dataUsingEncoding:NSASCIIStringEncoding]; + + // In Echo Server, each packet consists of a line of text, delimited by "\n". + // This is not a technique you should use. I do not know what "\n" actually + // means in terms of bytes. It could be CR, LF, or CRLF. + // + // In your own networking protocols, you must be more explicit. AsyncSocket + // provides byte sequences for each line ending. These are CRData, LFData, + // and CRLFData. You should use one of those instead of "\n". + + // Start reading. + [sock readDataToData:newline withTimeout:-1 tag:[sockets indexOfObject:sock]]; +} + + +/* + This method is called whenever a packet is read. In Echo Server, a packet is + simply a line of text, and it is transmitted to the connected Echo clients. + Once you have dealt with the incoming packet, you should set up another read or + write request, or -- unless there are other requests queued up -- AsyncSocket + will sit idle. +*/ +-(void) onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag +{ + NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + + // Print string. + NSString *trimStr = [str stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]]; + [str release]; + NSLog (@"Socket %d sent text \"%@\".", tag, trimStr); + + // Forward string to other sockets. + int i; for (i = 1; i < [sockets count]; ++i) + [(AsyncSocket *)[sockets objectAtIndex:i] writeData:data withTimeout:-1 tag:i]; + + // Read more from this socket. + NSData *newline = [@"\n" dataUsingEncoding:NSASCIIStringEncoding]; + [sock readDataToData:newline withTimeout:-1 tag:tag]; +} + + +/* + This method is called when AsyncSocket has finished writing something. Echo + Server does not need to do anything after writing, but your own app might need + to wait for a command from the remote application, or begin writing the next + packet. +*/ +-(void) onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag +{ + NSLog (@"Wrote to socket %d.", tag); +} + +@end + +#pragma mark - +#pragma mark Main + +int main (int argc, const char * argv[]) +{ + printf ("ECHO SERVER by Dustin Voss copyright 2003. Sample code for using AsyncSocket."); + printf ("\nSYNTAX: %s port", argv[0]); + printf ("\nAccepts multiple connections from ECHO clients, echoing any client to all\nclients.\n"); + if (argc != 2) exit(1); + + printf ("Press Ctrl-C to exit.\n"); + fflush (stdout); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + EchoServer *es = [[EchoServer alloc] init]; + NSString *portString = [NSString stringWithCString:argv[1]]; + + // Here, I use perform...afterDelay to put an action on the run-loop before + // it starts running. That action will actually start the accept socket, and + // AsyncSocket will then be able to create other activity on the run-loop. + // But main() will have no other opportunity to do so; the run-loop does not + // give me any way in, other than the AsyncSocket delegate methods. + + // Note that I cannot call AsyncSocket's -acceptOnPort:error: outside of the + // run-loop. + + [es performSelector:@selector(acceptOnPortString:) withObject:portString afterDelay:1.0]; + [[NSRunLoop currentRunLoop] run]; + [EchoServer release]; + [pool release]; + + return 0; +} diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/changes.txt b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/changes.txt new file mode 100644 index 0000000..d0e6538 --- /dev/null +++ b/CocoaAsyncSocket/CocoaAsyncSocket-4.3.3/changes.txt @@ -0,0 +1,52 @@ +CHANGES IN VERSION 4.3.1: + +Bugfix: + If user called acceptOnPort:0, then the OS would automatically pick a port for you. + This is what is supposed to happen, except that it would pick a different port for IPv4 and IPv6 + Added code to make sure both protocols are listening on the same port + +Added comments in many places + +Altered bits of code to match Apple's coding style guidelines + +Renamed method "attachAcceptSockets" to "attachSocketsToRunLoop:error:" + + + + +CHANGES IN VERSION 4.3.2 + +Removed polling - it's not needed + +Added another delegate method: onSocket:didReadPartialDataOfLength:tag: +Often, when using the AsyncSocket class, it was important to display a progress bar to the user. +This was possible using Timers, and calling progressOfRead, but it wasn't the easiest solution. +This delegate method will allow for automatic notification when using readToLength: or readToData: + + + + +CHANGES IN VERSION 4.3.3 + +Bugfix: + The methods connectedHost, connectedPort, localHost, and localPort all assumed IPv4 connection. + In other words they all assumed theSocket was valid, causing a crash when the OS connected via IPv6. + Updated all methods to properly check either theSocket or theSocket6. + +Bugfix: + In the doStreamOpen method, there was an assumption that IPv4 was used and thus a valid theSocket variable. + This was not always the case, causing this to fail: + CFSocketCopyPeerAddress(theSocket) + Fixed the problem by simply using the connectedHost and connectedPort methods. + +Tweak: + Added safety check in addressPort: method: + if (cfaddr == NULL) return 0; + +Tweak: + The kCFStreamPropertyShouldCloseNativeSocket was previously getting set in the configureStreamsAndReturnError: method. + This may have been OK, but it would have caused a problem if the following sequence occurred: + A socket was accepted and doAcceptWithSocket: method was called, + This method then encoutered an error while attaching the streams to the run loop. + If this sequence happened, the BSD socket would not have been closed. + So I moved this into the createStreams... methods. \ No newline at end of file -- 2.11.0