Статьи

Ruby TCP Chat

Сегодня мы собираемся создать небольшое приложение TCP ruby ​​chat, используя стандартную библиотеку ruby ​​Socket. Я использую ruby ​​2.0.0 и Ubuntu Linux 12.04LTS, но он должен работать и на Mac OS. Я не пробовал это на Windows.

Сначала краткий обзор TCP (Transmission Control Protocol):

TCP является одним из основных протоколов набора протоколов Интернета (IP) и настолько распространен, что весь набор часто называют TCP / IP. Веб-браузеры используют TCP при подключении к серверам в World Wide Web, и он используется для доставки электронной почты и передачи файлов из одного места в другое. Для более подробной информации посетите TCP Википедию

Вот как будет работать наш TCP-чат:

TCP-чат

Описание

Сначала мы создадим сервер, который получает клиентские соединения и сохраняет их в словарях данных. Эти словари будут отслеживать, в какой комнате находится клиент, получать сообщения и передавать сообщения другим пользователям. Каждый пользователь ДОЛЖЕН иметь свое имя, которое будет нашим основным ключом для поиска наших соединений в словаре данных, чтобы мы могли отслеживать подключенных пользователей. Прямо сейчас мы не собираемся хранить сообщения в базе данных, но это не займет много времени, чтобы добавить такую ​​функцию.
По завершении мы проверим наш чат, открыв различные командные терминалы, по одному для каждого имитируемого пользователя.

Сначала мы собираемся создать необходимые файлы: ‘server.rb’ и ‘client.rb’
В server.rb и client.rb нам требуется библиотека Socket .

# in client.rb and server.rb require "socket" 

Затем создайте соответствующие классы с некоторыми атрибутами для обработки пользователей.

Клиент получает экземпляр сервера, чтобы он мог установить соединение с сервером. Нам нужно инициализировать запрос и ответ для отправки и получения сообщений через сервер. Прямо сейчас наши @response и @request являются нулевыми, но позже мы собираемся построить два потока и назначить их нашим объектам для чтения и записи одновременно.

 # in client.rb require "socket" class Client def initialize(server) @server = server @request = nil @response = nil end end 

Если вы хотите узнать больше о темах, проверьте:

Вот начало нашего сервера. Сервер получает порт, который будет нашим каналом для установления соединения между пользователями. Сервер прослушивает порт для любого события и отправляет ответ всем, кто заинтересован. Инициализатор также создает три хэша:

  • @connections — это пул пользователей, подключенных к серверу.
  • @rooms по имени комнаты и содержит пользователей в каждой комнате.
  • @clients наши подключенные клиентские экземпляры

 #in server.rb require "socket" class Server def initialize(port, ip) @server = nil @connections = {} @rooms = {} @clients = {} end end 

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

 # hash Connections preview connections: { clients: { client_name: {attributes}, ... }, rooms: { room_name: [clients_names], ... } } 

Затем нам нужно создать два потока на стороне клиента, чтобы он мог читать / писать сообщения одновременно. Без этой функциональности наш чат был бы очень скучным. Представьте, что вы печатаете свое сообщение, и только после того, как вы закончите, сможете найти ответ, не имея возможности сделать оба одновременно. Так работает большинство клиентов чата.

 # ( @request, @response ) objects preview and descriptions # The request and response objects implementation may look like this # in client.rb def send @request = Thread.new do loop { # write as much as you want # read from the console # with the enter key, send the message to the server } end end def listen @response = Thread.new do loop { # listen for ever # listen the server responses # show them in the console } end end 

Вот файл client.rb:

 #!/usr/bin/env ruby -w require "socket" class Client def initialize( server ) @server = server @request = nil @response = nil listen send @request.join @response.join end def listen @response = Thread.new do loop { msg = @server.gets.chomp puts "#{msg}" } end end def send puts "Enter the username:" @request = Thread.new do loop { msg = $stdin.gets.chomp @server.puts( msg ) } end end end server = TCPSocket.open( "localhost", 3000 ) Client.new( server ) 

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

 # in server.rb def run loop { Thread.start do |client| # each client thread end } end 

Для нашего теста IP-адрес является локальным. Порт ДОЛЖЕН быть одинаковым на стороне клиента и сервера и, в этом случае. Помните, порты являются виртуальными:

