Connecting to a server with NSNetService but failing- automatic socket disconnect? - objective-c

NEW SIMPLER VERSION OF PROBLEM:
I'm trying to connect to and communicate with a Bonjour device using an Objective-C client and I'm having trouble resolving the service (server). Originally I tried to do a more complicated example, but I found the basic socket connection even with port/ip specified wasn't working.
I have the most barebones possible code using the cocoaAsyncSocket library:
AsyncSocket *xxx = [[[AsyncSocket alloc] initWithDelegate:self] autorelease];
NSError **err;
[xxx connectToHost: #"localhost" onPort: 5000 error: err];
NSLog(#"err=%#",err);
And here's the server I'm trying to connect to (Python script):
# TCP server with bonjour broadcast!
import select
import sys
import pybonjour
import socket
from time import sleep
#name = sys.argv[1]
#regtype = sys.argv[2]
#port = int(sys.argv[3])
# Bonjour service parameters
name = "TEST"
regtype = "_xxx._tcp."
port = 5000
# Tcp socket stuff
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("", port))
server_socket.listen(5)
def register_callback(sdRef, flags, errorCode, name, regtype, domain):
if errorCode == pybonjour.kDNSServiceErr_NoError:
print 'Registered service:'
print ' name =', name
print ' regtype =', regtype
print ' domain =', domain
sdRef = pybonjour.DNSServiceRegister(name = name,
regtype = regtype,
port = port,
callBack = register_callback)
# register bonjour service
print "Registering Bonjour service"
ready = select.select([sdRef], [], [])
print "ready=",ready
if sdRef in ready[0]:
pybonjour.DNSServiceProcessResult(sdRef)
def configLoop():
data = "Entering configuration mode"
client_socket.send (data)
data = "1) Network SSID: "
client_socket.send (data)
ssid = client_socket.recv(512)
print "Network SSID:",ssid
data = "2) Login: "
client_socket.send (data)
login = client_socket.recv(512)
print "Login:",login
data = "3) Password: "
client_socket.send (data)
passw = client_socket.recv(512)
print "Password:",passw
data = "Restarting server and attempting to connect to "+ssid
client_socket.send (data)
sleep(1)
sys.exit(0)
print "TCPServer Waiting for client on port",port
try:
while 1:
client_socket, address = server_socket.accept()
print "I got a connection from ", address
data = "connection!"
client_socket.send (data)
while 1:
data = client_socket.recv(512)
if ( data == 'q' or data == 'Q'):
client_socket.close()
break;
elif (data == 'C' or data == 'config'): # Enter configuration mode
configLoop();
else:
print "RECIEVED:" , data
finally:
sdRef.close()
The server never sees an incoming connection. On the client side, the error var has the value or something- nothing what I'd expect. Help please?
OLD (more complicated version, uses the same server):
My debug output basically says the service is resolved... and then I get a socket disconnect immediately after. Meanwhile my server sits there and hasn't seen incoming connections.
I've walked through with the debugger when I get the initial connection and print out the name of the service- and then it hangs until I press continue and then I get a socket disconnect immediately after???
I'm very new to Objective-C and event driven programming, so perhaps I'm handling something wrong? I appreciate any advice!
Client (Debugger) output:
Running…
2011-05-10 14:10:26.822 Client[34709:a0f] TEST
2011-05-10 14:10:26.850 Client[34709:a0f] Socket disconnected
2011-05-10 14:10:29.724 Client[34709:a0f] Could not resolve: {
NSNetServicesErrorCode = -72003;
NSNetServicesErrorDomain = 10;
}
Server output:
Registering Bonjour service
ready= ([<DNSServiceRef object at 0x100583290>], [], [])
Registered service:
name = TEST
regtype = _xxx._tcp.
domain = local.
TCPServer Waiting for client on port 5000
The client code (ClientController.m, basically lifted from http://www.macresearch.org/cocoa-scientists-part-xxix-message):
#import "ClientController.h"
#import "AsyncSocket.h"
#interface ClientController ()
#property (readwrite, retain) NSNetServiceBrowser *browser;
#property (readwrite, retain) NSMutableArray *services;
#property (readwrite, assign) BOOL isConnected;
#property (readwrite, retain) NSNetService *connectedService;
#property (readwrite, retain) MTMessageBroker *messageBroker;
#end
#implementation ClientController
#synthesize browser;
#synthesize services;
#synthesize isConnected;
#synthesize connectedService;
#synthesize socket;
#synthesize messageBroker;
-(void)awakeFromNib {
services = [NSMutableArray new];
self.browser = [[NSNetServiceBrowser new] autorelease];
self.browser.delegate = self;
self.isConnected = NO;
}
-(void)dealloc {
self.connectedService = nil;
self.browser = nil;
self.socket = nil;
self.messageBroker = nil;
[services release];
[super dealloc];
}
-(IBAction)search:(id)sender {
[self.browser searchForServicesOfType:#"_xxx._tcp." inDomain:#""];
}
-(IBAction)connect:(id)sender {
NSNetService *remoteService = servicesController.selectedObjects.lastObject;
remoteService.delegate = self;
[remoteService resolveWithTimeout:30];
NSLog(#"%#",remoteService.name);
}
-(IBAction)send:(id)sender {
NSData *data = [textView.string dataUsingEncoding:NSUTF8StringEncoding];
NSLog(textView.string);
// Use socket to send raw text data once connected
}
#pragma mark AsyncSocket Delegate Methods
-(void)onSocketDidDisconnect:(AsyncSocket *)sock {
NSLog(#"Socket disconnected");
}
-(BOOL)onSocketWillConnect:(AsyncSocket *)sock {
if ( messageBroker == nil ) {
[sock retain];
return YES;
}
return NO;
}
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
//MTMessageBroker *newBroker = [[[MTMessageBroker alloc] initWithAsyncSocket:socket] autorelease];
//[sock release];
//newBroker.delegate = self;
//self.messageBroker = newBroker;
self.isConnected = YES;
}
#pragma mark Net Service Browser Delegate Methods
-(void)netServiceBrowser:(NSNetServiceBrowser *)aBrowser didFindService:(NSNetService *)aService moreComing:(BOOL)more {
[servicesController addObject:aService];
}
-(void)netServiceBrowser:(NSNetServiceBrowser *)aBrowser didRemoveService:(NSNetService *)aService moreComing:(BOOL)more {
[servicesController removeObject:aService];
if ( aService == self.connectedService ) self.isConnected = NO;
}
-(void)netServiceDidResolveAddress:(NSNetService *)service {
NSError *error;
self.connectedService = service;
self.socket = [[[AsyncSocket alloc] initWithDelegate:self] autorelease];
[self.socket connectToAddress:service.addresses.lastObject error:&error];
}
-(void)netService:(NSNetService *)service didNotResolve:(NSDictionary *)errorDict {
NSLog(#"Could not resolve: %#", errorDict);
}
#end

I am having the same issue. It seems the problem is that a connection is already in place : "Attempting to connect while connected or accepting connections. Disconnect first". Unfortunately i haven't been able to resolve it yet. Any idea ? It might a delay or ordering process problem.
EDIT :
Actually, changing connectToAddress to ConnectToHost made it for me ! :)

Related

Programmatically creating an ad-hoc network in Big Sur

Before Mac OS Big Sur, one could create an ad-hoc network by calling the startIBSSModeWithSSID:security:channel:password:error: function of a CWInterface obtained from a CWWifiClient. It seems that after an update to Big Sur, the above function is deprecated and throws a kCWOperationNotPermittedErr (-3930) error every time.
I tried launching the application from root, and it still refused to create an ad-hoc network. Meanwhile, using the "Create Network" option in the WiFi dropdown menu works with an administrator password.
A previous answer on this site I have come across is outdated and the code does not work anymore. There is a post on the Apple Developer forums created 5 months ago but it remains unanswered, with the "solution" being to file a tech support incident.
This is the code I am using:
#import <Foundation/Foundation.h>
#import <CoreWLAN/CoreWLAN.h>
#import <SecurityFoundation/SFAuthorization.h>
#import <objc/message.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
bool success = 0;
CWWiFiClient* wifiClient = [CWWiFiClient sharedWiFiClient];
CWInterface* interface = [wifiClient interface];
NSString* namestr = #"very_creative_ssid";
NSData* name = [namestr dataUsingEncoding:NSUTF8StringEncoding];
NSString* pass = #"very_cruel_framework"; // not used
NSError* err = nil;
success = [interface startIBSSModeWithSSID:name
security:kCWIBSSModeSecurityNone
channel:11
password:nil
error:&err];
if (!success) {
NSLog(#"%#", err);
return 1;
}
[NSRunLoop.currentRunLoop run];
}
return 0;
}
Is there a way to programmatically create an ad-hoc network in Big Sur without throwing an error?
Edit: Here is the console output (1 line):
2022-01-12 05:25:03.723 cwlantest[15305:448617] Error Domain=com.apple.coreWLAN.error Code=-3930 "(null)"
I'm going to put this as an answer, if anyone finds anything new or Apple adds this feature in the future, I'll be very happy to be wrong.
TLDR: Not anymore!
Since Apple removed the "Create network..." option from the wifi menubar, the only way to create an ad-hoc network is through Network Sharing. I followed https://www.makeuseof.com/how-to-create-a-secure-ad-hoc-network-in-macos/ under the How to Create a Secure Ad Hoc Network section to make a network:
sudo networksetup -createnetworkservice AdHoc lo0
sudo networksetup -setmanual AdHoc 192.168.1.88 255.255.255.255
And in System Preferences, share your network connection from AdHoc over WiFi.
With that on, I checked the CWInterface.interfaceMode() and it was in HostAP mode. Pure speculation, but I think IBSS was removed completely, it's marked as Deprecated in the developer documentation. -3930 is kCWOperationNotPermittedErr, so I'm not 100% sure that's accurate, but it's possible.
There are private interfaces to set HostAP mode in CoreWLAN:
https://github.com/onmyway133/Runtime-Headers/blob/master/macOS/10.13/CoreWLAN.framework/CWInterface.h https://medium.com/swlh/calling-ios-and-macos-hidden-api-in-style-1a924f244ad1 https://gist.github.com/wolever/4418079
After replacing objc_msgsend with NSInvocation in the last link since objc_msgsend seems to have been removed:
#import <CoreWLAN/CoreWLAN.h>
#import <objc/message.h>
int main(int argc, char* argv[]) {
#autoreleasepool {
int ch;
NSString *ssid = nil, *password = nil;
while((ch = getopt(argc, argv, "s:p:h")) != -1) {
switch(ch) {
case 's':
ssid = [NSString stringWithUTF8String:optarg];
break;
case 'p':
password = [NSString stringWithUTF8String:optarg];
break;
case '?':
case 'h':
default:
printf("USAGE: %s [-s ssid] [-p password] [-h] command\n", argv[0]);
printf("\nOPTIONS:\n");
printf(" -s ssid SSID\n");
printf(" -p password WEP password\n");
printf(" -h Print help\n");
printf("\nCOMMAND:\n");
printf(" status Print interface mode\n");
printf(" start Start Host AP mode\n");
printf(" stop Stop Host AP mode\n");
return 0;
}
}
NSString *command = nil;
if(argv[optind]) {
command = [NSString stringWithUTF8String:argv[optind]];
}
CWInterface *iface = [[CWWiFiClient sharedWiFiClient] interface];
if(!command || [command isEqualToString:#"status"]) {
NSString *mode = nil;
switch(iface.interfaceMode) {
case kCWInterfaceModeStation:
mode = #"Station";
break;
case kCWInterfaceModeIBSS:
mode = #"IBSS";
break;
case kCWInterfaceModeHostAP:
mode = #"HostAP";
break;
case kCWInterfaceModeNone:
default:
mode = #"None";
}
printf("%s\n", [mode UTF8String]);
} else if([command isEqualToString:#"stop"]) {
// Stop Host AP mode
if(getuid() != 0) {
printf("this may need root (trying anyway)...\n");
}
SEL selector = #selector(stopHostAPMode);
NSMethodSignature *signature = [iface methodSignatureForSelector: selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = iface;
invocation.selector = selector;
[invocation invoke];
printf("Done?");
//objc_msgSend(iface, #selector(stopHostAPMode));
} else if([command isEqualToString:#"start"]) {
if(!ssid) {
printf("error: an ssid must be specified\n");
return 1;
}
// known security types:
// 2: no securiry
// 16: wep
// Note: values [-127..127] have been tried, and all but these return errors.
unsigned long long securityType = 2;
if(password) {
if([password length] < 10) {
printf("error: password too short (must be >= 10 characters)\n");
return 1;
}
securityType = 16;
}
NSSet *chans = [iface supportedWLANChannels];
//printf("chan count: %lu\n", [chans count]);
NSEnumerator *enumerator = [chans objectEnumerator];
CWChannel *channel;
while ((channel = [enumerator nextObject])) {
//printf("channel: %lu\n", [channel channelNumber]);
if ([channel channelNumber] == 11)
break;
}
printf("Found Channel: %d\n", channel.channelNumber);
// Start Host AP mode
NSError *error = nil;
NSError **errorptr = &error;
SEL selector = #selector(startHostAPModeWithSSID:securityType:channel:password:error:);
NSMethodSignature *signature = [iface methodSignatureForSelector: selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = iface;
invocation.selector = selector;
NSString * ssidstr = #"Test";
NSString * pass = #"barbarbarr";
NSData * ssidArg = [ssidstr dataUsingEncoding:NSUTF8StringEncoding];
[invocation setArgument: &ssidArg atIndex:2];
[invocation setArgument: &securityType atIndex:3];
[invocation setArgument: &channel atIndex:4];
[invocation setArgument: &pass atIndex:5];
[invocation setArgument: &errorptr atIndex:6];
[invocation invoke];
BOOL success;
[invocation getReturnValue:&success];
if (!success) {
printf("startHostAPModeWithSSID error: %s\n", [(*errorptr).localizedDescription UTF8String]);
return 1;
} else {
printf("Success?\n");
return 0;
}
}
return 0;
}
}
./hostap stop does successfully kick me out of hostap mode started from network sharing, but ./hostap start fails with -3903 kCWNotSupportedErr.
Also, using startHostAPMode: without other settings does succeed, but the wifi menu shows WiFi: Internet Sharing, so I think this is a private api meant specifically for network sharing and will likely need other configuration to get working. You could potentially continue down that road, but it didn't look very promising. The best bet is to just use network sharing or potentially look into scripting System Preferences with AppleScript if you really want a scripted approach.

Need create VPN connect L2TP on osx

Need create VPN connection L2TP on osx without Shared Secret
NSString *server = #"serverIP";
NSString *username = #"user";
NSString *password = #"pass";
const void* passwordData = [[password dataUsingEncoding:NSUTF8StringEncoding] bytes];
[vpnManager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Load config failed [%#]", error.localizedDescription);
return;
}
NEVPNProtocol *p = (NEVPNProtocol *)vpnManager.protocolConfiguration;
if (!p) {
p = [[NEVPNProtocol alloc] init];
}
p.username = username;
p.serverAddress = server;
p.passwordReference = (__bridge NSData * _Nullable)(passwordData);
p.disconnectOnSleep = NO;
vpnManager.protocolConfiguration = p;
vpnManager.localizedDescription = #"L2TPOverIPSec";
vpnManager.enabled = YES;
[vpnManager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Save config failed [%#]", error.localizedDescription);
}
}];
}];
NEVPNConnection *connect = [vpnManager connection];
NSError *error1;
if ([connect startVPNTunnelAndReturnError: &error1]) {
NSLog(#"connect");
} else {
NSLog(#"not connect");
}
after building i get this error Missing protocol or protocol has invalid type, and other 2 protocols use IKE tunnel, what can u advice to me? other option is run terminal from code and add this string networksetup -connectpppoeservice VPNConnect but i dont know if it possible
I know this is the old question, but I'm here to note, that Network Extension framework (where NEVPNProtocol is from) can't go on with OSI level 2 protocols, which is L2TP. (https://forums.developer.apple.com/thread/29909)
It seems now (starting from iOS 8 and os x 10.10) the recommended way on Apple devices is to use built-in protocols, or implement your own but on L3 / L4: https://developer.apple.com/documentation/networkextension
(And so there is no public API for using L2TP)
No, you need use SCNetwork and Helper Tool (to get root access) With this link you will be able to create L2TP Protocol and connect with it.
This works even on the latest version MacOS 11 "Big Sure"

associate arbitrary information with the socket using GCDAyncSocket

I have created a simple client server application with GCDAsyncSocket.
host broadcast its service via bonjour, clients get connected to it, then each client send specific information to host, after completing requests by host, it should now be able to send each request to its corresponding client.
Here when client disconnected (maybe rebooting WiFi) before It gets its completed request and connected again its connected host and port can change.
so for I denitrifying clients I want to store list of connected clients in a dictionary
key : vendorID (specific to each client and can't change)
value : connected host and port (gcdAsyncSocket instance)
how can I send arbitrary information (vendorID) with socket?
it seems GCDAsyncSocket has a property named UserData, I set it with vendorID in the client
but in the host it's NULL.
client:
- (BOOL)connectWithService:(NSNetService *)service {
BOOL _isConnected = NO;
// Copy Service Addresses
NSArray *addresses = [[service addresses] mutableCopy];
if (!self.socket || ![self.socket isConnected]) {
// Initialize Socket
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//adding deviceID to sockect to identify connected clients in the host
self.socket.userData = #"vendorID2308746238764021";
// Connect
while (!_isConnected && [addresses count]) {
NSData *address = [addresses objectAtIndex:0];
NSError *error = nil;
if ([self.socket connectToAddress:address error:&error]) {
_isConnected = true;
}
else if (error) {
NSLog(#"Unable to connect to address. Error %# with user info %#.", error, [error userInfo]);
}
}
} else {
_isConnected = [self.socket isConnected];
}
return _isConnected;
}
Host:
- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
NSLog(#"Accepted New Socket from %#:%hu", [newSocket connectedHost], [newSocket connectedPort]);
//both userData is null!
NSLog(#"%#",newSocket.userData);
NSLog(#"%#",socket.userData);
//adding new socket to the list of connected clients
//using ip and port of the scoket as key
[self.clients setObject:newSocket forKey:[NSString stringWithFormat:#"%#-%d", [newSocket connectedHost], [newSocket connectedPort]]];
// Notify Delegate
[self.delegate tcpHost:self didHostClientOnSocket:newSocket];
}
does anyone has any idea how should I solve this kind of client-host information exchange?
ClientSide: In my TCPClient class when client connected to the server, the first packet I send is Identity packet:
//server can identify device
- (void)sendIdentity
{
NSData *identifcationData = [[UIDevice deviceID] dataUsingEncoding:NSUTF8StringEncoding];
HAPacket *packet = [[HAPacket alloc] initWithData:identifcationData type:HAPacketTypeIdentity action:HAPacketActionUnknown];
[self sendPacket:packet];
}
Server: gets this packet and identify the Client
In TCPHost: after client connected I store it in a temporary array.
in socket:didReadData I check:
if (tag == TAG_BODY) {
//first packet is always for identifaction. if client has not identified, its id is empty
Client *unknownClient = [[self.temp linq_where:^BOOL(Client *client)
{
return client.ID == nil || [client.ID isEqualToString: #""];
}] linq_firstOrNil];
if (unknownClient && (unknownClient.ID == nil || [unknownClient.ID isEqualToString: #""])) {
NSLog(#"identifying client...");
[self identifyClient:unknownClient withData:data];
}
else
{
// parse the packet as usual
}

Threading and Sockets in Objective-C

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!

Problem with creating sockets using CFSocket in Objective-C (iPhone app)

Ok, I have a problem with building a socket using Objective-C. If you'll take a look at my code below, through help with example code and other sources, I was able to build a complete socket I believe. The problem is that when I compile it, it builds fine (no syntax problems), but there are no sockets being created. As you'll notice I've commented out a lot of things in Server2.m and have isolated the problem to the very beginning when I create the struct for the listeningSocket. By the way, if this helps, it is part of the the server side of server-client application. Does anyone know why I would be getting this problem? Everything seemed to be working fine yesterday, and this morning I thought I would take a different approach to building the sockets, so I tried this. Thanks for any help!
Server_TrialViewController.m
#include <CFNetwork/CFSocketStream.h>
#import <UIKit/UIKit.h>
#import "Server2.h"
#import "Client_Test.h"
#interface Server_TrialViewController : UIViewController {
IBOutlet UIButton *ServerButton;
IBOutlet UIButton *ClientButton;
IBOutlet UILabel *statusLabel;
Server2 *server;
Client_Test *client;
}
#property(nonatomic, retain) UILabel *statusLabel;
#property(nonatomic, retain) Server2 *server;
#property(nonatomic, retain) Client_Test *client;
-(IBAction)serverButtonPressed;
-(IBAction)clientButtonPressed;
//-(void)sendMessageWithServer:(Server_Test *)SERVER AndClient:(Client_Test *)CLIENT;
#end
Server_TrialViewController.h
#import "Server_TrialViewController.h"
#implementation Server_TrialViewController
#synthesize statusLabel;
#synthesize server;
#synthesize client;
-(IBAction)serverButtonPressed {
if ([server start]) {
[statusLabel setText:#"Success"];
}
else {
if (server.status == NULL) {
[statusLabel setText: #"No Server: No statUpdate"];
}
else {
[statusLabel setText: #"No Server: Found statUpdate"];
}
}
}
-(IBAction)clientButtonPressed {
if ([client start]) {
[statusLabel setText:#"Client Started"];
}
else {
[statusLabel setText:#"Client Not Started"];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
- (void)dealloc {
[super dealloc];
}
#end
Server2.h
#import <Foundation/Foundation.h>
#import "Server2Delegate.h"
#interface Server2 : NSObject
{
uint16_t port;
CFSocketRef listeningSocket;
id<Server2Delegate> delegate;
NSNetService* netService;
NSString *status;
}
// Initialize connection
- (BOOL)start;
- (void)stop;
// Delegate receives various notifications about the state of our server
#property(nonatomic,retain) id<Server2Delegate> delegate;
#property(nonatomic, retain) NSString *status;
#end
Server2.m
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <CFNetwork/CFSocketStream.h>
#import "Server2.h"
#import "Connection2.h"
#import "AppConfig2.h"
// Declare some private properties and methods
#interface Server2 ()
#property(nonatomic,assign) uint16_t port;
#property(nonatomic,retain) NSNetService* netService;
-(BOOL)createServer;
-(void)terminateServer;
#end
// Implementation of the Server interface
#implementation Server2
#synthesize delegate;
#synthesize port, netService;
#synthesize status;
// Cleanup
- (void)dealloc
{
self.netService = nil;
self.delegate = nil;
[super dealloc];
}
// Create server and announce it
- (BOOL)start
{
// Start the socket server
if ( ! [self createServer] )
{
status = #"Server Not Created";
return FALSE;
}
status = #"Server Created";
return TRUE;
}
// Close everything
- (void)stop {
[self terminateServer];
}
#pragma mark Callbacks
// Handle new connections
- (void)handleNewNativeSocket:(CFSocketNativeHandle)nativeSocketHandle {
Connection2* connection = [[[Connection2 alloc] initWithNativeSocketHandle:nativeSocketHandle] autorelease];
// In case of errors, close native socket handle
if ( connection == nil ) {
close(nativeSocketHandle);
return;
}
// finish connecting
if ( ! [connection connect] ) {
//status = #"Connection Not Made";
[connection close];
return;
}
//status = #"Connection Made";
// Pass this on to our delegate
[delegate handleNewConnection:connection];
}
// This function will be used as a callback while creating our listening socket via 'CFSocketCreate'
static void serverAcceptCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
Server2 *server = (Server2*)info;
// We can only process "connection accepted" calls here
if ( type != kCFSocketAcceptCallBack ) {
return;
}
// for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle*)data;
[server handleNewNativeSocket:nativeSocketHandle];
}
#pragma mark Sockets and streams
- (BOOL)createServer
{
//// PART 1: Create a socket that can accept connections
// Socket context
// struct CFSocketContext {
// CFIndex version;
// void *info;
// CFAllocatorRetainCallBack retain;
// CFAllocatorReleaseCallBack release;
// CFAllocatorCopyDescriptionCallBack copyDescription;
// };
CFSocketContext socketContext = {0, self, NULL, NULL, NULL};
listeningSocket = CFSocketCreate(
kCFAllocatorDefault,
PF_INET, // The protocol family for the socket
SOCK_DGRAM, // The socket type to create
IPPROTO_UDP, // The protocol for the socket. TCP vs UDP.
0, //kCFSocketAcceptCallBack, // New connections will be automatically accepted and the callback is called with the data argument being a pointer to a CFSocketNativeHandle of the child socket.
NULL, //(CFSocketCallBack)&serverAcceptCallback,
&socketContext );
// Previous call might have failed
if ( listeningSocket == NULL ) {
status = #"listeningSocket Not Created";
return FALSE;
}
else {
status = #"listeningSocket Created";
return TRUE;
}
}
/*
// getsockopt will return existing socket option value via this variable
int existingValue = 1;
// Make sure that same listening socket address gets reused after every connection
setsockopt( CFSocketGetNative(listeningSocket),
SOL_SOCKET, SO_REUSEADDR, (void *)&existingValue,
sizeof(existingValue));
//// PART 2: Bind our socket to an endpoint.
// We will be listening on all available interfaces/addresses.
// Port will be assigned automatically by kernel.
struct sockaddr_in socketAddress;
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sin_len = sizeof(socketAddress);
socketAddress.sin_family = AF_INET; // Address family (IPv4 vs IPv6)
socketAddress.sin_port = 0; // Actual port will get assigned automatically by kernel
socketAddress.sin_addr.s_addr = htonl(INADDR_ANY); // We must use "network byte order" format (big-endian) for the value here
// Convert the endpoint data structure into something that CFSocket can use
NSData *socketAddressData =
[NSData dataWithBytes:&socketAddress length:sizeof(socketAddress)];
// Bind our socket to the endpoint. Check if successful.
if ( CFSocketSetAddress(listeningSocket, (CFDataRef)socketAddressData) != kCFSocketSuccess ) {
// Cleanup
if ( listeningSocket != NULL ) {
status = #"Socket Not Binded";
CFRelease(listeningSocket);
listeningSocket = NULL;
}
return FALSE;
}
status = #"Socket Binded";
//// PART 3: Find out what port kernel assigned to our socket
// We need it to advertise our service via Bonjour
NSData *socketAddressActualData = [(NSData *)CFSocketCopyAddress(listeningSocket) autorelease];
// Convert socket data into a usable structure
struct sockaddr_in socketAddressActual;
memcpy(&socketAddressActual, [socketAddressActualData bytes],
[socketAddressActualData length]);
self.port = ntohs(socketAddressActual.sin_port);
//// PART 4: Hook up our socket to the current run loop
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
CFRunLoopSourceRef runLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, listeningSocket, 0);
CFRunLoopAddSource(currentRunLoop, runLoopSource, kCFRunLoopCommonModes);
CFRelease(runLoopSource);
return TRUE;
}
*/
- (void) terminateServer {
if ( listeningSocket != nil ) {
CFSocketInvalidate(listeningSocket);
CFRelease(listeningSocket);
listeningSocket = nil;
}
}
#pragma mark -
#pragma mark NSNetService Delegate Method Implementations
// Delegate method, called by NSNetService in case service publishing fails for whatever reason
- (void)netService:(NSNetService*)sender didNotPublish:(NSDictionary*)errorDict {
if ( sender != self.netService ) {
return;
}
// Stop socket server
[self terminateServer];
}
#end
For people looking for information about CFSocket server here's the answer: The code above is working fine if you change "SOCK_DGRAM" to "SOCK_STREAM".
Have you tried setting kCFSocketAcceptCallBack to something other than 0?
If you're interested in socket programming on Mac OS X or the iPhone, I suggest you look at this example from Apple's documentation.