I'm using cocoa and i'm trying to create a tcp socket server for a program i'm writing, i'm using NSFileHandle and the method acceptConnectionInBackgroundAndNotify. I've been following the code from here.
The problem i'm having is that I can connect my client once but when I try to connect again I get a connection refused error.
Before I connect for the first time the command sudo lsof -i -P returns the following information:
RubyBeat 23964 pauljohnson 8u IPv4 0x0632c274 0t0 TCP *:1234 (LISTEN)
After connecting the first time with the client I get this:
RubyBeat 23964 pauljohnson 5u IPv4 0x06a30334 0t0 TCP localhost:1234->localhost:51579 (CLOSED)
My application doesn't seem to be reopening the socket to listen for connections after the first one comes in and gets a error number 22 when I try to connect a second time.
I'm using the garbage collection in cocoa and was wondering if that would cause problems with sockets?
My code is:
[self createSocket];
}
-(void)createSocket
{
// create socket and wait for events to come in
NSSocketPort* serverSock = [[NSSocketPort alloc] initWithTCPPort: 1234];
socketHandle = [[NSFileHandle alloc] initWithFileDescriptor: [serverSock socket]
closeOnDealloc: NO];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(newConnection:)
name: NSFileHandleConnectionAcceptedNotification
object: socketHandle];
[socketHandle acceptConnectionInBackgroundAndNotify];
}
- (void)newConnection:(NSNotification*)notification
{
NSLog(#"connection accepted");
NSDictionary* userInfo = [notification userInfo];
NSFileHandle* remoteFileHandle = [userInfo objectForKey:
NSFileHandleNotificationFileHandleItem];
if([[userInfo allKeys] containsObject:#"NSFileHandleError"]){
NSNumber* errorNo = [userInfo objectForKey:#"NSFileHandleError"];
if( errorNo ) {
NSLog(#"NSFileHandle Error: %#", errorNo);
return;
}
}
[socketHandle acceptConnectionInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self
selector: #selector(processSocketData:)
name: NSFileHandleReadCompletionNotification
object: remoteFileHandle];
// Send a message to the client, acknowledging that the connection was accepted
[remoteFileHandle writeData: [#"OK" dataUsingEncoding: NSASCIIStringEncoding]];
[remoteFileHandle readInBackgroundAndNotify];
}
/*
Handle client data
*/
- (void)processSocketData:(NSNotification *)note
{
NSData *data = [[note userInfo]
objectForKey:NSFileHandleNotificationDataItem];
NSNumber* errorNo = [[note userInfo] objectForKey:#"NSFileHandleError"];
if( errorNo ) {
NSLog(#"NSFileHandle Error: %#", errorNo);
return;
}
// Do something here with your data
// search string for \n\n that terminates event
NSString* stringData = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding];
NSLog(#"data received: %#", stringData);
NSDictionary* lastEvent = nil;
NSRange range = [stringData rangeOfString: #"\n\n"];
if (range.location != NSNotFound) {
NSArray* subStrings = [stringData componentsSeparatedByString:#"\n\n"];
NSMutableArray* events = [[NSMutableArray alloc] init];
[eventBuffer appendString: [subStrings objectAtIndex: 0]];
// handle first event - could be in parts
NSDictionary * json = (NSDictionary*)[eventBuffer JSONValue];
[events addObject:json];
for(int i = 1; i < [subStrings count]-1; i++){
NSString* subString = [subStrings indexOfObject:i];
NSDictionary * eventJson = (NSDictionary*)[subString JSONValue];
[events addObject:eventJson];
}
// we have at least one event to draw here
for(NSDictionary* event in events){
NSLog(#"event: %#", [event objectForKey:#"type"]);
}
lastEvent = [events lastObject];
// clear eventBuffer
[eventBuffer setString:#""];
// add end of data to eventBuffer?
}else {
[eventBuffer appendString:stringData];
}
// check event if it is a program exit event then stop receiving data
if([[lastEvent objectForKey: #"type"] compare: #"exit"] != NSOrderedSame){
// Tell file handle to continue waiting for data
[[note object] readInBackgroundAndNotify];
}else {
NSLog(#"exit received stopping receiving data");
}
}
I have met this problem before.
Once all data is read, do another "accept connection in background" for another round of reading data.
Related
I have two Cocoa Applications, one is going to be the sender and another the receiver in this XPC relationship.
In the applicationDidFinishLaunching in the sender, I first open the second receiver application
NSError* error = nil;
NSURL* url = [[NSBundle mainBundle] bundleURL];
url = [url URLByAppendingPathComponent:#"Contents" isDirectory:YES];
url = [url URLByAppendingPathComponent:#"MacOS" isDirectory:YES];
url = [url URLByAppendingPathComponent:#"TestXPCHelper.app" isDirectory:YES];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:url
options:NSWorkspaceLaunchWithoutActivation
configuration:[NSDictionary dictionary]
error:&error];
if ( error )
{
NSLog(#"launchApplicationAtURL:%# error = %#", url, error);
[[NSAlert alertWithError:error] runModal];
}
Then I create my NSXPCConnection
assert([NSThread isMainThread]);
if (self.testConnection == nil) {
self.testConnection = [[NSXPCConnection alloc] initWithMachServiceName:NEVER_TRANSLATE(#"com.TechSmith.TestXPCHelper") options:NSXPCConnectionPrivileged];
self.testConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestXPCProtocol)];
self.testConnection.interruptionHandler = ^{
NSLog(#"Connection Terminated");
};
self.testConnection.invalidationHandler = ^{
self.testConnection.invalidationHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.testConnection = nil;
}];
};
[self.testConnection resume];
}
Then I try to send a message over the connection (the connection is already invalidated by here)
id<TestXPCProtocol> testRemoteObject= [self.testConnection remoteObjectProxy];
[testRemoteObject testXPCMethod2];
[[self.testConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError){
NSLog(#"%#", proxyError);
}] testXPCMethod:^(NSString* reply) {
NSLog(#"%#", reply);
}];
And here is the app delegate for my receiver application:
#interface AppDelegate () <NSXPCListenerDelegate, TestXPCProtocol>
#property (weak) IBOutlet NSWindow *window;
#property NSXPCListener *xpcListener;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSLog(#"TESTING123");
self.xpcListener = [[NSXPCListener alloc] initWithMachServiceName:#"com.TechSmith.TestXPCHelper"];
self.xpcListener.delegate = self;
[self.xpcListener resume];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
NSLog(#"ACTIVE234");
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)run
{
NSLog(#"RUNNING");
// Tell the XPC listener to start processing requests.
[self.xpcListener resume];
// Run the run loop forever.
[[NSRunLoop currentRunLoop] run];
}
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
NSLog(#"LISTENING");
assert(listener == self.xpcListener);
#pragma unused(listener)
assert(newConnection != nil);
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestXPCProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}
- (void)testXPCMethod:(void(^)(NSString * version))reply
{
NSLog(#"HEY");
reply(#"REPLY HERE");
}
- (void)testXPCMethod2
{
NSLog(#"TWO!");
}
Here is the proxyError when I try to send a message over the connection:
Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service
named com.TechSmith.TestXPCHelper was invalidated." UserInfo={NSDebugDescription=The
connection to service named com.TechSmith.TestXPCHelper was invalidated.}
So I think I am doing something wrong with my instantiation of the NSXPCConnection. I can't find a good example of two applications speaking to eachother-- it's always one application and a service. Is that what my problem is? I need a service inbetween the applications talking?
Is there any way to get more information on why this connection is being invalidated? That would also help a lot
So pretty straight forward problem here,
Turns out initWithMachServiceName is explicitly looking for a mach service. I was using an identifier of another application process.
If I actually use an identifier of a valid mach service, there is no issue
Note that there are two other ways to create an NSXPCConnection,
with an NSXPCEndpoint or with a XPCService identifier
I know there are many threads about NSManagedObjectContexts and threads but my problem seems to be only specific to iOS7. (Or at least not visible in OS6)
I have an app that makes use of dispatch_queue_ and runs multiple threads to fetch data from the server and update the UI. The app was working fine on iOS6 but on iOS7 it seems to get into deadlocks(mutex wait). See below the stack trace -
The "wait" happens in different methods usually when executing a fetch request and saving a (different) context. The commit Method is as follows :
-(void)commit:(BOOL) shouldUndoIfError forMoc:(NSManagedObjectContext*)moc {
#try {
// shouldUndoIfError = NO;
// get the moc for this thread
NSManagedObjectContext *moc = [self safeManagedObjectContext];
NSThread *thread = [NSThread currentThread];
NSLog(#"got login");
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
NSLog(#"not main thread");
}
NSError *error;
if (![moc save:&error]) {
// fail
NSLog(#"ERROR: SAVE OPERATION FAILED %#", error);
if(shouldUndoIfError) {
[moc undo];
}
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
#catch (NSException *exception) {
NSLog(#"Store commit - %#",exception);
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"name",#"store commit",#"exception", exception.description, nil];
[Flurry logEvent:#"MyException" withParameters:dictionary timed:YES];
}
#finally {
NSLog(#"Store saved");
}
}
How I'm creating new contexts for each thread :
-(NSManagedObjectContext *)safeManagedObjectContext {
#try {
if(self.managedObjectContexts == nil){
NSMutableDictionary *_dict = [[NSMutableDictionary alloc]init];
self.managedObjectContexts = _dict;
[_dict release];
_dict = nil;
}
NSManagedObjectContext *moc = self.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:#"%p", thread];
if ( [self.managedObjectContexts valueForKey:threadKey] == nil) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] retain];
[threadContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
[threadContext setUndoManager:nil];
// cache the context for this thread
[self.managedObjectContexts setObject:threadContext forKey:threadKey];
NSLog(#"added a context to dictionary, length is %d",[self.managedObjectContexts count]);
}
return [self.managedObjectContexts objectForKey:threadKey];
}
#catch (NSException *exception) {
//
}
#finally {
//
}
}
What I have so far :
One Persistent Store coordinator.
Each New thread has its own Managed Object Context.
Strange part is that the same code worked fine on OS6 but not on OS7. I am still using the xcode4.6.3 to compile the code. Most of the code works on this principle, I run a thread, fetch data, commit it and then post a notification. Could the freeze/deadlock be because the notification gets posted and my UI elements fetch the data before the save(&merge) are reflected? Anything else that I'm missing ?
I know its something to do with locks or dispatch groups, but I just cant seem to code it...
I need to know if the address was a valid address before leaving the method. Currently the thread just overruns and returns TRUE. I've tried locks, dispatchers the works but can't seem to get it correct. Any help appreciated:
- (BOOL) checkAddressIsReal
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
}];
return result;
}
The documentation says that CLGeocoder calls the completionHandler on the main thread. Since you are probably also calling your method from the main thread it cannot wait for the geocoder's answer without giving it the opportunity to deliver the result.
That would be done by polling the runloop, using some API as -[NSRunLoop runMode:beforeDate:].
The disadvantage is that depending on the mode this will also deliver events and fire timers while waiting for the result.
Just use block as parameter:
- (void) checkAddressIsRealWithComplectionHandler:(void (^)(BOOL result))complectionHandler
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
complectionHandler(result);
}];
}
I have one basic question,
While working with NSOutputStream, should we wait for NSStreamEventHasSpaceAvailable to send the packet, so we can call , [NSOutputStream write] as and when its needed,
I believe NSStream should take care of write function...
if this is not correct, then please provide your views on following logic,
===== To Write on NSOutputStream =================
Have Queue to add packet, that to be sent
// StreamQueue.h
#interface StreamQueue : NSObject <NSCoding>
{
NSMutableArray * data;
NSRecursiveLock * theLock;
}
#pragma mark �Initialization & Deallocation�
- (id)init;
- (id)initWithQueue:(CommQueue *)queue;
- (id)initWithCoder:(NSCoder *)coder;
- (void)dealloc;
- (void)encodeWithCoder:(NSCoder *)coder;
#pragma mark
#pragma mark �Accessor Methods�
- (int)size;
- (BOOL)isEmpty;
- (id)top;
- (NSArray *)data;
#pragma mark
#pragma mark �Modifier Methods�
- (void)enqueue:(id)object;
- (id)dequeue;
- (void)removeAll;
#end
and its implementation
#import "StreamQueue.h"
#implementation StreamQueue
#pragma mark �Initialization & Deallocation�
- (id)init
{
if (self = [super init]) {
data = [[NSMutableArray alloc] init];
theLock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (id)initWithQueue:(StreamQueue *)queue
{
if (self = [super init]) {
data = [[NSMutableArray alloc] initWithArray:[queue data]];
theLock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super init]) {
data = [[NSMutableArray alloc] initWithArray:[coder decodeObject]];
theLock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (void)dealloc
{
[data release];
[theLock release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:data];
}
#pragma mark
#pragma mark �Accessor Methods�
- (int)size
{
int size;
[theLock lock];
size = [data count];
[theLock unlock];
return size;
}
- (BOOL)isEmpty
{
BOOL empty;
[theLock lock];
empty = ([data count] == 0);
[theLock unlock];
return empty;
}
- (id)top
{
id object = nil;
[theLock lock];
if (![self isEmpty])
object = [data objectAtIndex:0];
[theLock unlock];
return object;
}
- (NSArray *)data
{
NSArray * array;
[theLock lock];
array = [NSArray arrayWithArray:data];
[theLock unlock];
return array;
}
#pragma mark
#pragma mark �Modifier Methods�
- (void)enqueue:(id)object
{
[theLock lock];
[data addObject:object];
[theLock unlock];
}
- (id)dequeue
{
id object = [self top];
if (object != nil) {
[theLock lock];
[object retain];
[data removeObjectAtIndex:0];
[theLock unlock];
}
return [object autorelease];
}
- (void)removeAll
{
[theLock lock];
while (![self isEmpty])
[data removeObjectAtIndex:0];
[theLock unlock];
}
#end
Now when Application have something to send over socket(NSStream), it should add it into the queue,
-(bool)sendRawData:(const uint8_t *)data length:(int)len{
// if still negotiating then don't send data
assert(!networkConnected);
NSData *pData = [NSData dataWithBytes:(const void *)data length:len];
// pToSendPacket is of type StreamQueue
[pToSendPacket enqueue:pData];
return;
}
and this piece of code when we get NSHasSpaceAvailableEvent
-(void)gotSpaceAvailable{
// is there any pending packets that to be send.
NSData *pData = (NSData *)[pToSendPacket dequeue];
if(pData == nil){
// no pending packets..
return;
}
const uint8_t *data = (const uint8_t *)[pData bytes];
int len = [pData length];
int sendlength = [pOutputStream write:data maxLength:len];
if(sendlength == -1 ){
NSError *theError = [pOutputStream streamError];
NSString *pString = [theError localizedDescription];
int errorCode = [theError code];
return ;
}
}
I was expecting Application will keep on receiving the event, whenever OutputStream sends data, but i recieved only once... :(
Please help ...
If you don't wait for the event, the write call will block until space is available. Generally you want to aim to design your code to work asychronously, so waiting for NSStreamEventHasSpaceAvailable is the best solution.
As for when you receive the space available notification, see the documentation here:
If the delegate receives an NSStreamEventHasSpaceAvailable event and
does not write anything to the stream, it does not receive further
space-available events from the run loop until the NSOutputStream
object receives more bytes. When this happens, the run loop is
restarted for space-available events. If this scenario is likely in
your implementation, you can have the delegate set a flag when it
doesn’t write to the stream upon receiving an
NSStreamEventHasSpaceAvailable event. Later, when your program has
more bytes to write, it can check this flag and, if set, write to the
output-stream instance directly.
There is no firm guideline on how many bytes to write at one time.
Although it may be possible to write all the data to the stream in one
event, this depends on external factors, such as the behavior of the
kernel and device and socket characteristics. The best approach is to
use some reasonable buffer size, such as 512 bytes, one kilobyte (as
in the example above), or a page size (four kilobytes).
So you should be getting regular NSStreamEventHasSpaceAvailable events as long as you do write data for each event.
I'm just trying to close an NSPanel after a couple second delay, but I can't get my NSTimer to start. It will fire if I explicitly call the fire method on it, but it will never go by itself. Here's my code:
- (void)startRemoveProgressTimer:(NSNotification *)notification {
NSLog(#"timer should start");
timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(removeProgress:) userInfo:nil repeats:NO];
}
- (void)removeProgress:(NSTimer *)timer {
[progressPanel close];
}
I do have some threading in my code as such. I assume this is what's messing my timer up.
-(void)incomingTextUpdateThread:(NSThread*)parentThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//mark the thread as running
readThreadRunning = TRUE;
const int BUFFER_SIZE = 100;
char byte_buffer[BUFFER_SIZE]; //buffer for holding incoming data
int numBytes = 0; //number of bytes read
NSString *text; //incoming text from the serial port
[NSThread setThreadPriority:1.0];
//this will loop until the serial port closes
while (TRUE) {
//read() blocks until some data is available or the port is closed
numBytes = read(serialFileDescriptor, byte_buffer, BUFFER_SIZE);
if(numBytes > 0) {
//creat a string from the incoming bytes
text = [[[NSString alloc] initWithBytes:byte_buffer length:numBytes encoding:[NSString defaultCStringEncoding]] autorelease];
if(!([text rangeOfString:SEND_NEXT_COORDINATE].location == NSNotFound)) {
//look for <next> to see if the next data should be sent
if(coordinateNum <[coordinatesArray count]) {
[self sendNextCoordinate]; //send coordinates
}
else {
[self writeString:FINISH_COORDINATES_TRANSMIT]; //send <end> to mark transmission as complete
NSNumber *total = [NSNumber numberWithUnsignedInteger:[coordinatesArray count]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:total forKey:#"progress"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"uploadProgressChange" object:self userInfo:userInfo]; //update progress bar to completed
}
}
[self performSelectorOnMainThread:#selector(appendToIncomingText:) withObject:text waitUntilDone:YES]; //write incoming text to NSTextView
} else {
break; //Stop the thread if there is an error
}
}
// make sure the serial port is closed
if (serialFileDescriptor != -1) {
close(serialFileDescriptor);
serialFileDescriptor = -1;
}
// mark that the thread has quit
readThreadRunning = FALSE;
// give back the pool
[pool release];
}
Which is called from another method by: [self performSelectorInBackground:#selector(incomingTextUpdateThread:) withObject:[NSThread currentThread]];
Thank you rgeorge!!
Adding the timer to the run loop manually made it work!
timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:#selector(removeProgress:) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];