Socket data buffering?

I am trying to create a simple HTTP server that can analyze client requests and send responses back.

Now I have a problem. I have to read and process one line at a time in the request, and I don't know if I should:

  • read one byte at a time or
  • read fragments of N bytes at a time, put them in a buffer, and then process bytes one by one before reading a new fragment of bytes.

What would be the best option, and why ?

Also, are there any alternative solutions? How is a function that will read a string at a time from a socket or something else?

+4
source share
6 answers

The short answer to your question is that I would go reading one byte at a time. Unfortunately, this is one of those cases where in both cases there are pros and cons.

To use a buffer is the fact that an implementation may be more efficient in terms of network I / O. Against using a buffer, I believe that the code will be inherently more complex than the single-byte version. Thus, its effectiveness against complexity is minimized. The good news is that you can first implement a simple solution, profile the result and “zoom in” to a buffered approach if testing shows that it is worth it.

Also, just to be noted as a thought experiment, I wrote some pseudo-code for a loop that does the buffered reads of the http packets included below. The complexity of implementing buffered reads doesn't seem bad. Please note, however, that I did not pay much attention to error handling or checked if this would even work. However, it should avoid excessive “double processing” of the data, which is important because it reduces the efficiency that was the goal of this approach.

#define CHUNK_SIZE 1024 nextHttpBytesRead = 0; nextHttp = NULL; while (1) { size_t httpBytesRead = nextHttpBytesRead; size_t thisHttpSize; char *http = nextHttp; char *temp; char *httpTerminator; do { temp = realloc(httpBytesRead + CHUNK_SIZE); if (NULL == temp) ... http = temp; httpBytesRead += read(httpSocket, http + httpBytesRead, CHUNK_SIZE); httpTerminator = strstr(http, "\r\n\r\n"); }while (NULL == httpTerminator) thisHttpSize = ((int)httpTerminator - (int)http + 4; // Include terminator nextHttpBytesRead = httpBytesRead - thisHttpSize; // Adding CHUNK_SIZE here means that the first realloc won't have to do any work nextHttp = malloc(nextHttpBytesRead + CHUNK_SIZE); memcpy(nextHttp, http + thisHttpSize, nextHttpSize); http[thisHttpSize] = '\0'; processHttp(http); } 
+4
source

One byte at a time will kill performance. Consider a circular buffer of a decent size.

Read fragments of any size in the buffer. Most of the time you will get short readings. Check the end of the http command in bytes read. Process termination commands and the next byte become the buffer head. If the buffer fills up, copy it to the backup buffer, use the second circular buffer, report an error or whatever is appropriate.

+4
source

The TCP data stream arrives in one IP packet at a time, which can be up to 1500 or so bytes depending on the configuration of the IP layer. On Linux, this will wait in one SKB, waiting for the application layer to read the queue. If you read one byte at a time, you suffer from a lack of context switches between the application and the kernel to simply copy one byte from one structure to another. The best solution is to use a non-blocking IO to read the contents of one SKB at a time and thus minimize the switches.

If you use optimal bandwidth, you can read a longer byte size to further reduce context switches at an expensive delay, since more time will be spent copying the application memory. But this applies only to extreme values, and if necessary, such code changes should be implemented.

If you research many existing HTTP technologies, you can find alternative approaches, such as using multiple threads and blocking sockets, as well as redirect more work to the kernel to reduce the overhead of switching to and from the application.

I implemented an HTTP server library very similar to the torak pseudo-code, http://code.google.com/p/openpgm/source/browse/trunk/openpgm/pgm/http.c The biggest speedup for this implementation is due to so that everything is asynchronous, so nothing is blocked.

+1
source

Indy , for example, takes a buffer approach. When the code asks Indy to read a line, it first checks its current buffer to see if a line break exists. If not, the network is read in chunks and added to the buffer until a line break occurs. As soon as this happens, only the data before the line break is removed from the buffer and returned to the application, leaving all the remaining data in the buffer for the next read operation. This provides a good balance between a simple API at the application level (ReadLine, ReadStream, etc.), while ensuring efficient network I / O (read everything that is currently available on the socket, buffer it and enable it, so that the sender does not wait too long - less reading is required at the network level).

+1
source

Start by reading one byte at a time (although note that lines end in cr / lf in HTTP) because it is simple. If this is not enough, do more complex things.

0
source

Reading the byte array buffer at a time. Reading individual characters will be slow with the dog due to the many contextual switches between user and kernel mode (depending on the libc actually).

If you are reading buffers, you need to be prepared for the fact that the buffer cannot be filled completely (see the length return), so that the buffer does not contain enough bytes for the end of the line, or that the buffer contains more than one line.

A network template is common in network applications that allows you to match your requests in rows or fixed blocks with this variable buffer password (and often does not work correctly, for example, a response to a length of 0 bytes). Higher languages ​​will hide you from this complexity.

0
source

All Articles