views:

296

answers:

3

Hi' I'm writing a simple http port forwarder. I read data from port 80, and pass the data to my lighttpd server, on port 8080.

As long as I write() data on the socket on port 8080 (forwarding the request) there's no problem, but when I read() data from that socket (forwarding the response), the last read() hangs a lot (about 1 or 2 seconds) before realizing there's no more data and returning 0.

I tried to set the socket to non-blocking, but this doesn't work, as sometimes it returns EWOULDBLOCKING even if there's some data left (lighttpd + cgi can be quite slow). I tried to set a timeout with select(), but, as above, a slow cgi could timeout the socket when there's actually some data to transmit.


Update: SOLVED. It was the keepalive after all. After I disabled it in my lighttpd configuration file, the whole thing runs flawlessly.

A: 

I guess that I would use non-blocking I/O to full extend. Instead of setting timeouts I'd rather wait for event's:

while(select(...)) {
    switch(...) {
    case ...: // Handle accepting new connection
    case ...: // Handle reading from socket
    ...
    }
}

Sinle-thread, blocking forwarder will cause problems anyway with multiple clients.

Sorry - I don't remember exact calls. Also it can be strange in some cases (IIRC - you need to handle write), but there are libraries which simplify the task.

Maciej Piechotka
Yes, but it blocks on the select()!Forwarder is *not* single-threaded. I run a thread per request, but the browser will wait for the request to end after making another.so, it will wait 1+ seconds per request, waiting for the forwarder (stuck in the select) to close the socket.
janesconference
+1  A: 

Using both non-blocking sockets and select is the right way to go. Returning EWLOULDBLOCK doesn't mean that the entire stream of data is finished being received, it means that, instantaneously, there is nothing to read right now. That's exactly what you want, because it means that read won't wait even half a second for more data to show up. If the data isn't immediately available it will return.

Now, obviously, this means you will need to call read multiple times to get the complete data. The general format for doing this is a select loop. In pseudocode:

do
  select ( my_sockets )

  if ( select error ) 
    handle_error
  else
    for each ( socket in my_sockets ) do
      if ( socket is ready ) then
        nonblocking read from socket
        if ( no data was read ) then
          close socket
          remove socket from my_sockets
        endif
      endif
    loop
  endif
loop

The idea is that select will tell you which sockets have data available for reading right now. If you read one of those sockets, you are guaranteed either to get data or to get a return value of 0, indicating that the remote end closed the socket.

If you use this method, you will never be stuck in a read call that is not reading data, for any length of time. The blocking operation is the select call, and you can also select over writeable sockets if you need to write, and set a timeout if you need to do things periodically.

Tyler McHenry
Yeah, but this blocks on the select() call if there's no more data, exactly as it blocked on the read().
janesconference
Okay, well, what else do you want to be doing instead of waiting for data? If you want to write data, put write descriptors in the `select` call as well. If you want to do something periodically, give `select` a timeout. If you want to do something continuously, make that other thing your main loop and then call over to the `select` sequence whenever you have the time to. If you want to do something in parallel, make a new thread for the I/O.
Tyler McHenry
A: 
dreamlax