7

I have an issue with the development of a tcp server/client in objective c with Bonjour.

On the server side I open correctly the streams and I use the handleEvent function to send and receive data. But I don't know what is the proper way to send and receive data. I read this excellent post : Correct way to send data through a socket with NSOutputStream So I use a packet queued system to send data :

    switch(eventCode) {

    case NSStreamEventOpenCompleted: {
        NSLog(@"Complete");

    } break;

    case NSStreamEventHasSpaceAvailable: {
        if (stream == _outputStream)
            [self _sendData];
    } break;

...

- (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;
    }
}

}

. So basically i just send separate my data on many packets. But I don't see how to reconstruct the data on the client side. And I think i didn't correctly understood the NSStreamEventHasBytesAvailable event. Because here I send many packets but this event on the client side is call only once. And when I reconstruct the data from the buffer the data appears to be corrupted. I would really appreciate if someone can clarify all this, the documentation is not really clear on this point (or maybe I miss a point ..) .

        case NSStreamEventHasBytesAvailable: {
        if (stream == _inputStream)
        {
            //read data
            uint8_t buffer[1024];
            int len;
            while ([_inputStream hasBytesAvailable])
            {
                len = [_inputStream read:buffer maxLength:sizeof(buffer)];
                if (len > 0)
                {
                    NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
                    //NSData *theData = [[NSData alloc] initWithBytes:buffer length:len];
                    UnitySendMessage("AppleBonjour_UnityNetworkManager(Clone)", "OnLocalClientReceiveMessageFromServer", [output cStringUsingEncoding:NSUTF8StringEncoding]);
                }
            }
        }
Community
  • 1
  • 1
thegrandwaazoo
  • 301
  • 2
  • 15
  • Have you tried a simple test? Send a single short string. What do you get in your variable "output" when you do this? fwiw; there doesn't have to be a single call of bytes available for each send, they will be combined if they arrive in time and one read will get them all. Please try this test and tell us what you get. – john elemans Nov 17 '15 at 21:52
  • Oh, I forgot one more thing; you can test the receiver with a simple tcp program like telnet. Just connect to your port and type some text. If the receiver code is working you should get your text directly. – john elemans Nov 21 '15 at 19:20
  • Okay a simple string worked. So if i send a string bigger than the buffer, I just have to reconstruct it each time the NSStreamEventHasBytesAvailable is called. But how can i have a notification when the string has been sent completely ? – thegrandwaazoo Nov 23 '15 at 14:56
  • You cannot. A stream is a stream. You have to encode the end of message in the stream itself. When the receiver processes incoming bytes, it has to look for the 'end of message marker'. Depending on the sender and since the receiver can be busy you have to be ready to process a block which might have an end of message in and more bytes as well, the start of the next message. – john elemans Nov 23 '15 at 15:37

1 Answers1

3

It seems, that you use two variables currentDataOffset -- an instance variable and a property -- for the same purpose. If you indeed need both of them, you should set 0 to currentDataOffset as well. But I think the fragment should look like:

readBytes += currentDataOffset;
NSUInteger dataLength = [data length];
NSUInteger lengthOfDataToWrite = MIN(dataLength - currentDataOffset, 1024);
NSInteger bytesWritten = [_outputStream write:readBytes maxLength:lengthOfDataToWrite];
if (bytesWritten > 0) {
    currentDataOffset += bytesWritten;
    if (currentDataOffset == dataLength) {
        [self.dataWriteQueue removeLastObject];
        currentDataOffset = 0;
    }
}
tonso
  • 1,760
  • 1
  • 11
  • 19