Сегодня мы собираемся создать небольшое приложение 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-чат:
Описание
Сначала мы создадим сервер, который получает клиентские соединения и сохраняет их в словарях данных. Эти словари будут отслеживать, в какой комнате находится клиент, получать сообщения и передавать сообщения другим пользователям. Каждый пользователь ДОЛЖЕН иметь свое имя, которое будет нашим основным ключом для поиска наших соединений в словаре данных, чтобы мы могли отслеживать подключенных пользователей. Прямо сейчас мы не собираемся хранить сообщения в базе данных, но это не займет много времени, чтобы добавить такую функцию.
По завершении мы проверим наш чат, открыв различные командные терминалы, по одному для каждого имитируемого пользователя.
Сначала мы собираемся создать необходимые файлы: ‘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>