networking 0.0.8

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.
@@ -0,0 +1,217 @@
1
+ require 'log'
2
+ require 'socket'
3
+
4
+ module BBFS
5
+ module Networking
6
+
7
+ Params.integer('client_retry_delay', 60, 'Number of seconds before trying to reconnect.')
8
+
9
+ # TODO(kolman): To get robustness, just use try catch + return 0 bytes on write +
10
+ # return false on status on read.
11
+ def Networking.write_to_stream(stream, obj)
12
+ Log.debug3('Writing to stream.')
13
+ marshal_data = Marshal.dump(obj)
14
+ Log.debug2("Writing data size: #{marshal_data.length}")
15
+ data_size = [marshal_data.length].pack("l")
16
+ if data_size.nil? || marshal_data.nil?
17
+ Log.debug3 'Send data size is nil!'
18
+ end
19
+ begin
20
+ bytes_written = stream.write data_size
21
+ bytes_written += stream.write marshal_data
22
+ return bytes_written
23
+ rescue Exception => e
24
+ Log.warning("Could not write tcp/ip stream, #{e.to_s}")
25
+ begin
26
+ stream.close()
27
+ rescue IOError => e
28
+ Log.warning("Could not close stream, #{e.to_s}.")
29
+ end
30
+ return 0
31
+ end
32
+ end
33
+
34
+ # Returns pair [status(true/false), obj]
35
+ def Networking.read_from_stream(stream)
36
+ Log.debug3('Read from stream.')
37
+ begin
38
+ return [false, nil] unless size_of_data = stream.read(4)
39
+ size_of_data = size_of_data.unpack("l")[0]
40
+ Log.debug2("Reading data size:#{size_of_data}")
41
+ data = stream.read(size_of_data)
42
+ rescue Exception => e
43
+ Log.warning("Could not read tcp/ip stream, #{e.to_s}.")
44
+ begin
45
+ stream.close()
46
+ rescue IOError => e
47
+ Log.warning("Could not close stream, #{e.to_s}.")
48
+ end
49
+ return [false, nil]
50
+ end
51
+
52
+ unmarshalled_data = Marshal.load(data)
53
+ Log.debug3('Read good.')
54
+ return [true, unmarshalled_data]
55
+ end
56
+
57
+ # Limit on number of concurrent connections?
58
+ # The TCP server is not responsible for reconnection.
59
+ class TCPServer
60
+ attr_reader :tcp_thread
61
+
62
+ def initialize(port, obj_clb, new_clb=nil, closed_clb=nil, max_parallel=1)
63
+ @port = port
64
+ @obj_clb = obj_clb
65
+ @new_clb = new_clb
66
+ @closed_clb = closed_clb
67
+ # Max parallel connections is not implemented yet.
68
+ @max_parallel = max_parallel
69
+ @sockets = {}
70
+ @tcp_thread = run_server
71
+ @tcp_thread.abort_on_exception = true
72
+ end
73
+
74
+ def send_obj(obj, addr_info=nil)
75
+ Log.debug3("addr_info=#{addr_info}")
76
+ unless addr_info.nil?
77
+ if @sockets.key?(addr_info)
78
+ Networking.write_to_stream(@sockets[addr_info], obj)
79
+ else
80
+ Log.warning("Could not find client socket: #{addr_info}")
81
+ return 0
82
+ end
83
+ else
84
+ out = {}
85
+ @sockets.each { |key, sock| out[key] = Networking.write_to_stream(sock, obj) }
86
+ return out
87
+ end
88
+ end
89
+
90
+ private
91
+ # Creates new thread/pool
92
+ def run_server
93
+ Log.debug3('run_server')
94
+ return Thread.new do
95
+ Log.debug3('run_server2')
96
+ loop {
97
+ begin
98
+ Socket.tcp_server_loop(@port) do |sock, addr_info|
99
+ Log.debug3("----- #{@port} -----")
100
+ Log.debug3("tcp_server_loop... #{sock} #{addr_info.inspect}")
101
+ @sockets[addr_info] = sock
102
+ @new_clb.call(addr_info) if @new_clb != nil
103
+ loop do
104
+ # Blocking read.
105
+ Log.debug3('read_from_stream')
106
+ stream_ok, obj = Networking.read_from_stream(sock)
107
+ Log.debug3("Server returned from read: #{stream_ok}")
108
+ @obj_clb.call(addr_info, obj) if @obj_clb != nil && stream_ok
109
+ break if !stream_ok
110
+ end
111
+ Log.warning("Connection broken, #{addr_info.inspect}")
112
+ begin
113
+ @sockets[addr_info].close() unless @sockets[addr_info].nil?
114
+ rescue IOError => e
115
+ Log.warning("Could not close socket, #{e.to_s}.")
116
+ end
117
+ @sockets.delete(addr_info)
118
+ @closed_clb.call(addr_info) if @closed_clb != nil
119
+ end
120
+ # !!! note this break is needed for tests only !!!! server loop should never end.
121
+ # and if it ends, it will fail, and rescue will work.
122
+ break
123
+ rescue IOError => e
124
+ Log.info("Connection broken during tcp_server_loop. Restarting server loop " \
125
+ "port:#{port}.")
126
+ end
127
+ }
128
+ end
129
+ end
130
+ end # TCPServer
131
+
132
+ class TCPClient
133
+ attr_reader :tcp_thread
134
+
135
+ def initialize(host, port, obj_clb=nil, reconnected_clb=nil)
136
+ @host = host
137
+ @port = port
138
+ @tcp_socket = nil
139
+ @obj_clb = obj_clb
140
+ @reconnected_clb = reconnected_clb
141
+ Log.debug1('TCPClient init.')
142
+ Log.debug1("TCPClient init...#{@obj_clb}...")
143
+ if @obj_clb != nil
144
+ @tcp_thread = start_reading
145
+ @tcp_thread.abort_on_exception = true
146
+ end
147
+ # Variable to signal when remote server is ready.
148
+ @remote_server_available = ConditionVariable.new
149
+ @remote_server_available_mutex = Mutex.new
150
+ open_socket unless socket_good?
151
+ end
152
+
153
+ def send_obj(obj)
154
+ Log.debug1('send_obj')
155
+ open_socket unless socket_good?
156
+ Log.debug1('after open')
157
+ if !socket_good?
158
+ Log.warning('Socket not opened for writing, skipping send.')
159
+ return false
160
+ end
161
+ Log.debug1('writing...')
162
+ #Log.debug3("socket port: #{@tcp_socket.peeraddr}")
163
+ bytes_written = Networking.write_to_stream(@tcp_socket, obj)
164
+ return bytes_written
165
+ end
166
+
167
+ # This function may be executed only from one thread!!! or in synchronized manner.
168
+ # private
169
+ def open_socket
170
+ Log.debug1("Connecting to content server #{@host}:#{@port}.")
171
+ begin
172
+ @tcp_socket = TCPSocket.new(@host, @port)
173
+ rescue Errno::ECONNREFUSED
174
+ Log.warning('Connection refused')
175
+ end
176
+ Log.debug1("Reconnect clb: '#{@reconnected_clb.nil? ? 'nil' : @reconnected_clb}'")
177
+ if socket_good?
178
+ @remote_server_available_mutex.synchronize {
179
+ @remote_server_available.signal
180
+ }
181
+ @reconnected_clb.call if @reconnected_clb != nil && socket_good?
182
+ end
183
+ end
184
+
185
+ private
186
+ def socket_good?
187
+ Log.debug1 "socket_good? #{@tcp_socket != nil && !@tcp_socket.closed?}"
188
+ return @tcp_socket != nil && !@tcp_socket.closed?
189
+ end
190
+
191
+ private
192
+ def start_reading
193
+ Log.debug1('start_reading (TCPClient).')
194
+ return Thread.new do
195
+ loop do
196
+ Log.debug3('Start blocking read (TCPClient).')
197
+ # Blocking read.
198
+ if !socket_good?
199
+ Log.warning("Socket not good, waiting for reconnection with " \
200
+ "#{Params['client_retry_delay']} seconds timeout.")
201
+ @remote_server_available_mutex.synchronize {
202
+ @remote_server_available.wait(@remote_server_available_mutex,
203
+ Params['client_retry_delay'])
204
+ }
205
+ else
206
+ read_ok, obj = Networking.read_from_stream(@tcp_socket)
207
+ Log.debug3("Client returned from read: #{read_ok}")
208
+ # Handle case when socket is closed in middle.
209
+ # In that case we should not call obj_clb.
210
+ @obj_clb.call(obj) if (read_ok && @obj_clb != nil)
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end # TCPClient
216
+ end
217
+ end
@@ -0,0 +1,5 @@
1
+ module BBFS
2
+ module Networking
3
+ VERSION = "0.0.8"
4
+ end
5
+ end
data/lib/networking.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'networking/tcp'
2
+
3
+ module BBFS
4
+ module Networking
5
+
6
+ end
7
+ end
@@ -0,0 +1,96 @@
1
+ require 'log'
2
+ require 'rspec'
3
+ require 'socket'
4
+ require 'stringio'
5
+
6
+ require_relative '../../lib/networking/tcp'
7
+
8
+ # Uncomment to debug spec.
9
+ BBFS::Params['log_write_to_console'] = true
10
+ BBFS::Params['log_debug_level'] = 0
11
+ BBFS::Log.init
12
+
13
+ module BBFS
14
+ module Networking
15
+ module Spec
16
+
17
+ describe 'TCPClient' do
18
+ it 'should warn when destination host+port are bad' do
19
+ data = 'kuku!!!'
20
+ stream = StringIO.new
21
+ stream.close()
22
+ ::TCPSocket.stub(:new).and_return(stream)
23
+ BBFS::Log.should_receive(:warning).with('Socket not opened for writing, skipping send.')
24
+
25
+ # Send data first.
26
+ tcp_client = TCPClient.new('kuku', 5555)
27
+ # Send has to fail.
28
+ tcp_client.send_obj(data).should be(false)
29
+ end
30
+
31
+ it 'should send data and server should receive callback function' do
32
+ info = 'info'
33
+ data = 'kuku!!!'
34
+ stream = StringIO.new
35
+ ::Socket.stub(:tcp_server_loop).and_yield(stream, info)
36
+ ::TCPSocket.stub(:new).and_return(stream)
37
+
38
+ # Send data first.
39
+ tcp_client = TCPClient.new('kuku', 5555)
40
+ # Send has to be successful.
41
+ tcp_client.send_obj(data).should be(21)
42
+
43
+ # Note this is very important so that reading the stream from beginning.
44
+ stream.rewind
45
+
46
+ func = lambda { |info, data| Log.info("info, data: #{info}, #{data}") }
47
+ # Check data is received.
48
+ func.should_receive(:call).with(info, data)
49
+
50
+ tcp_server = TCPServer.new(5555, func)
51
+ # Wait on server thread.
52
+ tcp_server.tcp_thread.join
53
+ end
54
+
55
+ # TODO(kolman): Don't work, missing client send_obj/open_socket execution in
56
+ # the correct place.
57
+ it 'should connect and receive callback from server' do
58
+ info = 'info'
59
+ data = 'kuku!!!'
60
+ stream = StringIO.new
61
+ ::Socket.stub(:tcp_server_loop).once.and_yield(stream, info)
62
+ ::TCPSocket.stub(:new).and_return(stream)
63
+
64
+ tcp_server = nil
65
+ new_clb = lambda { |i|
66
+ Log.info("#2 - After after @sockets is filled. Send has to be successful.")
67
+ tcp_server.send_obj(data).should eq({info => 21})
68
+
69
+ stream.rewind
70
+
71
+ func = lambda { |d|
72
+ Log.info("#6, After client initialized, it should send object.")
73
+ Log.info("data: #{d}")
74
+ # Validate received data.
75
+ d.should eq(data)
76
+ # Exit tcp client reading loop thread.
77
+ Thread.exit
78
+ }
79
+
80
+ Log.info("#4 - Create client and wait for read data.")
81
+ tcp_client = TCPClient.new('kuku', 5555, func)
82
+ tcp_client.tcp_thread.abort_on_exception = true
83
+ tcp_client.tcp_thread.join()
84
+ # We can finish now. Exit tcp server listening loop.
85
+ Thread.exit
86
+ }
87
+
88
+ Log.info("#1 - Create server, send data and wait.")
89
+ tcp_server = TCPServer.new(5555, nil, new_clb)
90
+ tcp_server.tcp_thread.abort_on_exception = true
91
+ tcp_server.tcp_thread.join()
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: networking
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kolman Vornovitsky
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: params
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Easy to use simple tools for networking (tcp/ip, ect,...).
31
+ email: kolmanv@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/networking.rb
37
+ - lib/networking/tcp.rb
38
+ - lib/networking/version.rb
39
+ - spec/networking/tcp_spec.rb
40
+ homepage: http://github.com/kolmanv/bbfs
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.23
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Easy to use simple tools for networking (tcp/ip, ect,...).
64
+ test_files:
65
+ - spec/networking/tcp_spec.rb