Re: Understanding NSNetService and peer-to-peer streaming


Graham Cox
 

On 22 Aug 2017, at 2:46 am, Jens Alfke <jens@mooseyard.com> wrote:


On Aug 20, 2017, at 7:00 PM, Graham Cox <graham@mapdiva.com> wrote:

If I send two ‘messages’ too quickly, they get concatented on the stream, and the receiver can’t tell where the boundary is between the two (or more) messages.
This is a classic newbie networking mistake. (No offense I hope!)

None at all! I have very little experience in this area.


TCP is a stream, and there is absolutely no delimiter between writes, and no association between the number of bytes written vs. the number read at the receiver. It's true that if you only intermittently send bytes, the receiver will usually receive the same number of bytes from its read call … but not always, depending on circumstances, because the data can get broken up at arbitrary locations.

To send and receive messages you have to delimit them somehow. The classic ways are to prefix every message with a count, or to have a reserved byte sequence that denotes end-of-message. (Pascal strings and C strings, anyone?) The former is better as long as you know the message length ahead of time.

So for example, on the sending end just write a 32-bit byte count (big- or little-endian, pick one), then the message. On the receiving end, have a resizable read buffer (an NSMutableData works well) and keep reading bytes into it, growing if necessary. After each read decode the message length at the start, then check if you have that many more bytes in the buffer; if so, hand the message to the higher-level code, then delete the count and the message from the buffer, so what remains starts at the next message boundary. Then check if you've got another complete message, and when you don't, issue another read.
Great, I was hoping someone would help me in exactly this way. I thought the length field approach would work, but those with more experience may steer me in a better direction. Good to know I was thinking along the right lines.

The data I’m sending is actually a plist. I am using [NSPropertyListSerialization writePropertyList:toStream:…], which is very convenient. But call it twice and the other end gets confused. It surprised me a little bit because I would have thought that having provided such a convenient method for writing a plist to a stream, the apparently equally convenient inverse method would be able to sort this problem out for itself. But no, it appears not. Which makes me wonder in what case it is useful.

I have a protocol/framework called BLIP that's basically this to the nth power. It does message queueing, multiplexing, high/low priority, message headers (like HTTP), etc. Might be overkill for your needs, but check it out.
I definitely will!

many thanks,

—Graham

Join cocoa@apple-dev.groups.io to automatically receive all group messages.