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);
Related
Does anyone know if there is a (really) fast way to check for an inactive internet connection?
I have implemented the solution suggested here and it work but takes about 30 sec to determine if there was no internet connection.
I have also tried out:
- (BOOL) isConnectionAvailable {
SCNetworkReachabilityFlags flags;
BOOL receivedFlags;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(CFAllocatorGetDefault(), [#"www.google.com" UTF8String]);
receivedFlags = SCNetworkReachabilityGetFlags(reachability, &flags);
CFRelease(reachability);
return (!receivedFlags || flags == 0) ? FALSE : TRUE;
}
which works too but it still takes about 20-30 seconds to determine if there is no active internet connection.
I feel that it got to be a fast way to determine if there is no internet connection.
Would really appreciate if someone can point me in a good direction.
I guess the "fastest" way, would be to simply utilize the connection with minimal data. Just set an appropriate timeout that works for you.
Apple even recommended at WWDC, that you should just simply try to establish a connection, because the reachability API might provide an inaccurate answer with certain conditions. The only way to measure properly is by sending a real request.
You can make a background thread in you application that will be checking if there is internet connection.
I use this customised reachability class in one of my apps: (code put elsewhere due to length)
Reachability.h - http://pastebin.com/qUVp4tFb
Reachability.m - http://pastebin.com/3C8LUjkS
Make sure to #import it, and call the following which returns a BOOL:
([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == ReachableViaWWAN);
It takes around a second to determine if the internet connection is active or not with this class.
Hope that helped :)
I looked into GCDAsyncSocket.m at the code that handles read timeout. If I don't extend the timeout, it seems that socket got closed and there is no option to the socket alive keep. I can't use infinite timeout (timeout = -1) because I still need to know when it is timed out, but also doesn't want it to disconnect. I'm not sure there is a reason behind this. Does anyone know?
- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
{
if (currentRead)
{
if (timeoutExtension > 0.0)
{
currentRead->timeout += timeoutExtension;
// Reschedule the timer
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC));
dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
// Unpause reads, and continue
flags &= ~kReadsPaused;
[self doReadData];
}
else
{
LogVerbose(#"ReadTimeout");
[self closeWithError:[self readTimeoutError]];
}
}
}
FYI, there is a pull request at https://github.com/robbiehanson/CocoaAsyncSocket/pull/126 that adds this keep-alive feature but it is not pulled yet.
I am the original author of AsyncSocket, and I can tell you why I did it that way: there are too many ways for protocols to handle timeouts. So I implemented a "hard" timeout and left "soft" timeouts up to the application author.
The usual way to do a "soft" timeout is with an NSTimer or dispatch_after. Set one of those up, and when the timer fires, do whatever you need to do. Meanwhile, use an infinite timeout on the actual readData call. Note that infinite timeouts aren't actually infinite. The OS will still time out after, say, 10 minutes without successfully reading. If you really want to keep the connection alive forever, you might be able to set a socket option.
In my application I connect to a server using
- (void)connectToServerUsingCFStream:(NSString *) urlStr portNo: (uint) portNo
This function is called by another method
- (void)connectToServer:(NSString *)serverName onPort:(int)portNo
{
[self connectToServerUsingCFStream:serverName portNo:portNo];
while (!((iStream.streamStatus == 2) || (oStream.streamStatus == 2))) {
continue;
}
NSLog(#"Streams connected");
[self sendLoginRequest];
}
Now I want to know wether there is an easy possibility to check if my connection request is timed out (maybe with a certain time value?). Is there a way to handle this in my while loop or should I use something different?
Thanks in advance,
Bautzi
I have no idea how you exactly implement the connection, but here I have some connection codes from the XMPPFramework , as the code comments:
/**
* XMPPReconnect handles automatically reconnecting to the xmpp server due to accidental disconnections.
* That is, a disconnection that is not the result of calling disconnect on the xmpp stream.
*
* Accidental disconnections may happen for a variety of reasons.
* The most common are general connectivity issues such as disconnection from a WiFi access point.
*
* However, there are several of issues that occasionaly occur.
* There are some routers on the market that disconnect TCP streams after a period of inactivity.
* In addition to this, there have been iPhone revisions where the OS networking stack would pull the same crap.
* These issue have been largely overcome due to the keepalive implementation in XMPPStream.
*
* Regardless of how the disconnect happens, the XMPPReconnect class can help to automatically re-establish
* the xmpp stream so as to have minimum impact on the user (and hopefully they don't even notice).
*
* Once a stream has been opened and authenticated, this class will detect any accidental disconnections.
* If one occurs, an attempt will be made to automatically reconnect after a short delay.
* This delay is configurable via the reconnectDelay property.
* At the same time the class will begin monitoring the network for reachability changes.
* When the reachability of the xmpp host has changed, a reconnect may be tried again.
* In addition to all this, a timer may optionally be used to attempt a reconnect periodically.
* The timer is started if the initial reconnect fails.
* This reconnect timer is fully configurable (may be enabled/disabled, and it's timeout may be changed).
*
* In all cases, prior to attempting a reconnect,
* this class will invoke the shouldAttemptAutoReconnect delegate method.
* The delegate may use this opportunity to optionally decline the auto reconnect attempt.
*
* Auto reconnect may be disabled at any time via the autoReconnect property.
*
* Note that auto reconnect will only occur for a stream that has been opened and authenticated.
* So it will do nothing, for example, if there is no internet connectivity when your application
* first launches, and the xmpp stream is unable to connect to the host.
* In cases such as this it may be desireable to start monitoring the network for reachability changes.
* This way when internet connectivity is restored, one can immediately connect the xmpp stream.
* This is possible via the manualStart method,
* which will trigger the class into action just as if an accidental disconnect occurred.
**/
I don't know if this XMPPReconect class meets your demand.
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.
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