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