[Gambas-user] Trouble writing to Accepted Socket stream

Tobias Boege taboege at gmail.com
Sat Mar 30 09:37:42 CET 2019


On Fri, 29 Mar 2019, T Lee Davidson wrote:
> I'm using ServerSocket to listen for connections on localhost:8080. When I
> make a request to that URI with my browser, it just hangs, "Waiting for
> localhost..."
> 
> In the ServerSocket.Connection event, I accept the connection which creates
> a Socket object to manage that connection. The issue seems to be that the
> accepted socket stream never becomes ready for writing unless I actually try
> to write to it. If I then write to the stream in the Socket.Write event
> (because that's when it is supposed to be ready for writing), I get a nearly
> endless loop that doesn't give up until around 23000 iterations.
> 

The Write event is supposed (as per source code) to only fire when you have
enqueued data to be sent. It works like this:

  Write #Socket --> gb.net tries to send it, installs a Write event
  callback --> which when fired raises the Gambas event and deinstalls
  itself again until the next write happens

I hope that makes it clearer why writing to the stream *unconditionally*
inside a Write event may create an endless loop. I killed mine after 99000
iterations (twice). I'm not sure under which condition, by chance, the
loop even halts. Is an error raised when it terminates on your side?

Anyway, here is why it makes sense: One thing to remember is that Gambas'
event handling is built around the select() syscall, so a Write event on
a file descriptor Streams (File, Socket) is *usually* to be interpreted
in the select(2) sense:

      int select(int nfds, fd_set *readfds, fd_set *writefds,
                 fd_set *exceptfds, struct timeval *timeout);

  select() and pselect() allow a program to monitor multiple file
  descriptors, waiting until one or more of the file descriptors
  become "ready" for some class of I/O operation (e.g., input possible).
  A file descriptor is considered ready if it is possible to perform
  a corresponding I/O operation (e.g., read(2) without blocking, or a
  sufficiently small write(2)). [...]

  Three independent sets of file descriptors are watched. [...]
  The file descriptors in writefds will be watched to see if space
  is available for write (though a large write may still block).

It concerns the status of kernel buffers, if they can keep up sending data
(over the network in this case) with how fast your application produces it.
And that's how I take it you should use this event: your application appends
to an arbitrary-sized Gambas String buffer the data which should be sent,
and your Write event handler takes care of feeding the small kernel buffers
from that string; the Write event will be raised whenever you can feed more.

To respond to a client query, I'd use the Read event. Read all the data
until you have the full query assembled, then process it and answer,
possibly involving the Write event as described above.

In your program what's missing is the Content-Length header. If missing
the browser doesn't know when your HTTP response is fully received and you'd
have to close the connection to indicate it. If you add that, sending the
response from the Read event will work:

  Private sPayload As String = "Hello World!"
  Private sResponse As String = Subst$("HTTP/1.1 200 OK\r\nContent-Length: &1\r\n\r\n&2", Len(sPayload), sPayload)

> Private crlf As String = Chr(13) & Chr(10)

BTW that particular constant is built into Gambas as gb.CrLf.

Regards,
Tobi

-- 
"There's an old saying: Don't change anything... ever!" -- Mr. Monk


More information about the User mailing list