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

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.

Related

Setting media playback info in MacOs not working

I'm a newbie in ObjC and MacOs development.
My final goal is understand how setting playback info works from ObjC to try to implement that later on Rust (using generated ObjC runtime bindings).
So right now I'm trying to write small piece of code on ObjC that would set playback info (without actually playing anything).
I have found https://github.com/MarshallOfSound/electron-media-service/blob/master/src/darwin/service.mm and used it as a base.
Here's code I have right now:
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <objc/runtime.h>
#include "MediaPlayer/MPNowPlayingInfoCenter.h"
#include "MediaPlayer/MPRemoteCommandCenter.h"
#include "MediaPlayer/MPRemoteCommand.h"
#include "MediaPlayer/MPMediaItem.h"
#include "MediaPlayer/MPRemoteCommandEvent.h"
#interface NativeMediaController : NSObject { }
#end
#implementation NativeMediaController
- (MPRemoteCommandHandlerStatus)remotePlay {
NSLog(#"Play");
return MPRemoteCommandHandlerStatusSuccess;
}
- (MPRemoteCommandHandlerStatus)remotePause {
NSLog(#"Pause");
return MPRemoteCommandHandlerStatusSuccess;
}
- (MPRemoteCommandHandlerStatus)remoteTogglePlayPause {
NSLog(#"PlayPause");
return MPRemoteCommandHandlerStatusSuccess;
}
- (MPRemoteCommandHandlerStatus)remoteNext {
NSLog(#"Next");
return MPRemoteCommandHandlerStatusSuccess;
}
- (MPRemoteCommandHandlerStatus)remotePrev {
NSLog(#"Previous");
return MPRemoteCommandHandlerStatusSuccess;
}
- (MPRemoteCommandHandlerStatus)remoteChangePlaybackPosition:(MPChangePlaybackPositionCommandEvent*)event {
NSLog(#"ChangePlaybackPosition");
return MPRemoteCommandHandlerStatusSuccess;
}
- (MPRemoteCommandHandlerStatus)move:(MPChangePlaybackPositionCommandEvent*)event {
NSLog(#"Move");
return MPRemoteCommandHandlerStatusSuccess;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
// insert code here...
NSLog(#"Hello, World!");
NativeMediaController* controller = [[NativeMediaController alloc] init];
MPRemoteCommandCenter *remoteCommandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[remoteCommandCenter playCommand].enabled = true;
[remoteCommandCenter pauseCommand].enabled = true;
[remoteCommandCenter togglePlayPauseCommand].enabled = true;
[remoteCommandCenter changePlaybackPositionCommand].enabled = true;
[remoteCommandCenter nextTrackCommand].enabled = true;
[remoteCommandCenter previousTrackCommand].enabled = true;
[[remoteCommandCenter playCommand] addTarget:controller action:#selector(remotePlay)];
[[remoteCommandCenter pauseCommand] addTarget:controller action:#selector(remotePause)];
[[remoteCommandCenter togglePlayPauseCommand] addTarget:controller action:#selector(remoteTogglePlayPause)];
[[remoteCommandCenter changePlaybackPositionCommand] addTarget:controller action:#selector(remoteChangePlaybackPosition:)];
[[remoteCommandCenter nextTrackCommand] addTarget:controller action:#selector(remoteNext)];
[[remoteCommandCenter previousTrackCommand] addTarget:controller action:#selector(remotePrev)];
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
[songInfo setObject:[NSString stringWithUTF8String:"Test title"] forKey:MPMediaItemPropertyTitle];
[songInfo setObject:[NSString stringWithUTF8String:"Test artist"] forKey:MPMediaItemPropertyArtist];
[songInfo setObject:[NSString stringWithUTF8String:"Test albumtitle"] forKey:MPMediaItemPropertyAlbumTitle];
[songInfo setObject:[NSNumber numberWithFloat:60.0] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[songInfo setObject:[NSNumber numberWithFloat:360.0] forKey:MPMediaItemPropertyPlaybackDuration];
[songInfo setObject:[NSNumber numberWithFloat:112233] forKey:MPMediaItemPropertyPersistentID];
[MPNowPlayingInfoCenter defaultCenter].playbackState = MPNowPlayingPlaybackStatePlaying;
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
NSLog(#"End!");
char input[50] = {0};
printf("Enter anything to quit: ");
scanf("%s", input);
}
return 0;
}
When this code is executed I see no effect in MacOs playing info widget.
Unfortunately right now I have no idea how to debug this or where can I find better ObjC example.
Finally, I found the reason that caused my problem playing with Apple Swift example.
There're two requirements to show playback widget (i didn't find them documented anywhere) that have been missing:
You should register a callback for at least one action (play, pause)
You should build your application as an App, not as Console. If your program is not shown in the dock as a running app - the playback widget wouldn't work.

Reading Serial Port iOS

I have the following code to read and write to serial ports on iOS 10.3.3 Jailbroken iPhone 6S (I used h3lix to jailbreak):
Serial.h:
//
// Serial.h
// iOUSB
//
// Created by Brandon on 2018-05-21.
// Copyright © 2018 XIO. All rights reserved.
//
#if !defined(__cplusplus) //-fmodules -fcxx-modules
#import Foundation;
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wauto-import"
#import <Foundation/Foundation.h>
#pragma clang diagnostic pop
#endif
#define BAUD_RATE 9600
#interface Serial : NSObject
+ (instancetype)shared;
- (bool)setup:(NSString *)filePath;
- (bool)disconnect;
- (size_t)available;
- (size_t)read:(uint8_t *)bytes length:(int32_t)length;
- (size_t)write:(uint8_t *)bytes length:(int32_t)length;
- (void)flushInputStream;
- (void)flushOutputStream;
- (void)flush;
- (NSString *)lastError;
- (void)eraseLastError;
#end
Serial.mm:
//
// Serial.mm
// iOUSB
//
// Created by Brandon on 2018-05-21.
// Copyright © 2018 XIO. All rights reserved.
//
#import "Serial.h"
#include <fstream>
#include <iostream>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#define DEFAULT_BAUD_RATE 9600
#interface Serial()
#property (nonatomic, assign) int file_descriptor;
#property (nonatomic, strong) NSString *lastError;
#end
#implementation Serial
+ (instancetype)shared {
static Serial *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Serial alloc] init];
});
return instance;
}
- (instancetype)init {
if ((self = [super init])) {
self.file_descriptor = -1;
self.lastError = nil;
}
return self;
}
- (void)dealloc {
[self disconnect];
}
- (bool)setup:(NSString *)filePath {
if (self.file_descriptor != -1)
{
return true;
}
self.file_descriptor = open(filePath.UTF8String, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
if (self.file_descriptor == -1)
{
const char* error = strerror(errno);
self.lastError = [[NSString alloc] initWithUTF8String:error];
perror(error);
return false;
}
struct termios options;
struct termios oldoptions;
tcgetattr(self.file_descriptor, &oldoptions);
options = oldoptions;
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
int baud_rates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B7200, B14400, B28800, B57600, B76800, B115200, B230400
};
#else
int baud_rates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B1200, B1800, B2400, B4800, B9600, B19200, B38400};
#endif
auto it = std::find(std::begin(baud_rates), std::end(baud_rates), BAUD_RATE);
if (it != std::end(baud_rates))
{
cfsetispeed(&options, *it);
cfsetospeed(&options, *it);
std::cout<<"BAUD_RATE Set Successfully!\n";
}
else
{
cfsetispeed(&options, DEFAULT_BAUD_RATE);
cfsetospeed(&options, DEFAULT_BAUD_RATE);
std::cerr<<"Invalid BAUD_RATE.. Setting to default: "<<DEFAULT_BAUD_RATE<<"\n";
}
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag |= CS8;
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
tcsetattr(self.file_descriptor, TCSANOW, &options);
return true;
}
- (bool)disconnect {
if (self.file_descriptor != -1)
{
close(self.file_descriptor);
self.file_descriptor = -1;
self.lastError = nil;
}
return self.file_descriptor == -1;
}
- (size_t)available {
if (self.file_descriptor == -1)
{
return -1;
}
int available = -1;
ioctl(self.file_descriptor, FIONREAD, &available);
return available;
}
- (size_t)read:(uint8_t *)bytes length:(int32_t)length {
if (self.file_descriptor == -1)
{
return -1;
}
ssize_t bytesRead = read(self.file_descriptor, bytes, length);
if (bytesRead < 0)
{
const char* error = strerror(errno);
self.lastError = [[NSString alloc] initWithUTF8String:error];
perror(error);
}
return bytesRead;
}
- (size_t)write:(uint8_t *)bytes length:(int32_t)length {
if (self.file_descriptor == -1)
{
return -1;
}
ssize_t bytesWritten = write(self.file_descriptor, bytes, length);
if (bytesWritten <= 0)
{
const char* error = strerror(errno);
self.lastError = [[NSString alloc] initWithUTF8String:error];
perror(error);
}
return bytesWritten;
}
- (void)flushInputStream {
if (self.file_descriptor == -1)
{
return;
}
tcflush(self.file_descriptor, TCIFLUSH);
}
- (void)flushOutputStream {
if (self.file_descriptor == -1)
{
return;
}
tcflush(self.file_descriptor, TCOFLUSH);
}
- (void)flush {
if (self.file_descriptor == -1)
{
return;
}
tcflush(self.file_descriptor, TCIOFLUSH);
}
- (NSString *)lastError {
return _lastError ?: #"";
}
- (void)eraseLastError {
_lastError = nil;
}
#end
I purchased the OTG adapter: "Lightning to USB 3 Camera Adapter" shown here: https://www.apple.com/ca/shop/product/MK0W2AM/A/lightning-to-usb-3-camera-adapter
I have plugged in the device and it shows up as unsupported device which is fine, since it's not MFI-Certified.
However, when I try to open /dev/tty.iap, I keep getting Error: Resource Busy.
I am able to open /dev/tty.wlan for example.. Only some ports give me trouble like the iap one.
What am I doing wrong that I cannot read tty.iap? I have tried to run the application as root by moving it to /Applications from /var/containers/Bundle/Applications.. I've tried to chown root:wheel the app and chmod 0777 the app. Still I get resource busy..
I've read online that you get this error when you are NOT root.
How can I run my app as root?
How can I fix this so I can read the lightning port?
I tried https://stackoverflow.com/a/15970080/1462718 but it doesn't seem to work. The app would launch and then immediately close no matter how many times I try.
By default the serial port is in use as an out-of-band management interface (allowing you to connect to the system via a serial connection if the network is down).
You will need to disable this before you can reuse the serial port for any other purpose.
The specifics of how to do this vary by OS version.

NSOutputStream is always waiting for space available after second NSOperation tries to write to it

I am creating an NSOutputStream and passing it to an NSOperation, which I call from an NSOperationQueue. In the operation, I am polling for hasSpaceAvailable so that I can write out to the socket. This works fine the first time I write out to the socket. After the operation returns, however, and I try to write again to the socket, the write never goes through as I'm infinitely waiting for space to become available in the output socket. I've tried opening/closing the output stream each time I write, but have the same problem.
I open the output stream in the init function (the NSOutputStream is created from a Bluetooth EASession:
_commandSession = [[EASession alloc] initWithAccessory:self.selectedAccessory forProtocol:commandProtocolString];
_commandOutputStream = [_commandSession outputStream];
[_commandOutputStream open];
I also create the operation queue in the init:
_senderOperationQueue = [[NSOperationQueue alloc] init];
_senderOperationQueue.name = #"Send Queue";
_senderOperationQueue.maxConcurrentOperationCount = 1;
I have a text field with the data I want to send over the output stream. This function is called each time I click the send button:
-(void)sendCommandData:(NSData *)buf
{
_commandSendOperation =[[SenderOperation alloc] initWithStream:_commandOutputStream data:buf delegate:self];
[_senderOperationQueue addOperation:_commandSendOperation];
}
This is how my operation code looks like:
(SenderOperation.h)
#import <Foundation/Foundation.h>
#protocol SenderOperationDelegate;
#interface SenderOperation : NSOperation
{
NSOutputStream *_stream;
NSData *_sendData;
}
#property (nonatomic, assign) id <SenderOperationDelegate> delegate;
#property (nonatomic, retain) NSOutputStream *stream;
#property (nonatomic, retain) NSData *sendData;
- (id)initWithStream:(NSOutputStream *)stream data:(NSData *)buf delegate:(id<SenderOperationDelegate>)theDelegate;
#end
// Delegate to notify main thread the completion of a BT input buffer stream
#protocol SenderOperationDelegate <NSObject>
-(void)sendComplete:(SenderOperation *)sender;
#end
(SenderOperation.m)
#import "SenderOperation.h"
#implementation SenderOperation
#synthesize delegate = _delegate;
#synthesize stream = _stream;
#synthesize sendData = _sendData;
- (id)initWithStream:(NSOutputStream *)stream data:(NSData *)buf delegate:(id<SenderOperationDelegate>)theDelegate
{
if (self = [super init])
{
self.delegate = theDelegate;
self.stream = stream;
self.sendData = buf;
}
return self;
}
#define MAX_PACKET_SIZE 20
- (void)main
{
if (self.isCancelled)
return;
// total length of the data packet we need to send
int totalLength = [_sendData length];
// length of packet to send (given our upper bound)
int len = (totalLength <= MAX_PACKET_SIZE) ? totalLength:MAX_PACKET_SIZE;
// stream write response
int streamWriteResponse;
// bytes already written out to the output stream
int bytesWritten = 0;
while (1)
{
if (!len) break;
if ([self.stream hasSpaceAvailable])
{
streamWriteResponse = [self.stream write:[self.sendData bytes] maxLength:len];
if (streamWriteResponse == -1)
{
break;
}
bytesWritten += streamWriteResponse;
// update the data buffer with left over data that needs to be written
if (totalLength - bytesWritten)
self.sendData = [self.sendData subdataWithRange:NSMakeRange(bytesWritten, totalLength - bytesWritten)];
// update length of next data packet write
len = (totalLength - bytesWritten) >= MAX_PACKET_SIZE ? MAX_PACKET_SIZE : (totalLength - bytesWritten);
}
}
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(sendComplete:) withObject:self waitUntilDone:NO];
}
You'll need to use run-loop scheduling for your stream, rather than polling. Quoth the docs for -[EASession outputStream]:
This stream is provided for you automatically by the session object but you must configure it if you want to receive any associated stream events. You do this by assigning a delegate object to the stream that implements the stream:handleEvent: delegate method. You must then schedule the stream in a run loop so that it can send data asynchronously from one of your application’s threads.

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

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 ! :)

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!