PDA

View Full Version : Weird BSD Sockets Issue




Matt342
Jun 12, 2008, 11:08 AM
Hi!

I'm writing a simple iPhone program that sends data to a BSD Sockets server. The program works fine with WiFi but EDGE is what I'm having problems with.

If I start the client and no networking program has been opened prior to running the client, it won't connect to the server.

While if I start Weather.app for example, and then my client program, everything works fine!

Why does my app only work over EDGE when a networked program has been started before?

~Matt



yeroen
Jun 12, 2008, 11:50 AM
can you give us any error messages or source code?

Matt342
Jun 12, 2008, 12:06 PM
the variable errno contains error code 1 which states - The user tried to connect to a broadcast addresswithout having the socket broadcast flag enabled orthe connection request failed because of a localfirewall rule.

Any ideas? Why does my code work on EDGE when I start a networked program like Stocks or Weather prior to starting my client?

~Matt


can you give us any error messages or source code?

Cromulent
Jun 12, 2008, 12:09 PM
Because Weather or Stocks set the appropriate flag for you I would imagine.

The error message tells you what the problem is and the solution.

Matt342
Jun 12, 2008, 12:12 PM
How do I set the flags? Which method?

Thanks!

Because Weather or Stocks set the appropriate flag for you I would imagine.

The error message tells you what the problem is and the solution.

Cromulent
Jun 12, 2008, 12:16 PM
How do I set the flags? Which method?

Thanks!

I think the flag is SO_BROADCAST and you add it to the setsockopt() function. Seems to require a UDP connection though (SOCK_DGRAM) which is a bit odd. Try it and see. If it does not work I'll have another look for you.

Edit : Fixed a mistake above.

Matt342
Jun 12, 2008, 12:33 PM
Thanks. I just tried it. It still won't work.

Any other ideas?

~Matt

I think the flag is SO_BROADCAST and you add it to the setsockopt() function. Seems to require a UDP connection though (SOCK_DGRAM) which is a bit odd. Try it and see. If it does not work I'll have another look for you.

Edit : Fixed a mistake above.

Cromulent
Jun 12, 2008, 12:35 PM
Thanks. I just tried it. It still won't work.

Any other ideas?

~Matt

Post the code you have.

Matt342
Jun 12, 2008, 12:59 PM
Socket.m (custom Class I made for wrapping BSD sockets)

- (id)initWithUnixSocket:(int)socket{
self = [super init];
if (self){
sock = socket;
}
return self;
}

- (int)createWithDomain:(int)domain Type:(int)type Protocol:(int)protocol{
sock = socket(domain, type, protocol);
if (sock == -1){
return ERROR;
}
return SUCCESS;
}

- (int)connectToAddress:(const char *)address onPort:(unsigned int)port{
struct sockaddr_in data;
bzero(&data, sizeof(data));
data.sin_family = AF_INET;
data.sin_port = htons(port);
data.sin_addr.s_addr = inet_addr(address);

int result = connect(sock, (const struct sockaddr *)&data, sizeof(data));
if (result == 0){
return SUCCESS;
}
printf("%i", errno);
return ERROR;
}

- (int)bindOnPort:(unsigned int)port{
struct sockaddr_in data;
bzero(&data, sizeof(data));
data.sin_family = AF_INET;
data.sin_port = htons(port);
data.sin_addr.s_addr = INADDR_ANY;

int result = bind(sock, (const struct sockaddr *)&data, sizeof(data));
if (result == 0){
return SUCCESS;
}
return ERROR;
}

- (int)listenWithBackLog:(int)backLog{
int result = listen(sock, backLog);
if (result == 0){
listeningThread = [[NSThread alloc] initWithTarget:self selector:@selector(listeningThread:) object:nil];
[listeningThread start];

return SUCCESS;
}
return ERROR;
}

- (void)listeningThread:(id)object{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

int incomingClient = accept(sock, NULL, NULL);
if (incomingClient != -1){
Socket *incomingSocket = [[Socket alloc] initWithUnixSocket:incomingClient];
incomingSocket.delegate = self.delegate;
if ([delegate respondsToSelector:@selector(connectionAccepted:)]){
[delegate connectionAccepted:incomingSocket];
}
}

[pool release];
}

- (int)sendData:(const char *)data{
int length = strlen(data);

int result = send(sock, data, length, 0);
if (result > 0){
return result;
}
return ERROR;
}

- (void)waitForData{
dataThread = [[NSThread alloc] initWithTarget:self selector:@selector(listenForData:) object:nil];
[dataThread start];
}

