Отправка данных на TCPServer более одного раза

#ruby #tcp #tcpserver #tcpsocket

Вопрос:

Я новичок в ruby и пытаюсь заставить клиента подключиться к TCPServer, и, похоже, для этого мне приходится вызывать метод close_write каждый раз, когда я заканчиваю отправку данных в одну сторону, чтобы клиент/сервер знал, что другой конец закончил отправку данных. Всякий раз, когда я это делаю, я не могу снова записать информацию на сервер или клиент, потому что сокет больше не открыт для записи. Это мой код:

клиент.рб

требуется «сокет»

сокет = TCPSocket.open(«локальный хост», 6666)

 loop do
  input = gets.chomp
  socket.puts input # Send data to server
  socket.close_write
  while(line = socket.gets)
    puts line
  end # Print sever response
  break if input=="EXIT"
end
socket.close
 

сервер.rb

 require "socket"

server = TCPServer.new 6666
data = Hash.new { |hash, key| hash[key] = {} }

WAITING_SET_VALUE = "1"
WAITING_NEW_COMMAND = "0"

loop do

  Thread.start(server.accept) do |session|
    thread_status ||= WAITING_NEW_COMMAND
    ....

    puts "Entering If..."
    if(thread_status == WAITING_NEW_COMMAND) #Check thread status
      puts "thread_status == WAITING_NEW_COMMAND"
      puts "CHECKING COMMAND.."
      case line.strip
      when /^set w* w* d{1,7} d{1,7}$/
        puts "COMMAND SET"
        thread_status = WAITING_SET_VALUE
        lineArr = line.strip.split(" ")
        varName = lineArr[1]
        flag = lineArr[2]
        ttl = lineArr[3]
        size = lineArr[4]
        puts "END SET EXECUTION"
        session.write "Executed"
        session.close_write
      ...
 

Есть ли способ снова открыть сокет для записи или лучший способ сделать это взад и вперед между сервером и клиентом без потери контекста? Спасибо!

Комментарии:

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

2. И как я могу сообщить файлу client.rb, когда сервер закончил отправку данных обратно?

3. Я понял, что сервер отправляет клиенту «Данные\n», так что теперь вопрос в том, как мне обнаружить или проверить, что в цикле while с помощью get в строке есть \n?

4. Если новая строка является вашим разделителем, а ответ сервера состоит всего из одной строки, вы можете заменить свой while цикл одним gets . (он читает, пока не встретит новую строку, а затем возвращает эту строку)

5. Есть ли способ сохранить цикл while, но сказать функции gets остановиться, когда она столкнется с \n?

Ответ №1:

При разработке протокола клиент-сервер вам необходимо принять решение:

  • Как клиент узнает, когда в ответе больше строк/данных.
  • Как клиент узнает, когда ответ будет завершен.
  • Как клиент узнает, когда ответ недействителен/действителен.
  • Как клиент узнает, когда произошла какая-либо ошибка сервера.

Простой подход заключается в том, чтобы сервер возвращал ответ с количеством строк (как в приведенном ниже коде). Однако вместо этого вы могли бы использовать END или что-то еще, чтобы клиент знал, когда больше нет данных для чтения. Я бы настоятельно рекомендовал изучить учебные пособия по протоколам.

Сохраните приведенное ниже в файл под названием client_server.rb . Сначала запустите сервер, ruby ./client_server.rb s а затем в отдельном терминале запустите клиент ruby ./client_server.rb c . Затем набирайте list снова и снова, чтобы увидеть различные ответы. Я добавил list это, чтобы вам не приходилось set w w 1 1 снова и снова вводить текст в целях тестирования. Проверьте это и дайте мне знать, если у вас возникнут какие-либо вопросы.

 # frozen_string_literal: true

require 'socket'


# Use:
#   First, run the server:  ruby ./client_server.rb s
#   Then, run the client:   ruby ./client_server.rb c

# Use "netcat localhost 6666" on the command line to test
# without implementing a client.


