Is it possible to run multiple servers within a single event machine?
What I mean is that multiple services can be used concurrently by a single client connection. For example, a login server authenticates a user and then a user can use both a chat room and a simple game such as checkers with a single client socket concurrently?
Or do I need multiple eventmachine reactors for each service?
I tried this and it's working:
#!/usr/bin/env ruby
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 8081, EchoServer
EventMachine::start_server "127.0.0.1", 8082, EchoServer
EventMachine::start_server "127.0.0.1", 8083, EchoServer
}
Here you get 3 echo services with different ports. (I was too lazy to implement different services.)
So, it's very easy to build a huge multi service wrapper.
Update
Simple code example for condition based start of a EM server:
#!/usr/bin/env ruby
# encoding: utf-8
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
$check_ok = false
EventMachine::run {
puts "checker var is: #{$check_ok}"
EventMachine::start_server "127.0.0.1", 8081, EchoServer
EventMachine::start_server "127.0.0.1", 8082, EchoServer
puts "echos on 8081 and 8082 started."
# periodic timer check - every 1 sec
EventMachine.add_periodic_timer(1) {
if $check_ok
EventMachine::start_server "127.0.0.1", 8083, EchoServer
$check_ok = false
puts "echo on 8083 started!"
end
}
# timer triggered after 10 secs - only once!
EventMachine.add_timer(10) {
$check_ok = true
puts "checker var is #{$check_ok} now!"
}
}
In this example the echo server on port 8083 is started ~10 secs after app start. Try to telnet localhost 8083 before and after this timer, you'll see the effect.
You also can use values lower than 1 sec like 0.01 for every 1/100th sec checks.
This might be your starting point for your own ideas. The periodic timer is your internal loop, where you hook in your conditional checks for starting further services.
Good tutorial (PDF): eventmachine introduction (blog post)
Related
I'm get stuck to write a tcp server daemon in Ruby 2.3. The issue is, that my connection is not going further, when postfix is communicating with the ruby tcp server. If i do connect to the ruby tcp server by telnet, everything works fine. My code is as follows:
require 'socket'
require_relative 'postfix_delegation_object'
class Server
attr_reader :binding, :port
def initialize(binding: '127.0.0.1', port: '1988')
puts "Starting server now!"
puts "Listening on tcp://#{binding}:#{port}"
socket = TCPServer.new(binding, port)
while client = socket.accept
Thread.new { handle_connection(client) }
end
end
def handle_connection(client)
hash_values = {}
puts "New client! #{client}"
while line = client.gets
if line.include? "="
key, val = line.split('=')
hash_values[key] = val.to_s.strip
else
pdo = PostfixDelegationObject.new(hash_values)
client.write("action=dunno")
end
end
end
end
I could solve it by my own. I just had to enter twice '\n'
like this:
client.write("action=dunno\n\n")
This one would not work:
client.write("action=dunno")
client.write("\n\n")
I have developed multiple eventmachine servers which are like
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 8081, EchoServer
EventMachine::start_server "127.0.0.1", 8082, EchoServer
EventMachine::start_server "127.0.0.1", 8083, EchoServer
}
Now I need to send data to the client as per the port only 8082. If I have all the connections open . Server needs to send back data to the perticular server.
So If From 8081 I get the request I need to send it to 8082 client.
How do I send that?
According to the modification of the original question, I posted a new answer.
You will need to track the server port for each connection. And when a new connection is established from port 8082, store that connection until it is closed. And when you get data from clients connected by 8081 port, send data to all connections stored before.
require 'eventmachine'
$clients = []
module EchoServer
def initialize(port)
#port = port
end
def post_init
puts "-- someone connected to the echo server!"
$clients << self if #port == 8082
end
def receive_data data
send_data ">>>you sent: #{data}"
# data is from a client connected by 8081 port
if #port == 8081
$clients.each do |c|
c.send_data ">>>server send: #{data}"
end
end
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
$clients.delete self if #port == 8082
end
end
# Note that this will block current thread.
EventMachine.run {
# arguments after handler will be passed to initialize method
EventMachine.start_server "127.0.0.1", 8081, EchoServer, 8081
EventMachine.start_server "127.0.0.1", 8082, EchoServer, 8082
EventMachine.start_server "127.0.0.1", 8083, EchoServer, 8083
}
Run telnet 127.0.0.1 8082 under you console/shell.
-> ~ $ telnet 127.0.0.1 8082
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
hello
>>>you sent: hello
quit
Connection closed by foreign host.
If you want to send data from Ruby code, take a look at socket library.
require 'socket'
s = TCPSocket.new '127.0.0.1', 8082
s.puts "Hello"
puts s.gets #=> >>>you sent: Hello
s.close
I wrote a TCP server using below code. This is to receive GPS location data via GSM network from a remote GPS sensor.
require 'socket'
server = TCPServer.open(2000) # Listen on port 2000
sockets = [server] # An array of sockets we'll monitor
log = STDOUT # Send log messages to standard out
while true
ready = select(sockets)
readable = ready[0]
readable.each do |socket|
if socket == server
client = server.accept
sockets << client
log.puts "Accepted connection from #{client.peeraddr[2]}"
while msg = client.gets
puts msg
end
else
input = socket.gets
if !input
log.puts "Client on #{socket.peeraddr[2]} disconnected"
sockets.delete(socket)
socket.close
next
end
input.chop!
if (input == "quit")
socket.puts("Bye");
log.puts "Closing connnection to #{socket.peeraddr[2]}"
sockets.delete(socket)
socket.close
else
socket.puts(input.reverse)
end
end
end
end
and then I wrote one using Eventmachine. Code as below:
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
puts data
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 2000, EchoServer
}
However, this eventmachine code will not receive nor display the data. Any part of the Eventmachine code that is wrong?
Thanks
I think your problem is that you are listening on localhost only, try this:
EM::run do
EM.start_server "0.0.0.0", 2000, EchoServer
end
I'm trying to build a websocket server where each client establish its own redis connections used for publish and subscribe.
When the redis server is running I can see the two new connections being established when a client connects to the websocket server and I can also publish data to the client, but when the client drops the connection to the websocket server I also want to disconnect from Redis . How can I do this?
Maybe I'm doing it wrong, but this is my code.
#require 'redis'
require 'em-websocket'
require 'em-hiredis'
require 'json'
CLIENTS = Hash.new
class PubSub
def initialize(client)
#socket = client.ws
# These clients can only be used for pub sub commands
#publisher = EM::Hiredis.connect #Later I will like to disconnect this
#subscriber = EM::Hiredis.connect #Later I will like to disconnect this
client.connections << #publisher << #subscriber
end
def subscribe(channel)
#channel = channel
#subscriber.subscribe(channel)
#subscriber.on(:message) { |chan, message|
#socket.send message
}
end
def publish(channel,msg)
#publisher.publish(channel, msg).errback { |e|
puts [:publisherror, e]
}
end
def unsubscribe()
#subscriber.unsubscribe(#channel)
end
end
class Client
attr_accessor :connections, :ws
def initialize(ws)
#connections = []
#ws = ws
end
end
EventMachine.run do
# Creates a websocket listener
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8081) do |ws|
ws.onopen do
# I instantiated above
puts 'CLient connected. Creating socket'
#client = Client.new(ws)
CLIENTS[ws] = #client
end
ws.onclose do
# Upon the close of the connection I remove it from my list of running sockets
puts 'Client disconnected. Closing socket'
#client.connections.each do |con|
#do something to disconnect from redis
end
CLIENTS.delete ws
end
ws.onmessage { |msg|
puts "Received message: #{msg}"
result = JSON.parse(msg)
if result.has_key? 'channel'
ps = PubSub.new(#client)
ps.subscribe(result['channel'])
elsif result.has_key? 'publish'
ps = PubSub.new(ws)
ps.publish(result['publish']['channel'],result['publish']['msg']);
end
}
end
end
This version of em-hiredis supports close connection: https://github.com/whatupdave/em-hiredis
Here is how I would (and did many times) this:
instead of always opening and closing connections for each client you can keep 1 connection open per Thread/Fiber dependeing on what you are basing your concurrency on, that way if you are using a poll of Thread/Fibers once each one of them have its connections they will keep it and reuse them.
I did not worked much with websocket until now (I was waiting for a standard implementation) but I am sure you can apply that thinking to it too.
You can also do what rails/activerecord: keeo a pool of redis connection, each time you need to use a connection you request one, use it and realease it, it could look like this:
def handle_request(request)
#redis_pool.get_connection do |c|
# [...]
end
end
before yielding the block a connection is taken from the available ones and after it the connection is marked as free.
This was added to em-hiredis: https://github.com/mloughran/em-hiredis/pull/6
I need to implement a server which only writes data, doesn't receive it. All of the eventmachine server examples I've found always have the server receive data first, and then respond with data. I need it to just start writing data to a client after a client connects.
I tried just putting a loop in post_init, but that doesn't seem to work... the client connects, the server writes, but the client never seems to receive anything. Suggestions?
The test server:
require 'rubygems'
require 'eventmachine'
require 'time'
module TestServer
def post_init
puts "-- client connected, sending data --"
while true do
send_data "Hello from TestServer\n"
puts "sent #{Time.now.iso8601}"
end
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 4001, TestServer
puts 'running test server on 4001'
}
The test client:
require 'rubygems'
require 'eventmachine'
module Forwarder
def post_init
puts "-- connected to server --"
end
def receive_data data
# none of the following is ever output
puts "in receive_data"
puts data
end
end
EventMachine::run {
EventMachine::connect '127.0.0.1', 4001, Forwarder
}
Thanks...
Thanks to tmm1 on #eventmachine, got this figured out. Client is the same. Server code is:
require 'rubygems'
require 'eventmachine'
require 'time'
module TestServer
def post_init
puts "-- client connected --"
#timer = EM::PeriodicTimer.new(0.1) {
send_data "Hello from TestServer at #{Time.now.iso8601}\n"
}
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 4001, TestServer
puts 'running test server on 4001'
}