associate arbitrary information with the socket using GCDAyncSocket - objective-c

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
}

Related

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"

Mac OS VPN Connection Programmatically

I want to create code which will create new connection with L2TP Protocol .
below is my code.
I didn't find any error but code run successfully and create new network but it's not showing data which i passes e.g username.
- (IBAction)connectVPN_Cliced:(NSButton *)sender{
VPNServiceConfig *config = [[VPNServiceConfig alloc]init];
config.username = #"username";
config.password = #"password";
config.endpointPrefix = #"Server IP end prefix";
config.endpointSuffix = #"Server IP end endpointSuffix";
config.sharedSecret = #"sharedkey";
config.name = #"L2TP By Mac Application";
config.endpoint = #"Server IP";
config.type = 1;
[self setupVPN:config];
}
- (AuthorizationRef) getAuth {
AuthorizationRef auth = NULL;
OSStatus status;
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, [self flags], &auth);
if (status == errAuthorizationSuccess) {
NSLog(#"Successfully obtained Authorization reference");
} else {
NSLog(#"Could not obtain Authorization reference");
exit(101);
}
return auth;
}
- (AuthorizationFlags) flags {
return kAuthorizationFlagDefaults |
kAuthorizationFlagExtendRights |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize;
}
- (void)setupVPN:(VPNServiceConfig *)vpnConfig {
// Obtaining permission to modify network settings
AuthorizationRef auth = [self getAuth];
SCPreferencesRef prefs = SCPreferencesCreateWithAuthorization(kCFAllocatorDefault, CFSTR("macVPN"), NULL, auth);
SCPreferencesUnlock(prefs);
//-------- This is i tried by me to add data but it's not working ------ Start //
SCPreferencesSetValue(prefs, CFSTR("Type"), CFSTR("PPP"));
SCPreferencesSetValue(prefs, CFSTR("SubType"), CFSTR("L2TP"));
SCPreferencesSetValue(prefs, CFSTR("AuthName"), CFSTR("username"));
SCPreferencesSetValue(prefs, CFSTR("AuthPassword"), CFSTR("password"));
SCPreferencesSetValue(prefs, CFSTR("CommRemoteAddress"), CFSTR("server ip"));
NSData *pppData = [#"0000" dataUsingEncoding:NSUTF8StringEncoding];
CFDataRef ref = CFDataCreate(kCFAllocatorDefault, pppData.bytes, pppData.length);
// CFPropertyListFormat
CFPropertyListRef propertyRef = CFPropertyListCreateWithData(kCFAllocatorDefault, ref, kCFPropertyListMutableContainers, nil,nil);
Boolean isValidData = CFPropertyListIsValid(propertyRef, kCFPropertyListXMLFormat_v1_0);
SCPreferencesSetValue(prefs, CFSTR("AuthPassword"), propertyRef);
//-------- This is i tried by me to add data but it's not working ------ End //
// Making sure other process cannot make configuration modifications
// by obtaining a system-wide lock over the system preferences.
if (SCPreferencesLock(prefs, TRUE)) {
NSLog(#"Gained superhuman rights.");
} else {
NSLog(#"Sorry, without superuser privileges I won't be able to add any VPN interfaces.");
return;
}
// If everything will work out fin
[self createService:vpnConfig usingPreferencesRef:prefs];
// We're done, other processes may modify the system configuration again
return;
}
- (void) createService:(VPNServiceConfig *)config usingPreferencesRef:(SCPreferencesRef)prefs {
NSLog(#"Creating new %# Service using %#", config.humanType, config);
// These variables will hold references to our new interfaces
SCNetworkInterfaceRef bottomInterface = SCNetworkInterfaceCreateWithInterface(kSCNetworkInterfaceIPv4,kSCNetworkInterfaceTypeL2TP);
SCNetworkInterfaceRef topInterface = SCNetworkInterfaceCreateWithInterface(bottomInterface, kSCNetworkInterfaceTypePPP);
// Creating a new, fresh VPN service in memory using the interface we already created
SCNetworkServiceRef service = SCNetworkServiceCreate(prefs, topInterface);
// That service is to have a name
SCNetworkServiceSetName(service, (__bridge CFStringRef)config.name);
// And we also woould like to know the internal ID of this service
NSString *serviceID = (__bridge NSString *)(SCNetworkServiceGetServiceID(service));
// It will be used to find the correct passwords in the system keychain
//config.serviceID = serviceID;
[config setServiceID:serviceID];
// Interestingly enough, the interface variables in itself are now worthless
// We used them to create the service and that's it, we cannot modify them any more.
CFRelease(topInterface);
CFRelease(bottomInterface);
topInterface = NULL;
bottomInterface = NULL;
topInterface = SCNetworkServiceGetInterface(service);
// Let's apply all configuration to the PPTP interface
// Specifically, the servername, account username and password
if (SCNetworkInterfaceSetConfiguration(topInterface, config.L2TPIPSecConfig)) {
NSLog(#"Successfully configured PPP interface of service %#", config.name);
} else {
NSLog(#"Error: Could not configure PPP interface for service %#", config.name);
return;
}
if (SCNetworkInterfaceSetExtendedConfiguration(topInterface, kSCEntNetIPSec, config.L2TPIPSecConfig)) {
NSLog(#"Successfully configured IPSec on PPP interface for service %#", config.name);
} else {
NSLog(#"Error: Could not configure PPTP on PPP interface for service %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
//return;
}
NSLog(#"Adding default protocols (DNS, etc.) to service %#...", config.name);
if (!SCNetworkServiceEstablishDefaultConfiguration(service)) {
NSLog(#"Error: Could not establish a default service configuration for %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Fetching set of all available network services...");
SCNetworkSetRef networkSet = SCNetworkSetCopyCurrent(prefs);
if (!networkSet) {
NSLog(#"Error: Could not fetch current network set when creating %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
if (!SCNetworkSetAddService (networkSet, service)) {
if (SCError() == 1005) {
NSLog(#"Skipping VPN Service %# because it already exists.", config.humanType);
return;
} else {
NSLog(#"Error: Could not add new VPN service %# to current network set. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
}
NSLog(#"Fetching IPv4 protocol of service %#....", config.name);
SCNetworkProtocolRef protocol = SCNetworkServiceCopyProtocol(service, kSCNetworkProtocolTypeIPv4);
if (!protocol) {
NSLog(#"Error: Could not fetch IPv4 protocol of %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Configuring IPv4 protocol of service %#...", config.name);
if (!SCNetworkProtocolSetConfiguration(protocol, config.L2TPIPSecConfig)) {
NSLog(#"Error: Could not configure IPv4 protocol of %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Commiting all changes including service %#...", config.name);
if (!SCPreferencesCommitChanges(prefs)) {
NSLog(#"Error: Could not commit preferences with service %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Preparing to add Keychain items for service %#...", config.name);
// The password and the shared secret are not stored directly in the System Preferences .plist file
// Instead we put them into the KeyChain. I know we're creating new items each time you run this application
// But this actually is the same behaviour you get using the official System Preferences Network Pane
[VPNKeychain createPasswordKeyChainItem:config.name forService:serviceID withAccount:config.username andPassword:config.password];
[VPNKeychain createSharedSecretKeyChainItem:config.name forService:serviceID withPassword:config.sharedSecret];
if (!SCPreferencesApplyChanges(prefs)) {
NSLog(#"Error: Could not apply changes with service %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Successfully created %# VPN %# with ID %#", config.humanType, config.name, serviceID);
return;
}
I tried this :
how to set up a vpn connection programmatically in MAC?
Update :
Code I used to auto connect to vpn server
SCNetworkConnectionRef connetionRef = SCNetworkConnectionCreateWithServiceID(kCFAllocatorDefault, (__bridge CFStringRef)(newServiceId), myCallBack, &callBackContext);
SCNetworkConnectionStart(connetionRef, config.L2TPPPPConfig, FALSE);

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!

Help with Sending/ Receiving UDP packets - C Sockets

Ok, if you look at some of my previous questions, I've been working on getting a simple connection up and running with C sockets (I'm still fairly new to the whole networking aspect of an program, but everyone has to start somewhere, right?). I've included the code below that I have so far and when I execute it, I get no errors, but at the same time, I don't get the packet on the other end. By the way, I'm programming multicast sockets in objective-C and "msgStatus" is just a label in my GUI (it's hooked up correctly, so there's no problem there). I'm just not seeing where I'm going wrong. Could someone possibly help me or point me in the right direction? Thanks!
#define MAX_LEN 1024 /* maximum string size to send */
#define MIN_PORT 1024 /* minimum port allowed */
#define MAX_PORT 65535 /* maximum port allowed */
#define MYPORT 5673 /* port we will be using for our multicast socket */
-(void)broadcastMessage {//(NSString*)msg {
NSLog(#"broadcastMessage - Stage 1");
NSString *msg = #"From Master";
mc_ttl = 3; // number of node hops the message is allowed to travel across the network
// define the port we will be using
mc_port = MYPORT;
/* create a socket for sending to the multicast address */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: broadcastMessage - socket() failed");
return;
}
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr("225.0.0.37");
mc_addr.sin_port = htons(mc_port);
if (bind(sock, (struct sockaddr *) &mc_addr, sizeof(struct sockaddr_in)) < 0) {
NSLog(#"ERROR: bind not successful");
return;
}
NSLog(#"broadcastMessage - Stage 2");
/* set the TTL (time to live/hop count) for the send */
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &mc_ttl, sizeof(mc_ttl))) < 0) {
NSLog(#"ERROR: broadcastMessage - setsockopt() failed");
return;
}
NSLog(#"broadcastMessage - Stage 3");
/* construct a multicast address structure - erase everything in the structure first*/
memset(&mc_addr, 0, sizeof(mc_addr));
// prepare the message to be sent
char send_str[MAX_LEN];
/* clear send buffer */
memset(send_str, 0, sizeof(send_str));
// convert the message to a C string to send
[msg getCString:send_str maxLength:MAX_LEN encoding:NSASCIIStringEncoding];
//while (fgets(send_str, MAX_LEN, stdin)) {
NSLog(#"broadcastMessage - Stage 4");
// send_len = strlen(send_str);
/* send string to multicast address */
if ((sendto(sock, send_str, sizeof(send_str), 0, (struct sockaddr *) &mc_addr, sizeof(mc_addr))) != sizeof(send_str)) {
NSLog(#"ERROR: broadcastMessage - sendto() sent incorrect number of bytes");
return;
}
NSLog(#"broadcastMessage - Stage 5");
/* clear send buffer */
memset(send_str, 0, sizeof(send_str));
NSLog(#"broadcastMessage - Stage 6");
close(sock);
}
-(void)listenForPackets {
listeningFlag_on = 1;
NSLog(#"listenForPackets - Stage 1");
if ((listeningSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: listenForPackets - socket() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
// set reuse port to on to allow multiple binds per host
if ((setsockopt(listeningSock, SOL_SOCKET, SO_REUSEADDR, &listeningFlag_on, sizeof(listeningFlag_on))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 2");
// construct a multicast address structure after erasing anything in the listeningmc_addr structure
memset(&listeningmc_addr, 0, sizeof(listeningmc_addr));
listeningmc_addr.sin_family = AF_INET;
listeningmc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listeningmc_addr.sin_port = htons(mc_port);
// bind multicast address to socket
if ((bind(listeningSock, (struct sockaddr *) &listeningmc_addr, sizeof(listeningmc_addr))) < 0) {
NSLog(#"ERROR: listenForPackets - bind() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
//******************************************************************************************************************************
//******************************************************************************************************************************
NSString *ipAddress = [[NSString alloc] initWithString:self.getIPAddress];
const char *tmp = [ipAddress UTF8String];
listeningMc_addr_str = tmp;
printf("%s\n", listeningMc_addr_str);
listeningMc_req.imr_multiaddr.s_addr = inet_addr("225.0.0.37");
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
// send an ADD MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
int err = errno;
NSLog(#"errno - %i", err);
NSLog(#"Error = %s", strerror(err));
perror("ERROR");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 3");
for (;;) { // loop forever
// clear the receive buffers & structs
memset(listeningRecv_str, 0, sizeof(listeningRecv_str));
listeningFrom_len = sizeof(listeningFrom_addr);
memset(&listeningFrom_addr, 0, listeningFrom_len);
// block waiting to receive a packet
if ((listeningRecv_len = recvfrom(listeningSock, listeningRecv_str, MAX_LEN, 0, (struct sockaddr*)&listeningFrom_addr, &listeningFrom_len)) < 0) {
NSLog(#"ERROR: listenForPackets - recvfrom() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 4");
NSString *tmpy = [[NSString alloc] initWithCString:listeningRecv_str encoding:NSASCIIStringEncoding];
msgStatus.text = tmpy;
NSLog(#"ERROR");
}
// received string
printf("Received %d bytes from %s: ", listeningRecv_len, inet_ntoa(listeningFrom_addr.sin_addr));
printf("%s", listeningRecv_str);
}
// send a DROP MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
//return 1; // make the method return an int instead of void and use this statement to check for errors
}
close(listeningSock);
NSLog(#"listenForPackets - Stage 5 - Complete");
}
Here is the code I'm using to extract my IP Address.
-(NSString *)getIPAddress {
NSString *address = #"error";
struct ifaddrs *interfaces; // = NULL;
struct ifaddrs *temp_addr; // = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0)
{
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL)
{
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:#"en0"])
{
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
In the listener, I think you need to set
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
... since that is also the interface on which you bind the socket. Depending on whether you run everything on a single host, you might need to consider the loopback interface and binding to INADDR_ANY will do that.
Is there a router between you and your destination? If so, there's some work that needs to be done to tell the router that you want to subscribe to the feed as well as tell the router you will be sending the feed.
I would start by tcpdump-ing the connection to make sure the packet is leaving your machine first.