You've already gotten a lot of comments, but I'll try to summarize at least a few of the bits and pieces into one place.
First of all, the problem: as you're doing things right now, there are basically three ways you call to read
can return:
- It returns a strictly positive value (i.e., at least 1) that tells you how may bytes you read.
- It returns 0 to indicate that the socket was closed.
- It return a negative value to indicate an error.
But there's not a defined way for it to return and tell you: "the socket's still open, there was no error, but there's no data waiting to be read."
So, as others have said, if you're going to transfer a file (or some other defined chunk of data) you generally need to define some application-level protocol on top of TCP to support that. The most obvious starting point is that you send the size of the file first (typically as a single fixed-size chunk, such as 4 or 8 bytes), followed by that many bytes of data.
If you do just that, you can define something that at least can work. There are all sorts of possible errors it can miss, but at least if things all work well, it can be fine.
The next step beyond that is typically to add something like some sort of checksum/CRC so when you think a transfer is complete, you can verify the data to get at least a reasonable assurance that it worked (i.e., that the data you received matches what was sent).
Another generally direction to consider is how you're doing your reading. There are a couple of choices here. One is to avoid calling read
until you're sure it can/will succeed. If you're dealing only with one (or a few) sockets at a time, you can call select
, which will tell you when your socket is ready to read, so issuing a read is guaranteed to succeed quickly. It might read less than you've asked, but it will return rather than waiting indefinitely for data. If you have to deal with a lot of sockets, you might prefer to look up epoll
, which does roughly the same thing, but reduces overhead when you have to deal with many handles.
Another possible way to deal with this problem is to set the O_NONBLOCK
option for your socket. In this case, attempting to read when no data is available will be treated as an error, so it'll return immediately with an error of EAGAIN
or EWOUDLBLOCK
(you have to be prepared for either). This gives you a fairly easy way to at least proceed when you have no more data available, but does nothing about any of the other difficulties in transferring data effectively.
As others have noted, there are quite a few existing protocols for doing things like this and reinventing it may not be the best use of your time. On the other hand, some protocols can be somewhat painful (e.g., ftp
's normal mode requires that you open/use two separate sockets). Others are complex enough that you probably don't want to try to implement them on your own, but libraries to support them well can be difficult to find.
Personally, I've found that websockets work pretty reasonably for quite a few tasks like this. They include framing (so what was sent as a single websocket write will be received with a single websocket read). They also use CRC to do error checking. So, for quite a few cases like this, it'll take care most of the details more or less automatically. It also includes (and in most cases uses) what they call a ping/pong protocol to detect loss of connection much faster than TCP normally does on its own.
But as noted above, there are lots of alternatives, some of them designed much more specifically for transferring files (so what you receive isn't just the content of the file, but things like the name and other metadata attached to that content).