I'm trying to add some simple peer-to-peer connection functionality to an iOS library. Coding for outgoing connections was simple enough; a call to CFStreamCreatePairWithSocketToHost connects to a remote host and sets up streams for reading/writing from/to it. Simple enough.
However, I couldn't find an equivalently easy way to set up a socket to listen for/accept incoming connections. So I went back to basics and used socket(), bind(), listen(), and accept() to implement low-level connection handling, patterned largely after the example here:
http://www.pcs.cnu.edu/~dgame/sockets/server.c
That's all fine, but now that I'm able to accept incoming connections I'm wondering how to go about creating CFReadStream and CFWriteStream instances to manage them. Is there a straightforward way of doing so?
As an aside, I'm aware that a CocoaAsyncSocket library exists that supports asynchronous server sockets, but I'm really not interested in an async solution.
Okay, turns out that the answer was actually fairly simple. You can use:
void CFStreamCreatePairWithSocket (
CFAllocatorRef alloc,
CFSocketNativeHandle sock,
CFReadStreamRef *readStream,
CFWriteStreamRef *writeStream
);
...to bind a CFReadStream and CFWriteStream to an already connected socket. That flow seems a little backwards to me (i.e. what if bytes have already been read from the socket, etc., and why isn't it possible to just bind something to the listening/server socket such that every time a new connection is accepted corresponding CFReadStream and CFWriteStream instances are automatically set up?), but whatever.
The code goes like:
int connectedSocketId = accept(socketId, (struct sockaddr *)&clientSocket, &addrlen);
if (connectedSocketId != -1) {
//successful connection
CFReadStreamRef clientInput = NULL;
CFWriteStreamRef clientOutput = NULL;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, connectedSocketId, &clientInput, &clientOutput);
if (clientInput && clientOutput) {
CFReadStreamSetProperty(clientInput, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(clientOutput, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if (! CFReadStreamOpen(clientInput) || ! CFWriteStreamOpen(clientOutput)) {
NSLog(#"Could not initialize streams!");
}
else {
//use the streams
}
}
}
So the important things to realize were:
It's not necessary to bind anything to the socket you are listening on; instead it's possible to bind CFReadStream and CFWriteStream instances arbitrarily to any already connected socket, even if you've already read/written data from/to it.
For all its fancy verboseness, a CFSocketNativeHandle is just an int. Oh how I loathe unnecessary aliasing of primitive types to things that sound like they aren't primitive types.
Related
I am just getting started with socket programming on iOS and I am struggling to determine the use of the NSStreamEventHasSpaceAvailable event for NSOutputStreams.
On the one hand, Apple's official documentation (Listing 2) shows that in the -stream:handleEvent: delegate method, data should be written to the output buffer with -write:maxLength: message, passing data continually from a buffer, whenever the NSStreamEventHasSpaceAvailable event is received.
On the other hand, this tutorial from Ray Wenderlich and this iOS TCP socket example on GitHub ignore the NSStreamEventHasSpaceAvailable event altogether, and just go ahead and -write:maxLength: to the buffer whenever they need to (even ignoring -hasSpaceAvailable).
Thirdly, there is this example code which appears to do both...
My question is therefore, what is the correct way(s) to handle writing data to an NSOutputStream that is attached to a socket? And of what use is the NSStreamEventHasSpaceAvailable event code if it can (apparently) be ignored? It seems to me that there is either very fortunate UB happening (in examples 2 and 3), or there are several ways of sending data through a socket-based NSOutputStream...
You can write to a stream at any time, but for network streams, -write:maxLength: returns only until at least one byte has been written to the socket write buffer. Therefore, if the socket write buffer is full (e.g. because the other end of the connection does not read the data fast enough),
this will block the current thread. If you write from the main thread, this will block
the user interface.
The NSStreamEventHasSpaceAvailable event is signalled when you can write to the stream
without blocking. Writing only in response to that event avoids that the current thread
and possibly the user interface is blocked.
Alternatively, you can write to a network stream from a separate "writer thread".
After seeing #MartinR's answer, I re-read the Apple Docs and did some reading up on NSRunLoop events. The solution was not as trivial as I first thought and requires some extra buffering.
Conclusions
While the Ray Wenderlich example works, it is not optimal - as noted by #MartinR, if there is no room in the outgoing TCP window, the call to write:maxLength will block. The reason Ray Wenderlich's example does work is because the messages sent are small and infrequent, and given an error-free and large-bandwidth internet connection, it will 'probably' work. When you start dealing with (much) larger amounts of data being sent (much) more frequently however, the write:maxLength: calls could start to block and the App will start to stall...
For the NSStreamEventHasSpaceAvailable event, Apple's documentation has the following advice:
If the delegate receives an NSStreamEventHasSpaceAvailable event and does not write anything to the stream, it does not receive further space-available events from the run loop until the NSOutputStream object receives more bytes. ... ... You can have the delegate set a flag when it doesn’t write to the stream upon receiving an NSStreamEventHasSpaceAvailable event. Later, when your program has more bytes to write, it can check this flag and, if set, write to the output-stream instance directly.
It is therefore only 'guaranteed to be safe' to call write:maxLength: in two scenarios:
Inside the callback (on receipt of the NSStreamEventHasSpaceAvailable event).
Outside the callback if and only if we have already received the NSStreamEventHasSpaceAvailable but elected not to call write:maxLength: inside the callback itself (e.g. we had no data to actually write).
For scenario (2), we will not receive the callback again until write:maxLength is actually called directly - Apple suggest setting a flag inside the delegate callback (see above) to indicate when we are allowed to do this.
My solution was to use an additional level of buffering - adding an NSMutableArray as a data queue. My code for writing data to a socket looks like this (comments and error checking omitted for brevity, the currentDataOffset variable indicates how much of the 'current' NSData object we have sent):
// Public interface for sending data.
- (void)sendData:(NSData *)data {
[_dataWriteQueue insertObject:data atIndex:0];
if (flag_canSendDirectly) [self _sendData];
}
// NSStreamDelegate message
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
// ...
case NSStreamEventHasSpaceAvailable: {
[self _sendData];
break;
}
}
// Private
- (void)_sendData {
flag_canSendDirectly = NO;
NSData *data = [_dataWriteQueue lastObject];
if (data == nil) {
flag_canSendDirectly = YES;
return;
}
uint8_t *readBytes = (uint8_t *)[data bytes];
readBytes += currentDataOffset;
NSUInteger dataLength = [data length];
NSUInteger lengthOfDataToWrite = (dataLength - currentDataOffset >= 1024) ? 1024 : (dataLength - currentDataOffset);
NSInteger bytesWritten = [_outputStream write:readBytes maxLength:lengthOfDataToWrite];
currentDataOffset += bytesWritten;
if (bytesWritten > 0) {
self.currentDataOffset += bytesWritten;
if (self.currentDataOffset == dataLength) {
[self.dataWriteQueue removeLastObject];
self.currentDataOffset = 0;
}
}
}
I've recently posted a question about reducing the timeout of an FTP connection (click here to see it if you want).
Now, I've been asked to post a more specific question, focusing on the component we're using for the FTP download.
We're using Nico Kreipke's FTPManager (click here to go to its GitHub).
What we're trying to implement is to download data from an FTP address, and if it fails we'll fallback to use an HTTPS web server.
When the FTP address we give isn't available, it takes about one minute to timeout.
The question is, how can I reduce that timeout?
Best regards,
Tiago
Some More Info
I forgot to say, the FTP connection is done with an IP (local network).
Johan's Tip
I added a property to FTPManager, a double named timeout.
Then I've overridden the accessor of serverReadStream, a property used throughout FTPManager to hold the read stream, so that it would configure the timeout interval for all requests.
- (NSInputStream *)serverReadStream
{
return _serverReadStream;
}
- (void)setServerReadStream:(NSInputStream *)serverReadStream
{
if ((_serverReadStream = serverReadStream)) {
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &_timeout);
CFReadStreamSetProperty((__bridge CFReadStreamRef)(_serverReadStream), _kCFStreamPropertyReadTimeout, number);
CFRelease(number);
}
}
_kCFStreamPropertyReadTimeout is defined by:
#define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout")
However, it still takes about one minute to timeout. I set the timeout before connecting to the FTP address, right after creating ftpManager. The code I use to set the timeout follows:
FTPManager *ftpManager = [[FTPManager alloc] init];
[ftpManager setTimeout:10];
Have you tried something like using performSelector:withObject:afterDelay: with a custom method that checks whether the connection has already been established and data could be received, otherwise calls [ftpManager abort]?
Not a real connection timeout and seems kinda dirty, but should do the job.
I think it can be done by simply setting a property of CFReadStream. So you probably need to subclass the FTPManager.
The property is called _kCFStreamPropertyReadTimeout.
#define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout")
Then add this to appropriate method.
double timeout = 30;
CFReadStreamRef readStream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef)[[server.destination ftpURLForPort:server.port] URLByAppendingPathComponent:fileName]);
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &timeout);
CFReadStreamSetProperty(readStream, _kCFStreamPropertyReadTimeout, number);
CFRelease(num);
I will try to be as detailed as I can. I am trying to connect to an acquisition unit from my iPhone in my app. We are using IP4 and the acquisition unit doesn't support DHCP so its always scanning for device with a specific static IP and port no.
Before I tested the connection between the unit and my iPhone, I created an adhoc network using my desktop and try it out with my iPhone. This is part of my code.
CFSocketContext CTX = { 0, description, NULL, NULL, NULL };
/* Create the server socket as a TCP IPv4 socket and set a callback */
/* for calls to the socket's lower-level accept() function */
TCPServer = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP,
kCFSocketAcceptCallBack
, (CFSocketCallBack)WiFiCallBack, &CTX);
/* Set the port and address we want to listen on */
struct sockaddr_in addr;
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 = htonl(INADDR_ANY);
CFDataRef addressData = CFDataCreate( NULL, (UInt8*)(&addr), sizeof( struct sockaddr_in ) );
CFSocketSetAddress(TCPServer, addressData);
It works and I can do data transfer between my desktop and iPhone if I feed in the IP that was assigned to iPhone to the PC app on my desktop. However if I set a static IP for iPhone and try to get the PC app to connect to any device with that IP it doesn't work.
Same goes with my acquisition unit. The call back function is not called at all.
I am in desperate need of help so any form of help is welcomed. Thanks.
I'm sorry, but your post is not very clear.
Are you are trying to establish a server socket on the iPhone, and connect to it from elsewhere?
This is going to be problematic for many reasons.
First is that your ip is not going to be the same. When connected to WIFI, you will have an ip that is routable at least on the current network.
But when connected to 3g (or lte, etc), you will likely not be able to route to the ip given at all.
Even if you did have a fully routable ip address on some interface that existed long enough, iOS is not designed for this. Your application will not be able to run efficiently in the background and listen to a server socket. You can simulate this with persistent sockets and voip background mode. However that requires a separate server component.
You could also try polling from the iPhone, that may satisfy your requirements.
My server needs to send data when client connects to it. I am using Epoll ET mode.
But how to do it? Could any one give me a simple example for me?
Assuming you are listening on your socket (socket, bind, listen), and have added it's descriptor to epoll (epoll_create and epoll_ctl), then epoll_wait will tell you when there is a new connection to accept.
First you accept the connection (sockfd is descriptor of socket you're listening on, efd is epoll instance) and add it to your epoll instance:
int connfd = accept4(sockfd, NULL, 0, SOCK_NONBLOCK);
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
ev.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)
Then you go back to your main loop and call epoll_wait again. It will tell you when the socket is ready for writing, and you just happily write or sendfile away.
Add lots of error checking, and probably TCP_CORK and you're done. There's a working example on github.com/grahamking/netshare/.
I hope this gives you enough information to get started.
I am sending data to the server twice. First, I send "Hello world" and then I send "Server".
But the server received the data at 1 read. But the server have to read the data in a two-read operation.
Also, I write the data. Then read data from server and then I write the data.
In this case, the server can read the first data. But server can not read the second data.
The server uses read, write, read.
So how to overcome this issue? How do I write data to socket in BlackBerry?
What you describe is how TCP is supposed to work by default. What you are seeing is the Nagle algorithm (RFC 896) at work, reducing the number of outbound packets being sent so they are processed as efficiently as possible. You may be sending 2 packets in your code, but they are being transmitted together as 1 packet. Since TCP is a byte stream, the receiver should not be making any assumptions about how many packets it gets. You have to delimit your packet data in a higher-level protocol, and the receiver has to process data according to that protocol. It has to handle cases where multiple packets arrive in a single read, a single pakcet arriving in multiple reads, and everything in between, only processing packet data when they have been received in full, caching whatever is left over for subsequent reads to process when needed.
Hard to say without a little more detail, but it sounds like you're using 1-directional communication in the first case - i.e. the client writes, then writes again. There are any number of reasons that the server would receive the 2 writes as 1 read. Buffering on the client, somewhere in the wireless stack (or in the BES), buffering on the server side. All of those are legal with TCP/IP.
Without knowing anything more about your solution, have you thought about defining a small protocol - i.e. the client writes a known byte or bytes (like a 0 byte?) before sending the second write? Then the server can read, then recognize the delimiting byte, and say 'aha, this is now a different write from the client'?
As previously said this is an expected TCP behavior to save bandwidth. Note that to deliver your package TCP adds lot of data (e.g. destination port,sequence number, checksums...).
Instead of flushing the data I´ll recommend you to put more work in your protocol. For example you can define a header that contains the number of bytes to read and then the payload (the actual data).
The following code is a protocol encoded in an string with the structure [length];[data]
StringBuffer headerStr = new StringBuffer();
StringBuffer data = new StringBuffer();
//read header
char headerByte = dataInputStream.readChar();
while (headerByte != ';') {
headerStr.append(headerByte);
headerByte = dataInputStream.readChar();
}
//header has the number of character to read
int header= Integer.parseInt(headerStr.toString());
int bytesReaded = 1;
char dataByte = dataInputStream.readChar();
//we should read the number of characters indicated in the header
while (bytesReaded < header) {
data.append(dataByte);
dataByte = dataInputStream.readChar();
bytesReaded++;
}
For the first query, I guess you are using TCP. If you use UDP, then the server will read the packets in the order you want.
Can you be more clear/elaborative on the second query ?
I would try explicitly telling Connector.open to open up the stream as read_write. Then I would ensure that I flush my connections after each time I talked to the server.
SocketConnection connection = (SocketConnection) Connector.open(url, Connector.READ_WRITE);
OutputStream out = connection.openOutputStream();
// ... write to server
out.flush()
I got a solution to overcome to extract both the string
On sender device
Create a header which contains details of that data eg the data
length, datatype etc.
Add this header to the actual data and send it
On recipient device
read the header
retrieve the actual data length from the header
read the next data upto the data length as specified by the header