Search code examples
socketscrystal-langfibers

Send and read data through socket from fiber


Trying to figure out how to send/read data through socket. On remote server I create new netcat -l 4444 and from local send text data echo "test" | netcat remote.host 4444. This is always works fine.

Trying to reproduce:

require "socket"

HOST = "remote.host"
PORT = 4444

ch_request = Channel(String).new
ch_response = Channel(String).new

spawn do
  socket = TCPSocket.new(HOST, PORT)
  loop do
    select
    when request = ch_request.receive
      socket << request
      socket.flush
    end

    if response = socket.gets
      ch_response.send response
    end
  end
end

sleep 0.1

ch_request.send "hello"

loop do
  select
  when response = ch_response.receive
    pp response
  end
end

In my dream I send data to channel, read it from first loop then send to socket. The same way but reverse order need for read it from second loop.

On practice this is not happens. On local after connect I got "test" and can't send back anything. On remote I can send to local but on local got only empty string once and nothing more after.

What mean this behavior and how to achieve planned?


Solution

  • You didn't show this, but I suppose you have a second implementation using TCPServer for the netcat -l equivalent.

    You need to use separate fibers for reading/writing to the socket and the channel. It's a bit hard to judge what exactly happens without seeing the server, but I suppose you end up in a deadlock where both sides are waiting on input of the other side or user, but cannot proceed to actually send or read anything. In other words you interlocked the sending/receiving part, requiring the other side to carefully react and interplay so to not lock up the client. This is obviously a brittle approach.

    Instead you should make sure any fiber does not do more than one operation in a loop. One receives from the socket and forwards that to a channel, the second one receives from a channel and forwards that to the socket, the third one receives from the reader side channel and prints or does whatever you want to do the data and the last one fills the sender channel. This way no operation can block one of the others. Of course one of those fibers should simply be the main program one.

    In the server you additionally need one fiber that accepts the client connections and spawns the sender and receiver loops for each.

    Finally note that a select statement with a single when branch has no effect, you can make the call directly. select is useful if you need to read or write to multiple channels concurrently in the same fiber, so for example if you would have multiple channels providing data to be send out to a socket, you would use select to not have the messages be corrupted by two fibers writing to the same socket at the same time. An additional usecase for select is to send or receive from a channel with a timeout.