I'm currently writing a simple TCP server class in Objective-C which should be able to bind to and listen on a particular port. I particular I have a function -(void) start which creates a CFSockets, binds it to the port specified and adds it to a run loop. It looks like this:
- (void) start {
//This function is called after the initialiser
if([self isStarted]) //if already started, return without doing anything
return;
//create a socket
CFSocketContext socketCtxt = {0, (__bridge void *)self, NULL, NULL, NULL};
socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&SocketAcceptCallback, &socketCtxt);
NSAssert(socket, #"Error: Cannot create a socket...");
int yes = 1;
setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
//bind it to the desired port
struct sockaddr_in serverAddress;
socklen_t nameLen = 0;
nameLen = sizeof(serverAddress);
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_len = nameLen;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(self.port);
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
NSData* address4 = [NSData dataWithBytes:&serverAddress length:nameLen];
if(kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)address4)) {
if (socket) CFRelease(socket);
socket = NULL;
NSAssert(NO, #"Unable to bind the created socket to the desired port");
}
//add to the current run loop
CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
assert(rls!=0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopCommonModes);
//done!
}
I also define the callback function SocketAcceptCallback like this:
static void SocketAcceptCallback(CFSocketRef sock, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
//this method is called after a connection is accepted
//here we bind the received connection to a pair of
//NSStreams
if(type == kCFSocketAcceptCallBack){
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
struct sockaddr_in peerAddress;
socklen_t peerLen = sizeof(peerAddress);
NSString * peer = nil;
if (getpeername(nativeSocketHandle, (struct sockaddr *)&peerAddress, (socklen_t *)&peerLen) == 0) {
peer = [NSString stringWithUTF8String:inet_ntoa(peerAddress.sin_addr)];
} else {
peer = #"Generic Peer";
}
NSLog(#"%# has connected", peer);
//Find corresponding SocketServer object -- it is passed to the callback in the INFO pointer
SocketServer *obj = (__bridge SocketServer*)info;
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocket(NULL, nativeSocketHandle, &readStream, &writeStream);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
//bridge to NSStreams
obj.inputStream = (NSInputStream *)readStream;
obj.outputStream = (NSOutputStream *)writeStream;
//set delegates
[obj.inputStream setDelegate:obj];
[obj.outputStream setDelegate:obj];
//schedule the runloops
[obj.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[obj.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//open connections
[obj.inputStream open];
[obj.outputStream open];
} else {
// on any failure, need to destroy the CFSocketNativeHandle
// since we are not going to use it any more
close(nativeSocketHandle);
}
/*
if (readStream) CFRelease(readStream);
if (writeStream) CFRelease(writeStream); */
}
}
In the callback, I bridge the connection to a pair of NSStreams which I have as member fields in my class and which I use to read and write.
Now the problem: I have tried to run this server and connect to it via telnet. It connects, but the aforementioned callback is not being called (I have checked by adding a diagnostic NSLog in it). I suspect I haven't added the socket to a run loop correctly, but I can't find any mistakes myself. Any ideas as to what it might be?
Related
I have some code for writing/reading over a TCP NSStream. I refactored it, and now the NSOutputStream:write hangs indefinitely. Spent hours looking at it, can't crack it. I see the TCP SYN->ACK process happening fine - and I went back to the old version to test, and it still works (so the device on the other end is fine).
Everything is done on a single thread (stream creation, delegate, reading/writing, etc) which is not the main thread. Threading did not change in the refactoring at all.
Here is the code to create the streams (no change in refactoring):
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)_cam.IP, PTPIP_PORT, &readStream, &writeStream);
_mainInputStream = (__bridge NSInputStream *)readStream;
_mainOutputStream = (__bridge NSOutputStream *)writeStream;
[_mainInputStream setDelegate:self];
[_mainOutputStream setDelegate:self];
[_mainInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // common mode!
[_mainOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // common mode!
[_mainInputStream open];
[_mainOutputStream open];
[[NSRunLoop currentRunLoop] run];
Here is the new code that fails while writing, it hangs on stream:write. When I check the stream status right before the write call, the stream is open (good). I also debugged the input to the function before & after the refactoring, there is no difference (good). I also thought to check for stream delegate's NSStreamEventHasSpaceAvailable, but that appears to NEVER be thrown for either the old (working) or new (broken) code, so I guess it isn't applicable for TCP streams the way I'm using them. Other stream delegate events like NSStreamEventHasBytesAvailable are being thrown just fine...
- (int)write:(const unsigned char*)bytes size:(unsigned int)size {
unsigned int bytesToWrite = size;
while( bytesToWrite > 0) {
unsigned int chunkSize = (bytesToWrite > PTP_IP_MAX_PACKET_LEN)?PTP_IP_MAX_PACKET_LEN:bytesToWrite;
// welcome to hangsville, population YOU
int result = [self.myWorker.mainOutputStream write:bytes+(size-bytesToWrite) maxLength:chunkSize];
if( result <= 0 )
return PTP_ERROR_IO;
bytesToWrite -= result;
}
return size-bytesToWrite;
}
Here's the old code that somehow works, even though input is the same...
static short
ptpip_write_func (unsigned char *bytes, unsigned int size, void *data)
{
PTPCamera* cam = (__bridge PTPCamera*)data;
unsigned int bytesToWrite = size;
while( bytesToWrite > 0) {
unsigned chunkSize = (bytesToWrite > PTP_IP_MAX_PACKET_LEN)?PTP_IP_MAX_PACKET_LEN:bytesToWrite;
unsigned char chunk[chunkSize];
memcpy(chunk, bytes+(size-bytesToWrite), chunkSize);
int result = [cam.outStream write:chunk maxLength:chunkSize];
if( result <= 0 )
return PTP_ERROR_IO;
bytesToWrite -= result;
}
return PTP_RC_OK;
}
What am I missing?
After a ton of debugging I concluded that there was really nothing different happening between the two versions, and that it must have been a fluke that it was ever working.
Sure enough, after additional researching I figured out that having stream open call on any thread OTHER than main is not going to work.
Here is the updated code, that works:
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)cam.IP, PTPIP_PORT, &readStream, &writeStream);
worker.mainInputStream = (__bridge NSInputStream *)readStream;
worker.mainOutputStream = (__bridge NSOutputStream *)writeStream;
[worker.mainInputStream setDelegate:worker];
[worker.mainOutputStream setDelegate:worker];
dispatch_async(dispatch_get_main_queue(), ^{
[worker.mainInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[worker.mainOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[worker.mainInputStream open];
[worker.mainOutputStream open];
});
Environment: Mac OS X 10.9, Xcode 5.0.2
I need to create an HTTP request for my mini WebSocket client, example request:
GET / HTTP/1.1
Host: serverwebsocket.com:10080
Upgrade: websocket
Connection: Upgrade
Origin: http://from.com
I've created NSURLSession with NSURLSessionConfiguration and set the headers, and Wireshark shows all header set except Connection which stays keep-alive, but should not.
// Create request based on Sessions
// Create sesson configuretion
NSURLSessionConfiguration* sessionConf = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure session config
// set header value, detail header websocket on http://learn.javascript.ru/websockets
sessionConf.HTTPAdditionalHeaders = #{#"Upgrade": #"websocket",
#"Connection": #"Upgrage",
#"Origin": #"http://from.com",
#"User-Agent": #"Chrome/36.0.198.5.143"};
// Declare handler block of response
__block void (^handler)(NSData* data, NSURLResponse* response, NSError* error);
handler = ^(NSData* data, NSURLResponse* response, NSError* error)
{
// If receive response from server
if(data)
{
NSString* result = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"response data: %#", result);
}
else // something wrong
{
NSString* errorText;
if(error)
{
errorText = [error localizedDescription];
}
else // Generic description
{
errorText = #"Error Interner connection";
}
NSLog(#"Request error: %#",errorText );
}
};
// Create Session
NSURLSession* session = [NSURLSession sessionWithConfiguration:sessionConf];
NSURL* url = [NSURL URLWithString: [_textUrl stringValue]];
[[session dataTaskWithURL:url completionHandler:handler] resume];
How can I change the header for Connection? And I do not want use another websocket library, I want to handle HTTP header in low level.
May, instead of the NSURLSession there are other classes work with network? In NSURLRequest same problem.
For manage HTTP header in low level best way use CFNetwork level. But do not use CFHTTPStream for send/receive of data, because CFHTTPStream support only two state for ‘Connection’ header: ‘keep-alive’ and ‘close’. See in file https://opensource.apple.com/source/CFNetwork/CFNetwork-128/HTTP/CFHTTPStream.c function ‘extern void cleanUpRequest()’.
Solution:
1 Create and customize request by CFHTTPMessageRef and CFHTTPMessageSetHeaderFieldValue
2 Convert request to raw data
3 Send/receive raw data uses NSOutputStream and NSInputStream
This is a minimal example send/receive HTTP WebSocket message in CFNetwork level:
#import "AppDelegate.h"
#implementation AppDelegate
NSInputStream* inputStream;
NSOutputStream* outputStream;
NSMutableData* inputBuffer; // This data receive from server
NSMutableData* outputBuffer; // This data send to server
- (IBAction)btnSend:(id)sender
{
NSURL* url = [NSURL URLWithString: [_textUrl stringValue]];
if( !outputBuffer)
{
outputBuffer = [[NSMutableData alloc] init];
}
///////////////////////////////////////////////////////////////
/////////////// Create GET request uses CFNetwork level
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
// Set Host header
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(url.port ? [NSString stringWithFormat:#"%#:%#", url.host, url.port] : url.host));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("User-Agent"), CFSTR("Chrome/36.0.198.5.143"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Pragma"), CFSTR("no-cache"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Cache-Control"), CFSTR("no-cache"));
// Set special headers for websocket, detail on http://learn.javascript.ru/websockets
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade"));
NSString* origin = #"http://from.com";
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)origin);
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), CFSTR("SIP"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), CFSTR("yuPCDHanXBphfIH83e4JVw=="));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), CFSTR("13"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Extensions"), CFSTR("permessage-deflate; client_max_window_bits, x-webkit-deflate-frame"));
// Convert request to raw data
NSData* rawHttpMessage = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request));
[outputBuffer appendData:rawHttpMessage];
CFRelease(request);
/////////////////////////////////////////////////////////////////
/////////////// Customize stream for sending/receive data
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)url.host, [url.port intValue],
&readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream*)readStream;
outputStream = (__bridge_transfer NSOutputStream*)writeStream;
[inputStream setDelegate:self]; // Activate stream event handler
[outputStream setDelegate:self]; // Activate stream event handler
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
[inputStream retain];
[outputStream retain];
}
// Handler of event for NSInputStream and NSOutputStream
// Detail see: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html
-(void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode)
{
case NSStreamEventHasSpaceAvailable: // when outputstream can send data
if( stream == outputStream)
{
//NSLog(#"Send data [%lu]", [outputBuffer length]);
[outputStream write:[outputBuffer bytes] maxLength:[outputBuffer length]]; // Send data
// Close output stream when all data sent
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
[outputBuffer release];
outputBuffer = nil;
}
break;
case NSStreamEventHasBytesAvailable: // when inputstream received data
{
const int bufSize = 2048;
if(!inputBuffer)
{
inputBuffer = [[NSMutableData data] retain];
}
uint8_t buf[bufSize];
long len = 0;
len = [inputStream read:buf maxLength:bufSize]; // get data
if(len)
{
[inputBuffer appendBytes:(const void*)buf length:len];
//NSLog(#"Received data from server [%lu]: %#", [inputBuffer length], inputBuffer); // Show in raw format
NSString* rs = [NSString stringWithUTF8String:[inputBuffer bytes]];
NSLog(#"Received data from server [%lu]:\n%#", [inputBuffer length], rs); // Show in string format
}
else
{
NSLog(#"Received data Error[%li]: %#",(long)[inputStream.streamError code], [inputStream.streamError localizedDescription]);
}
// Close inputStream stream when all data receive
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream release];
inputStream = nil;
[inputBuffer release];
inputBuffer = nil;
}
break;
case NSStreamEventErrorOccurred: // when error of transmited data
if( stream == outputStream)
{
NSError* error = [stream streamError];
NSLog(#"Error sending data [%li]: %#",(long)[error code], [error localizedDescription]);
}
else if( stream == inputStream)
{
NSError* error = [stream streamError];
NSLog(#"Error receive data [%li]: %#",(long)[error code], [error localizedDescription]);
}
break;
case NSStreamEventEndEncountered: // this is not work ;)
if( stream == outputStream)
{
NSLog(#"outputStream End");
}
else if( stream == inputStream)
{
NSLog(#"inputputStream End");
}
break;
}
}
#end
This sample send WebSocket request and receive WeSocket response:
WebSocket request to server:
GET / HTTP/1.1
Host: serverwebsocket.com:10080
Upgrade: websocket
Connection: Upgrade
Origin: http://from.com
Sec-WebSocket-Protocol:SIP
Sec-WebSocket-Key: yuPCDHanXBphfIH83e4JVw==
Sec-WebSocket-Version: 13
WebSocket response from server:
HTTP/1.1 101 Switching Protocols
Content-Length: 0
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: mEgcu0WkPuU6yMRtyUl/C+X8zJE=
Sec-WebSocket-Protocol: sip
Sec-WebSocket-Version: 13
For one thing
#"Connection": #"Upgrage"
should probably be
#"Connection": #"Upgrade"
I am trying to send a simple string over UDP in my iOS7 app to a known IP and could not find a simple explanation and sample code on how to do that.
There is plenty out there about TCP but not so much about UDP and it has to be UDP in my case.
You could use https://github.com/robbiehanson/CocoaAsyncSocket, which is an Objective-C wrapper for TCP and UDP connections. It also contains sample code for TCP and UPD clients and servers.
You could use this wrapper-less simple gist.
UDPEchoClient.h
#import <Foundation/Foundation.h>
#interface UDPEchoClient : NSObject
- (BOOL) sendData:(const char *)msg;
#end
UDPEchoClient.m
#import "UDPEchoClient.h"
//
// CFSocket imports
//
#import <CoreFoundation/CoreFoundation.h>
#import <sys/socket.h>
#import <arpa/inet.h>
#import <netinet/in.h>
#define IP "host ip"
#define PORT host_port
static void dataAvailableCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
//
// receiving information sent back from the echo server
//
CFDataRef dataRef = (CFDataRef)data;
NSLog(#"data recieved (%s) ", CFDataGetBytePtr(dataRef));
}
#implementation UDPEchoClient
{
//
// socket for communication
//
CFSocketRef cfsocketout;
//
// address object to store the host details
//
struct sockaddr_in addr;
}
- (instancetype)init
{
self = [super init];
if (self) {
//
// instantiating the CFSocketRef
//
cfsocketout = CFSocketCreate(kCFAllocatorDefault,
PF_INET,
SOCK_DGRAM,
IPPROTO_UDP,
kCFSocketDataCallBack,
dataAvailableCallback,
NULL);
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
//
// set runloop for data reciever
//
CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, cfsocketout, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopCommonModes);
CFRelease(rls);
}
return self;
}
//
// returns true upon successfull sending
//
- (BOOL) sendData:(const char *)msg
{
//
// checking, is my socket is valid
//
if(cfsocketout)
{
//
// making the data from the address
//
CFDataRef addr_data = CFDataCreate(NULL, (const UInt8*)&addr, sizeof(addr));
//
// making the data from the message
//
CFDataRef msg_data = CFDataCreate(NULL, (const UInt8*)msg, strlen(msg));
//
// actually sending the data & catch the status
//
CFSocketError socketErr = CFSocketSendData(cfsocketout,
addr_data,
msg_data,
0);
//
// return true/false upon return value of the send function
//
return (socketErr == kCFSocketSuccess);
}
else
{
NSLog(#"socket reference is null");
return false;
}
}
I have written this code to setup a stream with a server:
-(void)streamOpenWithIp:(NSString *)ip withPortNumber:(int)portNumber;
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)ip, portNumber, &readStream, &writeStream);
if(readStream && writeStream)
{
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
//Setup inpustream
inputStream = (__bridge NSInputStream *)readStream;
[inputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
//Setup outputstream
outputStream = (__bridge NSOutputStream *)writeStream;
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
}
}
I am able to connect to the server and send & receive data. But I want to check if the connection is still there. If I disconnect a cable from the wifi router, I still can send data in the stream and no error occurred.
You can solve this on application level by using a timer to send some message and check if you receive something back. But you can solve this also with TCP Keep-Alive technique on a lower level.
How do I implement this with NSStream? How can I set the checking interval?
I assume that you get a NSStreamEventErrorOcurred when the stream is down by checking it with TCP Keep-Alive?
I have checked this post, but I can't figure it out:
Keeping a socket connection alive in iOS
Thanks for your help!
You can get the native socket handle with
CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(inputStream), kCFStreamPropertySocketNativeHandle);
CFSocketNativeHandle socket;
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
CFRelease(socketData);
and then set socket options (you need to #include <sys/socket.h> for this):
int on = 1;
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
NSLog(#"setsockopt failed: %s", strerror(errno));
}
You could put this code in the event handler function for the kCFStreamEventOpenCompleted event:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
switch (event) {
case kCFStreamEventOpenCompleted:
if (stream == self.inputStream) {
CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(stream), kCFStreamPropertySocketNativeHandle);
CFSocketNativeHandle socket;
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
CFRelease(socketData);
int on = 1;
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
NSLog(#"setsockopt failed: %s", strerror(errno));
}
}
break;
/* ... other cases ... */;
}
}
There is a more complete answer to a similar question.
For the example of the app that starts sending keepalive after 10 seconds, sends keppalive every 2 seconds and closes the stream after 4 keepalives with no reply, take a look at this post:
Is it possible to activate TCP keepalive on Apple iOS devices
It also shows how to set retransmission timeout after which the connection is closed.
On iOS6.1, the following code should do the very same thing, but when I forgot to write addr.sin_len = sizeof(adde) the first block failed. The original error was:
GOT EVENT FROM INPUT Event: 8
ERR: Error Domain=NSPOSIXErrorDomain Code=12 "The operation couldn’t be completed. Cannot allocate memory"
After adding in the missing line to set the struct size, the first block worked just as the second. Probably other developers will see that error message and trip on this post.
The code:
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
#if 1 // LONG WAY
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr); // ORIGINALLY WAS MISSING
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
int ret = inet_pton(AF_INET, "192.168.1.2", &(addr.sin_addr.s_addr)); // IPv4
assert(ret == 1);
NSLog(#"CONNECT");
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)&addr, sizeof(addr));
assert(address);
CFHostRef macMini = CFHostCreateWithAddress(kCFAllocatorDefault, address);
CFRelease(address);
assert(macMini);
// (tried, makes not difference) CFHostScheduleWithRunLoop (macMini, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, macMini, 5566, &readStream, &writeStream);
CFRelease(macMini);
#else // SHORT WAY
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, CFSTR("192.168.1.2"), 5566, &readStream, &writeStream);
#endif
assert(readStream);
assert(writeStream);
iStream = CFBridgingRelease(readStream);
oStream = CFBridgingRelease(writeStream);
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
NSLog(#"ISTREAM %# status=%d", iStream, [iStream streamStatus]);
NSLog(#"ERR: %#", [iStream streamError]);
[oStream setDelegate:self];
[oStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[oStream open];
NSLog(#"OSTREAM %# status=%d", oStream, [oStream streamStatus]);
NSLog(#"ERR: %#", [oStream streamError]);
Problem was that sin_len was not set. The nice thing about having both sets of code above, is that you can see how to accomplish the task either way.