Socket Programming Send() Return Value

Socket programming send() return value

See the man pages, or the MSDN documentation if you are talking about Winsock, for the offical specification.

In practice, send() in blocking mode sends all the data, regardless of what the documentation says, unless there was an error, in which case nothing is sent.

In non-blocking mode, it sends whatever will fit into the socket send buffer and returns that length if > 0. If the socket send buffer is full, it returns -1 with errno = EWOULDBLOCK/EAGAIN.

When and why socket.send() returns 0 in python?

Upon seeing the question I was somehow stunned, because a send C call can return 0 bytes and the connection is of course still alive (the socket cannot simply send more bytes at that given moment in time)

  • https://github.com/python/cpython/blob/master/Modules/socketmodule.c

I decided to "use the source" and unless I am very wrong (which can always be and often is) this is a bug in the HOWTO.

Chain:

  • send is an alias for sock_send
  • sock_send calls in turn sock_call
  • sock_call calls in turn sock_call_ex
  • sock_call calls in turn sock_send_impl (which has been passed down the chain starting with sock_send)

Unwinding:

  • sock_send_impl returns true or false (1 or 0) with return (ctx->result >= 0)

  • sock_call_ex returns

    • -1 if sock_send_impl returns false
    • 0 if sock_send_impl returns true
  • sock_call returns this value transparently.

  • sock_send

    • returns NULL for a -1 (because an error has been set and an exception will be raised)

    • returns ctx->result for 0from sock_call

      And ctx->result is the number of bytes written by the C call send in sock_send_impl.

The chain shows that if 0 bytes have been sent, there is no error and this actually is a potential real life socket situation.

If my logic is wrong, someone please let me know.

With C TCP sockets, can 'send' return zero?

I'm pretty certain, though the memory is deep in the mists of time, that I've seen it return zero before, in the situation of massive data transfers where the other end was not keeping up.

From memory, in that case, the remote TCP stack buffers had filled up, the stack had notified the local end that it was to delay until some space was cleared out and the local buffers had filled up as well.

At that point, it's not technically an error (hence no -1 returned) but no data could be accepted by the local stack.

I'm not entirely certain that's the case now since the current Posix standard seems to indicate it will simply block in that case (or fail if it's set up for non-blocking).

However, I suspect it's a moot point. You do have the possibility that it will return less than the bytes you requested to send and you therefore should have code in place to handle that.

And, since it will be pretty much the same logic handling 'one less than what you requested' as handling 'zero bytes', you may as well assume it can return zero.

Blocking sockets: when, exactly, does send() return?

Does this mean that the send() call will always return immediately if there is room in the kernel send buffer?

Yes. As long as immediately means after the memory you provided it has been copied to the kernel's buffer. Which, in some edge cases, may not be so immediate. For instance if the pointer you pass in triggers a page fault that needs to pull the buffer in from either a memory mapped file or the swap, that would add significant delay to the call returning.

Is the behavior and performance of the send() call identical for TCP and UDP? If not, why not?

Not quite. Possible performance differences depends on the OS' implementation of the TCP/IP stack. In theory the UDP socket could be slightly cheaper, since the OS needs to do fewer things with it.

EDIT: On the other hand, since you can send much more data per system call with TCP, typically the cost per byte can be a lot lower with TCP. This can be mitigated with sendmmsg() in recent linux kernels.

As for the behavior, it's nearly identical.

For blocking sockets, both TCP and UDP will block until there's space in the kernel buffer. The distinction however is that the UDP socket will wait until your entire buffer can be stored in the kernel buffer, whereas the TCP socket may decide to only copy a single byte into the kernel buffer (typically it's more than one byte though).

If you try to send packets that are larger than 64kiB, a UDP socket will likely consistently fail with EMSGSIZE. This is because UDP, being a datagram socket, guarantees to send your entire buffer as a single IP packet (or train of IP packet fragments) or not send it at all.

Non blocking sockets behave identical to the blocking versions with the single exception that instead of blocking (in case there's not enough space in the kernel buffer), the calls fail with EAGAIN (or EWOULDBLOCK). When this happens, it's time to put the socket back into epoll/kqueue/select (or whatever you're using) to wait for it to become writable again.

As usual when working on POSIX, keep in mind that your call may fail with EINTR (if the call was interrupted by a signal). In this case you most likely want to call send() again.

Why is it assumed that send may return with less than requested data transmitted on a blocking socket?

The thing that is missing in above description is, in Unix, system calls might get interrupted with signals. That's exactly the reason blocking send(2) might return a short count.

transfer integer over a socket in C

First of all, sizeof(int) may differ on your sender and receiver machine. So I would recommend you to use something like int32_t from stdint.h.

Also, it is not guaranteed that read(..,..,sizeof(int)) will read exactly sizeof(int) bytes - it can read nothing, or it can read less bytes. So, the correct variant will be something more like this:

int send_int(int num, int fd)
{
int32_t conv = htonl(num);
char *data = (char*)&conv;
int left = sizeof(conv);
int rc;
do {
rc = write(fd, data, left);
if (rc < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
// use select() or epoll() to wait for the socket to be writable again
}
else if (errno != EINTR) {
return -1;
}
}
else {
data += rc;
left -= rc;
}
}
while (left > 0);
return 0;
}

int receive_int(int *num, int fd)
{
int32_t ret;
char *data = (char*)&ret;
int left = sizeof(ret);
int rc;
do {
rc = read(fd, data, left);
if (rc <= 0) { /* instead of ret */
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
// use select() or epoll() to wait for the socket to be readable again
}
else if (errno != EINTR) {
return -1;
}
}
else {
data += rc;
left -= rc;
}
}
while (left > 0);
*num = ntohl(ret);
return 0;
}


Related Topics



Leave a reply



Submit