packet-0.1.15/0000755000175000017500000000000011634166337012075 5ustar gwolfgwolfpacket-0.1.15/bin/0000755000175000017500000000000011634166337012645 5ustar gwolfgwolfpacket-0.1.15/bin/packet_worker_runner0000755000175000017500000000310711634166337017025 0ustar gwolfgwolf#!/usr/bin/env ruby PACKET_LIB_PATH = File.expand_path(File.dirname(__FILE__)) ["lib"].each { |x| $LOAD_PATH.unshift(File.join(PACKET_LIB_PATH,"..",x))} require "packet" module Packet class WorkerRunner include Packet::NbioHelper def initialize args cmd_args = args.split(':') worker_name = cmd_args[2] initial_arg_data_length = cmd_args[3].to_i @worker_root = cmd_args[4] @worker_load_env = cmd_args[5] @worker_read_fd = UNIXSocket.for_fd(cmd_args[0].to_i) @worker_write_fd = UNIXSocket.for_fd(cmd_args[1].to_i) initial_arg_data = @worker_read_fd.read(initial_arg_data_length) Packet::WorkerRunner.const_set(:WORKER_OPTIONS,Marshal.load(initial_arg_data)) require @worker_load_env if @worker_load_env && !@worker_load_env.empty? load_worker worker_name end def load_worker worker_name if @worker_root && (File.file? "#{@worker_root}/#{worker_name}.rb") require "#{@worker_root}/#{worker_name}" worker_klass = Object.const_get(packet_classify(worker_name)) worker_klass.start_worker(:read_end => @worker_read_fd,:write_end => @worker_write_fd,:options => WORKER_OPTIONS) else require worker_name worker_klass = Object.const_get(packet_classify(worker_name)) if worker_klass.is_worker? worker_klass.start_worker(:read_end => @worker_read_fd,:write_end => @worker_write_fd,:options => WORKER_OPTIONS) else raise Packet::InvalidWorker.new(worker_name) end end end end end Packet::WorkerRunner.new(ARGV[0]) packet-0.1.15/lib/0000755000175000017500000000000011634166337012643 5ustar gwolfgwolfpacket-0.1.15/lib/packet/0000755000175000017500000000000011634166337014112 5ustar gwolfgwolfpacket-0.1.15/lib/packet/packet_nbio.rb0000644000175000017500000000507511634166337016724 0ustar gwolfgwolfmodule Packet module NbioHelper def packet_classify(original_string) word_parts = original_string.split('_') return word_parts.map { |x| x.capitalize}.join end def gen_worker_key(worker_name,worker_key = nil) return worker_name if worker_key.nil? return "#{worker_name}_#{worker_key}".to_sym end def read_data(t_sock) sock_data = [] begin while(t_data = t_sock.read_nonblock((16*1024)-1)) sock_data << t_data end rescue Errno::EAGAIN return sock_data.join rescue Errno::EWOULDBLOCK return sock_data.join rescue raise DisconnectError.new(t_sock,sock_data.join) end end # method writes data to socket in a non blocking manner, but doesn't care if there is a error writing data def write_once(p_data,p_sock) t_data = p_data.to_s written_length = 0 data_length = t_data.length begin written_length = p_sock.write_nonblock(t_data) return "" if written_length == data_length return t_data[written_length..-1] rescue Errno::EAGAIN return t_data[written_length..-1] rescue Errno::EPIPE raise DisconnectError.new(p_sock) rescue Errno::ECONNRESET raise DisconnectError.new(p_sock) rescue raise DisconnectError.new(p_sock) end end # write the data in socket buffer and schedule the thing def write_and_schedule sock outbound_data.each_with_index do |t_data,index| leftover = write_once(t_data,sock) if leftover.empty? outbound_data[index] = nil else outbound_data[index] = leftover reactor.schedule_write(sock,self) break end end outbound_data.compact! reactor.cancel_write(sock) if outbound_data.empty? end # returns Marshal dump of the specified object def object_dump p_data object_dump = Marshal.dump(p_data) dump_length = object_dump.length.to_s length_str = dump_length.rjust(9,'0') final_data = length_str + object_dump end # method dumps the object in a protocol format which can be easily picked by a recursive descent parser def dump_object(p_data,p_sock) object_dump = Marshal.dump(p_data) dump_length = object_dump.length.to_s length_str = dump_length.rjust(9,'0') final_data = length_str + object_dump outbound_data << final_data begin write_and_schedule(p_sock) rescue DisconnectError => sock close_connection(sock) end end end end packet-0.1.15/lib/packet/packet_helper.rb0000644000175000017500000000237711634166337017256 0ustar gwolfgwolfmodule Packet module ClassHelpers def metaclass; class << self; self; end; end def iattr_accessor *args metaclass.instance_eval do attr_accessor *args args.each do |attr| define_method("set_#{attr}") do |b_value| self.send("#{attr}=",b_value) end end end args.each do |attr| class_eval do define_method(attr) do self.class.send(attr) end define_method("#{attr}=") do |b_value| self.class.send("#{attr}=",b_value) end end end end # end of method iattr_accessor def inheritable_attribute *options_args option_hash = options_args.last args = options_args[0..-2] args.each {|attr| instance_variable_set(:"@#{attr}",option_hash[:default] || nil )} metaclass.instance_eval { attr_accessor *args } args.each do |attr| class_eval do define_method(attr) do self.class.send(attr) end define_method("#{attr}=") do |b_value| self.class.send("#{attr}=",b_value) end end end end module_function :metaclass,:iattr_accessor,:inheritable_attribute end # end of module ClassHelpers end packet-0.1.15/lib/packet/timer_store.rb0000644000175000017500000000324511634166337016777 0ustar gwolfgwolf=begin There are many ordered hash implementation of ordered hashes, but this one is for packet. Nothing more, nothing less. =end require "event" require "packet_guid" module Packet class TimerStore attr_accessor :order def initialize @order = [] @container = { } end def store(timer) int_time = timer.scheduled_time.to_i @container[int_time] ||= [] @container[int_time] << timer if @container.empty? or @order.empty? @order << int_time return end if @order.last <= int_time @order << int_time else index = bin_search_for_key(0,@order.length - 1,int_time) if(int_time < @order[index-1] && index != 0) @order.insert(index-1,int_time) else @order.insert(index,int_time) end end end def bin_search_for_key(lower_index,upper_index,key) return upper_index if(upper_index - lower_index <= 1) pivot = (lower_index + upper_index)/2 if @order[pivot] == key return pivot elsif @order[pivot] < key bin_search_for_key(pivot,upper_index,key) else bin_search_for_key(lower_index,pivot,key) end end def each @order.each_with_index do |x,i| @container[x].each do |timer| yield timer end # @container.delete(x) if @container[x].empty? # @order.delete_at(i) end end def delete(timer) int_time = timer.scheduled_time.to_i @container[int_time] && @container[int_time].delete(timer) if(!@container[int_time] || @container[int_time].empty?) @order.delete(timer) end end end end packet-0.1.15/lib/packet/packet_invalid_worker.rb0000644000175000017500000000022411634166337021003 0ustar gwolfgwolfmodule Packet class InvalidWorker < RuntimeError attr_accessor :message def initialize message @message = message end end end packet-0.1.15/lib/packet/disconnect_error.rb0000644000175000017500000000032111634166337017775 0ustar gwolfgwolfmodule Packet class DisconnectError < RuntimeError attr_accessor :disconnected_socket,:data def initialize(t_sock,data = nil) @disconnected_socket = t_sock @data = data end end end packet-0.1.15/lib/packet/packet_parser.rb0000644000175000017500000000421311634166337017262 0ustar gwolfgwolfmodule Packet class BinParser attr_accessor :data,:numeric_length,:length_string,:remaining attr_accessor :parser_state def initialize @size = 0 @data = [] @remaining = "" # 0 => reading length # 1 => reading actual data @parser_state = 0 @length_string = "" @numeric_length = 0 end def reset @data = [] @parser_state = 0 @length_string = "" @numeric_length = 0 end def extract new_data remaining = new_data loop do if @parser_state == 0 length_to_read = 9 - @length_string.length len_str,remaining = remaining.unpack("a#{length_to_read}a*") break if len_str !~ /^\d+$/ if len_str.length < length_to_read @length_string << len_str break else @length_string << len_str @numeric_length = @length_string.to_i @parser_state = 1 if remaining.length < @numeric_length @data << remaining @numeric_length = @numeric_length - remaining.length break elsif remaining.length == @numeric_length @data << remaining yield(@data.join) reset break else pack_data,remaining = remaining.unpack("a#{@numeric_length}a*") @data << pack_data yield(@data.join) reset end end elsif @parser_state == 1 pack_data,remaining = remaining.unpack("a#{@numeric_length}a*") if pack_data.length < @numeric_length @data << pack_data @numeric_length = @numeric_length - pack_data.length break elsif pack_data.length == @numeric_length @data << pack_data yield(@data.join) reset break else @data << pack_data yield(@data.join) reset end end # end of beginning if condition end # end of loop do end # end of extract method end # end of BinParser class end # end of packet module packet-0.1.15/lib/packet/packet_guid.rb0000644000175000017500000000057411634166337016724 0ustar gwolfgwolfmodule Packet class Guid def self.hexdigest values = [ rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x1000000), rand(0x1000000), ] "%04x%04x%04x%04x%04x%06x%06x" % values end end end packet-0.1.15/lib/packet/packet_periodic_event.rb0000644000175000017500000000103111634166337020760 0ustar gwolfgwolfmodule Packet class PeriodicEvent attr_accessor :block, :timer_signature, :interval, :cancel_flag def initialize(interval, &block) @cancel_flag = false @timer_signature = Guid.hexdigest @block = block @scheduled_time = Time.now + interval @interval = interval end def run_now? return true if @scheduled_time <= Time.now return false end def cancel @cancel_flag = true end def run @scheduled_time += @interval @block.call end end end packet-0.1.15/lib/packet/packet_pimp.rb0000644000175000017500000000144111634166337016733 0ustar gwolfgwolfmodule Packet class Pimp include NbioHelper extend ClassHelpers extend Forwardable iattr_accessor :pimp_name attr_accessor :lifeline, :pid, :signature attr_accessor :fd_write_end attr_accessor :workers, :reactor,:outbound_data def initialize(lifeline_socket,worker_pid,p_reactor) @lifeline = lifeline_socket @pid = worker_pid @reactor = p_reactor @signature = Guid.hexdigest @outbound_data = [] pimp_init if self.respond_to?(:pimp_init) end # encode the data, before writing to the socket def send_data p_data dump_object(p_data,@lifeline) end def send_fd sock_fd @fd_write_end.send_io(sock_fd) end alias_method :do_work, :send_data def_delegators :@reactor, :connections end end packet-0.1.15/lib/packet/packet_worker.rb0000644000175000017500000000515711634166337017307 0ustar gwolfgwolf# class implements general worker module Packet class Worker include Core iattr_accessor :fd_reader,:msg_writer,:msg_reader,:worker_name iattr_accessor :worker_proxy iattr_accessor :no_auto_load attr_accessor :worker_started, :worker_options # method initializes the eventloop for the worker def self.start_worker(messengers = {}) # @fd_reader = args.shift if args.length > 2 @msg_writer = messengers[:write_end] @msg_reader = messengers[:read_end] t_instance = new t_instance.worker_options = messengers[:options] t_instance.worker_init if t_instance.respond_to?(:worker_init) t_instance.start_reactor t_instance end # copy the inherited attribute in class thats inheriting this class def self.inherited(subklass) subklass.send(:"connection_callbacks=",connection_callbacks) end def self.is_worker?; true; end def initialize super @read_ios << msg_reader @tokenizer = BinParser.new end def send_data p_data dump_object(p_data,msg_writer) end def send_request(options = {}) t_data = options[:data] if t_callback = options[:callback] callback_hash[t_callback.signature] = t_callback send_data(:data => t_data,:function => options[:function],:callback_signature => t_callback.signature) else send_data(:data => t_data,:function => options[:function],:requested_worker => options[:worker],:requesting_worker => worker_name,:type => :request) end end # method handles internal requests from internal sockets def handle_internal_messages(t_sock) begin t_data = read_data(t_sock) receive_internal_data(t_data) rescue DisconnectError => sock_error # Means, when there is an error from sockets from which we are reading better just terminate terminate_me() end end def receive_internal_data data @tokenizer.extract(data) do |b_data| data_obj = Marshal.load(b_data) receive_data(data_obj) end end def log log_data send_data(:requested_worker => :log_worker,:data => log_data,:type => :request) end # method receives data from external TCP Sockets def receive_data p_data raise "Not implemented for worker" end # method checks if client has asked to execute a internal function def invoke_internal_function raise "Not implemented for worker" end # message returns data to parent process, using UNIX Sockets def invoke_callback raise "Not implemented for worker" end end # end of class#Worker end packet-0.1.15/lib/packet/packet_master.rb0000644000175000017500000001310611634166337017262 0ustar gwolfgwolfmodule Packet class Reactor include Core #set_thread_pool_size(20) attr_accessor :fd_writers, :msg_writers,:msg_reader attr_accessor :result_hash attr_accessor :live_workers #after_connection :provide_workers def self.server_logger= (log_file_name) @@server_logger = log_file_name end def self.run master_reactor_instance = new master_reactor_instance.result_hash = {} master_reactor_instance.live_workers = DoubleKeyedHash.new yield(master_reactor_instance) master_reactor_instance.load_workers master_reactor_instance.start_reactor end # end of run method def set_result_hash(hash) @result_hash = hash end def update_result(worker_key,result) @result_hash ||= {} @result_hash[worker_key.to_sym] = result end def handle_internal_messages(t_sock) sock_fd = t_sock.fileno worker_instance = @live_workers[sock_fd] begin raw_data = read_data(t_sock) worker_instance.receive_data(raw_data) if worker_instance.respond_to?(:receive_data) rescue DisconnectError => sock_error worker_instance.receive_data(sock_error.data) if worker_instance.respond_to?(:receive_data) remove_worker(t_sock) end end def remove_worker(t_sock) @live_workers.delete(t_sock.fileno) read_ios.delete(t_sock) end def delete_worker(worker_options = {}) worker_name = worker_options[:worker] worker_name_key = gen_worker_key(worker_name,worker_options[:worker_key]) worker_options[:method] = :exit @live_workers[worker_name_key].send_request(worker_options) end def load_workers worker_root = defined?(WORKER_ROOT) ? WORKER_ROOT : "#{PACKET_APP}/worker" t_workers = Dir["#{worker_root}/**/*.rb"] return if t_workers.empty? t_workers.each do |b_worker| worker_name = File.basename(b_worker,".rb") require worker_name worker_klass = Object.const_get(packet_classify(worker_name)) next if worker_klass.no_auto_load fork_and_load(worker_klass) end end def start_worker(worker_options = { }) worker_name = worker_options[:worker].to_s worker_name_key = gen_worker_key(worker_name,worker_options[:worker_key]) return if @live_workers[worker_name_key] worker_options.delete(:worker) begin require worker_name worker_klass = Object.const_get(packet_classify(worker_name)) fork_and_load(worker_klass,worker_options) rescue LoadError puts "no such worker #{worker_name}" return end end def enable_nonblock io f = io.fcntl(Fcntl::F_GETFL,0) io.fcntl(Fcntl::F_SETFL,Fcntl::O_NONBLOCK | f) end # method should use worker_key if provided in options hash. def fork_and_load(worker_klass,worker_options = { }) t_worker_name = worker_klass.worker_name worker_pimp = worker_klass.worker_proxy.to_s # socket from which master process is going to read master_read_end,worker_write_end = UNIXSocket.pair(Socket::SOCK_STREAM) # socket to which master process is going to write worker_read_end,master_write_end = UNIXSocket.pair(Socket::SOCK_STREAM) option_dump = Marshal.dump(worker_options) option_dump_length = option_dump.length master_write_end.write(option_dump) worker_name_key = gen_worker_key(t_worker_name,worker_options[:worker_key]) if(!(pid = fork)) [master_write_end,master_read_end].each { |x| x.close } [worker_read_end,worker_write_end].each { |x| enable_nonblock(x) } begin if(ARGV[0] == 'start' && Object.const_defined?(:SERVER_LOGGER)) redirect_io(SERVER_LOGGER) end rescue puts $!.backtrace end exec form_cmd_line(worker_read_end.fileno,worker_write_end.fileno,t_worker_name,option_dump_length) end Process.detach(pid) [master_read_end,master_write_end].each { |x| enable_nonblock(x) } if worker_pimp && !worker_pimp.empty? require worker_pimp pimp_klass = Object.const_get(packet_classify(worker_pimp)) @live_workers[worker_name_key,master_read_end.fileno] = pimp_klass.new(master_write_end,pid,self) else t_pimp = Packet::MetaPimp.new(master_write_end,pid,self) t_pimp.worker_key = worker_name_key t_pimp.worker_name = t_worker_name t_pimp.invokable_worker_methods = worker_klass.instance_methods @live_workers[worker_name_key,master_read_end.fileno] = t_pimp end worker_read_end.close worker_write_end.close read_ios << master_read_end end # end of fork_and_load method # Free file descriptors and # point them somewhere sensible # STDOUT/STDERR should go to a logfile def redirect_io(logfile_name) begin; STDIN.reopen "/dev/null"; rescue ::Exception; end if logfile_name begin STDOUT.reopen logfile_name, "a" STDOUT.sync = true rescue ::Exception begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end end else begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end end begin; STDERR.reopen STDOUT; rescue ::Exception; end STDERR.sync = true end def form_cmd_line *args min_string = "packet_worker_runner #{args[0]}:#{args[1]}:#{args[2]}:#{args[3]}" min_string << ":#{WORKER_ROOT}" if defined? WORKER_ROOT min_string << ":#{WORKER_LOAD_ENV}" if defined? WORKER_LOAD_ENV min_string end end # end of Reactor class end # end of Packet module packet-0.1.15/lib/packet/packet_connection.rb0000644000175000017500000000422111634166337020124 0ustar gwolfgwolfmodule Packet module Connection attr_accessor :outbound_data,:connection_live attr_accessor :worker,:connection,:reactor, :initialized,:signature include NbioHelper def unbind; end def connection_completed; end def post_init; end def receive_data data; end def send_data p_data @outbound_data << p_data begin write_and_schedule(connection) rescue DisconnectError => sock close_connection end end def invoke_init @initialized = true @connection_live = true @outbound_data = [] post_init end def close_connection(sock = nil) unbind reactor.cancel_write(connection) reactor.remove_connection(connection) end def close_connection_after_writing connection.flush unless connection.closed? close_connection end def get_peername connection.getpeername end def send_object p_object dump_object(p_object,connection) end def ask_worker(*args) worker_name = args.shift data_options = args.last data_options[:client_signature] = connection.fileno t_worker = reactor.live_workers[worker_name] raise Packet::InvalidWorker.new("Invalid worker with name #{worker_name} and key #{data_options[:data][:worker_key]}") unless t_worker t_worker.send_request(data_options) end def start_server ip,port,t_module,&block reactor.start_server(ip,port,t_module,&block) end def connect ip,port,t_module,&block reactor.connect(ip,port,t_module,&block) end def add_periodic_timer interval, &block reactor.add_periodic_timer(interval,&block) end def add_timer(t_time,&block) reactor.add_timer(t_time,&block) end def cancel_timer(t_timer) reactor.cancel_timer(t_timer) end def reconnect server,port,handler reactor.reconnect(server,port,handler) end def start_worker(worker_options = {}) reactor.start_worker(worker_options) end def delete_worker worker_options = {} reactor.delete_worker(worker_options) end end # end of class Connection end # end of module Packet packet-0.1.15/lib/packet/packet_core.rb0000644000175000017500000002751211634166337016725 0ustar gwolfgwolf# FIXME: timer implementation can be optimized module Packet module Core def self.included(base_klass) base_klass.extend(ClassMethods) base_klass.instance_eval do iattr_accessor :connection_callbacks inheritable_attribute(:connection_callbacks,:default => {}) attr_accessor :read_ios, :write_ios, :listen_sockets attr_accessor :connection_completion_awaited,:write_scheduled attr_accessor :connections, :windows_flag attr_accessor :internal_scheduled_write,:outbound_data,:reactor include CommonMethods end end module ClassMethods include Packet::ClassHelpers def after_connection p_method connection_callbacks[:after_connection] ||= [] connection_callbacks[:after_connection] << p_method end # FIXME: following callbacks hasn't been tested and not usable. def after_unbind p_method connection_callbacks[:after_unbind] ||= [] connection_callbacks[:after_unbind] << p_method end def before_unbind p_method connection_callbacks[:before_unbind] ||= [] connection_callbacks[:before_unbind] << p_method end end # end of module#ClassMethods module CommonMethods include NbioHelper # method def connect(ip,port,t_module,&block) t_socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0) t_sock_addr = Socket.sockaddr_in(port,ip) t_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1) connection_completion_awaited[t_socket.fileno] = { :sock_addr => t_sock_addr, :module => t_module,:block => block } begin t_socket.connect_nonblock(t_sock_addr) immediate_complete(t_socket,t_sock_addr,t_module,&block) rescue Errno::EINPROGRESS write_ios << t_socket end end def reconnect(server,port,handler) raise "invalid handler" unless handler.respond_to?(:connection_completed) if !handler.connection.closed? && connections.keys.include?(handler.connection.fileno) return handler end connect(server,port,handler) end def immediate_complete(t_socket,sock_addr,t_module,&block) read_ios << t_socket write_ios.delete(t_socket) decorate_handler(t_socket,true,sock_addr,t_module,&block) connection_completion_awaited.delete(t_socket.fileno) end def accept_connection(sock_opts) sock_io = sock_opts[:socket] begin client_socket,client_sockaddr = sock_io.accept_nonblock client_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1) rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR return end read_ios << client_socket decorate_handler(client_socket,true,client_sockaddr,sock_opts[:module],&sock_opts[:block]) end def complete_connection(t_sock,sock_opts) actually_connected = true begin t_sock.connect_nonblock(sock_opts[:sock_addr]) rescue Errno::EISCONN puts "Socket already connected" rescue Errno::ECONNREFUSED actually_connected = false end connection_completion_awaited.delete(t_sock.fileno) read_ios << t_sock if actually_connected write_ios.delete(t_sock) decorate_handler(t_sock,actually_connected,sock_opts[:sock_addr],\ sock_opts[:module],&sock_opts[:block]) end # method removes the connection and closes the socket def remove_connection(t_sock) read_ios.delete(t_sock) write_ios.delete(t_sock) begin unless t_sock.closed? connections.delete(t_sock.fileno) t_sock.close end rescue puts "#{$!.message}" end end def next_turn &block @on_next_tick = block end # method opens a socket for listening def start_server(ip,port,t_module,&block) BasicSocket.do_not_reverse_lookup = true # Comment TCPServer for the time being #t_socket = TCPServer.new(ip,port.to_i) #t_socket = TCPSocket. t_socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0) t_socket.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,true) sockaddr = Socket.sockaddr_in(port.to_i,ip) t_socket.bind(sockaddr) t_socket.listen(50) t_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1) # t_socket.setsockopt(*@tcp_defer_accept_opts) rescue nil listen_sockets[t_socket.fileno] = { :socket => t_socket,:block => block,:module => t_module } @read_ios << t_socket end # method starts event loop in the process def start_reactor Signal.trap("TERM") { terminate_me } Signal.trap("INT") { shutdown } loop do check_for_timer_events @on_next_tick.call if @on_next_tick ready_read_fds,ready_write_fds,read_error_fds = select(read_ios,write_ios,[],0.005) if ready_read_fds && !ready_read_fds.empty? handle_read_event(ready_read_fds) elsif ready_write_fds && !ready_write_fds.empty? handle_write_event(ready_write_fds) end end end def schedule_write(t_sock,internal_instance = nil) fileno = t_sock.fileno if UNIXSocket === t_sock && internal_scheduled_write[fileno].nil? write_ios << t_sock internal_scheduled_write[t_sock.fileno] ||= internal_instance elsif write_scheduled[fileno].nil? && !(t_sock.is_a?(UNIXSocket)) write_ios << t_sock write_scheduled[fileno] ||= connections[fileno][:instance] end end def cancel_write(t_sock) if !t_sock.closed? fileno = t_sock.fileno if UNIXSocket === t_sock internal_scheduled_write.delete(fileno) else write_scheduled.delete(fileno) end end write_ios.delete(t_sock) end def handle_write_event(p_ready_fds) p_ready_fds.each do |sock_fd| fileno = sock_fd.fileno if UNIXSocket === sock_fd && (internal_instance = internal_scheduled_write[fileno]) internal_instance.write_and_schedule(sock_fd) elsif extern_opts = connection_completion_awaited[fileno] complete_connection(sock_fd,extern_opts) elsif handler_instance = write_scheduled[fileno] handler_instance.write_and_schedule(sock_fd) end end end def handle_read_event(p_ready_fds) ready_fds = p_ready_fds.flatten.compact ready_fds.each do |t_sock| if(t_sock.is_a?(UNIXSocket)) handle_internal_messages(t_sock) else handle_external_messages(t_sock) end end end def terminate_me # FIXME: close the open sockets # @thread_pool.kill_all exit end def shutdown # @thread_pool.kill_all # FIXME: close the open sockets exit end def handle_internal_messages(t_sock) raise "Method should be implemented by concerned classes" end def handle_external_messages(t_sock) sock_fd = t_sock.fileno if sock_opts = listen_sockets[sock_fd] accept_connection(sock_opts) else read_external_socket(t_sock) end end def read_external_socket(t_sock) handler_instance = connections[t_sock.fileno][:instance] begin t_data = read_data(t_sock) handler_instance.receive_data(t_data) rescue DisconnectError => sock_error handler_instance.receive_data(sock_error.data) unless (sock_error.data).empty? handler_instance.close_connection end end def add_periodic_timer(interval,&block) t_timer = PeriodicEvent.new(interval,&block) @timer_hash[t_timer.timer_signature] = t_timer return t_timer end def add_timer(elapsed_time,&block) t_timer = Event.new(elapsed_time,&block) # @timer_hash.store(timer) @timer_hash[t_timer.timer_signature] = t_timer return t_timer end def cancel_timer(t_timer) @timer_hash.delete(t_timer.timer_signature) end def binding_str @binding += 1 "BIND_#{@binding}" end def initialize @read_ios ||= [] @write_ios ||= [] @connection_completion_awaited ||= {} @write_scheduled ||= {} @internal_scheduled_write ||= {} # internal outbound data @outbound_data = [] @connections ||= {} @listen_sockets ||= {} @binding = 0 @on_next_tick = nil # @timer_hash = Packet::TimerStore @timer_hash ||= {} # @thread_pool = ThreadPool.new(thread_pool_size || 20) @windows_flag = windows? @reactor = self end def windows? return true if RUBY_PLATFORM =~ /win32/i return false end def unix? !@windows_flag end def check_for_timer_events # @timer_hash.delete_if do |key,timer| # if timer.cancel_flag # true # elsif timer.run_now? # timer.run # (timer.respond_to?(:interval)) ? false : true # else # false # end # end ready_timers = @timer_hash.collect { |key,timer| timer if timer.run_now? }.compact ready_timers.each { |timer| timer.run } @timer_hash.delete_if { |key,timer| timer.cancel_flag || (!timer.respond_to?(:interval) && ready_timers.include?(timer)) || false } end # close the connection with internal specified socket def close_connection(sock = nil) begin read_ios.delete(sock.fileno) write_ios.delete(sock.fileno) sock.close rescue; end end def initialize_handler(p_module) return p_module if(!p_module.is_a?(Class) and !p_module.is_a?(Module)) handler = if(p_module and p_module.is_a?(Class)) p_module and p_module.send(:include,Connection) else Class.new { include Connection; include p_module; } end return handler.new end def decorate_handler(t_socket,actually_connected,sock_addr,t_module,&block) handler_instance = initialize_handler(t_module) after_connection_callbacks = connection_callbacks ? connection_callbacks[:after_connection] : nil after_connection_callbacks && after_connection_callbacks.each { |t_callback| self.send(t_callback,handler_instance,t_socket)} handler_instance.worker = self handler_instance.connection = t_socket handler_instance.reactor = self handler_instance.invoke_init unless handler_instance.initialized unless actually_connected handler_instance.unbind remove_connection(t_socket) return end handler_instance.signature = binding_str # FIXME: An Struct is more fashionable, but will have some performance hit, can use a simple hash here # klass = Struct.new(:socket,:instance,:signature,:sock_addr) connection_data = { :socket => t_socket,:instance => handler_instance,:signature => binding_str,:sock_addr => sock_addr } connections[t_socket.fileno] = connection_data # connections[t_socket.fileno] = klass.new(t_socket,handler_instance,handler_instance.signature,sock_addr) block.call(handler_instance) if block handler_instance.connection_completed #if handler_instance.respond_to?(:connection_completed) handler_instance end end # end of module#CommonMethods end #end of module#Core end #end of module#Packet packet-0.1.15/lib/packet/packet_mongrel.rb0000644000175000017500000001472111634166337017436 0ustar gwolfgwolf# This module rewrites pieces of the very good Mongrel web server in # order to change it from a threaded application to an event based # application running inside an Packet event loop. It should # be compatible with the existing Mongrel handlers for Rails, # Camping, Nitro, etc.... PACKET_APP = File.expand_path(File.join(File.dirname(__FILE__) + "/..")) ["bin","config","parser","worker","framework","lib","pimp"].each { |x| $LOAD_PATH.unshift(PACKET_APP + "/#{x}")} require "rubygems" require "packet" require 'mongrel' module Mongrel class MongrelProtocol def post_init @parser = HttpParser.new @params = HttpParams.new @nparsed = 0 @request = nil @request_len = nil @linebuffer = '' end def receive_data data @linebuffer << data @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished? if @parser.finished? if @request_len.nil? @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH]) if handlers @params[::Mongrel::Const::PATH_INFO] = path_info @params[::Mongrel::Const::SCRIPT_NAME] = script_name @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] #|| ::Socket.unpack_sockaddr_in(get_peername)[1] @notifiers = handlers.select { |h| h.request_notify } end if @request_len > ::Mongrel::Const::MAX_BODY new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE) new_buffer.binmode new_buffer << @linebuffer[@nparsed..-1] @linebuffer = new_buffer else @linebuffer = StringIO.new(@linebuffer[@nparsed..-1]) @linebuffer.pos = @linebuffer.length end end if @linebuffer.length >= @request_len @linebuffer.rewind ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self) @linebuffer.delete if Tempfile === @linebuffer end elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER close_connection raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.") end rescue ::Mongrel::HttpParserError if $mongrel_debug_client STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!" STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" end close_connection rescue Exception => e close_connection raise e end def write data send_data data end def closed? false end end class HttpServer def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later. @socket = nil @classifier = URIClassifier.new @host = host @port = port @workers = ThreadGroup.new if y @throttle = x @timeout = y || 60 else @timeout = x end @num_processors = num_processors #num_processors is pointless for evented.... @death_time = 60 self.class.const_set(:Instance,self) end def run trap('INT') { raise StopServer } trap('TERM') { raise StopServer } @acceptor = Thread.new do Packet::Reactor.run do |t_reactor| begin t_reactor.start_server(@host,@port,MongrelProtocol) rescue StopServer t_reactor.start_server end end end end def process_http_request(params,linebuffer,client) if not params[Const::REQUEST_PATH] uri = URI.parse(params[Const::REQUEST_URI]) params[Const::REQUEST_PATH] = uri.request_uri end raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) if handlers notifiers = handlers.select { |h| h.request_notify } request = HttpRequest.new(params, linebuffer, notifiers) # request is good so far, continue processing the response response = HttpResponse.new(client) # Process each handler in registered order until we run out or one finalizes the response. dispatch_to_handlers(handlers,request,response) # And finally, if nobody closed the response off, we finalize it. unless response.done response.finished else response.close_connection end else # Didn't find it, return a stock 404 response. client.send_data(Const::ERROR_404_RESPONSE) client.close_connection end end def dispatch_to_handlers(handlers,request,response) handlers.each do |handler| handler.process(request, response) break if response.done end end end class HttpRequest def initialize(params, linebuffer, dispatchers) @params = params @dispatchers = dispatchers @body = linebuffer end end class HttpResponse def send_file(path, small_file = false) File.open(path, "rb") do |f| while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0 begin write(chunk) rescue Object => exc break end end end @body_sent = true end def write(data) @socket.send_data data end def close_connection_after_writing @socket.close_connection end def socket_error(details) @socket.close_connection done = true raise details end def finished send_status send_header send_body @socket.close_connection end end class Configurator # This version fixes a bug in the regular Mongrel version by adding # initialization of groups. def change_privilege(user, group) if user and group log "Initializing groups for {#user}:{#group}." Process.initgroups(user,Etc.getgrnam(group).gid) end if group log "Changing group to #{group}." Process::GID.change_privilege(Etc.getgrnam(group).gid) end if user log "Changing user to #{user}." Process::UID.change_privilege(Etc.getpwnam(user).uid) end rescue Errno::EPERM log "FAILED to change user:group #{user}:#{group}: #$!" exit 1 end end end packet-0.1.15/lib/packet/double_keyed_hash.rb0000644000175000017500000000134311634166337020076 0ustar gwolfgwolfmodule Packet class DoubleKeyedHash # include Enumerable attr_accessor :internal_hash def initialize @keys1 = {} @internal_hash = {} end def []=(key1,key2,value) @keys1[key2] = key1 @internal_hash[key1] = value end def [] key @internal_hash[key] || @internal_hash[@keys1[key]] end def delete(key) t_key = @keys1[key] if t_key @keys1.delete(key) @internal_hash.delete(t_key) else @keys1.delete_if { |key,value| value == key } @internal_hash.delete(key) end end def length @internal_hash.keys.length end def each @internal_hash.each { |key,value| yield(key,value)} end end end packet-0.1.15/lib/packet/packet_event.rb0000644000175000017500000000074011634166337017110 0ustar gwolfgwolfmodule Packet class Event attr_accessor :timer_signature, :block, :cancel_flag, :scheduled_time def initialize(elapsed_time,&block) @cancel_flag = false @timer_signature = Guid.hexdigest @block = block @scheduled_time = Time.now + elapsed_time end def run_now? return true if @scheduled_time <= Time.now return false end def cancel @cancel_flag = true end def run @block.call end end end packet-0.1.15/lib/packet/packet_callback.rb0000644000175000017500000000047011634166337017523 0ustar gwolfgwolf# class implements a simple callback mechanism for invoking callbacks module Packet class Callback attr_accessor :signature,:stored_proc def initialize(&block) @signature = Guid.hexdigest @stored_proc = block end def invoke(*args) @stored_proc.call(*args) end end end packet-0.1.15/lib/packet/packet_meta_pimp.rb0000644000175000017500000000461211634166337017744 0ustar gwolfgwolf# Class acts as a pimp for workers, which doesn't have a manually created pimp # The idea behind a manually added pimp is to let client handle low level messaging # beween workers. A meta pimp, does it for you. class Packet::MetaPimp < Packet::Pimp # initializer of pimp attr_accessor :callback_hash attr_accessor :worker_status, :worker_key,:worker_name attr_accessor :invokable_worker_methods def pimp_init @callback_hash ||= {} @worker_status = nil @worker_result = nil @worker_key = nil @tokenizer = Packet::BinParser.new end # will be invoked whenever there is a response from the worker def receive_data p_data @tokenizer.extract(p_data) do |b_data| t_data = Marshal.load(b_data) handle_object(t_data) end end def handle_object data_options = {} case data_options[:type] when :request process_request(data_options) when :response process_response(data_options) when :status save_worker_status(data_options) when :result save_worker_result(data_options) end end def save_worker_result(data_options = { }) @worker_result = data_options[:data] end def save_worker_status(data_options = { }) # @worker_status = data_options[:data] reactor.update_result(worker_key,data_options[:data]) end def process_request(data_options = {}) if((requested_worker = data_options[:requested_worker]) && (reactor.live_workers[requested_worker])) reactor.live_workers[requested_worker].send_request(data_options) end end def process_response(data_options = {}) if callback_signature = data_options[:callback_signature] callback = callback_hash[callback_signature] # there coule be bug when you are trying to send the data back to the client begin callback.invoke(data_options) rescue end elsif client_signature = data_options[:client_signature] begin reactor.connections[client_signature][:instance].worker_receive(data_options) rescue end end end # can be used to send request to correspoding worker def send_request(data_options = { }) if callback = data_options[:callback] callback_hash[callback.signature] = callback data_options.delete(:callback) data_options[:callback_signature] = callback.signature send_data(data_options) else send_data(data_options) end end end packet-0.1.15/lib/packet_mongrel.rb0000644000175000017500000001366511634166337016175 0ustar gwolfgwolfrequire "packet" require 'mongrel' module Mongrel class MongrelProtocol def post_init @parser = HttpParser.new @params = HttpParams.new @nparsed = 0 @request = nil @request_len = nil @linebuffer = '' end def receive_data data @linebuffer << data @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished? if @parser.finished? if @request_len.nil? @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH]) if handlers @params[::Mongrel::Const::PATH_INFO] = path_info @params[::Mongrel::Const::SCRIPT_NAME] = script_name @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] #|| ::Socket.unpack_sockaddr_in(get_peername)[1] @notifiers = handlers.select { |h| h.request_notify } end if @request_len > ::Mongrel::Const::MAX_BODY new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE) new_buffer.binmode new_buffer << @linebuffer[@nparsed..-1] @linebuffer = new_buffer else @linebuffer = StringIO.new(@linebuffer[@nparsed..-1]) @linebuffer.pos = @linebuffer.length end end if @linebuffer.length >= @request_len @linebuffer.rewind ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self) @linebuffer.delete if Tempfile === @linebuffer end elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER close_connection raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.") end rescue ::Mongrel::HttpParserError if $mongrel_debug_client STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!" STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" end close_connection rescue Exception => e close_connection raise e end def write data send_data data end def closed? false end end class HttpServer def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later. @socket = nil @classifier = URIClassifier.new @host = host @port = port @workers = ThreadGroup.new if y @throttle = x @timeout = y || 60 else @timeout = x end @num_processors = num_processors #num_processors is pointless for evented.... @death_time = 60 self.class.const_set(:Instance,self) end def run trap('INT') { raise StopServer } trap('TERM') { raise StopServer } @acceptor = Thread.new do Packet::Reactor.run do |t_reactor| begin t_reactor.start_server(@host,@port,MongrelProtocol) rescue StopServer t_reactor.start_server end end end end def process_http_request(params,linebuffer,client) if not params[Const::REQUEST_PATH] uri = URI.parse(params[Const::REQUEST_URI]) params[Const::REQUEST_PATH] = uri.request_uri end raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) if handlers notifiers = handlers.select { |h| h.request_notify } request = HttpRequest.new(params, linebuffer, notifiers) # request is good so far, continue processing the response response = HttpResponse.new(client) # Process each handler in registered order until we run out or one finalizes the response. dispatch_to_handlers(handlers,request,response) # And finally, if nobody closed the response off, we finalize it. unless response.done response.finished end else # Didn't find it, return a stock 404 response. client.send_data(Const::ERROR_404_RESPONSE) client.close_connection end end def dispatch_to_handlers(handlers,request,response) handlers.each do |handler| handler.process(request, response) break if response.done end end end class HttpRequest def initialize(params, linebuffer, dispatchers) @params = params @dispatchers = dispatchers @body = linebuffer end end class HttpResponse def send_file(path, small_file = false) File.open(path, "rb") do |f| while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0 begin write(chunk) rescue Object => exc break end end end @body_sent = true end def write(data) @socket.send_data data end def close_connection_after_writing @socket.close_connection end def socket_error(details) @socket.close_connection done = true raise details end def finished send_status send_header send_body @socket.close_connection end end class Configurator # This version fixes a bug in the regular Mongrel version by adding # initialization of groups. def change_privilege(user, group) if user and group log "Initializing groups for {#user}:{#group}." Process.initgroups(user,Etc.getgrnam(group).gid) end if group log "Changing group to #{group}." Process::GID.change_privilege(Etc.getgrnam(group).gid) end if user log "Changing user to #{user}." Process::UID.change_privilege(Etc.getpwnam(user).uid) end rescue Errno::EPERM log "FAILED to change user:group #{user}:#{group}: #$!" exit 1 end end end packet-0.1.15/lib/packet.rb0000644000175000017500000000156711634166337014450 0ustar gwolfgwolf$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) require "socket" require "yaml" require "forwardable" require "ostruct" require "thread" require "fcntl" #require "enumerable" require "packet/packet_parser" require "packet/packet_invalid_worker" require "packet/packet_guid" require "packet/packet_helper" require "packet/double_keyed_hash" require "packet/packet_event" require "packet/packet_periodic_event" require "packet/disconnect_error" require "packet/packet_callback" require "packet/packet_nbio" require "packet/packet_pimp" require "packet/packet_meta_pimp" require "packet/packet_core" require "packet/packet_master" require "packet/packet_connection" require "packet/packet_worker" PACKET_APP = File.expand_path'../' unless defined?(PACKET_APP) module Packet VERSION='0.1.15' end packet-0.1.15/lib/packet.rbc0000644000175000017500000000531111634166337014602 0ustar gwolfgwolf!RBIX 0 x M 1 n n x 10 __script__ i 339 45 26 0 6 1 36 2 1 23 3 6 4 48 36 5 1 36 6 1 15 10 47 16 45 26 0 6 1 36 7 1 23 3 23 3 6 4 48 36 8 1 36 9 1 36 10 1 10 70 45 26 0 6 1 36 11 1 23 3 6 4 48 36 12 1 36 13 1 8 71 1 16 7 6 14 48 57 1 36 15 1 16 7 6 16 48 57 1 36 17 1 16 7 6 18 48 57 1 36 19 1 16 7 6 20 48 57 1 36 21 1 16 7 6 22 48 57 1 36 23 1 16 7 6 24 48 57 1 36 25 1 16 7 6 26 48 57 1 36 27 1 16 7 6 28 48 57 1 36 29 1 16 7 6 30 48 57 1 36 31 1 16 7 6 32 48 57 1 36 33 1 16 7 6 34 48 57 1 36 35 1 16 7 6 36 48 57 1 36 37 1 16 7 6 38 48 57 1 36 39 1 16 7 6 40 48 57 1 36 41 1 16 7 6 42 48 57 1 36 43 1 16 7 6 44 48 57 1 36 45 1 16 7 6 46 48 57 1 36 47 1 16 7 6 48 48 57 1 36 49 1 16 7 6 50 48 57 1 36 51 1 16 7 6 52 48 57 1 36 53 1 16 7 6 54 48 57 1 36 55 1 16 7 6 56 48 57 1 36 57 1 16 5 6 58 36 59 1 10 303 1 8 305 6 60 10 323 5 6 58 23 3 6 61 48 36 62 1 36 63 2 8 324 1 16 31 64 15 6 65 14 27 66 16 35 67 16 2 12 I 6 I 0 I 0 I 0 n p 68 x 7 Globals x 2 $: S 2 [] x 4 File s 33 /home/hemant/packet/lib/packet.rb S 7 dirname S 8 include? S 2 [] S 7 dirname S 11 expand_path S 8 include? S 2 [] S 7 dirname S 7 unshift s 6 socket S 7 require s 4 yaml S 7 require s 11 forwardable S 7 require s 7 ostruct S 7 require s 6 thread S 7 require s 5 fcntl S 7 require s 20 packet/packet_parser S 7 require s 28 packet/packet_invalid_worker S 7 require s 18 packet/packet_guid S 7 require s 20 packet/packet_helper S 7 require s 24 packet/double_keyed_hash S 7 require s 19 packet/packet_event S 7 require s 28 packet/packet_periodic_event S 7 require s 23 packet/disconnect_error S 7 require s 22 packet/packet_callback S 7 require s 18 packet/packet_nbio S 7 require s 18 packet/packet_pimp S 7 require s 23 packet/packet_meta_pimp S 7 require s 18 packet/packet_core S 7 require s 20 packet/packet_master S 7 require s 24 packet/packet_connection S 7 require s 20 packet/packet_worker S 7 require x 10 PACKET_APP S 14 const_defined? s 8 constant s 3 ../ S 11 expand_path S 13 __const_set__ x 6 Packet M 1 n n x 6 Packet i 12 7 83 5 6 0 6 1 48 36 2 2 12 I 3 I 0 I 0 I 0 n p 3 x 7 VERSION s 6 0.1.14 S 13 __const_set__ p 0 p 1 p 3 I 0 I 12 I 29 x 33 /home/hemant/packet/lib/packet.rb n x 15 __module_init__ S 15 __module_init__ p 0 p 24 p 3 I 0 I 48 I 2 p 3 I 49 I 71 I 1 p 3 I 72 I 81 I 5 p 3 I 82 I 91 I 6 p 3 I 92 I 101 I 7 p 3 I 102 I 111 I 8 p 3 I 112 I 121 I 9 p 3 I 122 I 131 I 10 p 3 I 132 I 141 I 13 p 3 I 142 I 151 I 14 p 3 I 152 I 161 I 15 p 3 I 162 I 171 I 16 p 3 I 172 I 181 I 17 p 3 I 182 I 191 I 18 p 3 I 192 I 201 I 19 p 3 I 202 I 211 I 20 p 3 I 212 I 221 I 21 p 3 I 222 I 231 I 22 p 3 I 232 I 241 I 23 p 3 I 242 I 251 I 24 p 3 I 252 I 261 I 25 p 3 I 262 I 271 I 26 p 3 I 272 I 281 I 27 p 3 I 282 I 339 I 29 x 33 /home/hemant/packet/lib/packet.rb n packet-0.1.15/TODO0000644000175000017500000000054611634166337012572 0ustar gwolfgwolf* Overview * ** TODO Implement ability to have a pool of workers. ** TODO Remove class and class instance attributes from core classes. ** TODO Cleanup callback mechanism ** TODO Implement a sample worker, which provides capabilities to execute mysql statements async. ** TODO Implement more cleaner write thingies. ** TODO Implement more cleaner timers. packet-0.1.15/MIT-LICENSE0000644000175000017500000000204411634166337013531 0ustar gwolfgwolfCopyright (c) 2004-2007 Hemant Kumar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.packet-0.1.15/Rakefile0000644000175000017500000000351011634166337013541 0ustar gwolfgwolfrequire 'rubygems' require 'rake' require 'rake/clean' require 'rake/gempackagetask' require 'rake/rdoctask' require 'rake/testtask' require 'spec/rake/spectask' require 'fileutils' def __DIR__ File.dirname(__FILE__) end include FileUtils NAME = "packet" $LOAD_PATH.unshift __DIR__+'/lib' require 'packet' CLEAN.include ['**/.*.sw?', '*.gem', '.config','*.rbc'] Dir["tasks/**/*.rake"].each { |rake| load rake } @windows = (PLATFORM =~ /win32/) SUDO = @windows ? "" : (ENV["SUDO_COMMAND"] || "sudo") desc "Packages up Packet." task :default => [:package] task :doc => [:rdoc] spec = Gem::Specification.new do |s| s.name = NAME s.version = Packet::VERSION s.platform = Gem::Platform::RUBY s.has_rdoc = true s.extra_rdoc_files = ["README", "MIT-LICENSE", 'TODO'] #s.rdoc_options += RDOC_OPTS + # ['--exclude', '^(app|uploads)'] s.summary = "Packet, A Pure Ruby library for Event Driven Network Programming." s.description = s.summary s.author = "Hemant Kumar" s.email = 'mail@gnufied.org' s.homepage = 'http://code.google.com/p/packet/' s.required_ruby_version = '>= 1.8.5' s.files = %w(MIT-LICENSE README Rakefile TODO) + Dir.glob("{spec,lib,examples}/**/*") s.require_path = "lib" s.bindir = "bin" s.executables = "packet_worker_runner" end Rake::GemPackageTask.new(spec) do |p| #p.need_tar = true p.gem_spec = spec end task :install do sh %{rake package} sh %{#{SUDO} gem install pkg/#{NAME}-#{Packet::VERSION} --no-rdoc --no-ri} end task :uninstall => [:clean] do sh %{#{SUDO} gem uninstall #{NAME}} end desc "Converts a YAML file into a test/spec skeleton" task :yaml_to_spec do require 'yaml' puts YAML.load_file(ENV['FILE']||!puts("Pass in FILE argument.")&&exit).inject(''){|t,(c,s)| t+(s ?%.context "#{c}" do.+s.map{|d|%.\n xspecify "#{d}" do\n end\n.}*''+"end\n\n":'') }.strip end packet-0.1.15/README0000644000175000017500000001704611634166337012765 0ustar gwolfgwolfPacket is a pure ruby library for writing network applications in Ruby. It follows Evented Model of network programming and implements almost all the features provided by EventMachine. It also provides real easy to user UNIX workers for concurrent programming. Its best to have some examples going: == Examples === A Simple Echo Server: require "rubygems" require "packet" class Foo def receive_data p_data send_data(p_data) end def post_init puts "Client connected" end def connection_completed puts "Whoa man" end def unbind puts "Client Disconnected" end end Packet::Reactor.run do |t_reactor| t_reactor.start_server("localhost",11006,Foo) end Those new to network programming with events and callbacks, will note that, each time a new client connects an instance of class Foo is instantiated. When client writes some data to the socket, receive_data method is invoked. Although Packet implements an API similar to EventMachine, but it differs slightly because of the fact that, for a packet app, there can be more than one reactor loop running and hence, we don't use Packet.start_server(...). === A Simple Http Client class WikiHandler def receive_data p_data p p_data end def post_init end def unbind end def connection_completed send_data("GET / \r\n") end end Packet::Reactor.run do |t_reactor| t_reactor.connect("en.wikipedia.org",80,WikiHandler) end === Using Workers Packet enables you to write simple workers, which will run in different process and gives you nice evented handle for concurrent execution of various tasks. When, you are writing a scalable networking application using Event Model of network programming, sometimes when processing of certain events take time, your event loop is stuck there. With green threads, you don't really have a way of paralleling your request processing. Packet library, allows you to write simple workers, for executing long running tasks. You can pass data and callbacks as an argument. When you are going to use workers in your application, you need to define constant WORKER_ROOT, which is the directory location, where your workers are located. All the workers defined in that directory will be automatically, picked and forked in a new process when your packet app starts. So, a typical packet_app, that wants to use workers, will look like this: packet_app_root | |__ lib | |___ worker | |___ config | |___ log You would define WORKER_ROOT = PACKET_APP_ROOT/worker All the workers must inherit class Packet::Worker, and hence a general skeleton of worker will look like: class FooWorker < Packet::Worker set_worker_name :foo_worker #=> This is necessary. def receive_data p_data end def connection_completed end def unbind end def post_init end end All the forked workers are connected to master via UNIX sockets, and hence messages passed to workers from master will be available in receive_data method. Also, when you are passing messages to workers, or worker is passing message to master ( in a nutshell, all the internal communication between workers and master ) directly takes place using ruby objects. All the passed ruby objects are dumped and marshalled across unix sockets in a non blocking manner. BinParser class parses dumped binary objects and makes sure, packets received at other end are complete. Usually, you wouldn't need to worry about this little detail. Packet provides various ways of interacting with workers. Usually, when a worker is instantiated, a proxy for that worker will also be instantiated at master process. Packet automatically provides a worker proxy(See meta_pimp.rb) for you, but if you need to multiplex/demultiplex requests based on certain criteria, you may as well define your own worker proxies. Code, would like something like this: class FooWorker < Packet::Worker set_worker_proxy :foo_handler end When you define, :foo_handler as a proxy for this worker, packet is gonna search for FooHandler class and instantiate it when the worker gets started. All the worker proxies must inherit from Packet::Pimp. Have a look at, Packet::MetaPimp, which acts as a meta pimp for all the workers, which don't have a explicit worker proxy defined. === A complete Case : Just for kicks, lets write a sample server, which evals whatever clients send to it. But, assuming this 'eval' of client data can be potentially time/cpu consuming ( not to mention dangerous too ), we are gonna ask our eval_worker, to perform eval and return the result to master process, which in turn returns the result to happy client. # APP_ROOT/bin/eval_server.rb EVAL_APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__) + "/..")) ["bin","worker","lib"].each { |x| $LOAD_PATH.unshift(EVAL_APP_ROOT + "/#{x}")} WORKER_ROOT = EVAL_APP_ROOT + "/worker" require "packet" class EvalServer def receive_data p_data ask_worker(:eval_worker,:data => p_data, :type => :request) end # will be called, when any worker sends data back to master process # it should be noted that, you may have several instances of eval_server in # your master, for each connected client, but worker_receive will be always # be invoked for the instance, which originally made the request. # If you need fine control, over this behaviour, you can implement a worker proxy # on the lines of meta_pimp class. This API will change in future perhaps, as i # expect, better ideas to come. def worker_receive p_data send_data "#{p_data[:data]}\n" end def show_result p_data send_data("#{p_data[:response]}\n") end def connection_completed end def post_init end def wow puts "Wow" end end Packet::Reactor.run do |t_reactor| t_reactor.start_server("localhost", 11006,EvalServer) do |instance| instance.wow end end # APP_ROOT/worker/eval_worker.rb class EvalWorker < Packet::Worker set_worker_name :eval_worker def worker_init p "Starting no proxy worker" end def receive_data data_obj eval_data = eval(data_obj[:data]) data_obj[:data] = eval_data data_obj[:type] = :response send_data(data_obj) end end === Disable auto loading of certain workers: Sometimes, you would need to start a worker at runtime and don't want this pre-forking mechanism. Packet, allows this. You just need to define "set_no_auto_load true" in your worker class and worker will not be automatically forked. Although name is a bit misleading perhaps. Now, at runtime, you can call start_worker(:foo_worker, options) to start a worker as usual. It should be noted that, forking a worker, which is already forked can be disastrous, since worker names are being used as unique keys that represent a worker.Test == Performance: Although written in pure ruby, packet performs reasonably well. Mongrel, running on top of Packet is a tad slower than Mongrel running on top of EventMachine. More benchmarks coming soon. == SVN repo: Code for packet is on google code, svn repo is: http://packet.googlecode.com/svn/trunk/ == Credits Francis for awesome EventMachine lib, which has constantly acted as an inspiration. Ezra, for being a early user and porting mongrel to run on top of packet packet-0.1.15/examples/0000755000175000017500000000000011634166337013713 5ustar gwolfgwolfpacket-0.1.15/examples/sample_server.rb0000644000175000017500000000037411634166337017113 0ustar gwolfgwolfrequire "asteroid" module Server def receive_data data end def post_init puts "A new client connected" end def unbind puts "client disconnected" end end Asteroid::run("0.0.0.0", 11007, Server) do puts "Someone connected" end packet-0.1.15/examples/write_bulk.rb0000644000175000017500000000167611634166337016421 0ustar gwolfgwolfrequire "socket" require "thread" # sock = TCPSocket.open("localhost",11007) #data = File.open("netbeans.jpg").read data = File.open("nginx.dat").read # p data.length threads = [] 500.times do # sock.write(data) # select([sock],nil,nil,nil) # read_data = "" # loop do # begin # while(read_data << sock.read_nonblock(1023)); end # rescue Errno::EAGAIN # break # rescue # break # end # end threads << Thread.new do sock = TCPSocket.open("localhost",11007) # p read_data.length written_length = sock.write(data) p "Write Length: #{written_length}" read_length = sock.read(written_length) p "Read length: #{read_length.length}" end # # p read_data.length # written_length = sock.write(data) # #p "Write Length: #{written_length}" # read_length = sock.read(written_length) # #p "Read length: #{read_length.length}" end threads.each { |x| x.join } packet-0.1.15/examples/use_stuff.rb0000644000175000017500000000007511634166337016245 0ustar gwolfgwolfrequire "concurrent_thread" a = ConcurrentThread.new a.defer packet-0.1.15/examples/extconf.rb0000644000175000017500000000026011634166337015704 0ustar gwolfgwolfrequire 'mkmf' unless have_header("sys/epoll.h") || have_header("sys/event.h") message "epoll or kqueue required.\n" exit 1 end create_header create_makefile 'asteroid' packet-0.1.15/examples/persistent_print.rb0000644000175000017500000000072411634166337017657 0ustar gwolfgwolfrequire "rubygems" require "eventmachine" module Client def receive_data data @tokenizer.extract(data).each do |b_data| client_data = b_data p "Client has sent #{client_data}" end end def start_push_on_client send_data("Hello World : #{Time.now}\n") end def post_init @tokenizer = BufferedTokenizer.new EM::add_periodic_timer(1) { start_push_on_client } end end EM.run do EM.start_server("localhost",11009,Client) end packet-0.1.15/examples/extconf.h0000644000175000017500000000010611634166337015527 0ustar gwolfgwolf#ifndef EXTCONF_H #define EXTCONF_H #define HAVE_SYS_EPOLL_H 1 #endif packet-0.1.15/examples/concurrent_thread.c0000644000175000017500000000247511634166337017600 0ustar gwolfgwolf#include #include #include #include #include #include #include #include #include #include static VALUE ConcurrentThread; static VALUE ConcurrentThreadPool; static void count_to_10000(void* data){ int i = 0; for( i = 0; i < 10000; i++ ) { if ((*(VALUE *)data) == Qtrue) break; printf("Currently Counting from thr 1: %d\n",i); } } static void count_to_20000(void* data){ int i = 0; for( i = 0; i < 10000; i++ ) { if ((*(VALUE *)data) == Qtrue) break; printf("Currently Counting from thr 2: %d\n",i); } } static void stop_thr1(void* data) { printf("Calling thread1 break method\n"); *((VALUE *)data) = Qtrue; } static void stop_thr2(void* data) { printf("Calling thread2 break method\n"); *((VALUE *)data) = Qtrue; } static VALUE rb_concurrent_thread_method(VALUE self) { VALUE interrupt_flag; VALUE interrupt_flag2; rb_thread_blocking_region(count_to_10000,&interrupt_flag,stop_thr1,&interrupt_flag); rb_thread_blocking_region(count_to_20000,&interrupt_flag2,stop_thr2,&interrupt_flag2); } void Init_concurrent_thread(){ ConcurrentThread = rb_define_class("ConcurrentThread",rb_cObject); rb_define_method(ConcurrentThread,"defer",rb_concurrent_thread_method,0); } packet-0.1.15/examples/asteroid.c0000644000175000017500000002067711634166337015705 0ustar gwolfgwolf/* -------------------------------------------------------------------------- * Asteroid ruby extension. * by Genki Takiuchi * -------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include "extconf.h" #include "asteroid.h" /* -------------------------------------------------------------------------- * epoll / kqueue * - 2007/03/09 *BSD kqueue port, Mac OS X MSG_NOSIGNAL missing. * (Takanori Ishikawa) * -------------------------------------------------------------------------- */ #ifdef HAVE_SYS_EVENT_H #include typedef int asteroid_pollfd_t; typedef struct kevent asteroid_poll_event_t; #endif #ifdef HAVE_SYS_EPOLL_H #include typedef int asteroid_pollfd_t; typedef struct epoll_event asteroid_poll_event_t; #endif static asteroid_pollfd_t asteroid_poll_create(int sizeHint) { #ifdef HAVE_SYS_EVENT_H return kqueue(); #endif #ifdef HAVE_SYS_EPOLL_H return epoll_create(sizeHint); #endif } static int asteroid_poll_add( asteroid_pollfd_t pollfd, asteroid_poll_event_t *event, int fd) { #ifdef HAVE_SYS_EVENT_H EV_SET(event, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); return kevent(pollfd, event, 1, NULL, 0, NULL); #endif #ifdef HAVE_SYS_EPOLL_H event->events = (EPOLLIN | EPOLLPRI); event->data.fd = fd; return epoll_ctl(pollfd, EPOLL_CTL_ADD, fd, event); #endif } static int asteroid_poll_remove( asteroid_pollfd_t pollfd, asteroid_poll_event_t *event, int fd) { #ifdef HAVE_SYS_EVENT_H EV_SET(event, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); return kevent(pollfd, event, 1, NULL, 0, NULL); #endif #ifdef HAVE_SYS_EPOLL_H return epoll_ctl(pollfd, EPOLL_CTL_DEL, fd, event); #endif } static int asteroid_poll_wait( asteroid_pollfd_t pollfd, asteroid_poll_event_t *events, int maxevents, int timeout) { #ifdef HAVE_SYS_EVENT_H struct timespec tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) (timeout/1000); tv.tv_nsec = (long) (timeout%1000)*1000; tvptr = &tv; } return kevent(pollfd, NULL, 0, events, maxevents, tvptr); #endif #ifdef HAVE_SYS_EPOLL_H return epoll_wait(pollfd, events, maxevents, timeout); #endif } #ifdef HAVE_SYS_EVENT_H #define AST_POLL_EVENT_SOCK(event) ((event)->ident) #define AST_POLL_EVENT_CAN_READ(event) ((event)->filter == EVFILT_READ) #endif #ifdef HAVE_SYS_EPOLL_H #define AST_POLL_EVENT_SOCK(event) ((event)->data.fd) #define AST_POLL_EVENT_CAN_READ(event) ((event)->events & (EPOLLIN|EPOLLPRI)) #endif #ifdef SO_NOSIGPIPE #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif /* * The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when * sending data to a dead peer (instead of relying on the 4th argument to send * being MSG_NOSIGNAL). Possibly also existing and in use on other BSD * systems? * * curl-7.15.5/lib/connect.c */ static void nosigpipe(int sockfd) { int one = 1; setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one)); } #else #define nosigpipe(x) #endif #define MAX_CONNECTION (102400) #define EVENT_BUF_SIZE (1024) static VALUE Asteroid; static VALUE clients; static volatile int running = 0; static asteroid_pollfd_t epoll_fd = 0; static asteroid_poll_event_t events[EVENT_BUF_SIZE]; int dispatch(); void runtime_error(); void Init_asteroid(){ Asteroid = rb_define_module("Asteroid"); rb_define_singleton_method(Asteroid, "run", asteroid_s_run, 3); rb_define_singleton_method(Asteroid, "stop", asteroid_s_stop, 0); rb_define_singleton_method(Asteroid, "now", asteroid_s_now, 0); rb_define_class_variable(Asteroid, "@@clients", clients = rb_hash_new()); } static VALUE close_socket_proc(VALUE Pair, VALUE Arg, VALUE Self) { close(FIX2INT(RARRAY(Pair)->ptr[0])); return Qnil; } static VALUE asteroid_s_run(VALUE Self, VALUE Host, VALUE Port, VALUE Module){ char *host = StringValuePtr(Host); int port = FIX2INT(Port); epoll_fd = asteroid_poll_create(1024); if(epoll_fd == -1) runtime_error(); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(host); int s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP), c, one = 1; if(s == -1) runtime_error(); fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); nosigpipe(s); if(bind(s, (struct sockaddr*)&addr, sizeof(addr)) != 0) runtime_error(); if(listen(s, MAX_CONNECTION) != 0) runtime_error(); if(rb_block_given_p()) rb_yield(Qnil); VALUE Class = rb_define_class_under(Asteroid, "Server", rb_cObject); rb_define_method(Class, "send_data", asteroid_server_send_data, 1); rb_define_method(Class, "write_and_close", asteroid_server_write_and_close, 0); rb_include_module(Class, Module); // Mac OS X, Fedora needs explicit rb_thread_schedule call. for(running = 1; running; rb_thread_schedule()){ socklen_t len = sizeof(addr); while((c = accept(s, (struct sockaddr*)&addr, &len)) != -1){ printf("A New client connected here\n"); fcntl(c, F_SETFL, fcntl(c, F_GETFL, 0) | O_NONBLOCK); asteroid_poll_event_t event; memset(&event, 0, sizeof(event)); if(asteroid_poll_add(epoll_fd, &event, c) == -1) runtime_error(); // instantiate server class which responds to client. VALUE Server = rb_class_new_instance(0, NULL, Class); rb_iv_set(Server, "@fd", rb_fix_new(c)); rb_hash_aset(clients, rb_fix_new(c), Server); if(rb_respond_to(Server, rb_intern("post_init"))){ rb_funcall(Server, rb_intern("post_init"), 0); } } if(dispatch() != 0) asteroid_s_stop(Asteroid); // You must call them to give a chance for ruby to handle system events. // CHECK_INTS; } rb_iterate(rb_each, clients, close_socket_proc, Qnil); rb_funcall(clients, rb_intern("clear"), 0); close(s); close(epoll_fd); return Qnil; } static VALUE asteroid_s_stop(VALUE Self){ running = 0; return Qnil; } static VALUE asteroid_s_now(VALUE Self){ struct timeval now; gettimeofday(&now, NULL); return rb_float_new(now.tv_sec + now.tv_usec/1000000.0); } static VALUE asteroid_server_send_data(VALUE Self, VALUE Data){ VALUE Fd = rb_iv_get(Self, "@fd"); int fd = FIX2INT(Fd), remain = RSTRING(Data)->as.heap.len, len, trial = 100; char *data = StringValuePtr(Data); while(remain){ len = send(fd, data, remain, MSG_DONTWAIT|MSG_NOSIGNAL); if(len == -1){ if(errno == EAGAIN && --trial){ rb_thread_schedule(); // CHECK_INTS; }else{ if(rb_respond_to(Self, rb_intern("unbind"))){ rb_funcall(Self, rb_intern("unbind"), 0); } return Qnil; } }else{ remain -= len; data += len; } } return Qtrue; } static VALUE asteroid_server_write_and_close(VALUE Self){ VALUE Fd = rb_iv_get(Self, "@fd"); int fd = FIX2INT(Fd); char buf[1]; if(read(fd, buf, 1) == -1 && errno != EAGAIN){ if(rb_respond_to(Self, rb_intern("unbind"))){ rb_funcall(Self, rb_intern("unbind"), 0); } } asteroid_poll_event_t event; memset(&event, 0, sizeof(event)); asteroid_poll_remove(epoll_fd, &event, fd); close(fd); rb_hash_delete(clients, Fd); return Qnil; } int dispatch(){ int i, s, len; while(1){ TRAP_BEG; s = asteroid_poll_wait(epoll_fd, events, EVENT_BUF_SIZE, 1); TRAP_END; if(s <= 0) break; for(i = 0; i < s; ++i){ asteroid_poll_event_t event = events[i]; int fd = AST_POLL_EVENT_SOCK(&event); VALUE Fd = rb_fix_new(fd); VALUE Server = rb_hash_aref(clients, Fd); if(AST_POLL_EVENT_CAN_READ(&event)){ VALUE Buf = rb_str_new("", 0); char buf[1024]; while((len = read(fd, buf, 1023)) > 0){ buf[len] = '\0'; rb_str_concat(Buf, rb_str_new2(buf)); } if(len == -1 && errno == EAGAIN){ if(rb_respond_to(Server, rb_intern("receive_data"))){ rb_funcall(Server, rb_intern("receive_data"), 1, Buf); } }else{ if(rb_respond_to(Server, rb_intern("unbind"))){ rb_funcall(Server, rb_intern("unbind"), 0); } asteroid_poll_remove(epoll_fd, &event, fd); rb_hash_delete(clients, Fd); close(fd); } } } } return 0; } void runtime_error(){ rb_raise(rb_eRuntimeError, strerror(errno)); } packet-0.1.15/examples/asteroid.h0000644000175000017500000000043411634166337015677 0ustar gwolfgwolfstatic VALUE asteroid_s_run(VALUE Self, VALUE Host, VALUE Port, VALUE Module); static VALUE asteroid_s_stop(VALUE Self); static VALUE asteroid_s_now(VALUE Self); static VALUE asteroid_server_send_data(VALUE Self, VALUE Data); static VALUE asteroid_server_write_and_close(VALUE Self); packet-0.1.15/metadata.yml0000644000175000017500000000367511634166337014413 0ustar gwolfgwolf--- !ruby/object:Gem::Specification name: packet version: !ruby/object:Gem::Version version: 0.1.15 platform: ruby authors: - Hemant Kumar autorequire: bindir: bin cert_chain: [] date: 2009-04-07 00:00:00 +05:30 default_executable: dependencies: [] description: Packet, A Pure Ruby library for Event Driven Network Programming. email: mail@gnufied.org executables: - packet_worker_runner extensions: [] extra_rdoc_files: - README - MIT-LICENSE - TODO files: - MIT-LICENSE - README - Rakefile - TODO - lib/packet - lib/packet/disconnect_error.rb - lib/packet/packet_pimp.rb - lib/packet/packet_invalid_worker.rb - lib/packet/packet_connection.rb - lib/packet/packet_guid.rb - lib/packet/double_keyed_hash.rb - lib/packet/packet_master.rb - lib/packet/packet_parser.rb - lib/packet/packet_periodic_event.rb - lib/packet/packet_callback.rb - lib/packet/packet_worker.rb - lib/packet/packet_core.rb - lib/packet/packet_meta_pimp.rb - lib/packet/packet_mongrel.rb - lib/packet/packet_helper.rb - lib/packet/timer_store.rb - lib/packet/packet_event.rb - lib/packet/packet_nbio.rb - lib/packet.rb - lib/packet.rbc - lib/packet_mongrel.rb - examples/concurrent_thread.c - examples/sample_server.rb - examples/write_bulk.rb - examples/asteroid.h - examples/persistent_print.rb - examples/use_stuff.rb - examples/extconf.h - examples/asteroid.c - examples/extconf.rb has_rdoc: true homepage: http://code.google.com/p/packet/ post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.8.5 version: required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: requirements: [] rubyforge_project: rubygems_version: 1.3.1 signing_key: specification_version: 2 summary: Packet, A Pure Ruby library for Event Driven Network Programming. test_files: []