minitcp 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/minitcp.rb ADDED
@@ -0,0 +1,341 @@
1
+ # LGPL, Author: Regis d'Aubarede <regis.aubarede@gmail.com>
2
+ #
3
+ ##############################################################
4
+ # minitcp.rb
5
+ ##############################################################
6
+
7
+ require 'thread'
8
+ require 'timeout'
9
+ require 'socket'
10
+ require 'gserver'
11
+
12
+
13
+ module SocketReactive
14
+
15
+ def data_readed=(v) @data_readed=v end
16
+ def data_readed() @data_readed||="" end
17
+
18
+ # read n byte, block the caller, return nil if socket if close
19
+ # if block is defined, it is yield with data, method return whith the value of yield
20
+ # if looping is true, the method loop until socket close, (or current thread is killed)
21
+ def receive_n_bytes(sizemax,looping=false,&b)
22
+ s=sizemax
23
+ if self.data_readed.size>=sizemax
24
+ buff,self.data_readed=self.data_readed[0..sizemax-1],self.data_readed[sizemax..-1]
25
+ buff=b.call(buff) if block_given?
26
+ return buff unless looping
27
+ end
28
+ s=sizemax-self.data_readed.size
29
+ loop do
30
+ #p ["waiting ",s,data_readed]
31
+ sd=s>1024 ? 1024 : s
32
+ data=(self.recv(sd) rescue nil)
33
+ #p "nrec: w#{sizemax}/ rec:#{(data||'').size} / #{sd} old=#{data_readed.size} /// #{(data||'').size<70 ? data : "."}"
34
+ if data && data.size>0
35
+ self.data_readed=self.data_readed+data
36
+ s=sizemax-self.data_readed.size
37
+ if s<=0
38
+ buff,self.data_readed=self.data_readed,""
39
+ s=sizemax
40
+ buff=b.call(buff) if block_given?
41
+ return buff unless looping
42
+ end
43
+ else
44
+ close rescue nil
45
+ break # socket close
46
+ end
47
+ end #loop
48
+ end
49
+ # wait n byte or timeout. if block is defined, it is yielded with data
50
+ # return nil if timeout/socket closed, or data if no bloc, or yield value
51
+ def received_n_timeout(sizemax,timeout_ms,&b)
52
+ timeout(timeout_ms/1000.0) {
53
+ ret=receive_n_bytes(sizemax,false,&b)
54
+ return ret
55
+ }
56
+ rescue Timeout::Error
57
+ return nil
58
+ rescue Exception => e
59
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
60
+ end
61
+
62
+ def received_any_timeout(sizemax,timeout_ms)
63
+ timeout(timeout_ms/1000.0) {
64
+ return recv(sizemax)
65
+ }
66
+ rescue Timeout::Error
67
+ return nil
68
+ rescue Exception => e
69
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
70
+ end
71
+
72
+ # async wait and read data on socket, yield values readed,
73
+ # return thread spawned, which can be kill
74
+ def on_any_receive()
75
+ Thread.new() do
76
+ begin
77
+ if self.data_readed.size>0
78
+ buff,self.data_readed=self.data_readed,""
79
+ yield(buff)
80
+ end
81
+ loop do
82
+ data=(self.recv(64*1024) rescue nil)
83
+ data && data.size>0 ? yield(data) : break
84
+ end
85
+ rescue Exception => e
86
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
87
+ end
88
+ close rescue nil
89
+ end
90
+ end
91
+
92
+
93
+ # async yield on received n bytes
94
+ # return thread spawned, which can be kill
95
+ def on_n_receive(sizemax=1,&b)
96
+ Thread.new() do
97
+ begin
98
+ receive_n_bytes(sizemax,true,&b)
99
+ rescue Exception => e
100
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
101
+ end
102
+ end
103
+ end
104
+
105
+ # read until separator reached, block the caller, return nil if socket is close
106
+ # if block is defined, it is yield with data, method return whith the value of yield
107
+ # if looping is true, the method loop until socket close, (or current thread is killed)
108
+ # this read some extra data. they can be retrieve with in socket.data_readed.
109
+ # data_readed is use for next calls to receives_n_byte/receive_sep
110
+ def receive_sep(separator,sizemax=1024,looping=false,&b)
111
+ if self.data_readed.size>0
112
+ a=self.data_readed.split(separator,2)
113
+ while a.size>1
114
+ buff= a.size>2 ? a[0..-2] : a.first
115
+ self.data_readed=a.last
116
+ buff=b.call(buff) if block_given?
117
+ return buff unless looping
118
+ a=self.data_readed.split(separator,2)
119
+ end
120
+ end
121
+ loop do
122
+ data=(self.recv(sizemax-self.data_readed.size) rescue nil)
123
+ if data && data.size>0
124
+ self.data_readed=self.data_readed+data
125
+ a=(self.data_readed).split(separator,2)
126
+ while a.size>1
127
+ buff= a.size>2 ? a[0..-2] : a.first
128
+ self.data_readed=a.last
129
+ buff=b.call(buff) if block_given?
130
+ return buff unless looping
131
+ a=(self.data_readed).split(separator,2)
132
+ end
133
+ else
134
+ close rescue nil
135
+ break
136
+ end
137
+ end
138
+ end
139
+
140
+ # async yield on received data until end-buffer string
141
+ # end-buffer can be string or regexp (args of data.split(,2))
142
+ # return thread spawned, which can be kill
143
+ # this read some extra data. they can be retrieve with in socket.data_readed.
144
+ # data_readed is use for next calls to receives_n_byte/receive_sep
145
+ def on_receive_sep(separator,sizemax=1024,&b)
146
+ Thread.new() do
147
+ begin
148
+ receive_sep(separator,sizemax,looping=true,&b)
149
+ rescue Exception => e
150
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
151
+ end
152
+ end
153
+ end
154
+
155
+ # async yield after a duration, if socket is open
156
+ # return thread spawned, which can be kill
157
+ def after(duration_ms)
158
+ Thread.new() do
159
+ begin
160
+ sleep(duration_ms/1000.0)
161
+ yield unless self.connected?()
162
+ rescue Exception => e
163
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
164
+ end
165
+ end
166
+ end
167
+
168
+ # async yield periodicaly, if socket is open
169
+ # return thread spawned, which can be kill
170
+ def on_timer(value=1000)
171
+ Thread.new() {
172
+ begin
173
+ nbtick=(value/TICK)+1
174
+ loop do
175
+ i=0
176
+ sleep(TICK/1000.0) while self.connected?() && (i+=1)<nbtick
177
+ self.connected?() ? yield() : break
178
+ end
179
+ rescue Exception => e
180
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
181
+ end
182
+ }
183
+ end
184
+
185
+ # wait until curent socket is close.
186
+ def wait_end()
187
+ begin
188
+ loop do
189
+ sleep(TICK/1000.0) while (self.connected?() rescue nil)
190
+ break
191
+ end
192
+ rescue Exception => e
193
+ end
194
+ end
195
+
196
+ # Test if a socket is open. (use socket.remote_address() !)
197
+ def connected?()
198
+ (self.remote_address rescue nil) ? true : false
199
+ end
200
+ # duration of sleep when active wait (wait_end,on_timer...)
201
+ TICK=600
202
+
203
+ def self.make_socket_reactive(socket)
204
+ socket.extend(SocketReactive)
205
+ socket.data_readed=""
206
+ end
207
+ end
208
+
209
+ # Assure connection to server, extend socket connection by SocketReactive module.
210
+ #
211
+ # MClient.run_one_shot("localhost",2200) do |socket| .. end.join
212
+ #
213
+ # MClient.run_continous("localhost",2200,6000) do |socket| .. end.join
214
+ #
215
+ class MClient
216
+ # maintain a conntection to a TCP serveur, sleep timer_interconnection_ms millisecondes
217
+ # beetwen each reconnections
218
+ def self.run_continious(host,port,timer_interconnection_ms,&b)
219
+ Thread.new do
220
+ loop { run_one_shot(host,port,&b).join ; sleep timer_interconnection_ms/1000.0 }
221
+ end
222
+ end
223
+
224
+ def self.run_continous(host,port,timer_interconnection,&b)
225
+ self.run_continious(host,port,timer_interconnection,&b)
226
+ end
227
+
228
+ # Connecte to a TCP server, call block with client socket if connection sucess.
229
+ # enssure close connection after end of block
230
+ def self.run_one_shot(host="localhost",port=80)
231
+ begin
232
+ sleep(0.03) # give some time for server ready (for test...)
233
+ socket = TCPSocket.new(host,port)
234
+ rescue
235
+ puts "not connected to #{host}:#{port}: " + $!.to_s
236
+ return (Thread.new {})
237
+ end
238
+ SocketReactive::make_socket_reactive(socket)
239
+ Thread.new do
240
+ begin
241
+ yield(socket)
242
+ rescue Exception => e
243
+ puts "#{e}\n #{e.backtrace.join("\n ")}"
244
+ ensure
245
+ socket.close() rescue nil
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+
252
+ # Assure connection to server, extend socket connection by SocketReactive module.
253
+ #
254
+ # MClient.run_one_shot("localhost",2200) do |socket| .. end.join
255
+ #
256
+ # MClient.run_continous("localhost",2200,6000) do |socket| .. end.join
257
+ #
258
+ class UDPAgent
259
+ # maintain a conntection to a TCP serveur, sleep timer_interconnection_ms millisecondes
260
+ # beetwen each reconnections
261
+ def self.send_datagram(host,port,mess)
262
+ sock = UDPSocket.new
263
+ #p ["sock.send",mess, 0, host, port]
264
+ sock.send(mess, 0, host, port)
265
+ sock.close
266
+ end
267
+ def self.send_datagram_on_socket(socket,host,port,mess)
268
+ socket.send(mess, 0, host, port)
269
+ end
270
+
271
+ Thread.abort_on_exception=true
272
+
273
+ # send datagram on timer
274
+ def self.on_timer(periode,options)
275
+ Thread.new do
276
+ sleep periode/1000.0
277
+ sock = UDPSocket.new
278
+ if options[:port]
279
+ sock.bind("0.0.0.0", options[:port])
280
+ end
281
+ loop do
282
+ rep=IO.select([sock],nil,nil,periode/1000.0)
283
+ #puts "IO.SELECT => #{rep}"
284
+ if rep
285
+ Thread.new {
286
+ data,peer=sock.recvfrom(1024)
287
+ options[:on_receive].call(data,peer,sock)
288
+ } if options[:on_receive]
289
+ elsif options[:on_timer]
290
+ h=options[:on_timer].call()
291
+ self.send_datagram_on_socket(sock,h[:host], h[:port],h[:mess]) if h && h[:mess] && h[:host] && h[:port]
292
+ end
293
+ end
294
+ end
295
+ end
296
+ # recieved UDP datagramme, bloc can ellaborate a reply, which will be sending to client
297
+ def self.on_datagramme(host,port)
298
+ serv = UDPSocket.new
299
+ serv.bind(host, port)
300
+ Thread.new do
301
+ loop do
302
+ begin
303
+ Thread.new(*serv.recvfrom(1024)) do |data,peer| # peer=["AF_INET", 59340, "127.0.0.1", "127.0.0.1"]
304
+ proto,cli_port,srv_addr,cli_addr=*peer
305
+ response=yield(data,cli_addr,cli_port)
306
+ self.send_datagram_on_socket(serv,cli_addr,cli_port,response) if response
307
+ end
308
+ rescue Exception => e
309
+ puts "#{e}\n #{e.backtrace.join("\n ")}"
310
+ ensure
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ # Run a TCP serveur, with a max connection simultaneous,
318
+ # When connection succes, call the bloc given with socket (extended by SocketReactive).
319
+ #
320
+ # MServer( "8080" , "0.0.0.0" ,1) { |socket| loop { p socket.gets} }
321
+ class MServer < GServer
322
+ def self.service(port,host,max,&b)
323
+ srv=new(port,host,max,&b)
324
+ srv.audit = true
325
+ srv.start
326
+ srv
327
+ end
328
+ def initialize(port,host,max=1,&b)
329
+ super(port,host,max)
330
+ @bloc=b
331
+ end
332
+ def serve( io )
333
+ SocketReactive::make_socket_reactive(io)
334
+ begin
335
+ @bloc.call(io)
336
+ rescue Exception => e
337
+ puts "Error in Mserver block: #{e} :\n #{e.backtrace.join("\n ")}"
338
+ end
339
+ end
340
+ end
341
+
data/minitcp.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push('lib')
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "minitcp"
6
+ s.licenses = ['LGPL']
7
+ s.version = File.read("VERSION").strip
8
+ s.date = Time.now.to_s.split(/\s+/)[0]
9
+ s.email = "regis.aubarede@gmail.com"
10
+ s.homepage = "https://github.com/glurp/minitcp"
11
+ s.authors = ["Regis d'Aubarede"]
12
+ s.summary = "A DSL for programming little Tcp client and server"
13
+ s.description = <<EEND
14
+ A DSL for programming little Tcp client and server
15
+ EEND
16
+
17
+ dependencies = [
18
+ ]
19
+
20
+ s.files = Dir['**/*'].reject { |a| a =~ /^\.git/ || a =~ /\._$/ || a =~ /\.~$/}
21
+ s.test_files = Dir['samples/**']
22
+ s.require_paths = ["lib"]
23
+
24
+ ## Make sure you can build the gem on older versions of RubyGems too:
25
+ s.rubygems_version = "1.8.15"
26
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
27
+ s.specification_version = 3 if s.respond_to? :specification_version
28
+
29
+ dependencies.each do |type, name, version|
30
+ if s.respond_to?("add_#{type}_dependency")
31
+ s.send("add_#{type}_dependency", name, version)
32
+ else
33
+ s.add_dependency(name, version)
34
+ end
35
+ end
36
+ end
37
+
data/samples/README.md ADDED
@@ -0,0 +1,19 @@
1
+ Minitcp usages
2
+ ===
3
+
4
+ **proxy.rb** : real tcp proxy
5
+
6
+ **name_server.rb** : my dns service :)
7
+
8
+ **relay.rb** : not ready , tcp relay for bypass firewall/proxy protection
9
+
10
+ *spdy.rb** : SPDY server relay for http server
11
+
12
+ Dynamic ploting
13
+ ===
14
+
15
+ **plot.rb** : realy no tcp... , plot measures on stdin to screen
16
+
17
+ **pingpongplot.rb** : measure connection time to google.com (for piping to plot.rb)
18
+
19
+ **statcpu.rb** : linux : cpu time to stdout, for piping to plot.rb
@@ -0,0 +1,131 @@
1
+ # LGPL, Author: Regis d'Aubarede <regis.aubarede@gmail.com>
2
+ #################################################################
3
+ # name_server.rb
4
+ #################################################################
5
+ require_relative '../lib/minitcp.rb'
6
+
7
+ BasicSocket.do_not_reverse_lookup = true
8
+ Thread.abort_on_exception = true
9
+
10
+ $SIZE=30
11
+ $dico_name={}
12
+ $dico_ip={}
13
+
14
+ def run_net_relay()
15
+ MServer.service(4410,"0.0.0.0",1000) do |socket|
16
+ socket.on_n_receive($SIZE) do |data|
17
+ ip=socket.remote_address.ip_address
18
+ cmd,url,http=data.split(' ')
19
+ name,method,*args=url.split('/').last
20
+ next if cmd!="GET"
21
+ case method
22
+ when "server"
23
+ old=ActiveConnection.search(name)
24
+ if old && old.state==:free
25
+ old.deconnect
26
+ old=nil
27
+ end
28
+ if !old || old.state==:connected
29
+ ActiveConnection.add(socket)
30
+ end
31
+ when "client"
32
+ if srv=ActiveConnection.search(args.first)
33
+ srv.connect_to(name)
34
+ else
35
+ smess("NOTCONNECT")
36
+ end
37
+ end
38
+ rep=""
39
+ end
40
+ end
41
+ end
42
+
43
+ def smess(socket,*args)
44
+ data= args.join(";")+";"
45
+ socket.send(data+" "*($SIZE-data.length),0) rescue p $!
46
+ end
47
+
48
+ def run_server_name()
49
+ MServer.service(4400,"0.0.0.0",100) do |socket|
50
+ socket.on_n_receive($SIZE) do |data|
51
+ ip=socket.remote_address.ip_address
52
+ cmd,*args=data.split(";")
53
+ name=args.first
54
+ rep=""
55
+ case cmd
56
+ when "update"
57
+ $dico_name[name]=ip
58
+ $dico_ip[ip]=name
59
+ rep="OK"
60
+ when "forget"
61
+ oldip=$dico_ip[ip]
62
+ if oldip && $dico_ip[ip]==name && $dico_name[name]==ip
63
+ $dico_name.delete(name)
64
+ $dico_ip.delete(ip)
65
+ rep="OK"
66
+ end
67
+ when "get"
68
+ rep=$dico_name[name].to_s
69
+ when "geti"
70
+ rep=$dico_ip[name].to_s
71
+ end
72
+ rep.size>0 ? smess(socket,"ACK",name,rep) : smess(socket,"NACK","NOK","NOK")
73
+ end
74
+ socket.after(10*1000) { socket.close rescue nil}
75
+ socket.wait_end
76
+ end
77
+ end
78
+
79
+ ################# Client api ########################
80
+
81
+ def update_server_name(hostname)
82
+ MClient.run_one_shot("localhost",4400) do |socket|
83
+ socket.on_n_receive($SIZE) { |data| p data ; socket.close }
84
+ smess(socket,"update",hostname)
85
+ socket.wait_end
86
+ end
87
+ end
88
+ def fetch_name(hostname)
89
+ ret=""
90
+ MClient.run_one_shot("localhost",4400) do |socket|
91
+ socket.on_n_receive($SIZE) { |data| ret= data.split(";")[2] ; socket.close }
92
+ smess(socket,"get",hostname)
93
+ socket.wait_end
94
+ ret
95
+ end.join
96
+ ret
97
+ end
98
+ def fetch_ip(ip)
99
+ ret=""
100
+ MClient.run_one_shot("localhost",4400) do |socket|
101
+ socket.on_n_receive($SIZE) { |data| ret= data.split(";")[2] ; socket.close }
102
+ smess(socket,"geti",ip)
103
+ socket.wait_end
104
+ ret
105
+ end.join
106
+ ret
107
+ end
108
+ def forget_me(name)
109
+ MClient.run_one_shot("localhost",4400) do |socket|
110
+ ret="?"
111
+ socket.on_n_receive($SIZE) { |data| ret= data.split(";")[2] ; socket.close }
112
+ smess(socket,"forget",name)
113
+ socket.wait_end
114
+ p "forget : #{ret}"
115
+ end.join
116
+ end
117
+
118
+ if $0==__FILE__
119
+ run_server_name
120
+
121
+ Thread.new { 4.times { update_server_name("glurp") ; sleep 1 } }
122
+ Thread.new { 4.times { update_server_name("glurp22") ; sleep 1 } }
123
+
124
+ sleep 2
125
+ puts "\n\n***************** get name *******************\n\n"
126
+
127
+ p fetch_name("glurp")
128
+ p fetch_ip("127.0.0.1")
129
+ forget_me("glurp22")
130
+ sleep
131
+ end
@@ -0,0 +1,26 @@
1
+ # LGPL, Author: Regis d'Aubarede <regis.aubarede@gmail.com>
2
+ #################################################################
3
+ # pingpongplot.rb : measure pingpong time on google, plot it
4
+ #
5
+ # Usage :
6
+ # > ruby pingpongplot.rb | ruby plot.rb 0 0 300 pingpong auto
7
+ #################################################################
8
+ require_relative '../lib/minitcp.rb'
9
+
10
+
11
+ $stdout.sync=true
12
+
13
+ MClient.run_continious("google.com",80,100) do |socket|
14
+ s=Time.now.to_f
15
+ socket.on_receive_sep("\r\n") do |data|
16
+ $stdout.puts("#{(Time.now.to_f-s)*1000}")
17
+ socket.close rescue nil
18
+ Thread.current.kill
19
+ end
20
+ s=Time.now.to_f
21
+ socket.print "GET /blabla HTTP/1.0\r\n\r\n"
22
+ socket.wait_end
23
+ end
24
+
25
+
26
+ sleep