packet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ EVAL_APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__) + "/.."))
2
+ ["bin","worker","lib"].each { |x| $LOAD_PATH.unshift(EVAL_APP_ROOT + "/#{x}")}
3
+ WORKER_ROOT = EVAL_APP_ROOT + "/worker"
4
+
5
+ require "packet"
6
+ class Foo
7
+ def receive_data p_data
8
+ ask_worker(:no_proxy_worker,:data => p_data, :type => :request)
9
+ end
10
+
11
+ def worker_receive p_data
12
+ send_data "#{p_data[:data]}\n"
13
+ end
14
+
15
+ def show_result p_data
16
+ send_data("#{p_data[:response]}\n")
17
+ end
18
+
19
+ def connection_completed
20
+ end
21
+
22
+ def post_init
23
+ end
24
+
25
+ def wow
26
+ puts "Wow"
27
+ end
28
+ end
29
+
30
+ Packet::Reactor.run do |t_reactor|
31
+ t_reactor.start_server("localhost", 11006,Foo) do |instance|
32
+ instance.wow
33
+ end
34
+ end
35
+
@@ -0,0 +1,48 @@
1
+ # Extends the class object with class and instance accessors for class attributes,
2
+ # just like the native attr* accessors for instance attributes.
3
+ class Class # :nodoc:
4
+ def cattr_reader(*syms)
5
+ syms.flatten.each do |sym|
6
+ next if sym.is_a?(Hash)
7
+ class_eval(<<-EOS, __FILE__, __LINE__)
8
+ unless defined? @@#{sym}
9
+ @@#{sym} = nil
10
+ end
11
+
12
+ def self.#{sym}
13
+ @@#{sym}
14
+ end
15
+
16
+ def #{sym}
17
+ @@#{sym}
18
+ end
19
+ EOS
20
+ end
21
+ end
22
+
23
+ def cattr_writer(*syms)
24
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
25
+ syms.flatten.each do |sym|
26
+ class_eval(<<-EOS, __FILE__, __LINE__)
27
+ unless defined? @@#{sym}
28
+ @@#{sym} = nil
29
+ end
30
+
31
+ def self.#{sym}=(obj)
32
+ @@#{sym} = obj
33
+ end
34
+
35
+ #{"
36
+ def #{sym}=(obj)
37
+ @@#{sym} = obj
38
+ end
39
+ " unless options[:instance_writer] == false }
40
+ EOS
41
+ end
42
+ end
43
+
44
+ def cattr_accessor(*syms)
45
+ cattr_reader(*syms)
46
+ cattr_writer(*syms)
47
+ end
48
+ end
@@ -0,0 +1,61 @@
1
+ # FIXME: write this stupid parser in C.
2
+ class BinParser
3
+ def initialize
4
+ @size = 0
5
+ @data = []
6
+ # 0 => reading length
7
+ # 1 => reading actual data
8
+ @parser_state = 0
9
+ @length_string = ""
10
+ @numeric_length = 0
11
+ end
12
+
13
+ # if you look at it, it could be a suicidal function
14
+ def extract new_data
15
+ if @parser_state == 0
16
+ length_to_read = 9 - @length_string.length
17
+ len_str,remaining = new_data.unpack("a#{length_to_read}a*")
18
+ if len_str.length < length_to_read
19
+ @length_string << len_str
20
+ return
21
+ else
22
+ @length_string << len_str
23
+ @numeric_length = @length_string.to_i
24
+ @parser_state = 1
25
+ if remaining.length < @numeric_length
26
+ @data << remaining
27
+ @numeric_length = @numeric_length - remaining.length
28
+ else
29
+ @data << remaining
30
+ yield(@data.join)
31
+ @data = []
32
+ @parser_state = 0
33
+ @length_string = ""
34
+ @numeric_length = 0
35
+ end
36
+ end
37
+ elsif @parser_state == 1
38
+ pack_data,remaining = new_data.unpack("a#{@numeric_length}a*")
39
+ if pack_data.length < @numeric_length
40
+ @data << pack_data
41
+ @numeric_length = @numeric_length - pack_data.length
42
+ elsif pack_data.length == @numeric_length
43
+ @data << pack_data
44
+ yield(@data.join)
45
+ @data = []
46
+ @parser_state = 0
47
+ @length_string = ""
48
+ @numeric_length = 0
49
+ else
50
+ @data << pack_data
51
+ yield(@data.join)
52
+ @data = []
53
+ @parser_state = 0
54
+ @length_string = ""
55
+ @numeric_length = 0
56
+ extract(remaining)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,127 @@
1
+ # BufferedTokenizer - Statefully split input data by a specifiable token
2
+ #
3
+ # Authors:: Tony Arcieri, Martin Emde
4
+ #
5
+ #----------------------------------------------------------------------------
6
+ #
7
+ # Copyright (C) 2006-07 by Tony Arcieri and Martin Emde
8
+ #
9
+ # Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
10
+ #
11
+ #---------------------------------------------------------------------------
12
+ #
13
+
14
+ # (C)2006 Tony Arcieri, Martin Emde
15
+ # Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
16
+
17
+ # BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
18
+ # by default. It allows input to be spoon-fed from some outside source which
19
+ # receives arbitrary length datagrams which may-or-may-not contain the token
20
+ # by which entities are delimited.
21
+
22
+ class BufferedTokenizer
23
+ # New BufferedTokenizers will operate on lines delimited by "\n" by default
24
+ # or allow you to specify any delimiter token you so choose, which will then
25
+ # be used by String#split to tokenize the input data
26
+ def initialize(delimiter = "\n", size_limit = nil)
27
+ # Store the specified delimiter
28
+ @delimiter = delimiter
29
+
30
+ # Store the specified size limitation
31
+ @size_limit = size_limit
32
+
33
+ # The input buffer is stored as an array. This is by far the most efficient
34
+ # approach given language constraints (in C a linked list would be a more
35
+ # appropriate data structure). Segments of input data are stored in a list
36
+ # which is only joined when a token is reached, substantially reducing the
37
+ # number of objects required for the operation.
38
+ @input = []
39
+
40
+ # Size of the input buffer
41
+ @input_size = 0
42
+ end
43
+
44
+ # Extract takes an arbitrary string of input data and returns an array of
45
+ # tokenized entities, provided there were any available to extract. This
46
+ # makes for easy processing of datagrams using a pattern like:
47
+ #
48
+ # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
49
+ def extract(data)
50
+ # Extract token-delimited entities from the input string with the split command.
51
+ # There's a bit of craftiness here with the -1 parameter. Normally split would
52
+ # behave no differently regardless of if the token lies at the very end of the
53
+ # input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
54
+ # return "" in this case, meaning that the last entry in the list represents a
55
+ # new segment of data where the token has not been encountered
56
+ entities = data.split @delimiter, -1
57
+
58
+ # Check to see if the buffer has exceeded capacity, if we're imposing a limit
59
+ if @size_limit
60
+ raise 'input buffer full' if @input_size + entities.first.size > @size_limit
61
+ @input_size += entities.first.size
62
+ end
63
+
64
+ # Move the first entry in the resulting array into the input buffer. It represents
65
+ # the last segment of a token-delimited entity unless it's the only entry in the list.
66
+ @input << entities.shift
67
+
68
+ # If the resulting array from the split is empty, the token was not encountered
69
+ # (not even at the end of the buffer). Since we've encountered no token-delimited
70
+ # entities this go-around, return an empty array.
71
+ return [] if entities.empty?
72
+
73
+ # At this point, we've hit a token, or potentially multiple tokens. Now we can bring
74
+ # together all the data we've buffered from earlier calls without hitting a token,
75
+ # and add it to our list of discovered entities.
76
+ entities.unshift @input.join
77
+
78
+ =begin
79
+ # Note added by FC, 10Jul07. This paragraph contains a regression. It breaks
80
+ # empty tokens. Think of the empty line that delimits an HTTP header. It will have
81
+ # two "\n" delimiters in a row, and this code mishandles the resulting empty token.
82
+ # It someone figures out how to fix the problem, we can re-enable this code branch.
83
+ # Multi-character token support.
84
+ # Split any tokens that were incomplete on the last iteration buf complete now.
85
+ entities.map! do |e|
86
+ e.split @delimiter, -1
87
+ end
88
+ # Flatten the resulting array. This has the side effect of removing the empty
89
+ # entry at the end that was produced by passing -1 to split. Add it again if
90
+ # necessary.
91
+ if (entities[-1] == [])
92
+ entities.flatten! << []
93
+ else
94
+ entities.flatten!
95
+ end
96
+ =end
97
+
98
+ # Now that we've hit a token, joined the input buffer and added it to the entities
99
+ # list, we can go ahead and clear the input buffer. All of the segments that were
100
+ # stored before the join can now be garbage collected.
101
+ @input.clear
102
+
103
+ # The last entity in the list is not token delimited, however, thanks to the -1
104
+ # passed to split. It represents the beginning of a new list of as-yet-untokenized
105
+ # data, so we add it to the start of the list.
106
+ @input << entities.pop
107
+
108
+ # Set the new input buffer size, provided we're keeping track
109
+ @input_size = @input.first.size if @size_limit
110
+
111
+ # Now we're left with the list of extracted token-delimited entities we wanted
112
+ # in the first place. Hooray!
113
+ entities
114
+ end
115
+
116
+ # Flush the contents of the input buffer, i.e. return the input buffer even though
117
+ # a token has not yet been encountered
118
+ def flush
119
+ buffer = @input.join
120
+ @input.clear
121
+ buffer
122
+ end
123
+
124
+ def empty?
125
+ @input.empty?
126
+ end
127
+ end
@@ -0,0 +1,14 @@
1
+ # class implements a simple callback mechanism for invoking callbacks
2
+ module Packet
3
+ class Callback
4
+ attr_accessor :signature,:stored_proc
5
+ def initialize(&block)
6
+ @signature = Guid.hexdigest
7
+ @stored_proc = block
8
+ end
9
+
10
+ def invoke(*args)
11
+ @stored_proc.call(*args)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ # FIMXE: following class must modify the fd_watchlist thats being monitored by
2
+ # main eventloop.
3
+
4
+ module Packet
5
+ class Connection
6
+ # method gets called when connection to external server is completed
7
+ def connection_completed
8
+
9
+ end
10
+
11
+ # method gets called when external client is disconnected
12
+ def unbind
13
+
14
+ end
15
+
16
+ # method gets called just at the beginning of initializing things.
17
+ def post_init
18
+
19
+ end
20
+
21
+ def send_data
22
+
23
+ end
24
+
25
+ def ask_worker
26
+
27
+ end
28
+
29
+ def receive_data
30
+
31
+ end
32
+ end # end of class Connection
33
+ end # end of module Packet
@@ -0,0 +1,241 @@
1
+ # FIXME: timer implementation can be optimized
2
+ module Packet
3
+ module Core
4
+ def self.included(base_klass)
5
+ base_klass.extend(ClassMethods)
6
+ base_klass.instance_eval do
7
+ # iattr_accessor :connection_callbacks
8
+ @@connection_callbacks ||= {}
9
+ cattr_accessor :connection_callbacks
10
+ attr_accessor :read_ios, :write_ios, :listen_sockets
11
+ attr_accessor :connection_completion_awaited
12
+ attr_accessor :connections
13
+ include CommonMethods
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def after_connection p_method
19
+ connection_callbacks[:after_connection] ||= []
20
+ connection_callbacks[:after_connection] << p_method
21
+ end
22
+
23
+ def after_unbind p_method
24
+ connection_callbacks[:after_unbind] ||= []
25
+ connection_callbacks[:after_unbind] << p_method
26
+ end
27
+
28
+ def before_unbind p_method
29
+ connection_callbacks[:before_unbind] ||= []
30
+ connection_callbacks[:before_unbind] << p_method
31
+ end
32
+ end # end of module#ClassMethods
33
+
34
+ module CommonMethods
35
+ include NbioHelper
36
+ # method
37
+ def connect(ip,port,t_module,&block)
38
+ t_socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
39
+ t_sock_addr = Socket.sockaddr_in(port,ip)
40
+ t_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
41
+
42
+ connection_completion_awaited[t_socket.fileno] =
43
+ { :sock_addr => t_sock_addr, :module => t_module,:block => block }
44
+ begin
45
+ t_socket.connect_nonblock(t_sock_addr)
46
+ immediate_complete(t_socket,t_sock_addr,t_module,&block)
47
+ rescue Errno::EINPROGRESS
48
+ write_ios << t_socket
49
+ end
50
+ end
51
+
52
+ def reconnect(server,port,handler)
53
+ raise "invalid handler" unless handler.respond_to?(:connection_completed)
54
+ return handler if connections.keys.include?(handler.connection.fileno)
55
+ connect(server,port,handler)
56
+ end
57
+
58
+ def immediate_complete(t_socket,sock_addr,t_module,&block)
59
+ read_ios << t_socket
60
+ write_ios.delete(t_socket)
61
+ decorate_handler(t_socket,true,sock_addr,t_module,&block)
62
+ connection_completion_awaited.delete(t_socket.fileno)
63
+ end
64
+
65
+ def accept_connection(sock_opts)
66
+ sock_io = sock_opts[:socket]
67
+
68
+ begin
69
+ client_socket,client_sockaddr = sock_io.accept_nonblock
70
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
71
+ puts "not ready yet"
72
+ return
73
+ end
74
+ read_ios << client_socket
75
+ decorate_handler(client_socket,true,client_sockaddr,sock_opts[:module],&sock_opts[:block])
76
+ end
77
+
78
+ def complete_connection(t_sock,sock_opts)
79
+ actually_connected = true
80
+ begin
81
+ t_sock.connect_nonblock(sock_opts[:sock_addr])
82
+ rescue Errno::EISCONN
83
+ puts "Socket already connected"
84
+ rescue Errno::ECONNREFUSED
85
+ actually_connected = false
86
+ end
87
+
88
+ read_ios << t_sock if actually_connected
89
+ write_ios.delete(t_sock)
90
+ decorate_handler(t_sock,actually_connected,sock_opts[:sock_addr],sock_opts[:module],&sock_opts[:block])
91
+ connection_completion_awaited.delete(t_sock.fileno)
92
+ end
93
+
94
+ # method removes the connection and closes the socket
95
+ def remove_connection(t_sock)
96
+ @read_ios.delete(t_sock)
97
+ @write_ios.delete(t_sock)
98
+ connections.delete(t_sock.fileno)
99
+ t_sock.close
100
+ end
101
+
102
+ def socket_really_connected?(t_sock)
103
+ begin
104
+ t_data = read_data(t_sock)
105
+ return true
106
+ rescue DisconnectError
107
+ return false
108
+ end
109
+ end
110
+
111
+ # method opens a socket for listening
112
+ def start_server(ip,port,t_module,&block)
113
+ t_socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
114
+ t_socket.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,true)
115
+ sockaddr = Socket.sockaddr_in(port.to_i,ip)
116
+ t_socket.bind(sockaddr)
117
+ t_socket.listen(50)
118
+ t_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
119
+ listen_sockets[t_socket.fileno] = { :socket => t_socket,:block => block,:module => t_module }
120
+ @read_ios << t_socket
121
+ end
122
+
123
+ # method starts event loop in the process
124
+ def start_reactor
125
+ Signal.trap("TERM") { terminate_me }
126
+ Signal.trap("INT") { shutdown }
127
+ loop do
128
+ check_for_timer_events
129
+ ready_fds = select(@read_ios,@write_ios,nil,0.005)
130
+ next if ready_fds.blank?
131
+ ready_fds = ready_fds.flatten.compact
132
+ ready_fds.each do |t_sock|
133
+ if t_sock.is_a? UNIXSocket
134
+ handle_internal_messages(t_sock)
135
+ else
136
+ handle_external_messages(t_sock)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def terminate_me
143
+ exit
144
+ end
145
+
146
+ def shutdown
147
+ exit
148
+ end
149
+
150
+ def handle_internal_messages(t_sock)
151
+ raise "Method should be implemented by concerned classes"
152
+ end
153
+
154
+ def handle_external_messages(t_sock)
155
+ sock_fd = t_sock.fileno
156
+ if sock_opts = listen_sockets[sock_fd]
157
+ accept_connection(sock_opts)
158
+ elsif extern_opts = connection_completion_awaited[sock_fd]
159
+ complete_connection(t_sock,extern_opts)
160
+ else
161
+ read_external_socket(t_sock)
162
+ end
163
+ end
164
+
165
+ def read_external_socket(t_sock)
166
+ handler_instance = connections[t_sock.fileno].instance
167
+ begin
168
+ t_data = read_data(t_sock)
169
+ handler_instance.receive_data(t_data) if handler_instance.respond_to?(:receive_data)
170
+ rescue DisconnectError => sock_error
171
+ handler_instance.unbind if handler_instance.respond_to?(:unbind)
172
+ connections.delete(t_sock.fileno)
173
+ read_ios.delete(t_sock)
174
+ end
175
+ end
176
+
177
+ def add_periodic_timer(interval,&block)
178
+ t_timer = PeriodicEvent.new(interval,&block)
179
+ @timer_hash[t_timer.timer_signature] = t_timer
180
+ return t_timer
181
+ end
182
+
183
+ def add_timer(elapsed_time,&block)
184
+ t_timer = Event.new(elapsed_time,&block)
185
+ @timer_hash[t_timer.timer_signature] = t_timer
186
+ return t_timer
187
+ end
188
+
189
+ def cancel_timer(t_timer)
190
+ @timer_hash.delete(t_timer.timer_signature)
191
+ end
192
+
193
+ def initialize
194
+ @read_ios ||= []
195
+ @write_ios ||= []
196
+ @connection_completion_awaited ||= {}
197
+ @connections ||= {}
198
+ @listen_sockets ||= {}
199
+
200
+ @timer_hash ||= {}
201
+ end
202
+
203
+ def check_for_timer_events
204
+ @timer_hash.each do |key,timer|
205
+ if timer.run_now?
206
+ timer.run
207
+ @timer_hash.delete(key) if !timer.respond_to?(:interval)
208
+ end
209
+ end
210
+ end
211
+
212
+ def initialize_handler(p_module)
213
+ return p_module if(!p_module.is_a?(Class) and !p_module.is_a?(Module))
214
+ handler =
215
+ if(p_module and p_module.is_a?(Class))
216
+ p_module
217
+ else
218
+ Class.new(Connection) { p_module and include p_module }
219
+ end
220
+ return handler.new
221
+ end
222
+
223
+ def decorate_handler(t_socket,actually_connected,sock_addr,t_module,&block)
224
+ handler_instance = initialize_handler(t_module)
225
+ connection_callbacks[:after_connection].each { |t_callback| self.send(t_callback,handler_instance,t_socket)}
226
+ handler_instance.invoke_init unless handler_instance.initialized
227
+ unless actually_connected
228
+ handler_instance.unbind if handler_instance.respond_to?(:unbind)
229
+ return
230
+ end
231
+ t_signature = Guid.hexdigest
232
+ handler_instance.signature = t_signature
233
+ connections[t_socket.fileno] =
234
+ OpenStruct.new( :socket => t_socket, :instance => handler_instance, :signature => t_signature,:sock_addr => sock_addr)
235
+ block.call(handler_instance) if block
236
+ handler_instance.connection_completed if handler_instance.respond_to?(:connection_completed)
237
+ end
238
+
239
+ end # end of module#CommonMethods
240
+ end #end of module#Core
241
+ end #end of module#Packet