Cocoa for Scientists (Part XXIX): This is the Message

Author: Drew McCormack
Web Sites: www.mentalfaculty.com

In our last installment, we began a journey into the world of lowish-level networking on Mac OS X and iPhone. The first tutorial introduced the topic, and delved into Bonjour, which is a framework that helps devices find each other. In this tutorial, we are going to learn how you can make devices connect and talk to each other after they have been introduced, a process best described as ‘messaging’. We’ll be developing some basic messaging classes that will run on Mac and iPhone.

Sockets

As discussed last time, networking in Mac OS X and iPhone OS is based on BSD sockets. We will not be using these directly, but will instead use the open source class AsyncSocket, which will do most of the heavy lifting for us.

A socket can have a few basic tasks:

  • It can listen for attempts to connect to a particular network port.
  • It can attempt to connect to a listening socket.
  • It can send/receive a stream of data once a connection has been established.

If you are writing a server application, your application will need to listen on a particular port for any attempts to connect to it. If you are writing a client application, it will need to connect to a listening socket on a server. And once a connection has been established, both client and server need to be able to send (receive) data to (from) each other.

Streams

Sockets work with very simple streams of data. They do not know anything about the content of the data, and — from the perspective of the socket — the stream is homogenous and continuous, with no structure whatsoever. The meaning and content of the data stream is left to the messaging level, which is the primary focus of this tutorial.

Messaging

A messaging class or library establishes a system for structuring the data in a stream into messages that have some meaning to the communicating applications. There are two common ways to structure messages:

  1. Use a fixed size header that describes the message payload.
  2. Use a particular pattern of data to terminate a message.

The problem with a continuous stream of data is that you don’t know where one message ends, and the next begins. The approaches above are designed to address this issue.

The first approach breaks a message into two parts: the header, and the payload. The header is of fixed size, and describes how much payload data there is, as well as providing any other metadata, such as what type of data is contained in the payload. The receiver then knows how much data needs to be read before a new message begins.

The second approach doesn’t use a header, but a record termination sequence. The receiver looks at each piece of incoming data, and if a particular pattern is encountered, it assumes the message is complete. This is very similar to how periods are used to delineate sentences in written language.

The only downside to this is that you have to be sure that the termination sequence you are using cannot naturally occur in the data that is being transmitted, otherwise messages could be cut short. For example, if you are sending HTML data, and you decide to use the sequence <body> to terminate your messages, the receiver will see the <body> tag in a HTML page as a message terminator. So you need to make sure that you choose a sequence that will not occur in the message payload.

We will be using the header-payload approach for our messaging classes.

MTMessage

As in the last tutorial, we are going to build simple server and client applications to demonstrate messaging. We will start with the server, which you can download here.

The messaging classes are MTMessage and MTMessageBroker. MTMessage is a model class that represents a single message. It has a tag, which can be used to distinguish different types of messages, and a payload, which is simply an instance of the class NSData.

@interface MTMessage : NSObject <NSCoding> {
    int tag;
    NSData *dataContent;
}

-(int)tag;
-(void)setTag:(int)value;

-(NSData *)dataContent;
-(void)setDataContent:(NSData *)value;

@end

The MTMessage class conforms to the NSCoding protocol, which makes it very easy to package up into an instance of NSData, and send to a remote system via a socket.

MTMessageBroker

The MTMessageBroker class is what actually sends and receives messages, and where most of the interesting code is.

#import <Foundation/Foundation.h>

@class AsyncSocket;
@class MTMessage;
@class MTMessageBroker;

@interface NSObject (MTMessageBrokerDelegateMethods)

-(void)messageBroker:(MTMessageBroker *)server didSendMessage:(MTMessage *)message;
-(void)messageBroker:(MTMessageBroker *)server didReceiveMessage:(MTMessage *)message;
-(void)messageBrokerDidDisconnectUnexpectedly:(MTMessageBroker *)server;

@end

@interface MTMessageBroker : NSObject {
    AsyncSocket *socket;
    BOOL connectionLostUnexpectedly;
    id delegate;
    NSMutableArray *messageQueue;
    BOOL isPaused;
}

-(id)initWithAsyncSocket:(AsyncSocket *)socket;

-(id)delegate;
-(void)setDelegate:(id)value;

