axmol/samples/Javascript/CocosPlayer/Classes/iOS/ThoMoNetworking/ThoMoNetworkStub.m

444 lines
14 KiB
Objective-C

/*
* ThoMoNetworkStub.m
* ThoMoNetworkingFramework
*
* Created by Thorsten Karrer on 2.7.09.
* Copyright 2010 media computing group - RWTH Aachen University.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#import "ThoMoNetworkStub.h"
#import <arpa/inet.h>
#import "ThoMoTCPConnection.h"
#import <pthread.h>
NSString *const kThoMoNetworkInfoKeyUserMessage = @"kThoMoNetworkInfoKeyUserMessage";
NSString *const kThoMoNetworkInfoKeyData = @"kThoMoNetworkInfoKeyData";
NSString *const kThoMoNetworkInfoKeyRemoteConnectionIdString = @"kThoMoNetworkInfoKeyRemoteConnectionIdString";
NSString *const kThoMoNetworkInfoKeyLocalNetworkStub = @"kThoMoNetworkInfoKeyLocalNetworkStub";
NSString *const kThoMoNetworkPrefScopeSpecifierKey = @"kThoMoNetworkPrefScopeSpecifierKey";
// interface category for main thread relay methods
@interface ThoMoNetworkStub (RelayMethods)
-(void)netServiceProblemRelayMethod:(NSDictionary *)infoDict;
-(void)didReceiveDataRelayMethod:(NSDictionary *)infoDict;
-(void)connectionEstablishedRelayMethod:(NSDictionary *)infoDict;
-(void)connectionLostRelayMethod:(NSDictionary *)infoDict;
-(void)connectionClosedRelayMethod:(NSDictionary *)infoDict;
@end
@interface ThoMoNetworkStub ()
-(void)sendDataWithInfoDict:(NSDictionary *)theInfoDict;
@end
// =====================================================================================================================
#pragma mark -
#pragma mark Public Methods
// ---------------------------------------------------------------------------------------------------------------------
@implementation ThoMoNetworkStub
#pragma mark Housekeeping
-(id)initWithProtocolIdentifier:(NSString *)theProtocolIdentifier;
{
self = [super init];
if (self != nil)
{
// check if there is a scope specifier present in the user defaults and add it to the protocolId
NSString *scopeSpecifier = [[NSUserDefaults standardUserDefaults] stringForKey:kThoMoNetworkPrefScopeSpecifierKey];
if (scopeSpecifier)
{
protocolIdentifier = [[scopeSpecifier stringByAppendingFormat:@"-%@", theProtocolIdentifier] retain];
NSLog(@"Warning: ThoMo Networking Protocol Prefix in effect! If your app cannot connect to its counterpart that may be the reason.");
}
else
{
protocolIdentifier = [theProtocolIdentifier copy];
}
if ([protocolIdentifier length] > 14)
{
// clean up internally
[NSException raise:@"ThoMoInvalidArgumentException"
format:@"The protocol identifier plus the optional scoping prefix (\"%@\") exceed"
" Bonjour's maximum allowed length of fourteen characters!", protocolIdentifier];
}
connections = [[NSMutableDictionary alloc] init];
}
return self;
}
-(id)init;
{
return [self initWithProtocolIdentifier:@"thoMoNetworkStubDefaultIdentifier"];
}
// since the networkThread retains our object while it executes, this method will be called after the thread is done
- (void) dealloc
{
[connections release];
[protocolIdentifier release];
[networkThread release];
[super dealloc];
}
// these methods are called on the main thread
#pragma mark Control
-(void) start;
{
if ([networkThread isExecuting])
{
[NSException raise:@"ThoMoStubAlreadyRunningException"
format:@"The client stub had already been started before and cannot be started twice."];
}
// TODO: check if we cannot run a start-stop-start cycle
NSAssert(networkThread == nil, @"Network thread not released properly");
networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkThreadEntry) object:nil];
[networkThread start];
}
-(void) stop;
{
[networkThread cancel];
// TODO: check if we can release the networkthread here
}
-(NSArray *)activeConnections;
{
NSArray *result;
@synchronized(self)
{
result = [[connections allKeys] copy];
}
return [result autorelease];
}
-(void)send:(id<NSCoding>)theData toConnection:(NSString *)theConnectionIdString;
{
// NSData *sendData = [NSKeyedArchiver archivedDataWithRootObject:theData];
NSString *errorStr;
NSData *sendData = [NSPropertyListSerialization dataWithPropertyList:theData format:NSPropertyListBinaryFormat_v1_0 options:NULL error:&errorStr];
[self sendByteData:sendData toConnection:theConnectionIdString];
}
-(void)sendByteData:(NSData *)sendData toConnection:(NSString *)theConnectionIdString;
{
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
sendData, @"DATA",
theConnectionIdString, @"ID",
nil];
[self performSelector:@selector(sendDataWithInfoDict:) onThread:networkThread withObject:infoDict waitUntilDone:NO];
}
#pragma mark Send Data Relay
// only call on network thread
-(void)sendDataWithInfoDict:(NSDictionary *)theInfoDict;
{
NSData *sendData = [theInfoDict objectForKey:@"DATA"];
NSString *theConnectionIdString = [theInfoDict objectForKey:@"ID"];
ThoMoTCPConnection *connection = nil;
@synchronized(self)
{
connection = [[connections valueForKey:theConnectionIdString] retain];
}
if (!connection)
{
[NSException raise:@"ThoMoInvalidArgumentException"
format:@"No connection found for id %@", theConnectionIdString];
}
else
{
[connection enqueueNextSendObject:sendData];
}
[connection release];
}
#pragma mark Threading Methods
-(void)networkThreadEntry
{
#ifdef DEBUG
#ifdef THOMO_PTHREAD_NAMING_AVAILABLE
pthread_setname_np("ThoMoNetworking Dispatch Thread");
#endif
#endif
NSAutoreleasePool *networkThreadPool = [[NSAutoreleasePool alloc] init];
if([self setup])
{
while (![networkThread isCancelled])
{
NSDate *inOneSecond = [[NSDate alloc] initWithTimeIntervalSinceNow:1];
// catch exceptions and propagate to main thread
@try {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:inOneSecond];
}
@catch (NSException * e) {
[e performSelectorOnMainThread:@selector(raise) withObject:nil waitUntilDone:YES];
}
[inOneSecond release];
}
[self teardown];
}
[self performSelectorOnMainThread:@selector(networkStubDidShutDownRelayMethod) withObject:nil waitUntilDone:NO];
[networkThreadPool release];
}
#pragma mark -
#pragma mark Connection Delegate Methods
/// Delegate method that gets called from ThoMoTCPConnections whenever they did receive data.
/**
Takes the received data and relays it to a method on the main thread.
This method is typically overridden in the subclasses of ThoMoNetworkStub and then directly called from there.
\param[in] theData reference to the received data
\param[in] theConnection reference to the connection that received the data
*/
-(void)didReceiveData:(NSData *)theData onConnection:(ThoMoTCPConnection *)theConnection;
{
// look up the connection
NSString *connectionKey = [self keyForConnection:theConnection];
// unarchive the data
//id receivedData = [NSKeyedUnarchiver unarchiveObjectWithData:theData];
NSString *errorStr;
NSDictionary *receivedData = [NSPropertyListSerialization propertyListWithData:theData options:NULL format:NULL error:&errorStr];
NSLog(@"DICT: %@", [receivedData description]);
// package the parameters into an info dict and relay them to the main thread
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
connectionKey, kThoMoNetworkInfoKeyRemoteConnectionIdString,
self, kThoMoNetworkInfoKeyLocalNetworkStub,
receivedData, kThoMoNetworkInfoKeyData,
nil];
[self performSelectorOnMainThread:@selector(didReceiveDataRelayMethod:) withObject:infoDict waitUntilDone:NO];
}
-(void)streamsDidOpenOnConnection:(ThoMoTCPConnection *)theConnection;
{
// look up the connection
NSString *connectionKey = [self keyForConnection:theConnection];
// package the parameters into an info dict and relay them to the main thread
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
self, kThoMoNetworkInfoKeyLocalNetworkStub,
connectionKey, kThoMoNetworkInfoKeyRemoteConnectionIdString,
nil];
[self performSelectorOnMainThread:@selector(connectionEstablishedRelayMethod:) withObject:infoDict waitUntilDone:NO];
}
-(void)streamEndEncountered:(NSStream *)theStream onConnection:(ThoMoTCPConnection *)theConnection;
{
NSString *connectionKey = [self keyForConnection:theConnection];
NSString *userMessage = [NSString stringWithFormat:@"Stream end event encountered on stream to %@! Closing connection.", connectionKey];
[theConnection close];
@synchronized(self)
{
[connections removeObjectForKey:connectionKey];
}
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
self, kThoMoNetworkInfoKeyLocalNetworkStub,
connectionKey, kThoMoNetworkInfoKeyRemoteConnectionIdString,
userMessage, kThoMoNetworkInfoKeyUserMessage,
nil];
[self performSelectorOnMainThread:@selector(connectionClosedRelayMethod:) withObject:infoDict waitUntilDone:NO];
}
-(void)streamErrorEncountered:(NSStream *)theStream onConnection:(ThoMoTCPConnection *)theConnection;
{
NSString *connectionKey = [self keyForConnection:theConnection];
NSError *theError = [theStream streamError];
NSString *userMessage = [NSString stringWithFormat:@"Error %i: \"%@\" on stream to %@! Terminating connection.", (int)[theError code], [theError localizedDescription], connectionKey];
[theConnection close];
@synchronized(self)
{
[connections removeObjectForKey:connectionKey];
}
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
self, kThoMoNetworkInfoKeyLocalNetworkStub,
connectionKey, kThoMoNetworkInfoKeyRemoteConnectionIdString,
userMessage, kThoMoNetworkInfoKeyUserMessage,
nil];
[self performSelectorOnMainThread:@selector(connectionLostRelayMethod:) withObject:infoDict waitUntilDone:NO];
}
// =====================================================================================================================
#pragma mark -
#pragma mark Protected Methods
// ---------------------------------------------------------------------------------------------------------------------
-(BOOL)setup
{
return YES;
}
-(void)teardown
{
// close all open connections
@synchronized(self)
{
for (ThoMoTCPConnection *connection in [connections allValues]) {
[connection close];
}
[connections removeAllObjects];
}
}
-(NSString *)keyStringFromAddress:(NSData *)addr;
{
// get the peer socket address from the NSData object
// NOTE: there actually is a struct sockaddr in there, NOT a struct sockaddr_in!
// I heard from beej (<http://www.retran.com/beej/sockaddr_inman.html>) that they share the same 15 first bytes so casting should not be a problem.
// You've been warned, though...
struct sockaddr_in *peerSocketAddress = (struct sockaddr_in *)[addr bytes];
// convert in_addr to ascii (note: returns a pointer to a statically allocated buffer inside inet_ntoa! calling again will overwrite)
char *humanReadableAddress = inet_ntoa(peerSocketAddress->sin_addr);
NSString *peerAddress = [NSString stringWithCString:humanReadableAddress encoding:NSUTF8StringEncoding];
NSString *peerPort = [NSString stringWithFormat:@"%d", ntohs(peerSocketAddress->sin_port)];
NSString *peerKey = [NSString stringWithFormat:@"%@:%@", peerAddress, peerPort];
return peerKey;
}
-(NSString *)keyForConnection:(ThoMoTCPConnection *)theConnection;
{
NSString *connectionKey;
NSArray *keys;
@synchronized(self)
{
keys = [connections allKeysForObject:theConnection];
NSAssert([keys count] == 1, @"More than one connection record in dict for a single connection.");
connectionKey = [[keys objectAtIndex:0] copy];
}
return [connectionKey autorelease];
}
-(void) openNewConnection:(NSString *)theConnectionKey inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr;
{
// create a new ThoMoTCPConnection object and set ourselves as the delegate to forward the incoming data to our own delegate
ThoMoTCPConnection *newConnection = [[ThoMoTCPConnection alloc] initWithDelegate:self inputStream:istr outputStream:ostr];
// store in our dictionary, open, and release our copy
@synchronized(self)
{
// it should never happen that we overwrite a connection
NSAssert(![connections valueForKey:theConnectionKey], @"ERROR: Tried to create a connection with an IP and port that we already have a connection for.");
[connections setValue:newConnection forKey:theConnectionKey];
}
[newConnection open];
[newConnection release];
}
@end
#pragma mark -
#pragma mark Main Thread Relay Methods
@implementation ThoMoNetworkStub (RelayMethods)
-(void)networkStubDidShutDownRelayMethod
{
NSLog(@"%@ :: %@", [self description], NSStringFromSelector(_cmd));
}
-(void)netServiceProblemRelayMethod:(NSDictionary *)infoDict
{
NSLog(@"%@ :: %@", [self description], NSStringFromSelector(_cmd));
}
-(void)didReceiveDataRelayMethod:(NSDictionary *)infoDict;
{
NSLog(@"%@ :: %@", [self description], NSStringFromSelector(_cmd));
}
-(void)connectionEstablishedRelayMethod:(NSDictionary *)infoDict;
{
NSLog(@"%@ :: %@", [self description], NSStringFromSelector(_cmd));
}
-(void)connectionLostRelayMethod:(NSDictionary *)infoDict;
{
NSLog(@"%@ :: %@", [self description], NSStringFromSelector(_cmd));
}
-(void)connectionClosedRelayMethod:(NSDictionary *)infoDict;
{
NSLog(@"%@ :: %@", [self description], NSStringFromSelector(_cmd));
}
#pragma mark -
#pragma mark Debugging
@end