Background
I kind of taught myself about sockets and network programming from various code examples and the classic Unix Network Programming programming tutorial, while trying to put knowledge into the application I'm working on. I am currently working on a part of an application that requires a simple client-server setup.
Here's how it happens right now (well, how it should turn out):
- The server is published with
NSNetServiceand creates a socket usingCFSocketCreateWithNative() - The client finds the server with
NSNetServiceBrowser - The client resolves the discovered service
- The server receives a callback from CFSocket, which creates a new instance of the class (
MyConnection) to handle the connection. Reading and writing streams for the connection is achieved using CFStreamCreatePairWithSocket(). - The client tells the server (@ "hello")
- The server sends the data that it receives from the client back to the client (this is my problem)
- Client displays string in UIAlertView
Two questions
I get the error "Operation now in progress" when I try to send the server back to this client, as indicated in the connection code below. I believe that this is because it NSOutputStreamdoes not have free space. What is the best way to handle this? I know that he should wait for the event NSStreamEventHasSpaceAvailable, but it seems that this is not happening ...
EDIT: duh ... When I received this error, I only tested the application with the iPhone simulator and allowed it to act as a server and client, because I didn’t have Internet in my new apartment: P It seems that this is no problem when using two real devices.
, ? runloop - ? Apple concurrency, ... , , , .
: , , , . ? , ? , , bluetooth... , :)
. APNetService APNetServiceBrowser NSNetService NSNetServiceBrowser
- (void) startServerForGroup:(NSString *)name
{
self.groupName = name;
NSInteger port = [self prepareListeningSocket];
self.service = [[APNetService alloc] initWithDomain:@"local."
type:@"_example._tcp."
name:self.groupName
port:port];
self.service.delegate = self;
[self.service publish];
}
- (NSInteger) prepareListeningSocket
{
int listenfd, err, junk, port;
BOOL success;
struct sockaddr_in addr;
port = 0;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
success = (listenfd != -1);
if (success) {
bzero(&addr, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = INADDR_ANY;
err = bind(listenfd, (const struct sockaddr *) &addr, sizeof(addr));
success = (err == 0);
}
if (success) {
err = listen(listenfd, 5);
success = (err == 0);
}
if (success) {
socklen_t addrLen;
addrLen = sizeof(addr);
err = getsockname(listenfd, (struct sockaddr *) &addr, &addrLen);
success = (err == 0);
if (success) {
assert(addrLen == sizeof(addr));
port = ntohs(addr.sin_port);
}
}
if (success) {
CFSocketContext context = { 0,(__bridge void*) self, NULL, NULL, NULL };
CFSocketRef socket = CFSocketCreateWithNative(
NULL,
listenfd,
kCFSocketAcceptCallBack,
AcceptCallback,
&context
);
if (socket) {
self.listeningSocket = socket;
CFRelease(socket);
success = YES;
}
if (success) {
CFRunLoopSourceRef rls;
listenfd = -1;
rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0);
assert(rls != NULL);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
}
}
if ( success ) {
return port;
}
else {
NSLog(@"FAILED TO START SERVER");
if (listenfd != -1) {
junk = close(listenfd);
assert(junk == 0);
}
return -1;
}
}
#pragma mark - Callback
static void AcceptCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
MyServer * obj;
obj = (__bridge MyServer *) info;
assert(s == obj->_listeningSocket);
MyConnection *newCon = [[MyConnection alloc] initWithFileDescriptor:*(int*)data];
[newCon startReceive];
[obj.connections addObject:newCon];
}
- (void) startReceive
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocket(NULL, self.fd, &readStream, &writeStream);
self.inputStream = (__bridge_transfer NSInputStream *) readStream;
self.outputStream = (__bridge_transfer NSOutputStream*) writeStream;
[self.inputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
[self.outputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
self.inputStream.delegate = self;
self.outputStream.delegate = self;
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
}
#pragma mark - NSStreamDelegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1)...
else if (bytesRead == 0)...
else {
NSData *data = [NSData dataWithBytes:buffer length:bytesRead];
[self didReceiveData:data];
}
} break;
case NSStreamEventHasSpaceAvailable: {
self.space = YES;
} break;
. . .
}
}
- (void) didReceiveData:(NSData *)data
{
if (self.space)
NSLog(@"SPACE");
else
NSLog(@"NO SPACE");
NSInteger i = [self.outputStream write:data.bytes maxLength:data.length];
if (i < 0) {
printf("%s",strerror(errno));
}
}
#pragma mark - APNetServiceBrowserDelegate
- (void) browser:(APNetServiceBrowser *)browser didAddService:(APNetService *)service moreComing:(BOOL)moreComing
{
service.delegate = self;
[service resolveWithTimeout:20];
}
#pragma mark - APNetServiceDelegate
- (void) netServiceDidResolveAddress:(APNetService *)service
{
NSInputStream *input;
NSOutputStream *output;
[service getInputStream:&input outputStream:&output];
self.inputStream = input;
self.outputStream = output;
self.inputStream.delegate = self;
self.outputStream.delegate = self;
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.inputStream open];
[self.outputStream open];
}
#pragma mark - NSStreamDelegate
- (void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1) NSLog(@"Error reading data");
else if (bytesRead == 0) NSLog(@"no bytes read");
else {
NSData *data = [NSData dataWithBytes:buffer length:bytesRead];
[self didReceiveData:data];
}
} break;
case NSStreamEventHasSpaceAvailable: {
if (!self.isWaitingForReply) {
[self sendHelloMessage];
}
} break;
}
}
- (void) sendHelloMessage
{
NSData *d = [NSKeyedArchiver archivedDataWithRootObject:@"hi"];
[self.outputStream write:d.bytes maxLength:d.length];
self.isWaiting = YES;
}
- (void) didReceiveData:(NSData *)data
{
NSString *string = [NSKeyedUnarchiver unarchiveObjectWithData:data];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Message"
message:string
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}