In questo articolo voglio proporvi una piccola lezione sulla connessione fra due dispositivi attraverso il servizio bonjour.
Per semplificare questa pratica voglio proporvi una classe creata da me con la quale potrete facilmente connettervi ad un servizio (NSNetService) risolto.
Per connettere o comunque far comunicare due dispositivi occorre creare un socket per ogni
dispositivo il quale ha il compito di aprire una porta dalla quale potrà comunicare.
I due dispositivi comunicanti si distingueranno in “server” e “client” e le funzioni del socket differiranno in qualche parte.
In qualunque caso la procedura è molto semplice:
SERVER (TCP)
- creare un socket;
- eseguire il bind (assegnazione dell’indirizzo e apertura della porta);
- mettere il socket in attesa di una connessione;
- accettare la connessione;
- mettere in ascolto il socket per eventuali dati in ingresso.
CLIENT (TCP) (il client per connettersi al server deve conoscere l’indirizzo di quest’ultimo, bonjour e NSNetService servono a facilitare la scoperta dell’indirizzo)
- creare il socket (l’indirizzo e la porta verranno assegnati automaticamente appena il socket verrà connesso);
- connettere il socket all’indirizzo del “server”(ottenuto con bonjour);
- mettere in ascolto il socket per eventuali dati in ingresso.
Con l’object-c tutto è reso molto più semplice e certi passaggi vengono “saltati”. Comunque la classe da me creata fa le seguenti cose:
- prende un oggetto NSNetService già risolto;
- crea un socket e lo connette al servizio;
- ottiene dal socket connesso gli oggetti NSInputStream e NSOutputStream che ci faciliteranno la trasmissione dati;
- invia i dati;
- passa al delegato i dati che riceve.
Vi riporto di seguito l’header della mia classe
//
// SocketStreamClient.h
// FullControl
//
// Created by Francesco Burelli on 12/12/09.
// Copyright 2009 Francesco Burelli. All rights reserved.
//
#import
#import
@class SocketStreamClient;
@protocol SocketStreamClientDelegate
@optional
-(void)willConnectWithService:(NSNetService *)netService;
-(void)didConnectWithService:(NSNetService *)netService;
-(void)didNotConnectWithService:(NSNetService *)netService; // when disconnected
-(void)didReceiveData:(NSData *)theData from:(NSNetService *)netService;
-(void)didReceiveError:(NSError *)streamError;
@end
@interface SocketStreamClient : NSObject {
id _delegate;
NSNetService *_service;
CFSocketRef socket;
NSInputStream *_iStream;
NSOutputStream *_oStream;
BOOL input;
BOOL output;
BOOL _connected;
NSMutableData *dataToSend;
}
@property(assign) id delegate;
@property(readonly) NSNetService *service;
@property(assign) NSInputStream *iStream;
@property(assign) NSOutputStream *oStream;
@property(readonly) BOOL connected;
-(id)init;
-(void)connectWithService:(NSNetService *)netService; // service must be just resolved !
-(void)disconnect;
-(NSInteger)sendData:(NSData *)theData;
@end
Ora vi commento in ordine i vari metodi: partiamo dai metodi per il delegato.
-(void)willConnectWithService:(NSNetService *)netService;
-(void)didConnectWithService:(NSNetService *)netService;
-(void)didNotConnectWithService:(NSNetService *)netService;
Informano che ci si sta connettendo al servizio indicato; connessione riuscita al servizio indicato; disconnesso dal servizio indicato.
-(void)didReceiveData:(NSData *)theData from:(NSNetService *)netService;
Ricevuti dei dati dal servizio indicato.
-(void)didReceiveError:(NSError *)streamError;
Questo metodo informa il delegato di eventuali errori. Non ho curato particolarmente questa parte: fondamentalmente ho creato due tipo di errori, quelli che riguardano il socket e quelli che riguardano il servizio bonjour. Lascio a voi il perfezionamento degli errori.
Passiamo ora ai metodi della nostra classe.
-(void)connectWithService:(NSNetService *)netService;
E’ il metodo principale, connette il nostro oggetto ad un servizio, attenzione: il servizio deve essere già stato risolto con il suo metodo “resolve” (vedi documentazione Apple).
Questo metodo crea un socket, lo connette al servizio e ne ottiene gli oggetti NSInputStream e NSOutputStream, molto utili per il flusso dati.
-(void)disconnect;
Bhé non penso serva commentare questo metodo, semplicemente rilascia gli streams, chiude il socket e informa il delegato.
Invece a quest’ultimo metodo vorrei dedicarci un po’ di attenzione
-(NSInteger)sendData:(NSData *)theData;
Testando questa classe ho notato che non è sempre possibile inviare dati, soprattutto se di grandi dimensioni..
La soluzione che ho trovato è la seguente:
Se non è possibile inviare i dati, salvali e appena è possibile inviare nuovi dati, inviali.
Sembra banale, ma c’ho messo un po’ a scriverlo in codice, ed ecco a voi
-(NSInteger)sendData:(NSData *)theData {
NSInteger bytesWrite;
if ([_oStream hasSpaceAvailable]) {
bytesWrite = [_oStream write:[theData bytes] maxLength:[theData length]];
if (bytesWrite != [theData length]) {
// if can't send all, store part of data and send when _oStream hasSpaceAvailable
NSRange range = { bytesWrite, [theData length] - bytesWrite};
[dataToSend appendData:[theData subdataWithRange:range]];
}
}
else {
// if can't send, store data and send when _oStream hasSpaceAvailable
[dataToSend appendData:theData];
}
return bytesWrite;
}
case NSStreamEventHasSpaceAvailable: {
// if any, send data remaining
if (dataToSend && [dataToSend length]) {
NSInteger bytesWrite;
bytesWrite = [_oStream write:[dataToSend bytes] maxLength:[dataToSend length]];
if (bytesWrite == [dataToSend length]) {
[dataToSend initWithLength:0];
}
else {
NSRange range = { bytesWrite, [dataToSend length] - bytesWrite};
[dataToSend initWithData:[dataToSend subdataWithRange:range]];
}
}
break;
Le cose fondamentali penso di avervele dette, non resta che lasciarvi il link alla classe SocketStreamClient e un esempio di utilizzo.
// ...
#import "SocketStreamClient.h"
SocketStreamClient *_socketStreamClient = [[SocketStreamClient alloc] init];
[_socketStreamClient setDelegate:self];
[_socketStreamClient connectWithService:aService];
// ...
Ricordo che la classe può essere usata sia su applicazioni Mac che iPhone/iPodTouch basta modificare
#import <Foundation/Foundation.h> // per iphone
#import <Cocoa/Cocoa.h> // per mac
Ciao!
18 Responses to “T#019 – Connettersi ad un servizio Bonjour (NSNetService)”
5 Gennaio 2010
Pippoman at workmolto interessante
1 Febbraio 2010
andreaintanto grazie mille per il tutorial, era da tempo che cercavo come riuscire a inviare dati tra mac e iphone… ma ho ancora dei dubbi.
devo creare un oggetto nsnetservice per potermi connettere… bene, per dominio e type non ci sono problemi, ma per name devo mettere il nome del pc, o va bene anche un indirizzo ip?? su type devo mettere qualcosa che centra con bonjour?? per quanto riguarda la parte server, devo utilizzare sempre ConnectWithService??
grazie mille!!!
1 Febbraio 2010
Francesco BurelliCiao, allora ti faccio un po’ di chiarezza:
Questo oggetto è per il lato client, quindi non dovresti preoccuparti del nome o del tipo di NSNetService, perché questo oggetto non lo devi creare! Ma bensì ricavare da una ricerca eseguita con NSNetServiceBrowser. E dopo averlo trovato, lo risolve (metodo “resolve”) e una volta risolto usi questa classe per connetterti ad esso.
Dal lato server invece (farò una guida prossimamente) devi creare un oggetto NSNetService con il nome che preferisci (puoi usare @”” per l’assegnamento dinamico del nome) e il tipo che ti inventi tu per esempio @”_mioTipo._tcp.” . il dominio usa @”” che corrisponde a local.
Poi pubblichi il servizio NSNetService. (l’indirizzo ip lo assegnerà automaticamente)
1 Febbraio 2010
andreaquindi se ho capito bene, devo usare NSNetServiceBrowser con searchForBrowsableDomains per cercare un dominio a cui connettersi, poi utilizzare NSNerService con il dominio trovato e richiamare resolve.
nella parte server invece devo creare un NSNetService con @”local” come dominio e @”_prova._tcp.” come type.
ma come faccio a mettermi in ascolto???
AHAHAHHAHA nn ci capisco niente…. non avresti un piccolo esempio da inviarmi??:)
1 Febbraio 2010
Francesco BurelliSe hai un paio di giorni di pazienza pubblico un articolo completo con una classe già pronta! 😉 basterà inizializzarla e scegliere un tipo.
Comunque riassumendo:
SERVER
-inizializzo un socket (apro una porta per comunicare), metto in ascolto il socket (usa le funzioni CFSocket e vedi WiiTap come esempio);
– Pubblico un NSNetSerivice con la porta del socket creato prima, NSNetService basta scegliere il tipo come @”_prova._tcp.”, il domain lascialo @”” e il nome @”” la porta usa quella del socket.
CLIENT
– Usa un NSNetServiceBrowser col metodo “cerca per servizi di tipo @”_prova.tcp.” nel dominio @”local.” (penso funzioni anche con dominio @””) “;
– i servizi trovati li risolvi col metodo “resolve”;
– se il servizio viene risolto puoi usarlo con la mia classe per connettertici direttamente.
1 Febbraio 2010
andreaAppCodeok… grazie mille!!
7 Febbraio 2010
andreaAppCodeciao, son sempre io che rompo le palle….. stavo mettendo giu qualcosa, ma sto riscontrando un problema nella parte client, io faccio cosi:
NSNetServiceBrowser * serviceBrowser = [[NSNetServiceBrowser alloc] init];
[serviceBrowser searchForServicesOfType:@”_prova.tcp.” inDomain:@”local”];
if (serviceBrowser == nil)
{
NSLog(@”servizio non trovato”);
}
else {
}
solo che nella classe nsnetservicebrowser non si possono risolvere i servizi, quindi devo trasportarlo in nsnetservice, ma non so come fare… hai qualche consiglio??
il server per il momento è cosi:
NSSocketPort *sockPort;
sockPort = [[NSSocketPort alloc] initWithTCPPort:10000];
int socketFD = [sockPort socket];
NSFileHandle *listeningSocket;
listeningSocket = [[NSFileHandle alloc]
initWithFileDescriptor:socketFD];
NSNetService * service = [[NSNetService alloc] initWithDomain:@”” type:@”_prova._tcp.” name:@”” port:10000];
[service publish];
7 Febbraio 2010
Francesco Burelliciao! tranquillo non “rompi” è un piacere per me aiutarti.
Sbagli nell’utilizzare la classe NSNetServiceBrowser, ti spiego: se guardi nella documentazione apple sono elencati i metodi dei delegati, ora non so se tu sai cosa sia il delegato e come funzioni comunque dovresti implementare il tutto in questo modo:
NSNetServiceBrowser * serviceBrowser = [[NSNetServiceBrowser alloc] init];
[serviceBrowser setDelegate:self];
[serviceBrowser searchForServicesOfType:@”_prova.tcp.” inDomain:@”local.”];
– (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser{
// è iniziata la ricerca…
}
– (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didNotSearch:(NSDictionary *)errorInfo{
// c’è stato un errore, non viene effettuata la ricerca
}
– (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser{
// la ricerca è stata “stoppata”
}
– (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing {
// TROVATO UN NUOVO SERVIZIO
// ora posso per esempio provare a risolverlo…
[netService retain];
[netService setDelegate:self];
[netService resolveWithTimeout:5.0];
}
– (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didRemoveService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing{
// è stato rimosso un servizio
}
poi dovresti implementare i metodi delegati del servizio…
10 Febbraio 2010
andreaAppCodeciao, niente da fare…. se hai un attimo provi a dare un occhio al forum, ho messo tutto la… grazie mille!!
21 Settembre 2010
Gappaciao. Ho letto ora questo post.
Francesco hai fatto il tutorial riguardante una classe completa? xke cercando sul sito devApp nono ho trovato niente.
grazie ciao
15 Novembre 2010
NicodioEhi ciao, visto che hai scritto un tutorial utilizzando Bonjour vorrei chiederti una cosa che non ho trovato in giro.
Dovrei fare un progetto per l’università in cui creare un’applicazione grazie alla quale un iphone possa comunicare in broadcast (1 a molti) con altri iphone senza utilizzare l’accesso ad internet o senza essere all’interno di una rete LAN.
E’ possibile fare ciò attraverso Bonjour o funziona solo 1 a 1 oppure c’è bisogno di essere all’interno di una rete?
Grazie mille
16 Novembre 2010
Francesco BurelliCiao, allora per Bonjour che io sappia bisogna essere collegati tutti alla stessa rete wi-fi, ma si può fare una cosa del tipo un server e cento dispositivi collegati, non per forza 1 a 1, (sempre se l’iPhone regge tutte le connessioni). Invece un programma su un computer PC o Mac sicuramente regge più iPhone collegati a lui (non tra di loro).
Quindi la filosofia è sempre un SERVER e n CLIENT.
Nel tuo caso forse puoi usare il Bluetooth, ma qui entriamo in un campo che non ho mai approfondito.
17 Novembre 2010
NicodioGrazie della risposta tempestiva.
Il problema è che per quello che dovrei fare il range del bluetooth è troppo corto.
La cosa perfetta serebbe la connessione di più iphone tramite wifi senza però passare per una LAN ma direttamente con lo scambio di messaggi tra loro. Ho letto che una cosa del genere si può fare con Game Kit Framework ma anche lì o Bluetooth o wifi all’interno di una LAN… quindi mi sa che quello che voglio fare non è realizzabile al momento.
17 Novembre 2010
Francesco Burellinon capisco cosa intendi per rete LAN… cmq se hai un dispositivo tipo un Mac o PC, questo può creare un network, una rete. Non serve un router o un modem! La rete wifi la può creare un qualsiasi computer, e a questa rete qualsiasi dispositivo può connettersi
17 Novembre 2010
NicodioSì, hai ragione che è possibile creare una rete ad-hoc in cui un PC o un MAC possono creare la rete. Il mio problema era farlo tra iphone e iphone perché dovrebbe essere un’applicazione mobile. Grazie comunque.
29 Novembre 2010
andreaCiao, innanzitutto ringrazio per la guida e l’attenzione che so che metterai nel rispondere a questo messaggio….
Sul mio server windows ho scritto un applicazione server che usa i socket (c#.net) con la quale gestivo le comunicazioni fra pc per un piccolo giochetto di carte….
Ora ho intenzione di portare questo giochetto su iphone; posso usare la tua classe?funziona anche se il mio server è stato scritto in c#?m basta solo l’indirizzo ip del server?
ancora grazie mille in anticipo per l’attenzione
30 Novembre 2010
Francesco BurelliCiao Andrea, ti spiego prima un paio di cose: in realtà questa classe è fatta esclusivamente per connettersi ad un servizio bonjour, quindi non funzionerebbe su nessun server che non usi NSNetService o simili. Per questo ho creato un’altra classe (FBSocket) con la quale ci si può connettere a qualsiasi server (indipendentemente dal linguaggio).
Puoi trovare una guida e la classe stessa a questo indirizzo
http://www.cescobaz.altervista.org/home/?art=41
7 Gennaio 2011
andreaCiao, chiedo scusa se chiedo qui ma ho provato a chiedere sull’altro sito ma nn ho avuto risposta…..
Ho implementato la classe per connettersi ad un servizio qualsiasi (nel mio caso socket java) ma ho un gravissimo problema; nell’evento (spero si possa chiamare cosi anche in objectiveC) receivedData ho scritto questo:
NSString* mex;
NSString* str= @”f\n”;
NSData* data=[str dataUsingEncoding:NSUTF8StringEncoding];
int pre=0;
mex = [[NSString alloc] initWithData:bytes encoding:NSASCIIStringEncoding];
for (int i=0; i<[mex length];i++)
{
NSRange r={pre,i-pre};
if([mex characterAtIndex:i]=='\n')
{
str=[mex substringWithRange:r];
char c=[str characterAtIndex:0];
switch (c )
{
case 'f':
[soc sendData:data ];
break;
default:
break;
}
pre=i;
}
}
Come si evince dal codice mi serve a separare piu linee…..
Compilo ed eseguo sul compilatore e va tt kk…risponde alla grande!!!!
Compilo ed eseguo su ipad e su iphone4….crasha….
Faccio varie prove…..lascio solo il default….niente….cambio il for con un while…niente…levo l'intero switch….FUNZIONA….ma chiaramente non fa quello che voglio :-); allora provo a sostituire tutto lo switch con degli if ma ovviamente crasha….sono 3 giorni che c sbatto la testa e non riesco a venirne fuori….mi manca qualcosa?dove sbaglio?
Grazie mille per la tua attenzione….aspetto notizie…ciao