p2p2 0.5.9

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