-(AsyncSocket *)socket;

-(void)sendMessage:(MTMessage *)newMessage;

-(void)setIsPaused:(BOOL)yn;
-(BOOL)isPaused;

@end

MTMessageBroker works with a delegate, which is informed whenever a message is finished being sent, or a new message is received. To send a message, you simply call the sendMessage: method and pass in an instance of MTMessage. All going well, this will be sent via the AsyncSocket to the remote system, where it will be unpacked, and passed to the delegate on that system via the method messageBroker:didReceiveMessage:.

To create a message broker, you already need to have established a connection, with an instance of AsyncSocket ready to send and receive data. We’ll see how that is done in the next section; in this section, we’ll see how data is sent and received using the socket.

In the initializer of MTMessageBroker, a call is made to the readDataToLength:withTimeout:tag: method of AsyncSocket.

-(id)initWithAsyncSocket:(AsyncSocket *)newSocket {
    if ( self = [super init] ) {
        if ( [newSocket canSafelySetDelegate] ) {
            socket = [newSocket retain];
            [newSocket setDelegate:self];
            messageQueue = [NSMutableArray new];
            [socket readDataToLength:MessageHeaderSize withTimeout:SocketTimeout tag:0];
        }
        else {
            NSLog(@"Could not change delegate of socket");
            [self release];
            self = nil;
        }
    }
    return self;
}

The readDataToLength:withTimeout:tag: method is asynchronous, so it returns immediately before any data has been read; however, invoking it causes the socket to wait for data, and to notify its delegate when it has been received.

The method is told how much data it should wait for; the MessageHeaderSize variable corresponds to a single 64-bit unsigned integer (ie, 8 bytes), which represents the size of the payload. You can set a timeout, but the SocketTimeout constant is set to -1.0, which means there is effectively no timeout in this case. The tag is a convenience: you can use it on the receiver to identifier what type of data is being sent. In MTMessageBroker, a tag of 0 represents header data, and a tag of 1 represents the payload data.

Sending Data

The method for sending messages looks like this:

-(void)sendMessage:(MTMessage *)message {
    [messageQueue addObject:message];
    NSData *messageData = [NSKeyedArchiver archivedDataWithRootObject:message];
    UInt64 header[1];
    header[0] = [messageData length]; 
    header[0] = CFSwapInt64HostToLittle(header[0]);  // Send header in little endian byte order
    [socket writeData:[NSData dataWithBytes:header length:MessageHeaderSize] withTimeout:SocketTimeout tag:(long)0];
    [socket writeData:messageData withTimeout:SocketTimeout tag:(long)1];
}

This uses an NSKeyedArchiver to serialize the MTMessage passed in. It then determines how big this payload is using the NSData length method, and puts this number into an 8-byte, unsigned integer (UInt64), which is to be used as the fixed-size message header.

Now you have to be careful, because numbers on different devices are not all created equal. This comes back to endianness, which relates to significance of each byte in the binary representation of the number. Some devices are little-endian, which means that the first byte in memory is the least numerically significant, and others are big-endian, with the first byte being the most significant.

What this means in practice is that you need to ensure that, when you pass numbers as raw data, account is taken of byte order. In this case, we choose to send the header in little endian order. The Core Foundation framework makes it very easy to account for this by providing the function CFSwapInt64HostToLittle, which takes a native 64-bit unsigned integer, and converts it to little endian order.

    header[0] = CFSwapInt64HostToLittle(header[0]);  // Send header in little endian byte order

On a little endian system, this will have no effect, but on a big endian system, the byte order will be reversed.

The sendMessage: method sends the little endian integer using the AsyncSocket method writeData:length:withTimeout:tag:. It then uses the same method to send the payload.

    [socket writeData:[NSData dataWithBytes:header length:MessageHeaderSize] withTimeout:SocketTimeout tag:(long)0];
    [socket writeData:messageData withTimeout:SocketTimeout tag:(long)1];

Receiving Data

We have already seen in the initializer method that in order to receive data from AsyncSocket, you have to call a method like readDataToLength:timeout:tag:, and then implement the appropriate callback. Here is the callback from MTMessageBroker.

