PDA

View Full Version : Cocoa async socket message problem




Samppaa
Sep 4, 2010, 10:54 AM
Hey I started using cocoa sockets sometime ago, so here is the code I got now, I am using the echo server example as server which was provided with the class

Here is server .m file


#import "AppController.h"
#import "AsyncSocket.h"

#define WELCOME_MSG 0
#define ECHO_MSG 1

#define FORMAT(format, ...) [NSString stringWithFormat:(format), ##__VA_ARGS__]

@interface AppController (PrivateAPI)
- (void)logError:(NSString *)msg;
- (void)logInfo:(NSString *)msg;
- (void)logMessage:(NSString *)msg;
@end


@implementation AppController

- (id)init
{
if(self = [super init])
{
listenSocket = [[AsyncSocket alloc] initWithDelegate:self];
connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];

isRunning = NO;
}
return self;
}

- (void)awakeFromNib
{
[logView setString:@""];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(@"Ready");

// Advanced options - enable the socket to contine operations even during modal dialogs, and menu browsing
[listenSocket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}

- (void)scrollToBottom
{
NSScrollView *scrollView = [logView enclosingScrollView];
NSPoint newScrollOrigin;

if ([[scrollView documentView] isFlipped])
newScrollOrigin = NSMakePoint(0.0, NSMaxY([[scrollView documentView] frame]));
else
newScrollOrigin = NSMakePoint(0.0, 0.0);

[[scrollView documentView] scrollPoint:newScrollOrigin];
}

- (void)logError:(NSString *)msg
{
NSString *paragraph = [NSString stringWithFormat:@"%@\n", msg];

NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1];
[attributes setObject:[NSColor redColor] forKey:NSForegroundColorAttributeName];

NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes];
[as autorelease];

[[logView textStorage] appendAttributedString:as];
[self scrollToBottom];
}

- (void)logInfo:(NSString *)msg
{
NSString *paragraph = [NSString stringWithFormat:@"%@\n", msg];

NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1];
[attributes setObject:[NSColor purpleColor] forKey:NSForegroundColorAttributeName];

NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes];
[as autorelease];

[[logView textStorage] appendAttributedString:as];
[self scrollToBottom];
}

- (void)logMessage:(NSString *)msg
{
NSString *paragraph = [NSString stringWithFormat:@"%@\n", msg];

NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1];
[attributes setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];

NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes];
[as autorelease];

[[logView textStorage] appendAttributedString:as];
[self scrollToBottom];
}

- (IBAction)startStop:(id)sender
{
if(!isRunning)
{
int port = [portField intValue];

if(port < 0 || port > 65535)
{
port = 0;
}

NSError *error = nil;
if(![listenSocket acceptOnPort:port error:&error])
{
[self logError:FORMAT(@"Error starting server: %@", error)];
return;
}

[self logInfo:FORMAT(@"Echo server started on port %hu", [listenSocket localPort])];
isRunning = YES;

[portField setEnabled:NO];
[startStopButton setTitle:@"Stop"];
}
else
{
// Stop accepting connections
[listenSocket disconnect];

// Stop any client connections
int i;
for(i = 0; i < [connectedSockets count]; i++)
{
// Call disconnect on the socket,
// which will invoke the onSocketDidDisconnect: method,
// which will remove the socket from the list.
[[connectedSockets objectAtIndex:i] disconnect];
}

[self logInfo:@"Stopped Echo server"];
isRunning = false;

[portField setEnabled:YES];
[startStopButton setTitle:@"Start"];
}
}

- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
{
[connectedSockets addObject:newSocket];
}

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
[self logInfo:FORMAT(@"Accepted client %@:%hu", host, port)];

NSString *welcomeMsg = @"Welcome to server";
NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];

[sock writeData:welcomeData withTimeout:-1 tag:WELCOME_MSG];

// We could call readDataToData:withTimeout:tag: here - that would be perfectly fine.
// If we did this, we'd want to add a check in onSocket:didWriteDataWithTag: and only
// queue another read if tag != WELCOME_MSG.
}

- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
[sock readDataWithTimeout:-1 tag:0];
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] )];
NSString *msg = [[[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding] autorelease];
if(msg)
{
[self logMessage:msg];
[sock writeData:strData withTimeout:-1 tag:0];
}
else
{
[self logError:@"Error converting received data into UTF-8 String"];
}

// Even if we were unable to write the incoming data to the log,
// we're still going to echo it back to the client.
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
}

- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
[self logInfo:FORMAT(@"Client Disconnected: %@:%hu", [sock connectedHost], [sock connectedPort])];
}

- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
[connectedSockets removeObject:sock];
}

@end


Here is the .m file of my client program


//
// CheckerHandler.m
// PTZ place checker - OS X
//
// Created by Samuli Lehtonen on 3.9.2010.
// Copyright 2010 Test. All rights reserved.
//

#import "CheckerHandler.h"
#import "AsyncSocket.h"


@implementation CheckerHandler

#pragma mark Basicstuff

-(id)init
{
[super init];
connectSocket = [[AsyncSocket alloc] initWithDelegate:self];
[connectSocket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
updateAvailable = FALSE;
NSLog(@"Retain count of Checker: %i", [self retainCount]);
return self;
}

-(void)dealloc
{
[connectSocket release];
[super dealloc];

}

#pragma mark Functions for the app

-(bool)openConnection:(NSString*)IP:(int)port
{
NSError * error = nil;
if(![connectSocket connectToHost:IP onPort:port error:&error]){
return false;
}
else {
return true;
}


}

-(void)checkForUpdates
{
NSString * version = [[NSString alloc] initWithString:@"REQUPV2"];
NSData * data = [version dataUsingEncoding:NSUTF8StringEncoding];
[connectSocket writeData:data withTimeout:-1 tag:0];

}

-(bool)isUpdateAvailable
{
return updateAvailable;
}

- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
[sock readDataWithTimeout:-1 tag:0];
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] )];
NSString *msg = [[[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding] autorelease];
if(msg)
{
NSLog(@"Message is: %@", msg);

}
else
{

}

}



