Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read stream never completes #89

Closed
basvankuijck opened this issue Oct 12, 2012 · 9 comments
Closed

Read stream never completes #89

basvankuijck opened this issue Oct 12, 2012 · 9 comments

Comments

@basvankuijck
Copy link

I am playing with GCDAsyncSocket (MRC / iOS5.1) for a while, especially with "large" files (5 - 10 mb). Unfortunately sometimes the read stream is never completed (e.g. it gets stuck) just a few bytes at the end of the stream; the didReadPartialDataOfLength: stops giving me information and the didReadData is not fired at all.

Here's some of my code (for both writing / reading examples the connection between the host and client have been established)

WRITING

#define kHeadTag 0
#define kDataTag 1

typedef struct {
    NSUInteger size;
} head_t;

-(void)sendData:(NSData *)__data  {
    _sendData = [__data retain];

    head_t header;
    header.size = __data.length;
    [_socket writeData:[NSData dataWithBytes:&header
                                   length:sizeof(header)]
       withTimeout:-1
       tag:kHeadTag];
}

-(void)socket:(GCDAsyncSocket *)sock
       didWriteDataWithTag:(long)tag {
    if (tag == kHeadTag) {
        [sock writeData:_sendData withTimeout:-1 tag:kDataTag];
        [_sendData release];
        _sendData = nil;
    }
}

READING

-(void)socket:(GCDAsyncSocket *)sock
       didReadData:(NSData *)data
       withTag:(long)tag {
    switch (tag) {            
            // ------------------------------------- HEAD
        case kHeadTag:{
            head_t head;
            [data getBytes:&head length:sizeof(head)];
            _readHead = head;
            NSLog(@"Received header (total size = %i bytes)", head.size);
            [sock readDataToLength:head.size
                withTimeout:-1
                tag:kDataTag];
        }
            break;

            // ------------------------------------- BODY
        case kDataTag: {
             NSLog(@"Data received with %i bytes", data.length);
        }
            break;
    }
};

-(void)socket:(GCDAsyncSocket *)sock
       didReadPartialDataOfLength:(NSUInteger)partialLength
       tag:(long)tag {
    if (tag == kDataTag) {
        NSLog(@"Data read with %i bytes", partialLength);
    }
};

I hope this is enough code to see what I'm doing wrong or maybe this is a bad practice for writing/reading large chunks of data.

@ieswxia
Copy link

ieswxia commented Oct 12, 2012

You should send the large size file using write data method only once,and then reading small data in
readDataToLength ,for example:

// sender data client

  • (void)sendData

{

GCDAsyncSocket *socket=[[GCDAsyncSocketalloc] initWithDelegate:selfdelegateQueue:dispatch_get_main_queue()];

if (![socket connectToHost:servIPAdressonPort:servPortStr.intValuewithTimeout:4error:nil]){

    //    if (![socket connectToHost:TEST2IP onPort:9999 error:nil]) {

    NSLog(@"connect to host error..\n");

    return;

}

NSString * filePath=[[NSBundlemainBundle] pathForResource:@"xxx.ipa"ofType:nil];



NSData * fileData=[NSDatadataWithContentsOfFile:filePath];

int fileLength=[fileData length];

NSData * lengthData=[NSDatadataWithBytes:&fileLength length:sizeof(int)];

NSMutableData * sendData=[[NSMutableDataalloc] initWithLength:0];

[sendData appendData:lengthData];

[sendData appendData:fileData];

[socket writeData:sendData withTimeout:-1tag:1];

[sendData release];

}