- (void)listenForData:(id)object{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

BOOL waitingForData = YES;
while (waitingForData){
const char *buffer[1024];
int length = sizeof(buffer);
int result = recv(sock, &buffer, length, 0);

if (result > 0){
if ([delegate respondsToSelector:@selector(dataReceived:)]){
[delegate dataReceived:buffer];
}
}
}

[pool release];
}

- (void)close{
close(sock);
}


Server Code (Uses Socket.m)



if ([server createWithDomain:AF_INET Type:SOCK_STREAM Protocol:0] == SUCCESS){
[statusText setStringValue:STATUS_CREATED_SOCKET];
if ([server bindOnPort:PORT] == SUCCESS){
[statusText setStringValue:STATUS_BOUND];
if ([server listenWithBackLog:5] == SUCCESS){
[statusText setStringValue:STATUS_WAITING_FOR_CONNECTIONS];
}
}
}



Client Code (Uses Socket.m):

if ([client createWithDomain:AF_INET Type:SOCK_STREAM Protocol:0] == SUCCESS){
canConnect = YES;
}
if (connected == NO){
if (canConnect){
if ([client connectToAddress:[ipField.text UTF8String] onPort:PORT] == SUCCESS){
connected = YES;
}
}
}



[client connectToAddress:[ipField.text UTF8String] onPort:PORT] always fails on EDGE, unless a networked program is started before....

Matt342
Jun 14, 2008, 01:44 PM
Any ideas?

Cromulent
Jun 14, 2008, 05:34 PM
Well one mistake you have made is that you have declared your buffer in listenForData as a const which it is not. Const is used for strings that do not change. Your string is changed with the call to recv().

I'd declare buffer using the easier to manage malloc method.

char *buffer = (char *) malloc (1024);

free(buffer); // don't forget to put this at the end when you are finished with the buffer

lee1210
Jun 14, 2008, 06:01 PM
Well one mistake you have made is that you have declared your buffer in listenForData as a const which it is not. Const is used for strings that do not change. Your string is changed with the call to recv().

I'd declare buffer using the easier to manage malloc method.

char *buffer = (char *) malloc (1024);

free(buffer); // don't forget to put this at the end when you are finished with the buffer

const is tricky.
const char *buffer[1024];

That, however, is almost certainly not what was intended. if you are using a fixed size, using a local variable rather than mallocing from the heap is fine. The code above, though, is off. It is a list of 1024 character pointers, none of which are to be modified(the pointers are not to be assigned to). You have 1024*sizeof(char *) bytes there, but they aren't doing what you'd like.

You'll get the right length, so you won't have any overflow issues, and since recv just takes a void * it doesn't care, it'll stick the data in the 4096/8192 bytes buffer is pointing to.

This still really doesn't help in getting the EDGE connection activated before using it. I looked around the SDK documentation and couldn't find anything on this. Can you trace system/library calls in the debugger with the emulator? If so, maybe you can run one of the other programs and see if it makes a special call to activate the EDGE connection.

-Lee

Matt342
Jun 18, 2008, 10:09 AM
Thanks for the ideas. I finally figured it out!

You DO have to start the EDGE connection if it wasn't previously started by another program, but unfortunately, it's not in the official iPhone SDK.

To get EDGE working all the time, you have to link to Message.framework which is in the PrivateFrameworks directory. Then you have to include the NetworkController.h file and run this code:


if (![[NetworkController sharedInstance] isEdgeUp]){
[[NetworkController sharedInstance] keepEdgeUp];
[[NetworkController sharedInstance] bringUpEdge];
}

const is tricky.
const char *buffer[1024];

That, however, is almost certainly not what was intended. if you are using a fixed size, using a local variable rather than mallocing from the heap is fine. The code above, though, is off. It is a list of 1024 character pointers, none of which are to be modified(the pointers are not to be assigned to). You have 1024*sizeof(char *) bytes there, but they aren't doing what you'd like.

You'll get the right length, so you won't have any overflow issues, and since recv just takes a void * it doesn't care, it'll stick the data in the 4096/8192 bytes buffer is pointing to.

This still really doesn't help in getting the EDGE connection activated before using it. I looked around the SDK documentation and couldn't find anything on this. Can you trace system/library calls in the debugger with the emulator? If so, maybe you can run one of the other programs and see if it makes a special call to activate the EDGE connection.

-Lee

lee1210
Jun 18, 2008, 11:29 AM
Thanks for the ideas. I finally figured it out!

You DO have to start the EDGE connection if it wasn't previously started by another program, but unfortunately, it's not in the official iPhone SDK.

To get EDGE working all the time, you have to link to Message.framework which is in the PrivateFrameworks directory. Then you have to include the NetworkController.h file and run this code:


if (![[NetworkController sharedInstance] isEdgeUp]){
[[NetworkController sharedInstance] keepEdgeUp];
[[NetworkController sharedInstance] bringUpEdge];
}

It's unfortunate that there is not a supported way to do this. i would be careful, as they may change the names of these functions in a subsequent release of the SDK/frameworks. Especially considering that these functions have "Edge" in the name, and EDGE will no longer be the only wireless data service available to the iPhone once iPhone 3G is released.

-Lee

imho
Aug 17, 2008, 08:10 AM
Getting the exact same problem, but on 3G. Didn't test with EDGE at all. NetworkController.h seem to have been removed with the current official 2.0 SDK. Anyone know how to solve this problem? I need something to kickstart the 3G network...

Matt342
Aug 17, 2008, 09:23 AM
It was never in the official SDK. You gotta use the unofficial API's.

Getting the exact same problem, but on 3G. Didn't test with EDGE at all. NetworkController.h seem to have been removed with the current official 2.0 SDK. Anyone know how to solve this problem? I need something to kickstart the 3G network...

imho
Aug 17, 2008, 09:28 AM
Is there no way to do this properly with BSD sockets? There are apps on official SDK that seem to connect within a few seconds regardless of whether 3G is "dormant" or not. Is it because these apps are using Apple's CFNetwork instead or low-level BSD sockets?

Matt342
Aug 17, 2008, 09:33 AM
the BSD Sockets just send and receive data over the network. They don't actually start and stop network connections for you. That's done by the OS, and in this case, Apple doesn't give developers control over it.

As for the samples, the example WITap requires a WiFi connection, and the example Reachability only checks if you're connected to the net.

~Matt

Is there no way to do this properly with BSD sockets? There are apps on official SDK that seem to connect within a few seconds regardless of whether 3G is "dormant" or not. Is it because these apps are using Apple's CFNetwork instead or low-level BSD sockets?

imho
Aug 17, 2008, 01:40 PM
Thanks Matt, so the best route would be to use Apple's CFNetwork API?

Matt342
Aug 17, 2008, 01:42 PM
I would use the BSD sockets for maximum performance, and battery life. Starting the network connection using NetworkController only takes 2 lines of code.

Thanks Matt, so the best route would be to use Apple's CFNetwork API?

imho
Aug 17, 2008, 01:48 PM
I would use the BSD sockets for maximum performance, and battery life. Starting the network connection using NetworkController only takes 2 lines of code.

Can you elaborate on how to do that? (I don't have the header, hence i don't know what funcs are available...) You mentioned that NetworkController is in the unofficial SDK. I do not have that, can you kindly point me to somewhere where i could d/l that.

Thanks for helping.

Matt342
Aug 17, 2008, 01:55 PM
Create a new Header file by going into File->New File->C->Header File. Call it NetworkController.h

Paste this in:


#import "NSObject.h"

@class NSString, NSTimer;

@interface NetworkController : NSObject {
struct __SCDynamicStore *_store;
NSString *_domainName;
unsigned int _waitingForDialToFinish:1;
unsigned int _checkedNetwork:1;
unsigned int _isNetworkUp:1;
unsigned int _isFatPipe:1;
unsigned int _edgeRequested:1;
NSTimer *_notificationTimer;
}

+ (id)sharedInstance;
- (void)dealloc;
- (id)init;
- (BOOL)isNetworkUp;
- (BOOL)isFatPipe;
- (id)domainName;
- (BOOL)isHostReachable:(id)fp8;
- (id)primaryEthernetAddressAsString;
- (void)registerCTServerRunLoopSource;
- (id)IMEI;
- (id)edgeInterfaceName;
- (BOOL)isEdgeUp;
- (void)bringUpEdge;
- (void)keepEdgeUp;

@end

Then make sure you link against the Message.framework by right clicking on your Target, going to Info, pressing Add, and selecting message.framework.

Then do:


[[NetworkController sharedInstance] keepEdgeUp];
[[NetworkController sharedInstance] bringEdgeUp];


Can you elaborate on how to do that? (I don't have the header, hence i don't know what funcs are available...) You mentioned that NetworkController is in the unofficial SDK. I do not have that, can you kindly point me to somewhere where i could d/l that.

Thanks for helping.

imho
Aug 17, 2008, 02:36 PM
Million thanks Matt!