p2p2 0.5.9

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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 382032ac70220ae3157ea62a6cb7e951b7e3ad641012f374494059eb79de1f28
4
+ data.tar.gz: 78ca2bce5ef38041fa0daffe65cd765692677463b1d5f5d850bbe1e6806f24b3
5
+ SHA512:
6
+ metadata.gz: c4a6b9497f87eb5f47bc72cb6da0b4763d5aff974ab7fd24c7113a3ca7e80e8dd58e7c328ae174f73ccc13c013bbbd1ad2e20de513a3636b6536ff98123e2b4a
7
+ data.tar.gz: 886342bd2abc40eaa0a24aa215e37ae56b18051aee66bacbc3acec3a83c6291300f94c41a5ea5136210827ffbe787db7d19f4cbf74348f7a3d1e6327db85d0e8
@@ -0,0 +1,6 @@
1
+ require "p2p2/version"
2
+
3
+ module P2p2
4
+ class Error < StandardError; end
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,10 @@
1
+ module P2p2
2
+ PACK_SIZE = 1448 # 包大小
3
+ CHUNK_SIZE = PACK_SIZE * 1000 # 块大小
4
+ REP2P_LIMIT = 5 # p2p重试次数。到早了另一头还没从洞里出来,会吃ECONNREFUSED,不慌,再来一发。
5
+ HEARTBEAT = 1
6
+ SET_TITLE = 2
7
+ PAIRING = 3
8
+ NEED_CHUNK = true
9
+ NEED_RENEW = true
10
+ end
@@ -0,0 +1,11 @@
1
+ module P2p2
2
+ class Hex
3
+ def encode( data )
4
+ data
5
+ end
6
+
7
+ def decode( data )
8
+ data
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,397 @@
1
+ require 'p2p2/head'
2
+ require 'p2p2/hex'
3
+ require 'p2p2/version'
4
+ require 'socket'
5
+
6
+ ##
7
+ # P2p2::P1 - 处于各自nat里的两端p2p。p1端。
8
+ #
9
+ module P2p2
10
+ class P1
11
+
12
+ ##
13
+ # roomd_host 匹配服务器ip
14
+ # roomd_port 匹配服务器端口
15
+ # appd_host 任意的一个应用的ip
16
+ # appd_port 应用端口
17
+ # title 约定的房间名
18
+ # app_chunk_dir 文件缓存目录,缓存app来不及写的流量
19
+ # p1_chunk_dir 文件缓存目录,缓存p1来不及写的流量
20
+ def initialize( roomd_host, roomd_port, appd_host, appd_port, title, app_chunk_dir = '/tmp', p1_chunk_dir = '/tmp' )
21
+ @roomd_sockaddr = Socket.sockaddr_in( roomd_port, roomd_host )
22
+ @appd_sockaddr = Socket.sockaddr_in( appd_port, appd_host )
23
+ @title = title
24
+ @app_chunk_dir = app_chunk_dir
25
+ @p1_chunk_dir = p1_chunk_dir
26
+ @hex = P2p2::Hex.new
27
+ @mutex = Mutex.new
28
+ @roles = {} # sock => :room / :p1 / :app
29
+ @infos = {}
30
+ @closings = {} # sock => need_renew
31
+ @reads = []
32
+ @writes = []
33
+ @is_renew = false
34
+
35
+ new_room
36
+ end
37
+
38
+ def looping
39
+ puts 'looping'
40
+
41
+ loop_heartbeat
42
+
43
+ loop do
44
+ rs, ws = IO.select( @reads, @writes )
45
+
46
+ @mutex.synchronize do
47
+ rs.each do | sock |
48
+ case @roles[ sock ]
49
+ when :room
50
+ read_room( sock )
51
+ when :p1
52
+ read_p1( sock )
53
+ when :app
54
+ read_app( sock )
55
+ end
56
+ end
57
+
58
+ ws.each do | sock |
59
+ case @roles[ sock ]
60
+ when :p1
61
+ write_p1( sock )
62
+ when :app
63
+ write_app( sock )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ rescue Interrupt => e
69
+ puts e.class
70
+ quit!
71
+ end
72
+
73
+ def quit!
74
+ exit
75
+ end
76
+
77
+ private
78
+
79
+ def loop_heartbeat
80
+ Thread.new do
81
+ loop do
82
+ sleep 59
83
+
84
+ @mutex.synchronize do
85
+ @room.write( [ HEARTBEAT ].pack( 'C' ) )
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def read_room( sock )
92
+ begin
93
+ data = sock.read_nonblock( PACK_SIZE )
94
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable => e
95
+ return
96
+ rescue EOFError, Errno::ECONNRESET => e
97
+ puts "read room #{ e.class } #{ Time.new }"
98
+
99
+ if @is_renew
100
+ raise e
101
+ end
102
+
103
+ close_sock( @room )
104
+ sleep 5
105
+ new_room
106
+ @is_renew = true
107
+ return
108
+ end
109
+
110
+ @is_renew = false
111
+
112
+ if @p1
113
+ puts 'p1 already exist, ignore'
114
+ return
115
+ end
116
+
117
+ info = @infos[ sock ]
118
+ info[ :p2_sockaddr ] = data
119
+ new_p1
120
+ end
121
+
122
+ def read_p1( sock )
123
+ begin
124
+ data = sock.read_nonblock( PACK_SIZE )
125
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable => e
126
+ return
127
+ rescue Errno::ECONNREFUSED => e
128
+ if @room_info[ :rep2p ] >= REP2P_LIMIT
129
+ raise e
130
+ end
131
+
132
+ add_closing( sock, NEED_RENEW )
133
+ return
134
+ rescue Exception => e
135
+ add_closing( sock )
136
+ return
137
+ end
138
+
139
+ unless @app
140
+ @room_info[ :rep2p ] = 0
141
+ app = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
142
+ app.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
143
+
144
+ begin
145
+ app.connect_nonblock( @appd_sockaddr )
146
+ rescue IO::WaitWritable, Errno::EINTR
147
+ end
148
+
149
+ app_info = {
150
+ wbuff: '',
151
+ cache: '',
152
+ filename: [ Process.pid, app.object_id ].join( '-' ),
153
+ chunk_dir: @app_chunk_dir,
154
+ chunks: [],
155
+ chunk_seed: 0,
156
+ p1: sock,
157
+ need_encode: true
158
+ }
159
+
160
+ @app = app
161
+ @app_info = app_info
162
+ @roles[ app ] = :app
163
+ @infos[ app ] = app_info
164
+ @reads << app
165
+ end
166
+
167
+ info = @infos[ sock ]
168
+
169
+ if info[ :need_decode ]
170
+ len = data[ 0, 2 ].unpack( 'n' ).first
171
+ head = @hex.decode( data[ 2, len ] )
172
+ data = head + data[ ( 2 + len )..-1 ]
173
+ info[ :need_decode ] = false
174
+ end
175
+
176
+ add_write( @app, data, NEED_CHUNK )
177
+ end
178
+
179
+ def read_app( sock )
180
+ begin
181
+ data = sock.read_nonblock( PACK_SIZE )
182
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable => e
183
+ return
184
+ rescue Exception => e
185
+ add_closing( sock )
186
+ return
187
+ end
188
+
189
+ info = @infos[ sock ]
190
+
191
+ if info[ :need_encode ]
192
+ data = @hex.encode( data )
193
+ data = [ [ data.size ].pack( 'n' ), data ].join
194
+ info[ :need_encode ] = false
195
+ end
196
+
197
+ add_write( @p1, data, NEED_CHUNK )
198
+ end
199
+
200
+ def write_p1( sock )
201
+ if @closings.include?( sock )
202
+ close_sock( sock )
203
+ @p1 = nil
204
+
205
+ if @app && !@app.closed?
206
+ add_closing( @app )
207
+ end
208
+
209
+ need_renew = @closings.delete( sock )
210
+
211
+ if need_renew
212
+ sleep 1
213
+ new_p1
214
+ @room_info[ :rep2p ] += 1
215
+ end
216
+
217
+ return
218
+ end
219
+
220
+ info = @infos[ sock ]
221
+ data, from = get_buff( info )
222
+
223
+ if data.empty?
224
+ @writes.delete( sock )
225
+ return
226
+ end
227
+
228
+ begin
229
+ written = sock.write_nonblock( data )
230
+ rescue IO::WaitWritable, Errno::EINTR, IO::WaitReadable
231
+ return
232
+ rescue Exception => e
233
+ add_closing( sock )
234
+ return
235
+ end
236
+
237
+ data = data[ written..-1 ]
238
+ info[ from ] = data
239
+ end
240
+
241
+ def write_app( sock )
242
+ if @closings.include?( sock )
243
+ close_sock( sock )
244
+ @app = nil
245
+
246
+ if @p1 && !@p1.closed?
247
+ add_closing( @p1 )
248
+ end
249
+
250
+ @closings.delete( sock )
251
+ return
252
+ end
253
+
254
+ info = @infos[ sock ]
255
+ data, from = get_buff( info )
256
+
257
+ if data.empty?
258
+ @writes.delete( sock )
259
+ return
260
+ end
261
+
262
+ begin
263
+ written = sock.write_nonblock( data )
264
+ rescue IO::WaitWritable, Errno::EINTR, IO::WaitReadable
265
+ return
266
+ rescue Exception => e
267
+ add_closing( sock )
268
+ return
269
+ end
270
+
271
+ data = data[ written..-1 ]
272
+ info[ from ] = data
273
+ end
274
+
275
+ def get_buff( info )
276
+ data, from = info[ :cache ], :cache
277
+
278
+ if data.empty?
279
+ if info[ :chunks ].any?
280
+ path = File.join( info[ :chunk_dir ], info[ :chunks ].shift )
281
+ data = info[ :cache ] = IO.binread( path )
282
+
283
+ begin
284
+ File.delete( path )
285
+ rescue Errno::ENOENT
286
+ end
287
+ else
288
+ data, from = info[ :wbuff ], :wbuff
289
+ end
290
+ end
291
+
292
+ [ data, from ]
293
+ end
294
+
295
+ def add_closing( sock, need_renew = false )
296
+ unless @closings.include?( sock )
297
+ @closings[ sock ] = need_renew
298
+ end
299
+
300
+ add_write( sock )
301
+ end
302
+
303
+ def add_write( sock, data = nil, need_chunk = false )
304
+ if data
305
+ info = @infos[ sock ]
306
+ info[ :wbuff ] << data
307
+
308
+ if need_chunk && info[ :wbuff ].size >= CHUNK_SIZE
309
+ filename = [ info[ :filename ], info[ :chunk_seed ] ].join( '.' )
310
+ chunk_path = File.join( info[ :chunk_dir ], filename )
311
+ IO.binwrite( chunk_path, info[ :wbuff ] )
312
+ info[ :chunks ] << filename
313
+ info[ :chunk_seed ] += 1
314
+ info[ :wbuff ].clear
315
+ end
316
+ end
317
+
318
+ unless @writes.include?( sock )
319
+ @writes << sock
320
+ end
321
+ end
322
+
323
+ def close_sock( sock )
324
+ sock.close
325
+ @roles.delete( sock )
326
+ @reads.delete( sock )
327
+ @writes.delete( sock )
328
+ info = @infos.delete( sock )
329
+
330
+ if info && info[ :chunks ]
331
+ info[ :chunks ].each do | filename |
332
+ begin
333
+ File.delete( File.join( info[ :chunk_dir ], filename ) )
334
+ rescue Errno::ENOENT
335
+ end
336
+ end
337
+ end
338
+
339
+ info
340
+ end
341
+
342
+ def new_room
343
+ room = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
344
+ room.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
345
+ room.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
346
+
347
+ begin
348
+ room.connect_nonblock( @roomd_sockaddr )
349
+ rescue IO::WaitWritable, Errno::EINTR
350
+ end
351
+
352
+ room_info = {
353
+ p2_sockaddr: nil,
354
+ rep2p: 0
355
+ }
356
+ @room = room
357
+ @room_info = room_info
358
+ @roles[ room ] = :room
359
+ @infos[ room ] = room_info
360
+ @reads << room
361
+
362
+ bytes = @title.unpack( "C*" ).map{ | c | c.chr }.join
363
+ @room.write( [ [ SET_TITLE, bytes.size ].pack( 'Cn' ), bytes ].join )
364
+ end
365
+
366
+ def new_p1
367
+ p1 = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
368
+ p1.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
369
+ p1.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
370
+ p1.bind( @room.local_address ) # use the hole
371
+
372
+ begin
373
+ p1.connect_nonblock( @room_info[ :p2_sockaddr ] )
374
+ rescue IO::WaitWritable, Errno::EINTR
375
+ rescue Exception => e
376
+ puts "connect p2 #{ e.class } #{ Time.new }"
377
+ p1.close
378
+ return
379
+ end
380
+
381
+ p1_info = {
382
+ wbuff: '',
383
+ cache: '',
384
+ filename: [ Process.pid, p1.object_id ].join( '-' ),
385
+ chunk_dir: @p1_chunk_dir,
386
+ chunks: [],
387
+ chunk_seed: 0,
388
+ need_decode: true
389
+ }
390
+ @p1 = p1
391
+ @p1_info = p1_info
392
+ @roles[ p1 ] = :p1
393
+ @infos[ p1 ] = p1_info
394
+ @reads << p1
395
+ end
396
+ end
397
+ end
@@ -0,0 +1,423 @@
1
+ require 'p2p2/head'
2
+ require 'p2p2/hex'
3
+ require 'p2p2/version'
4
+ require 'socket'
5
+
6
+ ##
7
+ # P2p2::P2 - 处于各自nat里的两端p2p。p2端。
8
+ #
9
+ module P2p2
10
+ class P2
11
+
12
+ ##
13
+ # roomd_host 匹配服务器ip
14
+ # roomd_port 匹配服务器端口
15
+ # appd_host '0.0.0.0',或者只允许本地访问:'127.0.0.1'
16
+ # appd_port 代理p1的应用端口
17
+ # title 约定的房间名
18
+ # app_chunk_dir 文件缓存目录,缓存app来不及写的流量
19
+ # p2_chunk_dir 文件缓存目录,缓存p2来不及写的流量
20
+ def initialize( roomd_host, roomd_port, appd_host, appd_port, title, app_chunk_dir = '/tmp', p2_chunk_dir = '/tmp' )
21
+ @roomd_sockaddr = Socket.sockaddr_in( roomd_port, roomd_host )
22
+ @appd_sockaddr = Socket.sockaddr_in( appd_port, appd_host )
23
+ @title = title
24
+ @app_chunk_dir = app_chunk_dir
25
+ @p2_chunk_dir = p2_chunk_dir
26
+ @hex = P2p2::Hex.new
27
+ @mutex = Mutex.new
28
+ @roles = {} # sock => :appd / :app / :room / :p2
29
+ @infos = {}
30
+ @closings = {} # sock => need_renew
31
+ @reads = []
32
+ @writes = []
33
+ @is_renew = false
34
+
35
+ new_appd
36
+ end
37
+
38
+ def looping
39
+ puts 'looping'
40
+
41
+ loop do
42
+ rs, ws = IO.select( @reads, @writes )
43
+
44
+ @mutex.synchronize do
45
+ rs.each do | sock |
46
+ case @roles[ sock ]
47
+ when :appd
48
+ read_appd( sock )
49
+ when :app
50
+ read_app( sock )
51
+ when :room
52
+ read_room( sock )
53
+ when :p2
54
+ read_p2( sock )
55
+ end
56
+ end
57
+
58
+ ws.each do | sock |
59
+ case @roles[ sock ]
60
+ when :p2
61
+ write_p2( sock )
62
+ when :app
63
+ write_app( sock )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ rescue Interrupt => e
69
+ puts e.class
70
+ quit!
71
+ end
72
+
73
+ def quit!
74
+ exit
75
+ end
76
+
77
+ private
78
+
79
+ def loop_heartbeat
80
+ Thread.new do
81
+ loop do
82
+ sleep 59
83
+
84
+ @mutex.synchronize do
85
+ if @room
86
+ @room.write( [ HEARTBEAT ].pack( 'C' ) )
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def read_appd( sock )
94
+ begin
95
+ app, addr = sock.accept_nonblock
96
+ rescue IO::WaitReadable, Errno::EINTR
97
+ return
98
+ end
99
+
100
+ if @app && !@app.closed?
101
+ puts "app already exist, ignore"
102
+ app.close
103
+ return
104
+ end
105
+
106
+ app_info = {
107
+ wbuff: '',
108
+ cache: '',
109
+ filename: [ Process.pid, app.object_id ].join( '-' ),
110
+ chunk_dir: @app_chunk_dir,
111
+ chunks: [],
112
+ chunk_seed: 0,
113
+ need_encode: true,
114
+ rbuff: ''
115
+ }
116
+ @app = app
117
+ @app_info = app_info
118
+ @roles[ app ] = :app
119
+ @infos[ app ] = app_info
120
+ @reads << app
121
+
122
+ new_room
123
+ end
124
+
125
+ def read_app( sock )
126
+ begin
127
+ data = sock.read_nonblock( PACK_SIZE )
128
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable => e
129
+ return
130
+ rescue Exception => e
131
+ add_closing( sock )
132
+ return
133
+ end
134
+
135
+ info = @infos[ sock ]
136
+
137
+ if info[ :need_encode ]
138
+ data = @hex.encode( data )
139
+ data = [ [ data.size ].pack( 'n' ), data ].join
140
+ info[ :need_encode ] = false
141
+ end
142
+
143
+ if @p2
144
+ add_write( @p2, data, NEED_CHUNK )
145
+ else
146
+ info[ :rbuff ] << data
147
+ end
148
+ end
149
+
150
+ def read_room( sock )
151
+ begin
152
+ data = sock.read_nonblock( PACK_SIZE )
153
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable => e
154
+ return
155
+ rescue EOFError, Errno::ECONNRESET => e
156
+ puts "read room #{ e.class } #{ Time.new }"
157
+
158
+ if @is_renew
159
+ raise e
160
+ end
161
+
162
+ close_sock( @room )
163
+ sleep 5
164
+ new_room
165
+ @is_renew = true
166
+ return
167
+ end
168
+
169
+ @is_renew = false
170
+
171
+ if @p2
172
+ puts 'p2 already exist, ignore'
173
+ return
174
+ end
175
+
176
+ info = @infos[ sock ]
177
+ info[ :p1_sockaddr ] = data
178
+ new_p2
179
+ end
180
+
181
+ def read_p2( sock )
182
+ begin
183
+ data = sock.read_nonblock( PACK_SIZE )
184
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable => e
185
+ return
186
+ rescue Errno::ECONNREFUSED => e
187
+ if @room_info[ :rep2p ] >= REP2P_LIMIT
188
+ raise e
189
+ end
190
+
191
+ add_closing( sock, NEED_RENEW )
192
+ return
193
+ rescue Exception => e
194
+ add_closing( sock )
195
+ return
196
+ end
197
+
198
+ info = @infos[ sock ]
199
+
200
+ if info[ :need_decode ]
201
+ len = data[ 0, 2 ].unpack( 'n' ).first
202
+ head = @hex.decode( data[ 2, len ] )
203
+ data = head + data[ ( 2 + len )..-1 ]
204
+ info[ :need_decode ] = false
205
+ end
206
+
207
+ add_write( @app, data, NEED_CHUNK )
208
+ end
209
+
210
+ def write_p2( sock )
211
+ if @closings.include?( sock )
212
+ close_sock( sock )
213
+ @p2 = nil
214
+
215
+ if @app && !@app.closed?
216
+ add_closing( @app )
217
+ end
218
+
219
+ need_renew = @closings.delete( sock )
220
+
221
+ if need_renew
222
+ sleep 1
223
+ new_p2
224
+ @room_info[ :rep2p ] += 1
225
+ end
226
+
227
+ return
228
+ end
229
+
230
+ info = @infos[ sock ]
231
+ data, from = get_buff( info )
232
+
233
+ if data.empty?
234
+ @writes.delete( sock )
235
+ return
236
+ end
237
+
238
+ begin
239
+ written = sock.write_nonblock( data )
240
+ rescue IO::WaitWritable, Errno::EINTR, IO::WaitReadable
241
+ return
242
+ rescue Exception => e
243
+ add_closing( sock )
244
+ return
245
+ end
246
+
247
+ data = data[ written..-1 ]
248
+ info[ from ] = data
249
+ end
250
+
251
+ def write_app( sock )
252
+ if @closings.include?( sock )
253
+ close_sock( sock )
254
+ @app = nil
255
+
256
+ if @p2 && !@p2.closed?
257
+ add_closing( @p2 )
258
+ end
259
+
260
+ @closings.delete( sock )
261
+ return
262
+ end
263
+
264
+ info = @infos[ sock ]
265
+ data, from = get_buff( info )
266
+
267
+ if data.empty?
268
+ @writes.delete( sock )
269
+ return
270
+ end
271
+
272
+ begin
273
+ written = sock.write_nonblock( data )
274
+ rescue IO::WaitWritable, Errno::EINTR, IO::WaitReadable
275
+ return
276
+ rescue Exception => e
277
+ add_closing( sock )
278
+ return
279
+ end
280
+
281
+ data = data[ written..-1 ]
282
+ info[ from ] = data
283
+ end
284
+
285
+ def get_buff( info )
286
+ data, from = info[ :cache ], :cache
287
+
288
+ if data.empty?
289
+ if info[ :chunks ].any?
290
+ path = File.join( info[ :chunk_dir ], info[ :chunks ].shift )
291
+ data = info[ :cache ] = IO.binread( path )
292
+
293
+ begin
294
+ File.delete( path )
295
+ rescue Errno::ENOENT
296
+ end
297
+ else
298
+ data, from = info[ :wbuff ], :wbuff
299
+ end
300
+ end
301
+
302
+ [ data, from ]
303
+ end
304
+
305
+ def add_closing( sock, need_renew = false )
306
+ unless @closings.include?( sock )
307
+ @closings[ sock ] = need_renew
308
+ end
309
+
310
+ add_write( sock )
311
+ end
312
+
313
+ def add_write( sock, data = nil, need_chunk = false )
314
+ if data
315
+ info = @infos[ sock ]
316
+ info[ :wbuff ] << data
317
+
318
+ if need_chunk && info[ :wbuff ].size >= CHUNK_SIZE
319
+ filename = [ info[ :filename ], info[ :chunk_seed ] ].join( '.' )
320
+ chunk_path = File.join( info[ :chunk_dir ], filename )
321
+ IO.binwrite( chunk_path, info[ :wbuff ] )
322
+ info[ :chunks ] << filename
323
+ info[ :chunk_seed ] += 1
324
+ info[ :wbuff ].clear
325
+ end
326
+ end
327
+
328
+ unless @writes.include?( sock )
329
+ @writes << sock
330
+ end
331
+ end
332
+
333
+ def close_sock( sock )
334
+ sock.close
335
+ @roles.delete( sock )
336
+ @reads.delete( sock )
337
+ @writes.delete( sock )
338
+ info = @infos.delete( sock )
339
+
340
+ if info && info[ :chunks ]
341
+ info[ :chunks ].each do | filename |
342
+ begin
343
+ File.delete( File.join( info[ :chunk_dir ], filename ) )
344
+ rescue Errno::ENOENT
345
+ end
346
+ end
347
+ end
348
+
349
+ info
350
+ end
351
+
352
+ def new_appd
353
+ appd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
354
+ appd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
355
+ appd.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
356
+ appd.bind( @appd_sockaddr )
357
+ appd.listen( 511 )
358
+
359
+ @appd = appd
360
+ @roles[ appd ] = :appd
361
+ @reads << appd
362
+ end
363
+
364
+ def new_room
365
+ room = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
366
+ room.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
367
+ room.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
368
+
369
+ begin
370
+ room.connect_nonblock( @roomd_sockaddr )
371
+ rescue IO::WaitWritable, Errno::EINTR
372
+ end
373
+
374
+ room_info = {
375
+ p1_sockaddr: nil,
376
+ rep2p: 0
377
+ }
378
+ @room = room
379
+ @room_info = room_info
380
+ @roles[ room ] = :room
381
+ @infos[ room ] = room_info
382
+ @reads << room
383
+
384
+ bytes = @title.unpack( "C*" ).map{ | c | c.chr }.join
385
+ @room.write( [ [ PAIRING, bytes.size ].pack( 'Cn' ), bytes ].join )
386
+ end
387
+
388
+ def new_p2
389
+ p2 = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
390
+ p2.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
391
+ p2.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
392
+ p2.bind( @room.local_address ) # use the hole
393
+
394
+ begin
395
+ p2.connect_nonblock( @room_info[ :p1_sockaddr ] )
396
+ rescue IO::WaitWritable, Errno::EINTR
397
+ rescue Exception => e
398
+ puts "connect p1 #{ e.class } #{ Time.new }"
399
+ p2.close
400
+ return
401
+ end
402
+
403
+ p2_info = {
404
+ wbuff: @app_info[ :rbuff ],
405
+ cache: '',
406
+ filename: [ Process.pid, p2.object_id ].join( '-' ),
407
+ chunk_dir: @p2_chunk_dir,
408
+ chunks: [],
409
+ chunk_seed: 0,
410
+ need_decode: true
411
+ }
412
+ @p2 = p2
413
+ @p2_info = p2_info
414
+ @roles[ p2 ] = :p2
415
+ @infos[ p2 ] = p2_info
416
+ @reads << p2
417
+
418
+ unless p2_info[ :wbuff ].empty?
419
+ @writes << p2
420
+ end
421
+ end
422
+ end
423
+ end
@@ -0,0 +1,222 @@
1
+ require 'p2p2/head'
2
+ require 'p2p2/version'
3
+ require 'socket'
4
+
5
+ ##
6
+ # P2p2::P2pd - 处于各自nat里的两端p2p。匹配服务器端。
7
+ #
8
+ #```
9
+ # p2pd p2pd
10
+ # ^ ^
11
+ # ^ ^
12
+ # ssh --> p2 --> encode --> nat --> nat --> p1 --> decode --> sshd
13
+ #
14
+ #```
15
+ #
16
+ # usage
17
+ # =====
18
+ #
19
+ # 1. Girl::P2pd.new( 5050 ).looping # @server
20
+ #
21
+ # 2. Girl::P1.new( 'your.server.ip', 5050, '127.0.0.1', 22, '周立波' ).looping # @home1
22
+ #
23
+ # 3. Girl::P2.new( 'your.server.ip', 5050, '0.0.0.0', 2222, '周立波' ).looping # @home2
24
+ #
25
+ # 4. ssh -p2222 libo@localhost
26
+ #
27
+ module P2p2
28
+ class P2pd
29
+
30
+ ##
31
+ # roomd_port 配对服务器端口
32
+ # roomd_dir 可在该目录下看到所有的p1
33
+ def initialize( roomd_port = 5050, roomd_dir = '/tmp' )
34
+ @roomd_port = roomd_port
35
+ @roomd_dir = roomd_dir
36
+ @mutex = Mutex.new
37
+ @roles = {} # sock => :roomd / :room
38
+ @pending_p1s = {} # title => room
39
+ @pending_p2s = {} # title => room
40
+ @infos = {}
41
+ @reads = []
42
+
43
+ new_roomd
44
+ end
45
+
46
+ def looping
47
+ puts 'looping'
48
+
49
+ loop_expire
50
+
51
+ loop do
52
+ rs, _ = IO.select( @reads )
53
+
54
+ @mutex.synchronize do
55
+ rs.each do | sock |
56
+ case @roles[ sock ]
57
+ when :roomd
58
+ read_roomd( sock )
59
+ when :room
60
+ read_room( sock )
61
+ end
62
+ end
63
+ end
64
+ end
65
+ rescue Interrupt => e
66
+ puts e.class
67
+ quit!
68
+ end
69
+
70
+ def quit!
71
+ exit
72
+ end
73
+
74
+ private
75
+
76
+ def loop_expire
77
+ Thread.new do
78
+ loop do
79
+ sleep 900
80
+
81
+ if @infos.any?
82
+ @mutex.synchronize do
83
+ now = Time.new
84
+
85
+ @infos.select{ | _, info | info[ :last_coming_at ] && ( now - info[ :last_coming_at ] > 1800 ) }.each do | room, _ |
86
+ close_sock( room )
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def read_roomd( sock )
95
+ begin
96
+ room, addr = sock.accept_nonblock
97
+ rescue IO::WaitReadable, Errno::EINTR
98
+ return
99
+ end
100
+
101
+ @roles[ room ] = :room
102
+ @infos[ room ] = {
103
+ title: nil,
104
+ last_coming_at: nil,
105
+ sockaddr: addr.to_sockaddr
106
+ }
107
+ @reads << room
108
+ end
109
+
110
+ def read_room( sock )
111
+ begin
112
+ data = sock.read_nonblock( PACK_SIZE )
113
+ rescue IO::WaitReadable, Errno::EINTR, IO::WaitWritable
114
+ return
115
+ rescue Exception => e
116
+ close_sock( sock )
117
+ return
118
+ end
119
+
120
+ info = @infos[ sock ]
121
+ info[ :last_coming_at ] = Time.new
122
+
123
+ until data.empty?
124
+ ctl_num = data[ 0 ].unpack( 'C' ).first
125
+
126
+ case ctl_num
127
+ when HEARTBEAT
128
+ data = data[ 1..-1 ]
129
+ when SET_TITLE
130
+ len = data[ 1, 2 ].unpack( 'n' ).first
131
+
132
+ if len > 255
133
+ puts "title too long"
134
+ close_sock( sock )
135
+ return
136
+ end
137
+
138
+ title = data[ 3, len ]
139
+
140
+ if @pending_p2s.include?( title )
141
+ p2 = @pending_p2s[ title ]
142
+ p2_info = @infos[ p2 ]
143
+ sock.write( p2_info[ :sockaddr ] )
144
+ p2.write( info[ :sockaddr ] )
145
+ elsif @pending_p1s.include?( title )
146
+ puts "pending p1 #{ title.inspect } already exist"
147
+ close_sock( sock )
148
+ return
149
+ else
150
+ @pending_p1s[ title ] = sock
151
+ info[ :title ] = title
152
+
153
+ begin
154
+ File.open( File.join( @roomd_dir, title ), 'w' )
155
+ rescue Errno::ENOENT, ArgumentError => e
156
+ puts "open title path #{ e.class }"
157
+ close_sock( sock )
158
+ return
159
+ end
160
+ end
161
+
162
+ data = data[ ( 3 + len )..-1 ]
163
+ when PAIRING
164
+ len = data[ 1, 2 ].unpack( 'n' ).first
165
+
166
+ if len > 255
167
+ puts 'pairing title too long'
168
+ close_sock( sock )
169
+ return
170
+ end
171
+
172
+ title = data[ 3, len ]
173
+
174
+ if @pending_p1s.include?( title )
175
+ p1 = @pending_p1s[ title ]
176
+ p1_info = @infos[ p1 ]
177
+ sock.write( p1_info[ :sockaddr ] )
178
+ p1.write( info[ :sockaddr ] )
179
+ elsif @pending_p2s.include?( title )
180
+ puts "pending p2 #{ title.inspect } already exist"
181
+ close_sock( sock )
182
+ return
183
+ else
184
+ @pending_p2s[ title ] = sock
185
+ info[ :title ] = title
186
+ end
187
+
188
+ data = data[ ( 3 + len )..-1 ]
189
+ end
190
+ end
191
+ end
192
+
193
+ def close_sock( sock )
194
+ sock.close
195
+ @roles.delete( sock )
196
+ @reads.delete( sock )
197
+ info = @infos.delete( sock )
198
+
199
+ if info && info[ :title ]
200
+ @pending_p1s.delete( info[ :title ] )
201
+ @pending_p2s.delete( info[ :title ] )
202
+
203
+ begin
204
+ File.delete( File.join( @roomd_dir, info[ :title ] ) )
205
+ rescue Errno::ENOENT
206
+ end
207
+ end
208
+
209
+ info
210
+ end
211
+
212
+ def new_roomd
213
+ roomd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
214
+ roomd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
215
+ roomd.bind( Socket.pack_sockaddr_in( @roomd_port, '0.0.0.0' ) )
216
+ roomd.listen( 511 )
217
+
218
+ @roles[ roomd ] = :roomd
219
+ @reads << roomd
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,3 @@
1
+ module P2p2
2
+ VERSION = "0.5.9"
3
+ end
@@ -0,0 +1,29 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "p2p2/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "p2p2"
8
+ spec.version = P2p2::VERSION
9
+ spec.authors = ["takafan"]
10
+ spec.email = ["qqtakafan@gmail.com"]
11
+
12
+ spec.summary = %q{p2p}
13
+ spec.description = %q{p2p even both sides under its nat.}
14
+ spec.homepage = "https://github.com/takafan/p2p2"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = %w[
18
+ p2p2.gemspec
19
+ lib/p2p2.rb
20
+ lib/p2p2/head.rb
21
+ lib/p2p2/hex.rb
22
+ lib/p2p2/p1.rb
23
+ lib/p2p2/p2.rb
24
+ lib/p2p2/p2pd.rb
25
+ lib/p2p2/version.rb
26
+ ]
27
+
28
+ spec.require_paths = ["lib"]
29
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: p2p2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.9
5
+ platform: ruby
6
+ authors:
7
+ - takafan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: p2p even both sides under its nat.
14
+ email:
15
+ - qqtakafan@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/p2p2.rb
21
+ - lib/p2p2/head.rb
22
+ - lib/p2p2/hex.rb
23
+ - lib/p2p2/p1.rb
24
+ - lib/p2p2/p2.rb
25
+ - lib/p2p2/p2pd.rb
26
+ - lib/p2p2/version.rb
27
+ - p2p2.gemspec
28
+ homepage: https://github.com/takafan/p2p2
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.0.3
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: p2p
51
+ test_files: []