I am trying to write a client for my AF_UNIX server. The server occasionally does a
write(fd, buffer, bufferSize);
where the message always ends in a newline.
Now, I want a C client to read it, message by message.
With bash, it's possible to do something like this:
socat - UNIX-CONNECT:/tmp/myepicsocket.sock | while read line; do handle $line; done
How would I go about implementing this in C/C ?
I've tried a simple
char buffer[1024]; // my sent messages are max 1024 bytes
size_t recieved = recv(socketfd, buffer, 1024, NULL);
std::string messageRecieved(buffer);
messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n'));
in a loop, but it seems like recv() gets the message from the beginning of the "file" (meaning once the first message is sent, it will keep spamming that first one)
is there a way to read it line by line, message by message, or flush?
CodePudding user response:
recv(socketfd, buffer, 1024, NULL);
With stream sockets, like AF_UNIX
sockets, recv()
is equivalent to read()
. That's what makes them "stream" sockets. So, for all practical matters, this is:
read(socketfd, buffer, 1024);
And just like with a regular file you have no guarantees whatsoever that this reads only up until the first newline. If the peer managed to write two short messages before you get around here, this read()
will cough up both of them, at once, and place them into the buffer
.
So what to do?
Well, the answer here is the same answer this question always has: write C logic to implement it.
The buffer needs to be persistent. You need to keep track of how many "unread" characters exist in the buffer, and the buffer is effectively "initialized" to an empty state by setting the unread count to 0.
Each time your code decides to read a "line", it checks if the buffer contains anything, and that "anything" includes a newline. If so, there's your line. Pull it out of the buffer, and update the contents of the buffer, and the unread count, to reflect that this "line" is no longer there. If there isn't a newline in there, only then you read from the socket, and append what's read after the buffer's existing contents, so if the previous read left ended with a partially read line, this will correctly reassemble the pieces. Then go back to Step 1.
Of course, if the buffer carries a fixed size, like 1024 bytes, there's a possibility that you'll read 1024 characters without seeing a newline. You don't have anywhere to read()
anything more, into. You'll need to decide how to handle that situation, and implement the appropriate logic for that too. And you'll also need to figure out what you will do if your read()
or recv()
fails or indicates that the socket is closed.
So that's the most direct and the simplest way of handling that. Another approach, that involves slightly more advanced C knowledge and expertise is to implement your own subclass of std::streambuf
that uses the socket as the underlying data source, and override the appropriate methods, then use it to construct a std::istream
, and then simply use std::getline()
whenever you feel the need to pull out the next line out of it.