I am trying to implement a simple TCP server on ObjectiveC/iOS. The minimal example boils down to the following: The server shall maintain multiple TCP connections. Whenever data (4 bytes) has been read, the server shall reply with an answer (4 bytes) on that connection.
I first create the socket listener and schedule it in a CFRunLoop:
CFSocketRef initialCfSock = CFSocketCreate(
kCFAllocatorDefault,
PF_INET,
SOCK_STREAM,
IPPROTO_TCP,
kCFSocketAcceptCallBack,
&handleConnect,
NULL);
if( ! initialCfSock ) return;
int yes=1;
setsockopt(CFSocketGetNative(initialCfSock), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
struct sockaddr_in sin = {0};
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(50000);
sin.sin_addr.s_addr= htonl(INADDR_ANY);
CFDataRef sincfd = CFDataCreate(
kCFAllocatorDefault,
(UInt8 *)&sin,
sizeof(sin));
if (kCFSocketSuccess != CFSocketSetAddress(initialCfSock, sincfd)) return;
CFRelease(sincfd);
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource(
kCFAllocatorDefault,
initialCfSock,
0);
CFRunLoopAddSource(
CFRunLoopGetCurrent(),
socketsource,
kCFRunLoopCommonModes);
CFRelease(socketsource);
CFRunLoopRun();
Then, on incoming connections, the connection handler is called (this works):
NSInputStream * inputStream = NULL; // global variable
NSOutputStream * inputStream = NULL; // global variable
void handleConnect (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
{
NSLog(@"handleConnect");
CFSocketNativeHandle socketHandle = *(CFSocketNativeHandle *) data;
CFReadStreamRef currentInputStream = NULL;
CFWriteStreamRef currentOutputStream = NULL;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, (CFReadStreamRef *) ¤tInputStream, (CFWriteStreamRef *) ¤tOutputStream);
if (! currentInputStream || ! currentOutputStream)
return;
// runloop registration
CFStreamClientContext myContext = {
0,
NULL,
NULL,
NULL
};
CFOptionFlags registeredEventsInput = kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (! CFReadStreamSetClient(currentInputStream, registeredEventsInput, handleInput, &myContext))
return;
CFReadStreamScheduleWithRunLoop(currentInputStream, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFOptionFlags registeredEventsOutput = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (! CFWriteStreamSetClient(currentOutputStream, registeredEventsOutput, handleOutput, &myContext))
return;
CFWriteStreamScheduleWithRunLoop(currentOutputStream, CFRunLoopGetMain(), kCFRunLoopCommonModes);
if (!CFReadStreamOpen(currentInputStream))
return;
if (!CFWriteStreamOpen(currentOutputStream))
return;
inputStream = (NSInputStream *)currentInputStream;
outputStream = (NSOutputStream *)currentOutputStream;
NSLog(@"handleConnect: inputStream=%p, outputStream=%p", currentInputStream, currentOutputStream);
// 2)
}
And finally, on incoming bytes, the input handler is called. Four bytes are read and a four-byte reply is sent:
void handleInput(CFReadStreamRef currentInputStream, CFStreamEventType event, void *clientCallbackInfo)
{
NSLog(@"handleInput: inputStream=%p, outputStream=%p", inputStream, outputStream);
switch(event) {
case kCFStreamEventHasBytesAvailable:
{
// 1) begin
unsigned int a;
NSLog(@"before read: %d/%d, spaceAvail %d, bytesAvail %d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable]);
int err = [inputStream read:(uint8_t *)&a maxLength:sizeof(a)];
NSLog(@"after read: %d/%d, spaceAvail %d, bytesAvail %d, err=%d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable], err);
NSLog(@"before write: %d/%d, spaceAvail %d, bytesAvail %d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable]);
err = [outputStream write:(uint8_t *) &a maxLength:sizeof(a)];
NSLog(@"after write: %d/%d, spaceAvail %d, bytesAvail %d, err=%d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable], err);
// 1) end
break;
}
case kCFStreamEventErrorOccurred:
case kCFStreamEventEndEncountered:
default:
Log(@"[PK] handleInput: stream end/error");
break;
}
}
The problem is: The [outputStream write:...] call during handleInput does not send the bytes, instead it blocks and nothing happens. The client waits for data at the same time.
This is the log:
handleConnect
handleConnect: inputStream=0x15576cb0, outputStream=0x15576ce0
before read: 2/2, spaceAvail 0, bytesAvail 1
after read: 2/2, spaceAvail 0, bytesAvail 1, err=4
before write: 2/2, spaceAvail 0, bytesAvail 0
But: Everything works fine when I move the lines in handleInput marked with 1) to 2). But then I wait for data synchronously, which I don't want to do.
Then, the according logfile looks like:
handleConnect
handleConnect: inputStream=0x17632d30, outputStream=0x17632310
before read: 2/2, spaceAvail 0, bytesAvail 0
after read: 2/2, spaceAvail 1, bytesAvail 1, err=4
before write: 2/2, spaceAvail 1, bytesAvail 0
after write: 2/2, spaceAvail 1, bytesAvail 0, err=4
What's the problem here?