# Returns false to close this client socket.
# Returns true to continue reading from this client socket.
def handle_client(client_id, client_socket, command)

  # TODO: Define some type of client-server Protocol here.
  case command
  when /^set w* w* d{1,7} d{1,7}$/
    puts "Running command for client #{client_id}: #{command}"

    # This is just for testing purposes.
    case rand(3)
    when 0
      client_socket.puts 'lines 0'
    when 1
      client_socket.puts 'lines 3'
      client_socket.puts 'This is line 1.'
      client_socket.puts 'This is line 2.'
      client_socket.puts 'This is line 3.'
    when 2
      client_socket.puts 'The set command returned an error.'
    end
  when 'list'
    puts "Responding to client #{client_id} with list of messages."

    # This is just for testing purposes.
    case rand(3)
    when 0
      client_socket.puts 'messages 0'
    when 1
      client_socket.puts 'messages 2'
      client_socket.puts 'This is message 1.'
      client_socket.puts 'This is message 2.'
    when 2
      client_socket.puts 'Unable to retrieve messages due to error.'
    end
  when 'bye'
    puts "Killing client #{client_id}."

    return false # Disconnect and kill the thread.
  else
    client_socket.puts "[ERROR] Invalid command: #{command}"
  end

  client_socket.flush # Flush all output just in case.

  true # Continue connection.
end


case ARGV[0].to_s.downcase
when 's' # server
  TCPServer.open(6666) do |server_socket|
    global_client_id = 1

    loop do
      Thread.start(global_client_id, server_socket.accept) do |client_id, client_socket|
        puts "Accepting new client #{client_id}."

        loop do
          command = client_socket.gets

          if command.nil?
            puts "Client #{client_id} disconnected manually."
            break
          end

          command = command.strip
          keep_alive = handle_client(client_id, client_socket, command)

          break unless keep_alive
        end

        client_socket.close
      end

      global_client_id  = 1
    end
  end
when 'c' # client
  TCPSocket.open('localhost', 6666) do |socket|
    puts '[Commands]'
    puts 'set <word> <word> <num> <num>    Run set command.'
    puts 'list                             Get list of messages.'
    puts 'exit, bye                        Exit the client.'
    puts

    loop do
      print '> '
      input = $stdin.gets.strip

      # TODO: Define some type of client-server Protocol here.
      case input
      when /EXIT|BYE/i
        socket.puts 'bye'
        socket.flush
        break
      when /Aset . z/
        socket.puts input
        socket.flush

        response = socket.gets

        if response.nil?
          puts 'Server is not running anymore! Disconnecting.'
          break
        end

        response = response.strip
        match_data = response.match(/Alines (?<lines>d )z/)

        if match_data
          line_count = match_data[:lines].to_i

          puts "Received #{line_count} lines from server."

          1.upto(line_count) do |i|
            line = socket.gets

            puts ">> Resp[#{i}] #{line}"
          end
        else
          # Can check for "response == 'ERROR'" or something.
          puts "Server error or invalid response from server: #{response}"
        end
      when 'list'
        socket.puts input
        socket.flush

        response = socket.gets

        if response.nil?
          puts 'Server is not running anymore! Disconnecting.'
          break
        end

        response = response.strip
        match_data = response.match(/Amessages (?<messages>d )z/)

        if match_data
          message_count = match_data[:messages].to_i

          puts "Received #{message_count} messages from server."

          1.upto(message_count) do |i|
            line = socket.gets

            puts ">> Resp[#{i}] #{line}"
          end
        else
          # Can check for "response == 'ERROR'" or something.
          puts "Server error or invalid response from server: #{response}"
        end
      else
        puts "Invalid command: #{input}"
      end
    end
  end
else
  puts "Pass in 'c' for client or 's' for server."
end
 

Комментарии:

1. Потрясающе, я действительно ценю, что ты нашел время, чтобы научить меня этому. Похоже, теперь у меня все получилось благодаря этому ответу и вашему предыдущему. Спасибо!!