// receive data client

  • (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{

    if (!acceptSocket) {

    acceptSocket=[newSocket retain];
    
    receiveData=[[NSMutableDataalloc] initWithLength:0];
    
    NSLog(@"did accept new socket"); 
    

    }

    progressV = [[UIProgressViewalloc]initWithFrame:CGRectMake(0, 0, 200, 20)];

    [progressVsetProgressViewStyle:UIProgressViewStyleDefault];

    progressV.center = CGPointMake(160, 230);

    [self.viewaddSubview:progressV];

    [progressVrelease];

    [acceptSocketreadDataToLength:sizeof(int) withTimeout:-1tag:1];

}

  • (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

    NSLog(@"did read data");

    if(fileLength==0){

    [data getBytes:&fileLengthlength:sizeof(int)];
    
    NSLog(@"file length = %d\n",fileLength);
    
    [acceptSocketreadDataToLength:3000withTimeout:-1tag:1];
    

    }else {

    if ([receiveDatalength] < fileLength) {
    
        [receiveDataappendData:data];
    
        float progress = (float)receiveData.length / (float)fileLength;
    
        [progressVsetProgress:progress animated:YES];
    
        int leftLength=fileLength-[receiveDatalength];
    
        if(leftLength<3000){
    
            [acceptSocketreadDataToLength:leftLength withTimeout:-1tag:2];
    
        }else {
    
            [acceptSocketreadDataToLength:3000withTimeout:-1tag:2];
    
        }
    
        NSLog(@"receiveData Length===%d",[receiveDatalength]);
    
        if([receiveDatalength]==fileLength){
    
            NSString * destPath= NSHomeDirectory();
    
            NSString * strDir=[destPath stringByAppendingPathComponent:@"Documents"];
    
            NSString * de=[strDir stringByAppendingPathComponent:@"111.zip"];
    
            [receiveDatawriteToFile:de atomically:YES];
    
        }
    
    }else {
    
        [receiveDataappendData:data];
    
        NSLog(@"receive Finished && dataLength=%d",[receiveDatalength]);
    
    }
    

    }

}

I use this to send large size file successfully.You can refer it.I hope it will help you.
I am chinese.my english is poor.Hope you can pardon me.

At 2012-10-12 17:33:04,basvankuijck notifications@github.com wrote:

I am playing with GCDAsyncSocket (MRC / iOS6) for a while.
Especially with "large" files (5 - 10 mb). But sometimes the read stream is never completed (e.g. it gets stuck) just a few bytes at the end of the stream. the didReadPartialDataOfLength: stops giving me information and the didReadData is not fired at all.

Here's some of my code:

WRITING
#define kHeadTag 0#define kDataTag 1typedefstruct{NSUIntegersize;}head_t;-(void)sendData:(NSData_)__data{_sendData=[__dataretain];head_theader;header.size=__data.length;[socketwriteData:[NSDatadataWithBytes:&headerlength:sizeof(header)]withTimeout:-1tag:kHeadTag];}-(void)socket:(GCDAsyncSocket)sockdidWriteDataWithTag:(long)tag{if(tag==kHeadTag){[sockwriteData:_sendDatawithTimeout:-1tag:kDataTag];[sendDatarelease];sendData=nil;}}
READING
-(void)socket:(GCDAsyncSocket
)sockdidReadData:(NSData
)datawithTag:(long)tag{switch(tag){// ------------------------------------- HEADcasekHeadTag:{head_thead;[datagetBytes:&headlength:sizeof(head)];NSLog(@"Received header (total size = %i bytes)",head.size);[sockreadDataToLength:head.sizewithTimeout:-1tag:kDataTag];}break;// ------------------------------------- BODYcasekDataTag:{NSLog(@"Data received with %i bytes",data.length);}break;}};-(void)socket:(GCDAsyncSocket*)sockdidReadPartialDataOfLength:(NSUInteger)partialLengthtag:(long)tag{if(tag==kDataTag){NSLog(@"Data read with %i bytes",partialLength);}};

I hope this is enough code to see what I'm doing wrong or maybe this is a bad practice for writing/reading large chunks of data.

¡ª
Reply to this email directly or view it on GitHub.

@basvankuijck
Copy link
Author

I was under the assumption that GCDAsyncSocket did the 'packet chunking' for me.

@kcclark
Copy link

kcclark commented Apr 22, 2013

I also was under the assumption that GCDAsycSocket did the 'packet chunking' for me, but I am getting the same result you are. Under heavy load, and not all of the time, readDataToLength never completes even though tcpdump shows the data as being received and ack'd back to the sender.

Did you ever resolve this?

KCC

@timlukens
Copy link

Ugh, please tell me there is a solution to this. I am now experiencing the exact same thing. If I write out the NSData to disk after the last didRead, it's about one "read size" away from completion.

@PranavJaiswal
Copy link

Did someone in this thread find a solution/workaround to this issue?

@kcclark
Copy link

kcclark commented Oct 21, 2014

The only solution is to do a generic read ( readDataWithTimeout ) and do your own message aggregation with whatever length data you get.

@timlukens
Copy link

Chiming back in on this. Here we are 5 years later and it's still happening, though it only seems to happen to me when I'm using TLS. It seems to always be about 8 bytes away from completion regardless of how small I chunk the data reads.

jdeff pushed a commit to jdeff/CocoaAsyncSocket that referenced this issue Feb 23, 2018
When using GCDAsyncSocket, we have noticed that our reads can sometimes
hange before all of the data is read. The end result is that our read
times out and the connection is closed. This issue seems to be similar
to ones that others have reported (e.g. robbiehanson#89, robbiehanson#185, robbiehanson#225, and possibly

The problem seems to be that `doReadData` can get into a state where the
method finishes, we have not read all of the bytes for the current read,
but we do not resume the `readSource`, preventing `doReadData` from
being triggered again.

Here is a confusing but valient attempt to explain the issue: When we
enter `doReadData` we calculate the `estimatedBytesAvailable`. This
value includes how much data we think is available on the socket plus
how much data is available on some of our internal buffers. If that
number is greater than 0, we will initialize a `waiting` flag to false.
We then proceed to read from the socket, since there are bytes
available.  This is where it gets tricky. We calculate the number of
`bytesToRead` from the socket by taking the minimum of A.) The
`estimatedBytesAvailable` plus 16 kb or B.) The remaining bytes of the
current read. The trickiness comes from the fact that when `B` is larger
than `A` and the socket has the extra 16kb all ready to be read, then
the call to `SSLRead` will return with no error. This is problematic
because we rely on a result of `errSSLWouldBlock` in order to toggle the
`waiting` flag to true. Finally, when we reach the end of the
`doReadData` method and we have not completed the current read and
`waiting` is not set to true.  Because `waiting` is false, we do not
attempt to resume the readSource.  Since the `readSource` may be in a
suspended state, there is nothing to trigger another call to
`doReadData`. This results in what looks like a "hung" or "frozen" state
from the client. Even though data is flowing to our socket, we never
finish reading it out.

This fix in this commit, as simple as it is, just sets `waiting` to true
in the case of the "read to length" and "read to term" cases where we
are expecting to see more data.

Another possible fix could be pass the remaining bytes to read to
SSLRead to trigger the `errSSLWouldBlock`. The issue with this is that I
don't fully understand the logic around
`optimalReadLengthWithDefault:shouldPreBuffer:`. It may be more
desirable to not trigger that error and just wait until the readSource
says that there is more available from the file descriptor.

Another possible fix would be in how we initially set `waiting`.
Instead of setting it to `!hasAvailableBytes`, you could change it to
`(estimatedBytesAvailable + [preBuffer availableBytes]) < (currentRead->readLength -
currentRead->bytesRead). I am not sure if this is desireable and I
would neet some input from the maintainers.
jdeff pushed a commit to jdeff/CocoaAsyncSocket that referenced this issue Feb 23, 2018
When using GCDAsyncSocket, we have noticed that our reads can sometimes
hange before all of the data is read. The end result is that our read
times out and the connection is closed. This issue seems to be similar
to ones that others have reported (e.g. robbiehanson#89, robbiehanson#185, robbiehanson#225, and possibly

The problem seems to be that `doReadData` can get into a state where the
method finishes, we have not read all of the bytes for the current read,
but we do not resume the `readSource`, preventing `doReadData` from
being triggered again.

Here is a confusing but valient attempt to explain the issue: When we
enter `doReadData` we calculate the `estimatedBytesAvailable`. This
value includes how much data we think is available on the socket plus
how much data is available on some of our internal buffers. If that
number is greater than 0, we will initialize a `waiting` flag to false.
We then proceed to read from the socket, since there are bytes
available.  This is where it gets tricky. We calculate the number of
`bytesToRead` from the socket by taking the minimum of A.) The
`estimatedBytesAvailable` plus 16 kb or B.) The remaining bytes of the
current read. The trickiness comes from the fact that when `B` is larger
than `A` and the socket has the extra 16kb all ready to be read, then
the call to `SSLRead` will return with no error. This is problematic
because we rely on a result of `errSSLWouldBlock` in order to toggle the
`waiting` flag to true. Finally, when we reach the end of the
`doReadData` method and we have not completed the current read and
`waiting` is not set to true.  Because `waiting` is false, we do not
attempt to resume the readSource.  Since the `readSource` may be in a
suspended state, there is nothing to trigger another call to
`doReadData`. This results in what looks like a "hung" or "frozen" state
from the client. Even though data is flowing to our socket, we never
finish reading it out.

This fix in this commit, as simple as it is, just sets `waiting` to true
in the case of the "read to length" and "read to term" cases where we
are expecting to see more data.

Another possible fix could be pass the remaining bytes to read to
SSLRead to trigger the `errSSLWouldBlock`. The issue with this is that I
don't fully understand the logic around
`optimalReadLengthWithDefault:shouldPreBuffer:`. It may be more
desirable to not trigger that error and just wait until the readSource
says that there is more available from the file descriptor.

Another possible fix would be in how we initially set `waiting`.
Instead of setting it to `!hasAvailableBytes`, you could change it to
`(estimatedBytesAvailable + [preBuffer availableBytes]) < (currentRead->readLength -
currentRead->bytesRead). I am not sure if this is desireable and I
would neet some input from the maintainers.
@mikezriel
Copy link

mikezriel commented Feb 23, 2018

Add the following code on line 5480 for GCDAsyncSocket.m file.

 // Check if over received expected size and truncate, othersize message will nevver be done
if(!done && currentRead->readLength > currentRead->done) {
    currentRead->buffer = [currentRead->buffer subdataWithRange:NSMakeRange(0, currentRead->bytesDone)];
    done = YES;
}

This made all the difference for me!

@github-actions
Copy link

This issue has been marked as stale, it will be closed automatically if there is no further activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants