#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. Потрясающе, я действительно ценю, что ты нашел время, чтобы научить меня этому. Похоже, теперь у меня все получилось благодаря этому ответу и вашему предыдущему. Спасибо!!