networking 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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