-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if ( tag == 0 ) {
        // Header
        UInt64 header = *((UInt64*)[data bytes]);
        header = CFSwapInt64LittleToHost(header);  // Convert from little endian to native
        [socket readDataToLength:(CFIndex)header withTimeout:SocketTimeout tag:(long)1];
    }
    else if ( tag == 1 ) { 
        // Message body. Pass to delegate
        if ( delegate && [delegate respondsToSelector:@selector(messageBroker:didReceiveMessage:)] ) {
            MTMessage *message = [NSKeyedUnarchiver unarchiveObjectWithData:data];
            [delegate messageBroker:self didReceiveMessage:message];
        }

        // Begin listening for next message
        if ( !isPaused ) [socket readDataToLength:MessageHeaderSize withTimeout:SocketTimeout tag:(long)0];
    }
    else {
        NSLog(@"Unknown tag in read of socket data %d", tag);
    }
}

This method looks at the tag of the message, and takes action accordingly. If the tag is 0, it is a header, and — after applying the appropriate byte swapping — it reinvokes the readDataToLength:withTimeout:tag: method in order to receive the payload of the message. The amount of data it needs to read is now known, because that was the value sent in the header.

If the tag is 1, we are dealing with the payload, and a keyed unarchiver is used to deserialize the MTMessage object, which is then passed to the message broker delegate method messageBroker:didReceiveMessage:.

After the incoming payload has been processed, it is important to again make a call to readDataToLength:withTimeout:tag: to listen for the next incoming header. This chain of calls should not be broken, otherwise the socket will just fill up with data, and communications will be blocked.

One thing you might worry about when beginning low-level networking is what happens if one send ‘overtakes’ another on the network, and appears on the remote system first. Well, you needn’t worry, because the TCP protocol, and the AsyncSocket class, guarantee that this will not happen. Data is received by the AsyncSocket delegate on the receiver in the same order it was sent on the sender. It is not possible for the message payload to arrive after its corresponding header.

Listening Sockets

That pretty much covers MTMessageBroker, and sending and receiving data using AsyncSocket. What we haven’t covered yet is actually setting the socket up in the first place.

As mentioned in the beginning, apart from sending and receiving streams of data, a socket can also take on the role of listening for a new connection. The ServerController method startService sets up such socket.

-(void)startService {
    // Start listening socket
    NSError *error;
    self.listeningSocket = [[[AsyncSocket alloc] initWithDelegate:self] autorelease];
    if ( ![self.listeningSocket acceptOnPort:0 error:&error] ) {
        NSLog(@"Failed to create listening socket");
        return;
    }

    // Advertise service with bonjour
    NSString *serviceName = [NSString stringWithFormat:@"Cocoa for Scientists on %@", 
        [[NSProcessInfo processInfo] hostName]];
    netService = [[NSNetService alloc] initWithDomain:@"" 
        type:@"_cocoaforscientists._tcp." 
        name:serviceName 
        port:self.listeningSocket.localPort];
    netService.delegate = self;
    [netService publish];
}

The AsyncSocket is first initialized with the method initWithDelegate:, at which point a call is made to acceptOnPort:error:. This is the method that causes the socket to listen for attempts to connect. By passing ‘0’ for the port number, the system will assign an available port automatically. The port assigned is accessible via the socket’s localPort property, which has been used in the code above to initialize the Bonjour service.

If an attempt is made to connect to the port assigned to the listening socket, it creates a new AsyncSocket object with the same delegate, and that new socket calls the delegate methods onSocketWillConnect: and onSocket:didConnectToHost:port:.

-(BOOL)onSocketWillConnect:(AsyncSocket *)sock {
    if ( self.connectionSocket == nil ) {
        self.connectionSocket = sock;
        return YES;
    }
    return NO;
}

...

-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
    MTMessageBroker *newBroker = [[[MTMessageBroker alloc] initWithAsyncSocket:sock] autorelease];
    newBroker.delegate = self;
    self.messageBroker = newBroker;
}

The first method is invoked before the connection is established, and gives you a chance to reject the connection. In this case, we simply test to see whether there is already a connection in place, and if so, reject any new one by returning NO. In other applications, it is perfectly conceivable that you would have multiple connections to multiple remote machines at the same time.

If the onSocketWillConnect: delegate method returns YES, AsyncSocket forms the connection, and then invokes the onSocket:didConnectToHost:port: delegate method. In the implementation above, the new socket is used to create the message broker object.