Порт — это не физическое устройство, а абстракция для облегчения связи между сервером и клиентом. Машина может иметь максимум 65536 номеров портов (от 0 до 65535). Номера портов разделены на три диапазона: общеизвестные порты, зарегистрированные порты и динамические и / или частные порты. Краткое описание TCP и UDP

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

Реализация довольно проста. Все, что нам нужно, это закончить с методом run и проверить уникальность предоставленного имени пользователя. Если имя пользователя занято, сообщите клиенту сообщение об ошибке и прервите соединение. В противном случае передайте клиенту сообщение об успешном соединении.

 # server.rb ( server side ) class Server def initialize(port,ip) @server = TCPServer.open(ip, port) ... end def run loop { # for each user connected and accepted by server, it will create a new thread object # and which pass the connected client as an instance to the block Thread.start(@server.accept) do | client | nick_name = client.gets.chomp.to_sym @connections[:clients].each do |other_name, other_client| if nick_name == other_name || client == other_client client.puts "This username already exist" Thread.kill self end end puts "#{nick_name} #{client}" @connections[:clients][nick_name] = client client.puts "Connection established, Thank you for joining! Happy chatting" end } end end server = Server.new("localhost", 3000) # (ip, port) in each machine "localhost" = 127.0.0.1 server.run 

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

 # in server.rb def listen_user_messages(username, client) loop { # get client messages msg = client.gets.chomp # send a broadcast message, a message for all connected users, but not to its self @connections[:clients].each do |other_name, other_client| unless other_name == username other_client.puts "#{username.to_s}: #{msg}" end end } end 

Все, что listen_user_messages метод listen_user_messages , — это прослушивает пользовательские сообщения и отправляет их всем остальным пользователям. Теперь вызовите этот метод внутри метода run в экземпляре сервера и все.

 # in server.rb def run loop { Thread.start(@server.accept) do | client | ... listen_user_messages(nick_name, client) end } end 

Вот весь файл server.rb:

 #!/usr/bin/env ruby -w require "socket" class Server def initialize( port, ip ) @server = TCPServer.open( ip, port ) @connections = Hash.new @rooms = Hash.new @clients = Hash.new @connections[:server] = @server @connections[:rooms] = @rooms @connections[:clients] = @clients run end def run loop { Thread.start(@server.accept) do | client | nick_name = client.gets.chomp.to_sym @connections[:clients].each do |other_name, other_client| if nick_name == other_name || client == other_client client.puts "This username already exist" Thread.kill self end end puts "#{nick_name} #{client}" @connections[:clients][nick_name] = client client.puts "Connection established, Thank you for joining! Happy chatting" listen_user_messages( nick_name, client ) end }.join end def listen_user_messages( username, client ) loop { msg = client.gets.chomp @connections[:clients].each do |other_name, other_client| unless other_name == username other_client.puts "#{username.to_s}: #{msg}" end end } end end Server.new( 3000, "localhost" ) 

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

Давайте посмотрим наш маленький чат

Инициализировать сервер

 RubyChat: ./server.rb 

Инициализировать клиента Саймона

 RubyChat: ./client.rb Enter the username: Simon Connection established, Thank you for joining! Happy chatting 

Инициализировать клиент Foo

 RubyChat: ./client.rb Enter the username: Foo Connection established, Thank you for joining! Happy chatting 

Сообщение отправлено от Саймона

 RubyChat: ./client.rb Enter the username: Foo Connection established, Thank you for joining! Happy chatting Hi! It's Simon! 

Сообщение получено от Саймона на терминале Фу

 RubyChat: ./client.rbEnter the username: Foo Connection established, Thank you for joining! Happy chatting Simon: Hi! It's Simon 

Сообщение отправлено из Foo

 RubyChat: ./client.rbEnter the username: Foo Connection established, Thank you for joining! Happy chatting Simon: Hi! It's Simon Hey Simon! Foo here. 

Сообщение, полученное от Foo на терминале Саймона

 RubyChat: ./client.rb Enter the username: Simon Connection established, Thank you for joining! Happy chatting Hi! It's Simon! Foo: Hey Simon! Foo here! 

Соединения сервер-клиент

 RubyChat: ./server.rb Simon #<TCPSocket:0x007fbc94836820> Foo #<TCPSocket:0x007fbc94834a98> 

Следующие шаги

Удачного кодирования!