Отправка и чтение данных через сокет из волокна - PullRequest
0 голосов
/ 29 мая 2020

Пытаюсь понять, как отправлять / читать данные через сокет. На удаленном сервере я создаю новый netcat -l 4444 и из локальных отправляю текстовые данные echo "test" | netcat remote.host 4444. Это всегда работает нормально.

Пытаюсь воспроизвести:

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

Во сне я отправляю данные в канал, читаю их с первого l oop, затем отправляю в сокет. Таким же образом, но в обратном порядке нужно прочитать его со второго l oop.

На практике этого не происходит. На локальном компьютере после подключения я получил "тест" и не могу ничего отправить обратно. На удаленном я могу отправить на локальный, но на локальном я получил только пустую строку один раз и больше ничего после.

Что означает такое поведение и как достичь запланированного?

Ответы [ 2 ]

1 голос
/ 29 мая 2020

Вы этого не показали, но я предполагаю, что у вас есть вторая реализация, использующая TCPServer для эквивалента netcat -l.

Вам нужно использовать отдельные волокна для чтения / записи в сокет и канал. Немного сложно судить, что именно происходит, не видя сервер, но я полагаю, что вы попадаете в тупик, когда обе стороны ждут ввода другой стороны или пользователя, но не могут приступить к фактической отправке или чтению чего-либо. Другими словами, вы заблокировали часть отправки / получения, требуя, чтобы другая сторона тщательно реагировала и взаимодействовала, чтобы не блокировать клиента. Очевидно, что это хрупкий подход.

Вместо этого вы должны убедиться, что какое-либо волокно не выполняет более одной операции в al oop. Один получает из сокета и пересылает его в канал, второй получает из канала и пересылает его в сокет, третий получает из бокового канала считывателя и печатает или делает все, что вы хотите, данные и последний заполняет канал отправителя. Таким образом, ни одна операция не может заблокировать одно из других. Конечно, одно из этих волокон должно быть просто основным программным.

На сервере вам дополнительно понадобится одно волокно, которое принимает клиентские соединения и порождает циклы отправителя и получателя для каждого.

Наконец обратите внимание, что оператор select с одной ветвью when не имеет никакого эффекта, вы можете сделать вызов напрямую. select полезно, если вам нужно читать или записывать одновременно несколько каналов в одном и том же волокне, поэтому, например, если у вас будет несколько каналов, предоставляющих данные для отправки в сокет, вы должны использовать select, чтобы не иметь сообщения могут быть повреждены двумя волокнами, одновременно записывающими в один и тот же сокет. Дополнительным вариантом использования select является отправка или получение из канала с тайм-аутом.

0 голосов
/ 30 мая 2020

Кто ищет ответ на похожие вопросы. Окончательный результат, который я хотел, выглядит следующим образом:

# Open socket for simulate remote server: `netcat -v -4 -l 4444`

require "socket"

HOST = "remote.host"
PORT = 4444

# JFYI: In real life this packed into class and I use class variable instead consts.
TUBE_REQUEST  = Channel(String).new
TUBE_RESPONSE = Channel(String).new

SOCKET = TCPSocket.new(HOST, PORT)

spawn do
  until SOCKET.closed?
    if request = TUBE_REQUEST.receive
      SOCKET << request
      SOCKET.flush
    end
  end
end

spawn do
  until SOCKET.closed?
    if response = SOCKET.gets
      TUBE_RESPONSE.send response
    end
  end
end

sleep 0.1

def receive_response
  TUBE_RESPONSE.receive
end

def send(message, wait_for_response = true)
  TUBE_REQUEST.send message
  receive_response if wait_for_response
end

send("command with response")
send("command with new line and response\n")
send("command without new line and response", false)

Он будет отправлять каждую команду и ждать ответа (кроме последнего) с удаленного устройства, а затем вызывать следующую команду.

...