Client

You can download the client project here. The code is largely similar to the server, except that it is up to the client app to make contact in order to establish a connection. This occurs when the net service of the server has been resolved:

-(void)netServiceDidResolveAddress:(NSNetService *)service {
    NSError *error;
    self.connectedService = service;
    self.socket = [[[AsyncSocket alloc] initWithDelegate:self] autorelease];
    [self.socket connectToAddress:service.addresses.lastObject error:&error];
}

The method connectToAddress:error: is used to attempt to connect. The address of the server is retrieved from the resolved net service.

Apart from this small deviation, the connection code for the client is very similar to that of the server.

-(BOOL)onSocketWillConnect:(AsyncSocket *)sock {
    if ( messageBroker == nil ) {
        [sock retain];
        return YES;
    }
    return NO;
}

-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {      
    MTMessageBroker *newBroker = [[[MTMessageBroker alloc] initWithAsyncSocket:socket] autorelease];
    [sock release];
    newBroker.delegate = self;
    self.messageBroker = newBroker;
    self.isConnected = YES;
}

The AsyncSocket delegate method onSocketWillConnect: is invoked first, followed by onSocket:didConnectToHost:port:. (Note that it is necessary to retain the newly formed AsyncSocket in onSocketWillConnect: if you want to go ahead with the connection.)

Sending Messages

With a message broker on both ends of the connection, sending data between client and server is very easy. The sender, in this case the client app, invokes the sendMessage: method of MTMessageBroker.

-(IBAction)send:(id)sender {
    NSData *data = [textView.string dataUsingEncoding:NSUTF8StringEncoding];
    MTMessage *newMessage = [[[MTMessage alloc] init] autorelease];
    newMessage.tag = 100;
    newMessage.dataContent = data;
    [self.messageBroker sendMessage:newMessage];
}

The receiver, in this case the server app, receives this message via a delegate method invocation.

-(void)messageBroker:(MTMessageBroker *)server didReceiveMessage:(MTMessage *)message {
    if ( message.tag == 100 ) {
        textView.string = [[[NSString alloc] initWithData:message.dataContent encoding:NSUTF8StringEncoding] autorelease];
    }
}

It checks the message tag so that it knows what sort of data it contains, and then processes the data content appropriately.

As you can see, neither the sending or receiving method is very extensive. The messaging approach makes it pretty trivial to send even complex object graphs between apps.

Trying It Out

To test out the applications, build and run each. Then, on the client app, click the ‘Search’ button to look for servers. You should see your server appear in the list. Select it, and click the ‘Connect’ button. Finally, enter some text into the text view at the bottom of the client window, and click ‘Send’. You should see it appear in the Server window.

Here we have demonstrated the sending of a simple string, but the content of your data is immaterial to the messaging classes. You could send files or complex object graphs the same way. The only prerequisite is that you need to be able to serialize the data into an instance of NSData (eg with NSKeyedArchiver).

Concluding

That’s it for our short tour of messaging. For educations sake, we have looked at creating a simple messaging framework on top of the sockets layer. You should also be aware that there are existing messaging frameworks that can do this for you, and they may be appropriate depending on your use case. For example, if you only need to communicate between two Macs, and not with an iPhone or some other platform, you could consider using the Distributed Objects framework, which is built into Cocoa.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Serializing an image

How do we send images instead of text. Is there any sample code?

Thanks

Re: Sending images

Depends where you are sending them. If you send them to an iPhone from a Mac, for example, you need to convert to some format like PNG, and then send that data. Here is some sample code for that, written as a category for NSImage

-(NSData *)pngData {
    NSData *tiffData = [self TIFFRepresentation];
    NSBitmapImageRep *tiffRep = [NSBitmapImageRep imageRepWithData:tiffData];
    NSData *pngData = [tiffRep representationUsingType:NSPNGFileType properties:nil];
    return pngData;
}

If you are sending Mac to Mac, you can use keyed archiving:

NSImage *image = ...;
NSData *imageData = [NSKeyedArchiver archivedDataWithRootObject:image];

and the corresponding NSKeyedUnarchiver invocation to unpack again.

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

thanks

Hi, thanks for the tutorial.

Connectivity on Device

First off, great tutorials!

