├── README.md ├── examples ├── example.rb └── timers.rb └── lib └── netty-eventmachine ├── connection.rb ├── em_api.rb ├── namespace.rb ├── netty ├── pipeline.rb └── timertask.rb └── timer.rb /README.md: -------------------------------------------------------------------------------- 1 | # Netty-backed EventMachine API 2 | 3 | The goal of this project to provide an EventMachine model using Netty as the 4 | implementation. Overall, I aim to be API compatible with EventMachine as-is with 5 | long-term goal of replacing the current jruby eventmachine implementation. 6 | 7 | ## Example: 8 | 9 | require "netty-3.2.4.Final.jar" 10 | $: << "lib" 11 | require "netty-eventmachine/em_api.rb" 12 | 13 | class Conn < EventMachine::Connection 14 | def receive_data(data) 15 | p :received => data 16 | end 17 | end 18 | 19 | EventMachine.run do 20 | EventMachine.start_server("0.0.0.0", 3333, Conn) 21 | end 22 | -------------------------------------------------------------------------------- /examples/example.rb: -------------------------------------------------------------------------------- 1 | require "netty-3.2.4.Final.jar" 2 | $: << "lib" 3 | require "netty-eventmachine/em_api.rb" 4 | 5 | class Conn < EventMachine::Connection 6 | def receive_data(data) 7 | p :received => data 8 | end 9 | end 10 | 11 | EventMachine.run do 12 | EventMachine.start_server("0.0.0.0", 3333, Conn) 13 | end 14 | -------------------------------------------------------------------------------- /examples/timers.rb: -------------------------------------------------------------------------------- 1 | require "netty-3.2.4.Final.jar" 2 | $: << "lib" 3 | require "netty-eventmachine/em_api.rb" 4 | 5 | class Conn < EventMachine::Connection 6 | def receive_data(data) 7 | p :received => data 8 | end 9 | end 10 | 11 | def tputs(*args) 12 | p Thread.current, *args 13 | end 14 | 15 | EventMachine.run do 16 | #EventMachine.start_server("0.0.0.0", 3333, Conn) 17 | tputs "Hello" 18 | 1.upto(10) do |i| 19 | t = EventMachine::Timer.new(i * 0.1) do 20 | tputs "world #{i}" 21 | end 22 | t.cancel if i % 2 == 0 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/netty-eventmachine/connection.rb: -------------------------------------------------------------------------------- 1 | require "netty-eventmachine/namespace" 2 | require "java" 3 | 4 | class EventMachine::Connection < org.jboss.netty.channel.SimpleChannelUpstreamHandler 5 | # add this interface 6 | include org.jboss.netty.channel.Channel 7 | #include org.jboss.netty.channel.ChannelHandler 8 | 9 | USASCII = java.nio.charset.Charset.defaultCharset 10 | 11 | public 12 | def initialize(*args) 13 | # Ensure the no-args SimpleChannelUpstreamHandler constructor is called. 14 | super() 15 | end 16 | 17 | # EventMachine API, 18 | public 19 | def self.connect(bind_addr, bind_port, host, port) 20 | # TODO(sissel): 21 | end # def EventMachine::Connection.connect 22 | 23 | private 24 | def post_init_setup(context) 25 | @peeraddr = context.getChannel.getRemoteAddress 26 | p :port => @peeraddr.getPort 27 | p :address => @peeraddr.getAddress 28 | end # def post_init_setup 29 | 30 | # org.jboss.netty.channel.Channel#channelConnected 31 | public 32 | def channelConnected(context, event) 33 | if respond_to?(:post_init) 34 | post_init_setup(context) 35 | post_init 36 | end 37 | end # def channelConnected 38 | 39 | # org.jboss.netty.channel.Channel#messageReceived 40 | public 41 | def messageReceived(context, event) 42 | if respond_to?(:receive_data) 43 | #p event.getMessage.toString(USASCII) 44 | receive_data(event.getMessage.toString(USASCII)) 45 | end 46 | end # def messageReceived 47 | 48 | # org.jboss.netty.channel.Channel#exceptionCaught 49 | public 50 | def exceptionCaught(context, exception) 51 | puts "Exception -- " + exception.to_s 52 | puts "Backtrace: " + exception.cause.getStackTrace 53 | end # def exceptionCaught 54 | 55 | # org.jboss.netty.channel.Channel#channelConnected 56 | public 57 | def channelConnected(context, event) 58 | @channel = context.getChannel 59 | post_init 60 | end 61 | 62 | public # EventMachine api, post_init 63 | def post_init 64 | # Nothing by default 65 | end 66 | 67 | public # EventMachine api, receive_data 68 | def receive_data(data) 69 | puts "got data" 70 | end 71 | 72 | # This implementation of EM::Connection#get_peername doesn't return 73 | # a C-string representation of a sockaddr struct, because we just don't. 74 | # Instead, it returns an array [ address, port ] 75 | # 'address' will be a string of the address, and port a number 76 | public # EventMachine api, get_peername 77 | def get_peername 78 | if @channel.nil? 79 | # EM::C#get_peername returns nil on no peer. 80 | return nil 81 | end 82 | 83 | # ask for the channel's remote address 84 | socketaddr = @channel.getRemoteAddress 85 | return [ socketaddr.getAddress.getHostAddress, socketaddr.getPort ] 86 | end # def get_peername 87 | end # class EventMachine::Connection < org.jboss.netty.channel.SimpleChannelUpstreamHandler 88 | -------------------------------------------------------------------------------- /lib/netty-eventmachine/em_api.rb: -------------------------------------------------------------------------------- 1 | require "netty-eventmachine/namespace" 2 | require "netty-eventmachine/netty/pipeline" 3 | require "java" 4 | 5 | require "netty-eventmachine/connection" 6 | require "netty-eventmachine/timer" 7 | 8 | module EventMachine 9 | java_import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory 10 | 11 | public # standard EventMachine API, start_server 12 | def self.start_server(address, port=nil, handlerclass=nil, *args, &block) 13 | # handler can be a subclass of EM::Connection or a module implementing the 14 | # right methods 15 | 16 | if handlerclass.is_a?(Class) 17 | channelfactory = NioServerSocketChannelFactory.new( 18 | java.util.concurrent.Executors.newCachedThreadPool, 19 | java.util.concurrent.Executors.newCachedThreadPool 20 | ) 21 | 22 | bootstrap = org.jboss.netty.bootstrap.ServerBootstrap.new(channelfactory) 23 | bootstrap.setPipelineFactory(EventMachine::Netty::Pipeline.new(handlerclass, *args, &block)) 24 | # TODO(sissel): Make this tunable, maybe a 'class'-wide setting on 25 | # handlerclass? 26 | bootstrap.setOption("child.tcpNoDelay", true) 27 | bootstrap.setOption("child.keepAlive", true) 28 | 29 | # TODO(sissel): async dns would be nice. 30 | bootstrap.bind(java.net.InetSocketAddress.new(address, port)) 31 | else 32 | puts "EM.start_server with a module as handler not supported yet." 33 | end 34 | end # def start_server 35 | 36 | public 37 | def self.open_datagram_socket(address, port, handlerclass=nil, *args, &block) 38 | # TODO(sissel): Implement 39 | end # def open_datagram_socket 40 | 41 | public # EventMachine API, run 42 | def self.run(&block) 43 | block.call 44 | # TODO(sissel): we should block until netty exits. 45 | end # def run 46 | 47 | public # EventMachine API add_timer 48 | def self.add_timer(delay, &block) 49 | return EventMachine::Timer.new(delay, &block) 50 | end # def add_timer 51 | 52 | # Implement next_tick as a timer since Netty has no notion of "ticks" 53 | public # EventMachine API next_tick 54 | def self.next_tick(&block) 55 | self.add_timer(0, &block) 56 | return nil 57 | end # def next_tick 58 | 59 | # Can't implement EM::fork_reactor, JRuby (and java) don't have fork. Why? On 60 | # POSIX systems, fork doesn't keep any other pthreads, so all the management 61 | # threads the JVM and JRuby run will be lost. 62 | public # EventMachine API fork_reactor 63 | def self.fork_reactor(&block) 64 | raise NotImplemented.new 65 | end # def fork_reactor 66 | 67 | public 68 | def self.reactor_running? 69 | return true 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/netty-eventmachine/namespace.rb: -------------------------------------------------------------------------------- 1 | module EventMachine 2 | module Netty; end 3 | end 4 | -------------------------------------------------------------------------------- /lib/netty-eventmachine/netty/pipeline.rb: -------------------------------------------------------------------------------- 1 | require "netty-eventmachine/namespace" 2 | require "java" 3 | 4 | class EventMachine::Netty::Pipeline 5 | include org.jboss.netty.channel.ChannelPipelineFactory 6 | 7 | def initialize(handlerclass, *args, &block) 8 | @handlerclass = handlerclass 9 | @args = args 10 | @block = block 11 | end # def initialize 12 | 13 | public # org.jboss.netty.channel.ChannelPipelineFactory#getPipeline 14 | def getPipeline 15 | #p "getPipeline" => [ @handlerclass ] 16 | handler = @handlerclass.new(*@args, &@block) 17 | return org.jboss.netty.channel.Channels.pipeline(handler) 18 | end # def getPipeline 19 | end # class EventMachine::Netty::Pipeline 20 | 21 | -------------------------------------------------------------------------------- /lib/netty-eventmachine/netty/timertask.rb: -------------------------------------------------------------------------------- 1 | require "netty-eventmachine/namespace" 2 | require "java" 3 | 4 | class EventMachine::Netty::TimerTask 5 | include org.jboss.netty.util.TimerTask 6 | 7 | def initialize(&block) 8 | @block = block 9 | end 10 | 11 | def run(timeout) 12 | @block.call 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/netty-eventmachine/timer.rb: -------------------------------------------------------------------------------- 1 | require "netty-eventmachine/namespace" 2 | require "netty-eventmachine/netty/timertask" 3 | require "java" 4 | 5 | class EventMachine::Timer 6 | public 7 | def initialize(delay, &block) 8 | # Only want one HashedWheelTimer instance per application. 9 | # TODO(sissel): Make the tick interval configurable? Default is 100ms 10 | # TODO(sissel): Tunable size of wheel? (Number of schedulable tasks) 11 | @@timer ||= org.jboss.netty.util.HashedWheelTimer.new 12 | @timertask = EventMachine::Netty::TimerTask.new(&block) 13 | 14 | # Convert to ms 15 | delay_ms = delay * 1000 16 | @timeout = @@timer.newTimeout(@timertask, delay_ms, 17 | java.util.concurrent.TimeUnit::MILLISECONDS) 18 | end # def initialize 19 | 20 | # EventMachine::Timer#cancel 21 | public 22 | def cancel 23 | @timeout.cancel 24 | end # def cancel 25 | end 26 | --------------------------------------------------------------------------------