StreamingSocket Drops 1st Byte of Input

I had a peculiar sitation arising between a Juce plugin and a remote connection, where the plugin is supposed to provide telemetry on its inner workings.

The plugin opens a server socket and listens for a connection. Then once connected, it waits for one byte of info (anything), and sends back a block of 48 Float32. The connection was being made, but the plugin played deaf, until I checked to see what would happen if I sent 2 bytes.

Sure enough, that worked. Further testing shows that it consistently drops the first byte of input. I fixed, for myself, on the remote end by just always sending two request bytes instead of one.

Looking over the code in juce_Socket.cpp, function readSocket(). I see some -1 being used as sentinel values, but Iā€™d bet the code is getting tripped up on its counting - off by 1.

Datagram was acting strangely before I switched to StreamingSocket. Iā€™ll bet there are similar issues there too. Datagram was very erratic in responding to client requests. Mostly just ignoring them, and every so often it would see the request and send back the data.

How are you polling your socket(s)? Are you checking the return value from Socket::read?

Iā€™ve just whipped up a little example to test things:

The code is very simple:

StreamingSocket s;

s.createListener (12345);

if (auto p = s.waitForNextConnection())
{
    char buffer[32]{};

    printf ("Connection Accepted\n");

    const auto n = p->read (buffer, 32, true);

    printf ("Received %d bytes: \"%s\"\n", n, buffer);

    p->close();
    delete p;
}

Yes, and my code is similarly very simple:

void jCTelemetry::run() {
char buf[2];

while(!threadShouldExit()) {
    juce::StreamingSocket *cnx = m_sock.waitForNextConnection();
    if(0 == cnx)
        break;
    while(!threadShouldExit() && (cnx->read(buf, 1, true) > 0)) {
        Float32 staging[4*12];
        {
            const juce::ScopedReadLock myScopedLock(m_pproc->busyLock);
            memcpy(staging, m_pproc->get_liveInfo(), sizeof(staging));
        }
        // syslog(1, "Crescendo Telemetry Writing %lu bytes", sizeof(staging));
        cnx->write(staging, sizeof(staging));
    }
    cnx->close();
}
m_sock.close();

}

I just studied the code in readSocket(). It looks clean. The only essential difference between it, and one that I wrote myself a few years ago, is that readSocket() uses ::recv() and ::recvFrom(). Whereas, my version uses the OS ::read() against the underlying filedesc of the socket.

So perhaps ::recv() is the culprit?

I explicitly send 2 bytes of information, the Juce side receives one byte, and it is the second one that I sent. All my other receivers in the lab receive two bytes.

I recommend Packet Sender for debugging network packets. It can send arbitrary packets and open ports (TCP and UDP) for receiving packets too.

I tested your code verbatim (Windows ā†’ macOS):

I would emulate your server and client with Packet Sender, it will at minimum confirm the issue youā€™re seeing, or it could point you toward another potential problem area.

1 Like

I just added back a UDP Server and today it is working fine. So, since the code in readSocket() looks clean, and it was looking like ::recv() was suspect - it makes me think that my iMac got into a funky state yesterday.

I just happened to reboot it last night for entirely different reasons. But today things are looking better, at least for UDP connections.

However, I just tried falling back to sending only 1 byte over the SocketStream connection, and it still fails on the Juce side. So Juce is still dropping the first byte of every message and seeing only the second byte onward.

I dunnoā€¦ looks like maybe ::recv() has problems. My old code using ::read() and the socket filedesc still works just fine.

To reiterate - the remote code runs just fine against numerous other network nodes here in the lab. It has been and continues to run just fine, for the past several years. It is only the new Juce plugin that suffers the problem. The code in Juce support modules looks clean. But it is easily worked around on my remote end by sending a dummy byte ahead of every message to the Juce plugin.

Itā€™s not impossible that there is a bug in our Socket wrapper but it is quite old and ā€˜stableā€™.

UDP packets do get lost all the time, so that should be expected, TCP on the other hand will not drop packets.

Something to be mindful of when using TCP sockets is Nagleā€™s Algorithm. It might be buffering your outgoing data and not actually transmitting it. You can disable this with TCP_NODELAY .

1 Like

I just ran a test here. I am forcing the output buffer to write, so not a Nagle problem here.

I run the same send code to two different destinations. The first is the Juce plugin acting as a TCP server, and the second is my own server written in Lisp. When I send to Juce TCP the call to ::recv() definitely shows it dropping the first byte of a 2-byte message. My own server shows both bytes being received.

The code surrounding ::recv() in Juce looks clean. The problem occurs in the call to ::recv(). Maybe there is something funky about the way the socket port was opened? Some flag setting? My own server works just fine, but Juce use of ::recv() consistently fails.

Looking more closely at the code surrounding the call to ::recv(), located in juce_Socket.cpp, SocketHelpers::readSocket(), I see calls to set the flags to blocking (since Iā€™m using blocking reads).

As an aside, these fcntl() calls are happening outside of the read lock being used to avoid race conditions. That is itself a potential race condition. It isnā€™t a problem in this case, since there is no competition for the socket flags in my case. But to this extent, the Juce code is problematic if it wants to avoid race conditions.

It might be the case that flipping the flags from a non-blocking condition to a blocking condition causes the loss of the first byte in the messages. My own manually written server code in C++ does not perform any fcntl() calls prior to reading since the flags were initially set to blocking-reads by default. And my C++ server code does not drop any bytes in the messages.

Phew! I found the problemā€¦

If your sent message begins with a 0x00 byte, then that initial byte is discarded by Appleā€™s recv() and read() against a socket filedescr. The problem appears to be a ā€œbugā€ in the Apple socket library.

I looked everywhere for the answer and, while I still donā€™t understand why my own C++ and Lisp readers work just fine, the Juce code, which depends on Appleā€™s provided recv() exhibits this bug. It appears to be outside the control of Juce itself.

3 Likes