I was trying to send strings from my Mac to my iPod and was successful in doing so with the simulator. However, when I compile and run the app on the iPod it does not work. To clarify, it sees the host on the local network, but the text is not sent from the iPod(Client) to the Mac(Host).

Is there something I may have missed that would make the code work on the simulator but not on the device?

Thanks.

Re: Connectivity

One thing to check is that you aren't causing a deadlock by assuming something about the order in which the data arrives. For example, have you assumed something like the iPod sends its first data before it receives anything? Is it possible that the send happens after the first receive. Things like that.

The code should definitely work, because I use it in my own app (Mental Case) on the iPhone. But you do have to think a bit about the slow speed of the network, and the order that the messages are send and received.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Connectivity

Oh, one more thing: there was an issue in AsyncSocket on the iPhone, which may require you to make a change in the code. I'm don't think it is in the release version.

It is very simple: the iPhone does not support IPv6, but AsyncSocket looks for it. This causes a failure. You have to comment out a line in the code to prevent that check. Read about the issue here:

http://deusty.blogspot.com/2008/07/asyncsocket-on-iphone.html

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

NetService

Hi,

A very nice article, and thank you for this.

Not sure if you have noticed this, but when f.e the server is restarted, the client can't connect anymore.
The NetService search doesn't collect and update the new info.

Greetz

Netservice started

Hei,
I had the same problem as Hardcase and found that this was because the service on the Iphone or as in my case on the IPod touch was not started before the client was started. I presume the answer to this issue is given by Lord anubis. But I am new to this all and would like to know what should be changed to the conde so it would update correctly?
Stijn Willems

Garbage collection

The Iphone does not use garbage collection. Is it necessary to include extra code when used on the Iphone?

Stijn Willems

Re: Garbage Collection

On the iPhone you need to use standard retain/release practices to ensure memory is deallocated when an object is no longer being used (except autoreleased objects). This is the same as if you were writing a Mac desktop app with GC turned off.

If you use properties, the properties are set up to retain/release automatically for you on assignation. So only in cases where you manually retain an object or supply your own setter method requires this.

Hope that helps,

Dave

Multiple Messages at once

I want to use this code, but I want to send multiple messages from one method from the client program at once, but with different values to the
-(void)messageBroker:(MTMessageBroker *)server didReceiveMessage:(MTMessage *)message;
Method on the server application, but when I do that, I can only access one of the messages. How would I go about being able to access both message's contents? Thank you!

~Alexander

Re: Multiple messages

You can send one message after the other from the same method. You should then get two calls on the server to the delegate of the MTMessageBroker, one for each message. They will arrive separately, but in the same order as they were sent.

If you need to keep all the data together for some reason, just put it all in one message.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Determining when a client disconnects

Is there any way to determine when a client disconnects from the server? Thanks!

Re: Disconnections

I don't think so. I think you either have to arrange this yourself, by sending messages back and forth to terminate a connection, or you have to set a timeout, and if it gets triggered, assume something went wrong.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

response

Hey,
How would I have the server send a response back to each message it gets? Pass the received socket to a send on the server?

A simple example is fine, if you can.

Thanks!

Receiving

You don't need to do anything special at all. Once the connection is esablished, and you have a message broker in place, you can send messages both ways, even at the same time. Just make sure that you have a delegate set on each machine, so that received messages are passed to the delegate.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Connectivity

Hi!

First great thanks for this awsome post!!!

I'm experiencing the same problem. When I launch my app in the iPhone simulator, everything works fine. If I run it on the iPhone, the logging shows me that the NSNetService is being resolved, but nothing happens...

I Checked the link you posted and found out something about ipv6 being not supported on the iPhone. But I'm new to this stuff and don't understand what exactly to do to get it working. It would be great to post the solution here if someone knows it.

Greets
Benjamin

Re: Connectivity

You need to make sure you are using ipv4 addresses. There is a post on the AsyncSocket mailing list that addresses this:

http://groups.google.com/group/cocoaasyncsocket/msg/46929cbc16baced0?hl=en

Apple also has a technote on this:

http://developer.apple.com/qa/qa2001/qa1298.html

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Connectivity

From the link Drew posted, it was as easy as turning off IPv6 on OSX.

