Foamplate

You are not logged in. Login
PING
Aug. 19, 2026

PING? PONG!

That's the way of keep-alive in the land of TCP socket connections. It's all a matter of course when you are writing logic that needs to communicate with things outside of itself. Your application might connect to another instance of itself on another computer, or another instance of itself on the same computer, using sockets as inter-process communication (IPC). The issues you have to deal with when writing socket code are the same either way. You can't assume that things are going to go much more ideally if your socket connection is local.

Here's how socket connections tend to go. First, you make a socket object withe socket() function. You're already in the weeds here, and you haven't been told the system to do anything yet! There's choices to make right off the bat, because the socket() function has 3 parameters. You have to get it an address family, a socket type, and a protocol. For the address family, you're probably going to be choosing between IPv4 or IPv6, which is AF_INET or AF_INET6 respectively. There's others, but let's set those aside. Next you have the socket type. It could be a SOCK_STREAM or a SOCK_DGRAM, depending on whether you want a TCP connection or a UDP connection, respectively. For the third parameter you might pass a 0.

Here you'll probably grumble, why talk about such a low level method of making socket connections? I'm writing in my favorite language, which abstracts all that away, you cry. All that may be true, but if you'll peel away all those onion layers of abstraction, in the end you just have these Berkeley socket file descriptors and these socket methods that act on them. So it's helpful to think about the technology that underlies everything.

In any case, you'll make your socket, then you'll probably populate a sockaddr_in structure with an address and a port you wan to connect to. Fnally, call "connect()" and pass it your socket and your sockaddr_in structure, plus the length of that structure. All the magic then proceeds in the background to establish a TCP connection for you, associating that socket with a data stream that you can use to communicate with the outside world.

Once you've connected, you'll probably send() things and recv() things. In the happy path, your send() call sends all the things and your recv() call receives all the things. Your communication is flawless and every byte that you wanted to receive gets received, and every byte that you want to send makes it to the target.

Things rarely go that smoothly though, do they? If you want your program to be robust and production ready, and in other words, good, you'll need to take plenty more steps and handle all the edge cases. You might look at all the methods you call and put appropriate error handling for all the possible return values. Good, that's a start, but there's more to do.

About this send() call. No matter how many bytes you send it, there's no promise that they can or will be all sent at once. You could pass it 2 bytes and it might send 0 or 1, in which case you'll need to call it again. It is up to you and your program to keep track of how many bytes you sent so far and how many bytes you have left to send. Therefore, a program calling send() on a TCP socket can only be correct if it continually calls send() until all the bytes it is trying to send have been accepted. It doesn't matter if your socket is on the localhost. This is just the nature of TCP sockets. If you're going to use them, you'll have to play by their rules.

The recv() call is the same way. You might get all the bytes you need in one call, or you might not. You need to keep calling recv until you have all the bytes. Then the question becomes, how do you know you've received all the bytes? Getting this right is all about the shape of the data that you send over the TCP socket, in other words, your data protocol. What you need to do to solve this is send over the socket some meta data about the data that it's about to send. Kind of like, "I'm about to send a word which is 5 letters long" and then it sends "hello". On the receiving end, the program will get the first message, saying "there's about to be a 5 letter word on the socket. Maybe it calls recv() once and finds "he". Since it knows there are 3 more letters, it calls recv() again to receive them. It got "he" in the first call, then "llo" in the second call, and it needs to put the two together to make "hello". Perfect!

A whole host of other things can go wrong dealing with sockets. One thing that could happen is that the socket can die unexpectedly. In that case, you might call "recv()" and your application then freezes. The recv() call hangs forever. In some cases, this behvaior might be fine. If you were writing a command line application that connects to a server, receives some data, then closes the connection and moves on, a dead socket might be an acceptable risk. If the program freezes while performing a socket operation, the user might just press Ctrl-C to make the program stop and then try again. Nothing so wrong with that. For other cases, however, you'll probably want to make your socket connections asynchronous so that the send() and recv() calls don't block. Sockets being asynchronous are pretty much mandatory for programs that need to run as part of automation or programs that run with a user interface.


There are no comments for this post.


Would you like to leave a comment?

Your email will never be shared nor sold with anyone. You can unsubscribe from the mailing list at any time. If you permit us to display your comment, you agree to permanently transfer ownership of its content to us, to display on this page indefinitely. In that case, your name and comment may be published, but not your email.