While I learned the basic read/write (or send/recv) way of dealing with socket data in school I never heard of the more sophisticated and faster readv/writev stuff.
Read more for a simple example how you can improve your network code.
So, I was implementing some network server the last days. Through university and school I knew the socket send/recv way of dealing with things, which mostly goes this way (without error handling):
int fd = socket(AF_INET, SOCK_STREAM, 0);
uint32_t header = htonl(XXX_SOMESTUFF);
uint32_t other_header = htonl(1234);
uint32_t length = htonl(LENGTH_OF_DATA_PACKET);
void* data; /* data with length length */
connect(..);
send(socket, &header, sizeof(header), 0);
send(socket, &other_header, sizeof(other_header), 0);
send(socket, &length, sizeof(length), 0);
send(socket, data, ntohl(length), 0);
While this works, it uses quite a lot of syscalls (at least one for each send) and overall performance is rather bad. So the next step includes creating a new memory area and sending the whole as one:
int fd = socket(AF_INET, SOCK_STREAM, 0);
uint32_t header = htonl(XXX_SOMESTUFF);
uint32_t other_header = htonl(1234);
uint32_t length = htonl(LENGTH_OF_DATA_PACKET);
void* data; /* data with length length */
connect(..);
void *senddata= malloc(sizeof(uint32_t)*3 + length);
memcpy(senddata, &header, sizeof(header));
memcpy(senddata+sizeof(uint32_t), &other_header, sizeof(other_header));
memcpy(senddata+sizeof(uint32_t)*2, &length, sizeof(length));
memcpy(senddata+sizeof(uint32_t)*3, data, ntohl(length));
send(socket, senddata, sizeof(uint32_t)*4+length, 0);
This minimizes syscalls, but we need new memory to be allocated (and an insane amount of memory copies). Also nothing which looks to good in a high performance server.
Enters scatter/gather IO. The readv/writev functions do not need their parameters in a contagious memory area:
int fd = socket(AF_INET, SOCK_STREAM, 0);
uint32_t header = htonl(XXX_SOMESTUFF);
uint32_t other_header = htonl(1234);
uint32_t length = htonl(LENGTH_OF_DATA_PACKET);
void* data; /* data with length length */
connect(..);
struct iovec vectors[4];
iovec[0].iov_base = &header;
iovec[0].iov_len = sizeof(uint32_t);
iovec[1].iov_base = &other_header;
iovec[1].iov_len = sizeof(uint32_t);
iovec[2].iov_base = &length;
iovec[2].iov_mem = sizeof(uint32_t);
iovec[3].iov_base = data;
iovec[3].iov_mem = ntohl(length);
writev(socket, vectors, 4);
No memory allocation or copies are needed, the multitude of send syscalls are also avoided. As many network server already deal with memory buffers this might also be the more natural way of dealing with things.
Next to writev there’s also a readv function which can be used to receive from a file descriptor directly into a predefined vector array of memory locations. Can you thing: packet retrieval? With this functionality you do not have to receive the whole packet and parse its content but can receive the packet directly into the final fields.