System Preferences->Network->"Your Connected Network"->Advanced->Configure IPv6 -> OFF

Change required to MTMessageBroker.m

I found that a change was required to MTMessageBroker.m


-(void)onSocketDidDisconnect:(AsyncSocket *)sock {
if ( connectionLostUnexpectedly ) {
if ( delegate && [delegate respondsToSelector:@selector(messageBrokerDidDisconnectUnexpectedly:)] ) {
[delegate messageBrokerDidDisconnectUnexpectedly:self];
}
}
}

The issue was that the selector read as

@selector(messageBroker:didDisconnectUnexpectedly:)

and in fact should have read as

@selector(messageBrokerDidDisconnectUnexpectedly:)

Also, just wanted to say, FANTASTIC. Thank you. I've managed to get my project working and it's been a good ride with this code. Certainly I don't think I could have achieved the result I have without this solid footing.

Re: Change required

Thanks! Hadn't picked up on that one.
Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Difficulty in Implementation

I have used the above instructions for creating a window based application on the iPhone that will act a client for data transfer between the iPhone and a Mac server.

I created the client application and made the above mentioned changes. Now, my server program runs fine. However, I get the following error while building the client:

request for member 'string' in something not a structure or union

This error occurs after the send action.

I am new to this environment. Would appreciate some help on fixing the above error.

Thanks a lot.

Disconnecting from the host

Is there a way to have the client application be able to disconnect from the server application in such a way that it could connect once again if the user so wished?

Re: Difficult in Implementation

This type of error usually arises when you try to use the dot syntax to access a property called 'string', but the compiler does not know anything about the class. Solution: Either use the full accessor (eg setString:), or import the header for the class in question.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Disconnecting

To disconnect, simply tear down the socket on the server and the client. To reconnect, just create a new connection from the client to the server. I don't think you can disconnect the client and then reconnect to the same server socket later.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Disconnecting

How would I do this on the client? What i'm doing to make the server available to be connected to again is using the stopService method and then after that calling startService which is working pretty well right now, but I can't find a way to disconnect from the client in a way that would let it be able to connect back to another server after disconnecting. So how exactly would i tear down the socket on the client?

Log messages?

First off, excellent tutorial! I've implemented it on my iPhone as a server/client where I can recieve and send text but now I want to do more...

How would I go about logging messages? Instead of the messsages going into the same text field and getting overridden by the new text, is there away to have them logged, sort of like a chat looking window?

For example

Sender: Hello!
Your Response: How are you today?
Sender: I am fine, and you?
Your Response: I am fine as well.

Thanks again

Re: Difficulty in implementation

As mentioned we have already imported the header for the class, but I am not sure if its right or not. The following is the code for ClientController.h

#import Foundation/Foundation.h
#import Foundation/NSData.h
#import UIKit/UIKit.h
#import UIKit/UITextField.h

@class AsyncSocket;
@class MTMessageBroker;

@interface ClientController : NSObject {
IBOutlet UITextField *data;
IBOutlet UITextView* textView;
IBOutlet NSString* String;
NSNetService *netService;
AsyncSocket *listeningSocket;
AsyncSocket *connectionSocket;
MTMessageBroker *messageBroker;
}

@property (readwrite, retain) AsyncSocket *listeningSocket;
@property (readwrite, retain) AsyncSocket *connectionSocket;

@end

Please point out the error, if any. The ClientController.m is similar to what has been suggested on your site.

P.S angular brackets have been removed because of the formatting options on the site that do not allow such tags.

Re: Log messages?

Append the new string to the text storage of your text view. Eg.


NSString *string = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSRange theEnd = NSMakeRange([[_errorLogTextView string] length], 0);

[_errorLogTextView replaceCharactersInRange:theEnd withString:string]; // append new string to the end
theEnd.location += [string length]; // the end has moved
[_errorLogTextView scrollRangeToVisible:theEnd];

[string release];

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Disconnecting

Although there is not 'stop' method on the client at this point, you can easily write one. You have to release the message broker and the socket that it is using:


self.messageBroker = nil;
self.socket = nil;

Then start the search over (using search: method).

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Difficulty in Implementation

One thing that looks strange is that you have an instance variable called 'String' (with capital letter). The warning was about 'string' (lower case). Is that the problem?

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Difficulty in implementation

