I'm having a hard time finding good examples on how to use these functions.
static void * kQueue1Key = "key1";
static void * kQueue2Key = "key2";
dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, kQueue1Key, (void *)kQueue1Key, NULL);
dispatch_queue_set_specific(queue2, kQueue2Key, (void *)kQueue2Key, NULL);
dispatch_sync(queue1, ^{
if(dispatch_get_specific(kQueue1Key))
{
NSLog(#"I'm expecting this line to run (A)");
dispatch_sync(queue2, ^{
NSLog(#"I'm expecting this line to run (B)");
if(dispatch_get_specific(kQueue2Key))
{
if(dispatch_get_specific(kQueue1Key))
{
NSLog(#"I'm expecting this line to run (C)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (B)"];
}
});
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (A)"];
}
});
Result
I'm expecting this line to run (A)
I'm expecting this line to run (B)
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Should not end up here (C)'
Is it expected behavior? If I were to dispatch_sync to queue1 since I'm not on the queue I would deadlock. What am I missing?
Oh here, it popped into my head why you're getting what you're getting. Notes in line:
dispatch_sync(queue1, ^{
When you get to this point, the "current queue" is queue1
if(dispatch_get_specific(kQueue1Key))
You're asking the current queue for the value it has for kQueue1Key, you set that earlier, so it gives it back to you.
{
NSLog(#"I'm expecting this line to run (A)");
dispatch_sync(queue2, ^{
When you get to this point, the "current queue" is now queue2
NSLog(#"I'm expecting this line to run (B)");
if(dispatch_get_specific(kQueue2Key))
You're asking the current queue for the value it has for kQueue2Key, you set that earlier, so it gives it back to you.
{
if(dispatch_get_specific(kQueue1Key))
You're now asking the current queue for the value it has for kQueue1Key. Since the current queue is queue2 and you never set a value with kQueue1Key on queue2 you get back NULL.
{
NSLog(#"I'm expecting this line to run (C)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
The misunderstanding here is that dispatch_get_specific doesn't traverse the stack of nested queues, it traverses the queue targeting lineage. For instance, if you did this instead,
static void * kQueue1Key = (void*)"key1";
static void * kQueue2Key = (void*)"key2";
dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, kQueue1Key, (void *)kQueue1Key, NULL);
dispatch_queue_set_specific(queue2, kQueue2Key, (void *)kQueue2Key, NULL);
// Set Queue2 to target Queue1
dispatch_set_target_queue(queue2, queue1);
dispatch_sync(queue2, ^{
if(dispatch_get_specific(kQueue1Key))
{
NSLog(#"I'm expecting this line to run (A)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
if(dispatch_get_specific(kQueue2Key))
{
NSLog(#"I'm expecting this line to run (B)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
});
...the targeting relationship is the one that gets traversed, not the stack relationship. It would be nice if there were something that traversed the stack relationship, but I'm not aware of anything (that you wouldn't have to implement yourself).
As mentioned in my comment, recursive locking using dispatch_sync is, in the general case, not possible due to the possibility of non-default queue targeting. For what it's worth, given/assuming default queue targeting, here is one possible approach:
#import <unordered_set>
#import <pthread.h>
static dispatch_once_t recursiveLockWithDispatchQueueTLSKeyOnceToken;
static pthread_key_t recursiveLockWithDispatchQueueTLSKey;
typedef std::unordered_multiset<const void*> RecursiveLockQueueBag;
static void freeRecursiveLockWithDispatchQueueTLSValue(void* tlsValue)
{
RecursiveLockQueueBag* ms = reinterpret_cast<RecursiveLockQueueBag*>(tlsValue);
if (ms) delete ms;
}
static inline BOOL queueStackCheck(dispatch_queue_t q, BOOL checkAndPushNotPop) // If yes, check and push if not on. If no, pop.
{
dispatch_once(&recursiveLockWithDispatchQueueTLSKeyOnceToken, ^{
pthread_key_create(&recursiveLockWithDispatchQueueTLSKey, freeRecursiveLockWithDispatchQueueTLSValue);
});
RecursiveLockQueueBag* ms = reinterpret_cast<RecursiveLockQueueBag*>(pthread_getspecific(recursiveLockWithDispatchQueueTLSKey));
if (!ms)
{
ms = new RecursiveLockQueueBag();
pthread_setspecific(recursiveLockWithDispatchQueueTLSKey, reinterpret_cast<const void*>(ms));
}
const void* const vpq = reinterpret_cast<const void*>((__bridge const void*)q);
BOOL alreadyOn = NO;
if (checkAndPushNotPop)
{
alreadyOn = (ms->count(vpq) > 0);
if (!alreadyOn)
{
ms->insert(vpq);
}
}
else
{
ms->erase(vpq);
}
return alreadyOn;
}
void dispatch_recursive_sync(dispatch_queue_t queue, dispatch_block_t block)
{
if (queueStackCheck(queue, YES))
{
block();
}
else
{
#try
{
dispatch_sync(queue, block);
}
#finally
{
queueStackCheck(queue, NO);
}
}
}
#implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
dispatch_queue_t a = dispatch_queue_create("a", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t b = dispatch_queue_create("b", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t c = dispatch_queue_create("c", DISPATCH_QUEUE_SERIAL);
//dispatch_set_target_queue(a, c);
dispatch_recursive_sync(a, ^{
dispatch_recursive_sync(b, ^{
dispatch_recursive_sync(c, ^{
dispatch_recursive_sync(a, ^{
dispatch_recursive_sync(b, ^{
dispatch_recursive_sync(c, ^{
dispatch_recursive_sync(a, ^{
NSLog(#"got there");
});
});
});
});
});
});
});
}
#end
This is the lowest-overhead implementation I could think of in a few minutes. I used C++ to avoid message sending overhead. It requires that all uses of the queue use this function. This can be useful when there's a private queue protecting internal state of an object (i.e. where the queue is private and therefore guaranteed not to be retargeted, and where you can easily ensure that all consumers of the queue use dispatch_recursive_sync.
Related
I have a method from a class under tests which takes two delegates: the second delegate will call a method after the first delegate will be called with a callback function as input.
#implemenation ClassUnderTest
...
- (void) methodWithMultipleCallbacks: (id<MyDelegate>) delegate
withSecondDelegate: (id<MyDelegateWithCallback>) delegateWithCallback {
for (int i = 0; i < 2; i++){
Callback callback = ^(int input) {
NSLog(#"%d-th callback", i);
NSLog(#"input = %d", input);
};
[delegateWithCallback fetchInt:callback];
}
[delegate delegateDoStuff:32];
}
But the strange thing happens: I tried to test it using OCMock, mocking both delegates, but the crashes, and I got a EXEC_BAD_ACCESS.
I am utterly confused and would really appreciate any help here! Here's the test function
- (void) testWithMultipleCallbacks {
id <MyDelegateWithCallback> mockDelegateWithCallback = OCMProtocolMock(#protocol(MyDelegateWithCallback));
OCMStub([mockDelegateWithCallback fetchInt:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
void (^block)(int) = NULL;
[invocation getArgument:&block atIndex:2];
NSLog(#"got here");
block(33);
});
[_classUnderTest methodWithMultipleCallbacks: _mockedDelegate withSecondDelegate: mockDelegateWithCallback];
OCMVerify(OCMTimes(1), [_mockedDelegate delegateDoStuff:[OCMArg any]]);
}
These two function calls seem to be conflicting:
MagicalRecord.save({ (localContext) in
let items = NewsItem.staleNewsItems(in: localContext)
if ((items?.count)! > 0){
items?.forEach({ (item) in
if let object = item as? NSManagedObject {
object.mr_deleteEntity(in: localContext)
}
})
}
})
and
- (void) buildAndFetchFRCsInContext:(NSManagedObjectContext*)context {
self.newsItemsFRC = [self buildFetchResultsControllerForClass:[NewsItem class] sortedBy:#"id" withPredicate:nil inContext:context];
[context performBlock:^{
__unused NSDate* start = [NSDate date];
NSError* error;
[self.newsItemsFRC performFetch:&error]; // this line crashes
[self calculateAndBroadcastCounts];
}];
}
Is this save call thread safe? If so what could cause these two functions to cause each-other to crash?
The issue is I'm modifying the news items outside of the context they were created in. So to fix the issue I had to move the code to the main thread. I switched from using magical records save to just performBlockAndWait which is guaranteed to run on the calling thread:
private static func cleanUpNewsItems() -> Void {
let context = NSManagedObjectContext.mr_()
context.performAndWait {
var itemsToDelete = [NSManagedObject]()
if let items = NewsItem.staleNewsItems(in: context) {
items.forEach({ (item) in
itemsToDelete.append(item as! NSManagedObject)
})
}
for item in itemsToDelete {
context.delete(item)
}
do {
try context.save()
} catch let error as NSError {
print("Error While Deleting Note: \(error.userInfo)")
}
}
}
I am assing a client callback function on a network read stream below:
-(void)connectToServer
{
if([self isServerConfigured])
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)self.serverAddress);
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, self.port, &readStream, &writeStream);
CFStreamClientContext myContext = {
0,
self,
(void *(*)(void *info))CFRetain,
(void (*)(void *info))CFRelease,
(CFStringRef (*)(void *info))CFCopyDescription
};
CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable |
kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if(CFReadStreamSetClient(readStream, registeredEvents, serverCB, &myContext))
{
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
if(!CFReadStreamOpen(readStream)){
NSLog(#"Could not open read stream");
}
}
}
I would like this callback function to be able to call instance functions in the current object (self). It does not appear that CFReadStreamSetClient will allow me to change the callback functions signature and so I cannot just pass a reference to the current object to the callback I don't think.
This is the callback function:
void serverCB(CFReadStreamRef stream, CFStreamEventType event, void *myPtr)
{
switch(event) {
case kCFStreamEventHasBytesAvailable:
[self readStreamData:stream];
break;
case kCFStreamEventErrorOccurred:
NSLog(#"A Read Stream Error Has Occurred!");
break;
case kCFStreamEventEndEncountered:
NSLog(#"A Read Stream Event End!");
break;
default:
break;
}
}
ServerCB will not know what 'self' is in this context. Can someone give me a pointer on how I can work around this?
Thanks!
myPtr == self in this case, as self is the value you gave your context's info field. That field is then passed to the callback by way of the last argument. So:
void serverCB(CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
{
MyClass *self = [[(MyClass *)myPtr retain] autorelease];
// ...
// ...
// ...
}
[pool drain];
}
(Because this is the Core Foundation level, there's no guarantee that an autorelease pool is in place, so it's common to see Objective-C in these callbacks wrapped in an autorelease pool, just in case.)
NOTE: I've edited my question. I've got it to connect and perform the first callback, but subsequent callbacks don't go through at all.
This is my first time writing Objective-C (with GNUstep; it's for a homework assignment). I've got the solution working, but I am trying to add something more to it. The app is a GUI client that connects to a server and gets data from it. Multiple clients can connect to the same server. If any one of the clients changes data that is residing on the server, the server sends a callback to all registered clients. This solution was originally implemented in Java (both client and server) and for the latest assignment, the professor wanted us to write an Objective-C client for it. He said that we don't need to handle callbacks, but I wanted to try anyway.
I am using NSThread and I wrote something that looks like this:
CallbackInterceptorThread.h
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#interface CallbackInterceptorThread : NSThread {
#private
NSString* clientPort;
AppDelegate* appDelegate;
}
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate;
- (void) main;
#end
CallbackInterceptorThread.m
#import <Foundation/Foundation.h>
#import "CallbackInterceptorThread.h"
#define MAXDATASIZE 4096
#implementation CallbackInterceptorThread
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate {
if((self = [super init])) {
[clientPort autorelease];
clientPort = [aClientPort retain];
[appDelegate autorelease];
appDelegate = [anAppDelegate retain];
}
return self;
}
- (void) main {
GSRegisterCurrentThread();
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
char* buffer = malloc(MAXDATASIZE);
Cst420ServerSocket* socket = [[Cst420ServerSocket alloc] initWithPort: clientPort];
[socket retain];
NSString* returnString;
while(YES) {
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
if([socket accept]) {
printf("Connection accepted!\n");
while(YES) {
printf("Inner loop\n");
sleep(1);
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
printf("Going to sleep now\n");
sleep(1);
}
[socket close];
}
}
}
#end
Cst420ServerSocket has been provided to us by the instructor. It looks like this:
#import "Cst420Socket.h"
#define PORT "4444"
/**
* Cst420Socket.m - objective-c class for manipulating stream sockets.
* Purpose: demonstrate stream sockets in Objective-C.
* These examples are buildable on MacOSX and GNUstep on top of Windows7
*/
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
#implementation Cst420ServerSocket
- (id) initWithPort: (NSString*) port{
self = [super init];
int ret = 0;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
const char* portStr = [port UTF8String];
if ((rv = getaddrinfo(NULL, portStr, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
ret = 1;
}else{
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){
perror("server: socket create error");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
perror("server: bind error");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
ret = 2;
}else{
freeaddrinfo(servinfo); // all done with this structure
if (listen(sockfd, BACKLOG) == -1) {
perror("server: listen error");
ret = 3;
}
}
if (ret == 0){
return self;
} else {
return nil;
}
}
}
- (BOOL) accept {
BOOL ret = YES;
#if defined(WINGS)
new_fd = accept(sockfd, NULL, NULL);
#else
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
#endif
if (new_fd == -1) {
perror("server: accept error");
ret = NO;
}
connected = ret;
return ret;
}
- (int) sendBytes: (char*) byteMsg OfLength: (int) msgLength Index: (int) at{
int ret = send(new_fd, byteMsg, msgLength, 0);
if(ret == -1){
NSLog(#"error sending bytes");
}
return ret;
}
- (NSString* ) receiveBytes: (char*) byteMsg
maxBytes: (int) max
beginAt: (int) at {
int ret = recv(new_fd, byteMsg, max-1, at);
if(ret == -1){
NSLog(#"server error receiving bytes");
}
byteMsg[ret+at] = '\0';
NSString * retStr = [NSString stringWithUTF8String: byteMsg];
return retStr;
}
- (BOOL) close{
#if defined(WINGS)
closesocket(new_fd);
#else
close(new_fd);
#endif
connected = NO;
return YES;
}
- (void) dealloc {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
[super dealloc];
}
#end
Our professor also provided us an example of a simple echo server and client (the server just spits back whatever the client sent it) and I've used the same pattern in the thread.
My initial problem was that my callback interceptor thread didn't accept any (callback) connections from the server. The server said that it could not connect back to the client (ConnectException from Java; it said "Connection refused"). I was able to fix this by changing my instructor's code. In the connect function (not shown), he had set the hints to use AF_UNSPEC instead of AF_INET. So Java was seeing my localhost IP come through as 0:0:0:0:0:0:0:1 (in IPv6 format). When Java tried to connect back to send a callback, it received an exception (not sure why it cannot connect to an IPv6 address).
After fixing this problem, I tried out my app again and this time the callback from the server was received by my client. However, subsequent callbacks fail to work. After receiving the first callback, the busy-loop keeps running (as it should). But when the server sends a second callback, it looks like the client cannot read it in. On the server side I can see that it sent the callback to the client successfully. It's just that the client is having trouble reading in the data. I added some print statements (see above) for debugging and this is what I get:
Client waiting for callbacks on port 2020
Connection accepted!
Inner loop
Received from Server |A callback from server to 127.0.0.1:2020|
Got a callback from server
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
... (and it keeps going regardless of the second callback being sent)
Here is how I am starting the thread (from the GUI):
CallbackInterceptorThread* callbackInterceptorThread = [[CallbackInterceptorThread alloc] initWithClientPort: clientPort appDelegate: self];
[callbackInterceptorThread start];
I think I've got it working. So from the Java side (the server), this was what I was doing:
Socket socket = new Socket(clientAddress, clientPort);
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
out.write(("A callback from server to " + clientAddress + ":" + clientPort).getBytes());
out.flush();
out.close();
I put some debugging print-statements in my professor's code and noticed that in receiveBytes, recv was returning 0. The return value of recv is the length of the message that it received. So it received a zero-length string. But a return value of 0 also means that the peer closed the connection properly (which is exactly what I had done from the Java side with out.close()). So I figured that if I needed to respond to the second callback, I would need to accept the connection again. So I changed my busy loop to this:
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
while([socket accept]) {
printf("Connection accepted!\n");
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
}
[socket close];
and that seemed to do the trick. I am not sure if this is the right way to do it, so I am open to suggestions for improvement!
Ok, basically I have a run loop going in my application every second or two, while at the same time I have another thread going that is looping through the listenForPackets method; broadcastMessage is only initiated when another action method takes place. The important part of this question is that when the listener thread is running seperately from the main thread, it never print out any of the print commands and it seems to not allow access to a global variable that I have defined called recvMessage which lies outside of the interface and implementation sections.
In my code, I have it set up so that every time it runs through the main run loop, it updates a UILabel in my GUI. When the app is running, my label stays blank the whole time and never changes. I've double-checked the GUI and everything is linked up there correctly and my label is instantiated correctly too (I use the name "label" as an instance of UILabel in the code below). Does anyone have any ideas why my label is updating? The networking aspect of things is fine I believe, because I just got that done and everything is "talking" ok. Maybe it's a variable scope issue that I don't know about, or are separate threads allowed to access global variables, such as the one I have used below (rcvMessage)? I'm fairly new to multi-threaded applications but I don't believe it's really that hard to implement, using NSThread (only one line of code).
Global Variable
NSString *recvMessage;
Main Runloop - the section that updates the label every time it goes through the runloop
if (label.text != recvMessage)
label.text = recvMessage
Talker Method
-(void)broadcastMessage { // (NSString*)msg {
msg = #"From_Master";
NSLog(#"broadcastMessage - Stage 1");
mc_ttl = 15; // number of node hops the message is allowed to travel across the network
// define the port we will be using
mc_port = MYPORT;
// create a socket for sending to the multicast address
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: broadcastMessage - socket() failed");
return;
}
memset(&mc_addr, 0, sizeof(mc_addr));
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr(GROUP_ADDRESS);
mc_addr.sin_port = htons(MYPORT);
NSLog(#"broadcastMessage - Stage 2");
// set the TTL (time to live/hop count) for the send
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &mc_ttl, sizeof(mc_ttl))) < 0) {
NSLog(#"ERROR: broadcastMessage - setsockopt() failed");
return;
}
NSLog(#"broadcastMessage - Stage 3");
// clear send buffer
memset(send_str, 0, sizeof(send_str));
// convert the message to a C string to send
[msg getCString:send_str maxLength:MAX_LEN encoding:NSASCIIStringEncoding];
//while (fgets(send_str, MAX_LEN, stdin)) {
NSLog(#"broadcastMessage - Stage 4");
NSLog(#"Message =");
printf(send_str);
// send string to multicast address
if ((sendto(sock, send_str, sizeof(send_str), 0, (struct sockaddr *)&mc_addr, sizeof(mc_addr))) < 0) {
NSLog(#"ERROR: broadcastMessage - sendto() sent incorrect number of bytes");
//return;
}
NSLog(#"Sent Message -");
printf(send_str);
NSLog(#"broadcastMessage - Stage 5");
// clear send buffer
memset(send_str, 0, sizeof(send_str));
NSLog(#"broadcastMessage - Stage 6 - Complete");
close(sock);
}
Listener Method
-(void)listenForPackets {
listeningFlag_on = 1; // allows reuse of the same socket
NSLog(#"listenForPackets - Stage 1");
if ((listeningSock = socket(AF_INET, SOCK_DGRAM,IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: listenForPackets - socket() failed");
return;
// make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 2");
// set reuse port to on to allow multiple binds per host
if ((setsockopt(listeningSock, SOL_SOCKET, SO_REUSEADDR, &listeningFlag_on, sizeof(listeningFlag_on))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() Reuse failed");
return;
// make the method return an int instead of void and use this statement to check for errors
}
// construct a multicast address structure after erasing anything in the listeningmc_addr structure
memset(&listeningmc_addr, 0, sizeof(listeningmc_addr));
listeningmc_addr.sin_family = AF_INET;
listeningmc_addr.sin_addr.s_addr = htonl(INADDR_ANY); // different from sender
listeningmc_addr.sin_port = htons(MYPORT);
// bind multicast address to socket
if ((bind(listeningSock, (struct sockaddr *)&listeningmc_addr, sizeof(listeningmc_addr))) < 0) {
NSLog(#"ERROR: listenForPackets - bind() failed");
perror("Bind() -");
return; // make the method return an int instead of void and use this statement to check for errors
}
//*********************************************************************************
NSString *ipAddress = [[NSString alloc] initWithString:self.getIPAddress];
const char *tmp = [ipAddress UTF8String];
listeningMc_addr_str = tmp;
printf("%s\n", listeningMc_addr_str);
listeningMc_req.imr_multiaddr.s_addr = inet_addr(GROUP_ADDRESS);
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
// send an ADD MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
int err = errno;
NSLog(#"errno - %i", err);
NSLog(#"Error = %s", strerror(err));
perror("ERROR");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 3");
for (;;) { // loop forever
// clear the receive buffers & structs
memset(listeningRecv_str, 0, sizeof(listeningRecv_str));
listeningFrom_len = sizeof(listeningFrom_addr);
memset(&listeningFrom_addr, 0, listeningFrom_len);
NSLog(#"Test #1 Complete");
//msgStatus.text = #"Waiting...";
// block waiting to receive a packet
listeningFrom_len = sizeof(listeningmc_addr);
if ((listeningRecv_len = recvfrom(listeningSock, listeningRecv_str, MAX_LEN, 0, (struct sockaddr*)&listeningmc_addr, &listeningFrom_len)) < 0) {
NSLog(#"ERROR: listenForPackets - recvfrom() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"Test #2 Complete - Received a Message =");
NSLog(#"listenForPackets - Stage 4");
// listeningRecv_str
**tmpString = [[NSString alloc] initWithCString:listeningRecv_str encoding:NSASCIIStringEncoding];
NSLog(#"Message Received =");
NSLog(tmpString);
recvMessage = tmpString;**
//}
// received string
printf("Received %d bytes from %s: ", listeningRecv_len, inet_ntoa(listeningFrom_addr.sin_addr));
printf("%s", listeningRecv_str);
//}
}
// send a DROP MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() drop membership failed");
//return 1; // make the method return an int instead of void and use this statement to check for errors
}
close(listeningSock);
NSLog(#"listenForPackets - Stage 5 - Complete");
}
Yes, all threads can access global variables. There are certainly problems with how you are using the global--you leak the NSString you create every time the variable is updated, and you are accessing the same memory from two threads without any access control--but there's nothing that would prevent the variable from being updated.
If none of your log messages are being printed, the problem is that the code is never being run, which is why the variable isn't changing. You should take a look at the code that is supposed to kick off this new thread.
Also note that updating any UI components, you need to use the method "performSelectorOnMainThread" to do any value setting of label text or any other GUI elements. The value will not update from background threads.