@end


So what I am trying to do is, send a message from the server to client when the server receives the message, server gets the message, but client won't get the message that server is sending somehow. Only message client gets is the welcome message :S, I don't understand why it won't get the other messages, I've really struggled with this now, thanks in advance guys!



chown33
Sep 4, 2010, 02:10 PM
Post the code for CheckerHandler.h.

Post the code that creates a CheckerHandler instance. We need to see the main() or whatever that runs the client.

Considering the number of omissions, you should probably make a zip of all the necessary source files, rather than pasting them in as text in a post.


The EchoServer example only echos what the client writes. If the client doesn't write anything, then the server doesn't echo anything.

Also, after doing a diff between your posted AppController.m and the original, the following in your code looks questionable to me:
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] )];
NSString *msg = [[[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding] autorelease];
if(msg)
{
[self logMessage:msg];
[sock writeData:strData withTimeout:-1 tag:0];
}
else
{
[self logError:@"Error converting received data into UTF-8 String"];
}

// Even if we were unable to write the incoming data to the log,
// we're still going to echo it back to the client.
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
}

Why would you write data twice?

Are you sure you understand what the original EchoServer is doing? Modifying code that isn't completely understood is a recipe for disaster. I suggest that you study the original very carefully, and postpone any modifications. Careful study means setting breakpoints or adding logging so you can see every significant point.


From what little I can see in CheckerHandler, you're only sending one message to the server. When the server echos that message, you don't send anything else. Unless and until the client sends something else, the server won't echo anything.

You appear to have a logic problem, either in the code or in your expectation. This is resulting in a logical deadlock: you are expecting the server to send something, and the server is expecting you to send something.


I'm also wondering why you want to write a complete server and protocol. The checkForUpdates method in CheckerHandler appears to only check for an update. That's something that HTTP can easily accomplish using a very simple REST design. There is even an entire library that performs the update-checks, download, and app-update. It's called Sparkle:
http://en.wikipedia.org/wiki/Sparkle_(software)

Before going any further, you should probably explain what you're trying to accomplish. There may be a far simpler way of accomplishing it than messing around with AsyncSocket.

Samppaa
Sep 4, 2010, 03:16 PM
I think I will use sparkle for the update thing then, but is there a feature in sparkle that I can make so that user must update the program before using it again? Also my program needs to ask for one thing every second from the server, so I should use HTTP option?

chown33
Sep 4, 2010, 05:20 PM
I think I will use sparkle for the update thing then, but is there a feature in sparkle that I can make so that user must update the program before using it again? Also my program needs to ask for one thing every second from the server, so I should use HTTP option?

Please explain more completely what you're trying to accomplish.

Sparkle is not going to help you write single-use apps. That seems like a weird thing to do with a locally resident app. What are you trying to accomplish with a single-use app?

Why is it necessary to download and update the entire app, rather than simply issuing a single-use token, like a cryptographic ticket?

What are you asking for once a second? Whose server are you asking it from?

Samppaa
Sep 5, 2010, 05:28 AM
I am asking from my server every one second if one thing has changed. Got sparkle running now, it's amazing, but I basically I need to be able to ensure that the user has the latest version or he can't connect to server, you recommend me to use those HTTP things?

1.To be exact my server is going to check one page and see if it has changed, if it has changed it changes one variable.
2.Clients send packet to server asking for the variable and by using that to determine what to display.

What should I use for that?

chown33
Sep 5, 2010, 01:03 PM
I am asking from my server every one second if one thing has changed. Got sparkle running now, it's amazing, but I basically I need to be able to ensure that the user has the latest version or he can't connect to server, you recommend me to use those HTTP things?

I don't understand the question. Please describe the series of events or actions you want to happen in more detail.


1.To be exact my server is going to check one page and see if it has changed, if it has changed it changes one variable.
2.Clients send packet to server asking for the variable and by using that to determine what to display.

What should I use for that?

What protocol is your server running? How do the clients connect to it?

HTTP has an If-Modified-Since header that is optional in client requests. If the resource is unmodified since the requested date, the server responds with no resource. If the resource is modified since the date, the server sends the resource.

See heading 14.25 If-Modified-Since:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

Samppaa
Sep 5, 2010, 01:19 PM
I think I end up to make a php script which checks the page and my client checks it... Simplest way I can think off, anyways I don't have any server yet.

chown33
Sep 5, 2010, 01:43 PM
I think I end up to make a php script which checks the page and my client checks it... Simplest way I can think off, anyways I don't have any server yet.

What protocol do you intend your server to run?
Will it be HTTP? FTP? HTTPS? A custom protocol you write yourself?
You're not providing enough details. Without details, suggestions can only be general.

If you're running a php script once a second, for a large number of clients, you could have performance problems.

Samppaa
Sep 5, 2010, 02:45 PM
I guess I will use asyncsockets and write my own system with them.

Samppaa
Sep 6, 2010, 03:01 PM
I'll use asyncsockets, but can someone lighten me up, that do I always have to send a packet in order to get response? As I need to send a packet before even getting the welcome message, which is pretty strange.