That doesn't seem to be the problem. It says that in the textView.string, the string is in something that is not a structure or union. Is it because I am not importing cocoa.h? I did not import cocoa.h as I am developing a window based application for the iphone. I did not directly use the client project that you have given on your website as it is in C sharp and I am not able to use that to create a window based application. So I just created a new project that is similar to the server, but made it with the changes that you have mentioned in the above post. Please let me know if there a workaround for implementing your code on the iphone.

Re: Difficult in Implementation

I'm not quite sure what you mean by C#; I guess you mean Objective-C 2.0. There is no C# anywhere.

The text view should be in UIKit. If you can't get textView.string to work, just use the accessors directly. Either [textView string], or [textView setString:someString].

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Difficulty in implementation

My bad about the Objective C project. I tried the client project on the MAC OS X and it runs fine. But, the problem arises when I try to build a window based application using the same client project.

I think we cannot use the NSObjects to build an application for the iphone. Could you please suggest a method where I can create a window based application for the iphone? Thanks a lot again.

Re: Difficulty in Implementation

The iPhone doesn't really have window-based apps. Everything is view based. You can't take a Mac app and just run that on iPhone. You need to start a whole new iPhone project, and just bring across the code that is compatible (eg MTMessage, MTMessageBroker).

I suggest reading some of the introductory material that Apple has on their developer.apple.com site. There is too much to explain to do it all here in the comments.

Kind regards,
Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: Difficulty in implementation

Yes, it has view based apps. I have created a new project on Xcode to build a view based application on the iphone. I understand that AsyncSocket,MTMessage and MTMessageBroker are compatible with the iphone application.

