How to set VoIP mode on a socket in iOS 4 - iphone

I'm trying to set up a socket in VoIP mode on an iPhone, so that my app can be woken when an event happens. I have a simple server that will write to the socket if and only if the app should wake up and talk to the main web service about something. Calling
CFReadStreamSetProperty()
on the stream attached to the socket always seems to return zero, which if I'm not mistaken is FALSE, meaning the stream did not recognize and/or accept the property value. I read in a previous question that this facility is not available on the simulator, so I tried it on a real phone, with the same result.
How can I figure out why the call is failing?
The code is below:
- (id) init {
NSLog(#"NotificationClient init, host = %#", [self getNotificationHostName]);
CFHostRef notificationHost = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)[self getNotificationHostName]);
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, notificationHost, [self getNotificationPort], &_fromServer, &_toServer);
BOOL status;
status = CFReadStreamOpen(_fromServer);
status = CFReadStreamSetProperty(_fromServer, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
NSLog(#"status from setting VoIP mode on socket is %d", status);
status = CFWriteStreamOpen(_toServer);
[self sendMessage:#"STRT" withArgument:#"iPhone"];
[self startReceivingMessages];
return self;
}

Hmm... it looks like there were two problems. First, you need to set the property before opening the stream. And second, it looks like it only works if you are on the main thread when you do this.

Related

Accurately reading of iPhone signal strength

There are a few questions on this already, but nothing in them seems to provide accurate results. I need to determine simply if the phone is connected to a cell network at a given moment.
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Reference/CTCarrier/Reference/Reference.html
This class seems to be documented incorrectly, returning values for mobileCountryCode, isoCountryCode and mobileNetworkCode where no SIM is installed to the phone. carrierName indicates a 'home' network or a previous home network if the phone has been unlocked.
I also looked up and found some people claiming the following to work, which uses an undocumented method of the CoreTelephony framework, but the results have been useless to me, reporting seemingly random figures, where perhaps it is not itself updating consistently.
-(int) getSignalStrength
{
void *libHandle = dlopen("/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony", RTLD_LAZY);
int (*CTGetSignalStrength)();
CTGetSignalStrength = dlsym(libHandle, "CTGetSignalStrength");
if( CTGetSignalStrength == NULL) NSLog(#"Could not find CTGetSignalStrength");
int result CTGetSignalStrength();
dlclose(libHandle);
return result;
}
Thanks.
Edit: The app is connected to an internal wifi and must remain so, making a reachability check more difficult.
I'm playing with this function and I've noticed you're calling it in an interesting way. I'm calling it by adding CoreTelephony.framework as a compile-time link. For the function itself, you'll want to declare it's prototype somewhere (perhaps immediately above the method you call from):
int CTGetSignalStrength();
This needs to be declared since it isn't in a public header for CoreTelephony.
Now, I built a simple app that prints signal strength every second.
int CTGetSignalStrength();
- (void)viewDidLoad
{
[super viewDidLoad];
while (true) {
printf("signal strength: %d\n", CTGetSignalStrength());
sleep(1);
}
}
I ran it on my iPad mini and it shows steady values until I picked it up, where the number went up. Wrapping my iPad in tin foil (tin foil is a debugging tool I have never used before) caused the number to go down. When I put my iPad in airplane mode, it kept repeating the last value, so this will not be an accurate measure for you.
If you want to test if a device currently has a cellular data network connection, you may be more interested in Reachability, specifically kSCNetworkReachabilityFlagsIsWWAN.
Ok I think I have the correct solution now, which was a bit simpler in the end.
The issue with the CTGetSignalStrength() method is that it works normally, but if you remove a sim, it reports the last signal before the removal. I found another method in the same framework called CTSIMSupportGetSIMStatus(), also undocumented, which can tell you if a SIM is currently connected. Using both as follows should confirm the current network signal.
First declare the methods:
NSString * CTSIMSupportGetSIMStatus();
int CTGetSignalStrength();
Then check connectivity to cell network like so:
NSString *status = CTSIMSupportGetSIMStatus();
int signalstrength = CTGetSignalStrength();
BOOL connected = ( [status isEqualToString: #"kCTSIMSupportSIMStatusReady"] && signalstrength > 0 );

Checking private network for local devices with a certain port open (iPhone/Objective C)

I've spent the whole day creating a system between my Mac and iPhone where i have used cocoaasyncsocket to create a listen server on my mac and a client on my iPhone. The basic idea is to leave the app on the mac running while the computer is on and then when I wish to transfer data from the iPhone app, fire up the app an it connects and sends the data... I have this system working exactly how I want it to function however I have 1 issue what I have been trying to solve for about 4 hours in total!
I wanted to create something what scans my wireless network for my mac with the listener running... I thought this would be simple, but I was wrong. I have searched high and low with no luck on the case and I am using stackoverflow as my last resort.
My current plan was to "autoscan" by retrieving the internal IP of the iPhone (ie 192.168.1.94) then use that to figure out what the other IP's on the network will be (192.168.1.0-254), now I know what IP's to scan i can loop through each one and check to see if the port is open/I get a response.
Now I want to do this as quick as possible however I haven't been able to get ANYTHING to give me accurate results...
Using connectToAddress:error: in the cocoaasyncsocket will simply just return true for every one of the 255 different IP addresses, so will any other reachability functions that I have come across... I have read that it is because they only check to see if the connection gets made and don't care about what happens at the other end so I need to think of something else.
My only other solution that I can think of, is to maybe ping each internal IP and see if i get a response but im not sure if this is going to take up too much time having to go through 255 IP addresses... and then, once i get what IP's are active I still then have to check to see if the port is open somehow :/
If anyone here knows how it can be done or has any better idea how I can check for the open port (i'm not very good with networking) I would be VERY grateful.
Thanks for reading,
Liam
Ok, I had a pay around today and I managed to get it working using Bonjour!
As it took me time to figure it all out I thought I will help anyone else out...
First off, on the listener side we need to set up a NSNetService, this can be done like so:
listenService = [[NSNetService alloc] initWithDomain:#"" type:#"_appname._tcp" name:#"Display Name" port:2427];
[listenService setDelegate:self]; //make sure you include the NSNetServiceDelegate
[listenService publish];
You can then plug into the NSNetServiceDelegate to make sure the service was published successfully and I used Bonjour Browser to check that my service was running fine (and it was)...
Then on the client we need to search for the service using [NSNetServiceBrowser][3]... This can be done like so:
serviceBrowser = [[NSNetServiceBrowser alloc] init];
[serviceBrowser setDelegate:self]; //remember to include NSNetServiceBrowserDelegate
[serviceBrowser searchForServicesOfType:#"_appname._tcp" inDomain:#""];
If you then include the NSNetServiceBrowserDelegate methods you can listen in to
netServiceBrowser:didFindService:moreComing:
You must then retain the service, give it a delegate and then resolve the service... Then if you listen into the NSNetServiceDeligate for the netServiceDidResolveAddress: you can then run the following code to convert the sockaddr into a readable IP address:
#include <arpa/inet.h>
-(void)netServiceDidResolveAddress:(NSNetService *)sender {
for (NSData* data in [sender addresses]) {
char addressBuffer[100];
struct sockaddr_in* socketAddress = (struct sockaddr_in*) [data bytes];
int sockFamily = socketAddress->sin_family;
if (sockFamily == AF_INET ) {//|| sockFamily == AF_INET6) { /*only support ipv4 atm*/
const char* addressStr = inet_ntop(sockFamily,
&(socketAddress->sin_addr), addressBuffer,
sizeof(addressBuffer));
int port = ntohs(socketAddress->sin_port);
if (addressStr && port) {
//you now have the services IP and Port.. all done
}
}
}
[sender release];}
Hope this helps anyone who is stuck with this.. Note that I borrowed parts of other samples/ instructions into one complete post what explains the whole system.
Enjoy.
I haven't worked with it myself but basically Bonjour is actually what you are looking for it's purpose is publishing and discovery of services

Game Center Multiplayer using GKMatch but seems can't be connected

Hi I'm a new bie in Game Center for iOS. I'm trying to add the multiplayer feature using matches to my game and following the documentation.
So far I reached a point where 2 of my clients can successfully get a match, i.e. the matchmakerViewController:didFindMatch callback is called and a GKMatch object is delivered.
However after that I seems to be stuck there forever, because according to the documentation, I'll have to wait until all the players (2 in my case) are actually connected before starting my game. But it seems the match:player:didChangeState callback is never called to indicate a successful connection. Well, I'm sure my clients are all in the same wifi network ( or is it a must?) Could any one enlighten me on this case? Do I have to do any extra things to make the clients to connect? Thanks a lot for the help!
So I was running into this and the solution (for me) was somewhat embarrasing. I had copied and pasted a bunch of the code from the Apple docs..and they left out an obvious step. They never actually set the match's delegate!
My code now is:
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match {
[self dismissModalViewControllerAnimated:YES];
self.myMatch = match; // Use a retaining property to retain the match.
self.myMatch.delegate = self; // THIS LINE WAS MISSING IN THE APPLE DOCS. DOH.
// Start the game using the match.
NSLog(#"Match started! Expected Player Count:%d %#",match.expectedPlayerCount, match.playerIDs);}
Once I actually set the match delegate, the functions get called. Doh.
When you get the GKMatch object, be sure to check the expectedPlayerCount property. It is possible that the other player is already connected, and thus you will not get a match:player:didChangeState on the delegate.
I have had the same problem with a friend. The solution was quite strange but it works afterwards. On all devices you have to enable the Notifications (Sounds/Alerts/Badges) for the Game Center inside the Settings/Notifications options. Afterwards we could establish a connection and did receive a match object
Inside your callback matchmakerViewController:didFindMatch
Add this code then you'll see the callback "match:player:didChangeState" being called by GC
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
[[GKMatchmaker sharedMatchmaker] addPlayersToMatch:match matchRequest:request completionHandler:^(NSError* error) {
if(error)
NSLog(#"Error adding player: %#", [error localizedDescription]);
}];
It was working all along. The only difference is that... when you use invites the event "didChangeState" doensn't get called. You're connected without notice and you can start to receive data. I never tried to send/receive data... cuz i was expecting the event first, but i did send something by mistake one time, and it worked. :)
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *) match {
//Dismiss window
[self dismissModalViewControllerAnimated:YES];
//Retain match
self.myMatch = match;
//Delegate
myMatch.delegate = self;
//Flag
matchStarted = TRUE;
//Other stuff
}
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
//This code gets called only on auto-match
}
The above code works as expected.
Make sure that you've set your class as the delegate for GKSession. The class will need to implement the GKSessionDelegate protocol... otherwise, it'll never receive this callback. Here's the protocol reference. Hope this helps!

iPhone Socket gets closed unexpected

Well at least unexpected for me... This is the situation:
I open a Socket using the following code:
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)ipaddress , 3333,&readStream, &writeStream);
if(!CFReadStreamOpen(readStream) || !CFWriteStreamOpen(writeStream))
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection error"
message:#"There has been a connection error, please ensure your internet connection is working"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
return;
}
This all goes fine, next thing I do is setup a callback:
CFStreamClientContext ctxt = {0,(void*)NULL,NULL,NULL,NULL};
static const CFOptionFlags kReadNetworkEvents = kCFStreamEventEndEncountered |
kCFStreamEventErrorOccurred |
kCFStreamEventHasBytesAvailable |
kCFStreamEventOpenCompleted |
kCFStreamEventNone;
CFReadStreamSetClient(readStream, kReadNetworkEvents, ReadStreamClientCallBack, &ctxt);
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
Also works fine, I'll list the callback method to be complete:
static void ReadStreamClientCallBack( CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo ) {
switch (type)
{
case kCFStreamEventEndEncountered:
{
break;
}
case kCFStreamEventErrorOccurred:
break;
case kCFStreamEventHasBytesAvailable:
{
[this stopListen];
UInt8 buffer[1024];
int count = CFReadStreamRead(stream, buffer, 1024);
CFStreamStatus status = CFReadStreamGetStatus(this.readStream);
CFErrorRef error = CFReadStreamCopyError (this.readStream);
CFStringRef errorCode = CFErrorCopyDescription(error);
// Bunch of other irrelevant code
Now what goes wrong: All this code works perfectly fine as long as I'm staying in the application, even if I exit the application and enter it again it works still fine. If I enter standby while the application is open it also works fine. However if I exit the application, put my phone on standby, get my phone out of standby and reenter the application, the call back immediatly gets called, with eventtype kCFStreamEventHasBytesAvailable, even though I'm 100% sure no bytes have been send. If I then call CFReadStreamRead it returns -1 to me, since this means an error occured I figured out the error code which is 57, this mean that the socket has been closed.
Am I overlooking a certain aspect of socket programming on the iPhone? I must admit I'm new to it. Is it not possible to keep a TCP Socket open while out of the application (and entering standby)?
I have tried to call CFReadStreamOpen again which returned false to me.
I'm lost here, please help!
Thanks in advance
Part of the multi-tasking features in 4.0+ require extra coding to support keeping connections alive and performing tasks in the background. You are probably just a victim of the OS taking back those resources since you didn't "opt-in" for them at the appropriate time.
This section covers the basics of backgrounding:
http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
Specifically this:
Be prepared to handle connection
failures in your network-based
sockets. The system may tear down
socket connections while your
application is suspended for any
number of reasons. As long as your
socket-based code is prepared for
other types of network failures, such
as a lost signal or network
transition, this should not lead to
any unusual problems. When your
application resumes, if it encounters
a failure upon using a socket, simply
reestablish the connection.
However, there are three very special things that you can do in the background, and if you do you should declare them in your Info.plist to be a good App citizen. These three values are in the UIBackgroundModes property and they are audio location and voip
You can request more time for your task by registering a block (even though it isn't guarunteed you'll get it) like this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
See also A Short Practical Guide to Blocks for a quick overview and the Concurrency Programming Guide for more detailed information.

Why does Reachability sample app stall here?

Apple's sample app named Reachability shows how to detect connections. If you have only wifi but not internet, the app stalls for over a minute on the second line below:
SCNetworkReachabilityFlags reachabilityFlags;
BOOL gotFlags = SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
SCNetworkReachabilityGetFlags comes from SystemConfiguration.framework. Any suggestions on how to get around this?
To answer your question directly, no, there doesn't seem to be any way to "get around" SCNetworkReachabilityGetFlags() taking a long time to return under the specific circumstances you described (e.g., checking remote host reachability via WiFi connection to a router with no Internet). A couple of options:
OPTION 1. Make the call in a separate thread so that the rest of your app can keep running. Modify ReachabilityAppDelegate.m as follows for an example:
// Modified version of existing "updateStatus" method
- (void)updateStatus
{
// Query the SystemConfiguration framework for the state of the device's network connections.
//self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
self.remoteHostStatus = -1;
self.internetConnectionStatus = [[Reachability sharedReachability] internetConnectionStatus];
self.localWiFiConnectionStatus = [[Reachability sharedReachability] localWiFiConnectionStatus];
[tableView reloadData];
// Check remote host status in a separate thread so that the UI won't hang
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSTimer *timer = [NSTimer timerWithTimeInterval:0 target:self selector:#selector(updateRemoteHostStatus) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[pool release];
}
// New method
- (void) updateRemoteHostStatus
{
self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
[tableView reloadData];
}
OPTION 2. Use a different API/function that uses a timeout value when trying to connect to the remote host. That way your app would only hang for X seconds before it gives up.
Some other things to note:
The specific call to SCNetworkReachabilityGetFlags() that you're asking about (i.e., line ~399 in Reachability.m) is trying to see if www.apple.com is "reachable" to deduce if "the external internet" is "reachable" in general.
In Apple's System Config framework "reachable" might not mean what you think it does. According to the official docs, "reachable" seems to mean that, in theory, your computer could connect to host X if it wanted to, but it might need to actually establish a connection first (e.g., dial a modem first). In other words, SCNetworkReachabilityGetFlags() doesn't actually establish a connection.