I have done all that was required.Now I am getting the following error (can't figure out why)

gcc-4.0 failed with exit code 1

Any help would be appreciated.

Sorry for being a bother. Thanks a lot again.

Calling Methods

Thanks for these fantastic tutorials. This is (almost) exactly what I was looking for. I've used Distributed Objects to call remote methods, but I'm not sure how to extend the code in this tutorial to actually call methods in an app on the Mac from an iPhone. Logging strings is one thing, but how can I extend this to call methods on instances on the server?

Is it possible to use the message tags and a pList to build messages to call methods on the server? For instance, suppose I have a Scripting Bridge app to control Apple's DVD player. I want a button titled Play on the iPhone to target the DVDPlayerController instance on the server and call the play method. It looks like I could change MTMessageBroker so that if the tag of the message is, for example, 10, the message's payload gets sent to the DVDPlayerController instance. Do I have to parse each string there (for instance, a typedef enum for play, pause, stop, rewind, etc. and a switch case)? Or, is there a way to pass the string as the selector? Maybe MTMessageBroker adds a -performSelector:withObject: with the selector and object being passed in an NSDictionary?

Also, how secure are these sockets? Are they susceptible to packet sniffing?

Thanks again.

Re: Methods

There is no direct support for remote messaging. If you only have a few methods to call, you can just pass an integer flag with your data, and use a switch statement to invoke various methods.

I think you could pass the selector as a string (use NSStringFromSelector and NSSelectorFromString methods to convert), but this is not true messaging, because you don't have a handle on the remote object. However, if all messages go to the same server object, this would work fine I think.

Re: security, we haven't done anything special, so I suspect that the connection is not secure at all.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

I was using the tag to

I was using the tag to identify the class I wanted to target and then invoking NSSelectorFromString on the payload and that was meeting my needs.

Now, I've run into an issue where the code works perfectly well on the simulator, but not at all on the device. The server just doesn't see the device at all. Kinda weird.

Blip

I think Blip might be more suitable for my needs:

http://bitbucket.org/snej/mynetwork/wiki/Home

It looks a lot like Distributed Objects, and Jens is ex-Apple.

Re: Blip

Yes, I have heard that Blip is very good. Anyone reading this article, and thinking of using the code, should take a look at Blip.

Although you can use the messaging classes I've written here, they are primitive, and not industrial strength. They are intended more as a teaching aid.

So take a look at Blip. It also works on the iPhone.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Using AsyncSocket class for iPhone with .NET desktop client

Hi,

I'm trying to implement a synchronization between my iPhone & desktop PC. Your tutorial was a great help to understand how things are supposed to work from the iPhone side. I implemented it in my code and it works just great with the mac client (iPhone in my case has a server role), which i downloaded from this page. But how do I stay with AsyncSocket and can get the data, which is being sent from the .NET PC client?

There is a socket delegate, that is supposed to be called when the connection is established:
-(BOOL)onSocketWillConnect:(AsyncSocket *)sock

when i try to connect from .NET it doesn't get there. May be somebody here knows which parameters we should use on the .NET side? I mean type of connection, family and so on?

Any help will be greatly appreciated.

Thanks in advance,

Nava

NSArrayController for iPhone

Hi,

I have been trying to write a simple app for my iPhone, so that it can send messages to my MacBook. It consists of a two buttons and a UITableView. The buttons serve for finding and connecting to found servers. The UITableView should list the found services.

I studied the code posted. In the didFindService method the found service is being added to "servicesController" which is a IBOutlet NSArrayController. Do you know how to implement the NSArrayController on the iPhone?

I attempted to add the found service to a NSMutableArray, but unfortunately it doesn't accept.

Ultimately what I want is to add the found service to the UITableView. Could you tell me how to?

Many Thanks in Advance,

kind regards,
Pei-Yin

Re: NSArrayController for iPhone

The iPhone doesn't support Cocoa bindings, hence the lack of NSArrayController.

You are on the right track: just add an NSMutableArray in your controller class, and add the services to that. Then call 'reloadData' on the UITableView to get it to load the new services.

Hope that helps.

Drew

---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org

Hi Drew, How are you doing?

Hi Drew,

How are you doing? Thanks for your reply. Good to hear that I am on the right track. However your comment about making the "reload Data" call is still acra cadabra for me.

How and where in the code should you implement this call?

I drew a UI TableView in Interface Builder and have the delegates in my header file.
In my implementation file I have the numberOfRowsInSection, cellForRowAtIndexPath and didSelectRowAtIndexPath methods being written out.

Intuition says that that the "reload data" call should be in the "didFindService" method that is being called when the machine has successfully found a server. In this same method I also add aService to my NSMutable array named services. Does the "reloaddata" need to come right after the call: [services addObject:aService];?

What needs to go in front of the reloaddata call. Is it the name of the UItableview?

Thanks for your support in advance.

Kind regards,
Pei-Yin Chao

Reloading Data

You should reload your table view whenever your model data, in this case the mutable array, gets changed. So a good place to do it would be when you add the new service to the array:

[array addObject:newService];
[tableView reloadData];

That's all there is to it.

drew

---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org

tableView undeclared(first use in this function)

Hi Drew,

I have inserted the line [tableView reloadData] at the spot you sugested. However it gives me an error saying

"tableView undeclared(first use in this function)"

How do you declare tableView? When working with tableViews, I usually make one in Interface Builder and assign its delegates to the fileOwner. In my code I then use the delegate methods to draw the tableView and supply the data to it.

In the code I am writing now the tableView is being updated in a custom method called "didFindService".

Sorry to bother you with this many quesitons, I am a real newby to xCode. What would you advice to work around the error I am struggling with right now?

Kind regards,
Pei-Yin

Re: tableView

The code was only meant to be an example, not literal. I assume you have a pointer to your tableView. If not, create an outlet to it in IB, and use that.

If you are using a UITableViewController, you can get to the table view using

self.tableView

Hope that helps.

Drew

---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org

Got it working! Thanks Drew.

Got it working! Thanks Drew.

Send Method issue

Hey Drew,

This tutorial rocks! Thanks so much. I'm new to iphone programming and this has helped me so much. I've got one little problem.

I have created the Client of this application on the iPhone simulator. And when I run the app it gives me this error:
71: error: request for member 'string' in something not a structure or union
Its its refering to the method below,

-(IBAction)send:(id)sender {
NSData *data = [textView.string dataUsingEncoding:NSUTF8StringEncoding];

MTMessage *newMessage = [[[MTMessage alloc] init] autorelease];
newMessage.tag = 100;
newMessage.dataContent = data;
[self.messageBroker sendMessage:newMessage];
}

I'm pretty sure I have rigged up the Interface correctly, so I'm not too sure why it is giving me this error.

Any suggestions would be great,

Thanks.

Ross Symons
Aspiring Cocoa Developer/Dumbass