p2p2 0.15.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/p2p2/p2.rb CHANGED
@@ -1,1020 +1,1020 @@
1
- require 'p2p2/head'
2
- require 'p2p2/hex'
3
- require 'p2p2/version'
4
- require 'socket'
5
-
6
- ##
7
- # P2p2::P2 - 内网里的任意应用,访问另一个内网里的应用服务端。p2端。
8
- #
9
- # 两套关闭
10
- # ========
11
- #
12
- # 1-1. app.close -> ext.is_shadow_closed ? no -> send fin1 loop
13
- # 1-2. recv got_fin1 -> break loop
14
- # 1-3. recv fin2 -> send got_fin2 -> del ext
15
- #
16
- # 2-1. recv fin1 -> send got_fin1 -> ext.is_shadow_closed = true
17
- # 2-2. all sent && ext.biggest_shadow_pack_id == ext.continue_shadow_pack_id -> add closing app
18
- # 2-3. app.close -> ext.is_shadow_closed ? yes -> del ext -> loop send fin2
19
- # 2-4. recv got_fin2 -> break loop
20
- #
21
- module P2p2
22
- class P2
23
-
24
- ##
25
- # p2pd_host 配对服务器ip
26
- # p2pd_port 配对服务器端口
27
- # appd_host 代理地址 不限制访问:'0.0.0.0',或者只允许本地访问:'127.0.0.1'
28
- # appd_port 代理端口
29
- # title 约定的房间名
30
- # app_chunk_dir 文件缓存目录,缓存app来不及写的流量
31
- # p2_chunk_dir 文件缓存目录,缓存p2来不及写的流量
32
- #
33
- def initialize( p2pd_host, p2pd_port, appd_host, appd_port, title, app_chunk_dir = '/tmp', p2_chunk_dir = '/tmp' )
34
- @p2pd_sockaddr = Socket.sockaddr_in( p2pd_port, p2pd_host )
35
- @appd_sockaddr = Socket.sockaddr_in( appd_port, appd_host )
36
- @title = title
37
- @app_chunk_dir = app_chunk_dir
38
- @p2_chunk_dir = p2_chunk_dir
39
- @hex = P2p2::Hex.new
40
- @mutex = Mutex.new
41
- @reads = []
42
- @writes = []
43
- @closings = []
44
- @roles = {} # sock => :ctlr / :appd / :app / :p2
45
- @infos = {} # sock => {}
46
- @socks = {} # sock => sock_id
47
- @sock_ids = {} # sock_id => sock
48
-
49
- ctlr, ctlw = IO.pipe
50
- @ctlw = ctlw
51
- @roles[ ctlr ] = :ctlr
52
- @reads << ctlr
53
- end
54
-
55
- def looping
56
- puts 'looping'
57
-
58
- new_appd
59
-
60
- loop do
61
- rs, ws = IO.select( @reads, @writes )
62
-
63
- @mutex.synchronize do
64
- rs.each do | sock |
65
- case @roles[ sock ]
66
- when :ctlr
67
- read_ctlr( sock )
68
- when :appd
69
- read_appd( sock )
70
- when :app
71
- read_app( sock )
72
- when :p2
73
- read_p2( sock )
74
- end
75
- end
76
-
77
- ws.each do | sock |
78
- case @roles[ sock ]
79
- when :app
80
- write_app( sock )
81
- when :p2
82
- write_p2( sock )
83
- end
84
- end
85
- end
86
- end
87
- rescue Interrupt => e
88
- puts e.class
89
- quit!
90
- end
91
-
92
- def quit!
93
- if @p2 && !@p2.closed? && @p2_info[ :p1_addr ]
94
- ctlmsg = [ 0, P2_FIN ].pack( 'Q>C' )
95
- send_pack( @p2, ctlmsg, @p2_info[ :p1_addr ] )
96
- end
97
-
98
- exit
99
- end
100
-
101
- private
102
-
103
- ##
104
- # read ctlr
105
- #
106
- def read_ctlr( ctlr )
107
- case ctlr.read( 1 ).unpack( 'C' ).first
108
- when CTL_CLOSE
109
- sock_id = ctlr.read( 8 ).unpack( 'Q>' ).first
110
- sock = @sock_ids[ sock_id ]
111
-
112
- if sock
113
- add_closing( sock )
114
- end
115
- when CTL_RESUME
116
- sock_id = ctlr.read( 8 ).unpack( 'Q>' ).first
117
- sock = @sock_ids[ sock_id ]
118
-
119
- if sock
120
- add_write( sock )
121
- end
122
- end
123
- end
124
-
125
- ##
126
- # read appd
127
- #
128
- def read_appd( appd )
129
- begin
130
- app, _ = appd.accept_nonblock
131
- rescue IO::WaitReadable, Errno::EINTR
132
- return
133
- end
134
-
135
- if @p2.nil? || @p2.closed?
136
- new_p2
137
- end
138
-
139
- app_id = @hex.gen_random_num
140
- @roles[ app ] = :app
141
- @infos[ app ] = {
142
- id: app_id,
143
- p2: @p2
144
- }
145
- @socks[ app ] = app_id
146
- @sock_ids[ app_id ] = app
147
-
148
- @p2_info[ :waitings ][ app_id ] = []
149
- @p2_info[ :app_exts ][ app_id ] = {
150
- app: app,
151
- created_at: Time.new,
152
- last_recv_at: nil, # 上一次收到流量的时间
153
- wbuff: '', # 写前缓存
154
- cache: '', # 块读出缓存
155
- chunks: [], # 块队列,写前达到块大小时结一个块 filename
156
- spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
157
- wmems: {}, # 写后缓存 pack_id => data
158
- send_ats: {}, # 上一次发出时间 pack_id => send_at
159
- biggest_pack_id: 0, # 发到几
160
- continue_shadow_pack_id: 0, # 收到几
161
- pieces: {}, # 跳号包 shadow_pack_id => data
162
- is_shadow_closed: false, # 对面是否已关闭
163
- biggest_shadow_pack_id: 0, # 对面发到几
164
- completed_pack_id: 0, # 完成到几(对面收到几)
165
- last_traffic_at: nil # 收到有效流量,或者发出流量的时间戳
166
- }
167
-
168
- add_read( app )
169
- loop_send_a_new_app( app )
170
- end
171
-
172
- ##
173
- # read app
174
- #
175
- def read_app( app )
176
- begin
177
- data = app.read_nonblock( PACK_SIZE )
178
- rescue IO::WaitReadable, Errno::EINTR
179
- return
180
- rescue Exception => e
181
- add_closing( app )
182
- return
183
- end
184
-
185
- info = @infos[ app ]
186
- p2 = info[ :p2 ]
187
-
188
- if p2.closed?
189
- add_closing( app )
190
- return
191
- end
192
-
193
- app_id = @socks[ app ]
194
- p2_info = @infos[ p2 ]
195
- shadow_id = p2_info[ :app_ids ][ app_id ]
196
-
197
- if p2_info[ :p1_addr ].nil? || shadow_id.nil?
198
- p2_info[ :waitings ][ app_id ] << data
199
- return
200
- end
201
-
202
- p2_info[ :wbuffs ] << [ app_id, data ]
203
-
204
- if p2_info[ :wbuffs ].size >= WBUFFS_LIMIT
205
- p2_id = @socks[ p2 ]
206
- spring = p2_info[ :chunks ].size > 0 ? ( p2_info[ :spring ] + 1 ) : 0
207
- filename = "#{ p2_id }.#{ spring }"
208
- chunk_path = File.join( @p2_chunk_dir, filename )
209
- IO.binwrite( chunk_path, p2_info[ :wbuffs ].map{ | app_id, data | "#{ [ app_id, data.bytesize ].pack( 'Q>n' ) }#{ data }" }.join )
210
- p2_info[ :chunks ] << filename
211
- p2_info[ :spring ] = spring
212
- p2_info[ :wbuffs ].clear
213
- end
214
-
215
- unless p2_info[ :paused ]
216
- add_write( p2 )
217
- end
218
- end
219
-
220
- ##
221
- # read p2
222
- #
223
- def read_p2( p2 )
224
- data, addrinfo, rflags, *controls = p2.recvmsg
225
- sockaddr = addrinfo.to_sockaddr
226
- now = Time.new
227
- info = @infos[ p2 ]
228
- shadow_id = data[ 0, 8 ].unpack( 'Q>' ).first
229
-
230
- if shadow_id == 0
231
- case data[ 8 ].unpack( 'C' ).first
232
- when PEER_ADDR
233
- return if info[ :peer_addr ] || ( sockaddr != @p2pd_sockaddr )
234
-
235
- info[ :peer_addr ] = data[ 9..-1 ]
236
- # puts "debug peer addr #{ Addrinfo.new( info[ :peer_addr ] ).ip_unpack.inspect } #{ Time.new }"
237
- loop_punch_peer( p2 )
238
- when HEARTBEAT
239
- return if info[ :p1_addr ] || ( sockaddr != info[ :peer_addr ] )
240
-
241
- info[ :p1_addr ] = sockaddr
242
- # puts "debug p1 addr #{ Addrinfo.new( info[ :p1_addr ] ).ip_unpack.inspect } #{ Time.new }"
243
- info[ :last_traffic_at ] = now
244
- loop_send_heartbeat( p2 )
245
- loop_check_expire( p2 )
246
- loop_send_status( p2 )
247
- when PAIRED
248
- return if sockaddr != info[ :p1_addr ]
249
-
250
- app_id, shadow_id = data[ 9, 16 ].unpack( 'Q>Q>' )
251
- return if info[ :app_ids ].include?( app_id )
252
-
253
- # puts "debug got PAIRED #{ app_id } #{ shadow_id } #{ Time.new }"
254
- info[ :app_ids ][ app_id ] = shadow_id
255
- info[ :shadow_ids ][ shadow_id ] = app_id
256
- buffs = info[ :waitings ][ app_id ]
257
-
258
- if buffs.any?
259
- # puts "debug move #{ buffs.size } waiting buffs to wbuffs #{ Time.new } p#{ Process.pid }"
260
-
261
- buffs.each do | buff |
262
- info[ :wbuffs ] << [ app_id, buff ]
263
- end
264
-
265
- buffs.clear
266
- add_write( p2 )
267
- end
268
- when SHADOW_STATUS
269
- return if sockaddr != info[ :p1_addr ]
270
-
271
- shadow_id, biggest_shadow_pack_id, continue_app_pack_id = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
272
- app_id = info[ :shadow_ids ][ shadow_id ]
273
- return unless app_id
274
-
275
- ext = info[ :app_exts ][ app_id ]
276
- return unless ext
277
-
278
- # 更新对面发到几
279
- if biggest_shadow_pack_id > ext[ :biggest_shadow_pack_id ]
280
- ext[ :biggest_shadow_pack_id ] = biggest_shadow_pack_id
281
- end
282
-
283
- # 更新对面收到几,释放写后
284
- if continue_app_pack_id > ext[ :completed_pack_id ]
285
- pack_ids = ext[ :wmems ].keys.select { | pack_id | pack_id <= continue_app_pack_id }
286
-
287
- pack_ids.each do | pack_id |
288
- ext[ :wmems ].delete( pack_id )
289
- ext[ :send_ats ].delete( pack_id )
290
- end
291
-
292
- ext[ :completed_pack_id ] = continue_app_pack_id
293
- end
294
-
295
- if ext[ :is_shadow_closed ] && ( ext[ :biggest_shadow_pack_id ] == ext[ :continue_shadow_pack_id ] )
296
- add_write( ext[ :app ] )
297
- return
298
- end
299
-
300
- # 发miss
301
- if !ext[ :app ].closed? && ( ext[ :continue_shadow_pack_id ] < ext[ :biggest_shadow_pack_id ] )
302
- ranges = []
303
- curr_pack_id = ext[ :continue_shadow_pack_id ] + 1
304
-
305
- ext[ :pieces ].keys.sort.each do | pack_id |
306
- if pack_id > curr_pack_id
307
- ranges << [ curr_pack_id, pack_id - 1 ]
308
- end
309
-
310
- curr_pack_id = pack_id + 1
311
- end
312
-
313
- if curr_pack_id <= ext[ :biggest_shadow_pack_id ]
314
- ranges << [ curr_pack_id, ext[ :biggest_shadow_pack_id ] ]
315
- end
316
-
317
- pack_count = 0
318
- # puts "debug #{ ext[ :continue_shadow_pack_id ] }/#{ ext[ :biggest_shadow_pack_id ] } send MISS #{ ranges.size }"
319
- ranges.each do | pack_id_begin, pack_id_end |
320
- if pack_count >= BREAK_SEND_MISS
321
- puts "break send miss at #{ pack_id_begin } #{ Time.new }"
322
- break
323
- end
324
-
325
- ctlmsg = [
326
- 0,
327
- MISS,
328
- shadow_id,
329
- pack_id_begin,
330
- pack_id_end
331
- ].pack( 'Q>CQ>Q>Q>' )
332
-
333
- send_pack( p2, ctlmsg, info[ :p1_addr ] )
334
- pack_count += ( pack_id_end - pack_id_begin + 1 )
335
- end
336
- end
337
- when MISS
338
- return if sockaddr != info[ :p1_addr ]
339
-
340
- app_id, pack_id_begin, pack_id_end = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
341
- ext = info[ :app_exts ][ app_id ]
342
- return unless ext
343
-
344
- ( pack_id_begin..pack_id_end ).each do | pack_id |
345
- send_at = ext[ :send_ats ][ pack_id ]
346
-
347
- if send_at
348
- break if now - send_at < STATUS_INTERVAL
349
-
350
- info[ :resendings ] << [ app_id, pack_id ]
351
- end
352
- end
353
-
354
- add_write( p2 )
355
- when FIN1
356
- return if sockaddr != info[ :p1_addr ]
357
-
358
- shadow_id = data[ 9, 8 ].unpack( 'Q>' ).first
359
- ctlmsg = [
360
- 0,
361
- GOT_FIN1,
362
- shadow_id
363
- ].pack( 'Q>CQ>' )
364
-
365
- # puts "debug 2-1. recv fin1 -> send got_fin1 -> ext.is_shadow_closed = true #{ shadow_id } #{ Time.new }"
366
- send_pack( p2, ctlmsg, info[ :p1_addr ] )
367
-
368
- app_id = info[ :shadow_ids ][ shadow_id ]
369
- return unless app_id
370
-
371
- ext = info[ :app_exts ][ app_id ]
372
- return unless ext
373
-
374
- ext[ :is_shadow_closed ] = true
375
- when GOT_FIN1
376
- return if sockaddr != info[ :p1_addr ]
377
-
378
- # puts "debug 1-2. recv got_fin1 -> break loop #{ Time.new }"
379
- app_id = data[ 9, 8 ].unpack( 'Q>' ).first
380
- info[ :fin1s ].delete( app_id )
381
- when FIN2
382
- return if sockaddr != info[ :p1_addr ]
383
-
384
- # puts "debug 1-3. recv fin2 -> send got_fin2 -> del ext #{ Time.new }"
385
- shadow_id = data[ 9, 8 ].unpack( 'Q>' ).first
386
- ctlmsg = [
387
- 0,
388
- GOT_FIN2,
389
- shadow_id
390
- ].pack( 'Q>CQ>' )
391
-
392
- send_pack( p2, ctlmsg, info[ :p1_addr ] )
393
-
394
- app_id = info[ :shadow_ids ][ shadow_id ]
395
- return unless app_id
396
-
397
- del_app_ext( info, app_id )
398
- when GOT_FIN2
399
- return if sockaddr != info[ :p1_addr ]
400
-
401
- # puts "debug 2-4. recv got_fin2 -> break loop #{ Time.new }"
402
- app_id = data[ 9, 8 ].unpack( 'Q>' ).first
403
- info[ :fin2s ].delete( app_id )
404
- when P1_FIN
405
- return if sockaddr != info[ :p1_addr ]
406
- puts "recv p1 fin #{ Time.new }"
407
- add_closing( p2 )
408
- end
409
-
410
- return
411
- end
412
-
413
- return if sockaddr != info[ :p1_addr ]
414
-
415
- app_id = info[ :shadow_ids ][ shadow_id ]
416
- return unless app_id
417
-
418
- ext = info[ :app_exts ][ app_id ]
419
- return if ext.nil? || ext[ :app ].closed?
420
-
421
- pack_id = data[ 8, 8 ].unpack( 'Q>' ).first
422
- return if ( pack_id <= ext[ :continue_shadow_pack_id ] ) || ext[ :pieces ].include?( pack_id )
423
-
424
- data = data[ 16..-1 ]
425
-
426
- # 解混淆
427
- if pack_id == 1
428
- data = @hex.decode( data )
429
- end
430
-
431
- # 放进shadow的写前缓存,跳号放碎片缓存
432
- if pack_id - ext[ :continue_shadow_pack_id ] == 1
433
- while ext[ :pieces ].include?( pack_id + 1 )
434
- data << ext[ :pieces ].delete( pack_id + 1 )
435
- pack_id += 1
436
- end
437
-
438
- ext[ :continue_shadow_pack_id ] = pack_id
439
- ext[ :wbuff ] << data
440
-
441
- if ext[ :wbuff ].bytesize >= CHUNK_SIZE
442
- spring = ext[ :chunks ].size > 0 ? ( ext[ :spring ] + 1 ) : 0
443
- filename = "#{ app_id }.#{ spring }"
444
- chunk_path = File.join( @app_chunk_dir, filename )
445
- IO.binwrite( chunk_path, ext[ :wbuff ] )
446
- ext[ :chunks ] << filename
447
- ext[ :spring ] = spring
448
- ext[ :wbuff ].clear
449
- end
450
-
451
- add_write( ext[ :app ] )
452
- ext[ :last_traffic_at ] = now
453
- info[ :last_traffic_at ] = now
454
- else
455
- ext[ :pieces ][ pack_id ] = data
456
- end
457
-
458
- ext[ :last_recv_at ] = now
459
- end
460
-
461
- ##
462
- # write app
463
- #
464
- def write_app( app )
465
- if @closings.include?( app )
466
- close_app( app )
467
- return
468
- end
469
-
470
- info = @infos[ app ]
471
- p2 = info[ :p2 ]
472
-
473
- if p2.closed?
474
- add_closing( app )
475
- return
476
- end
477
-
478
- p2_info = @infos[ p2 ]
479
- app_id = @socks[ app ]
480
- ext = p2_info[ :app_exts ][ app_id ]
481
-
482
- # 取写前
483
- data = ext[ :cache ]
484
- from = :cache
485
-
486
- if data.empty?
487
- if ext[ :chunks ].any?
488
- path = File.join( @app_chunk_dir, ext[ :chunks ].shift )
489
-
490
- begin
491
- data = IO.binread( path )
492
- File.delete( path )
493
- rescue Errno::ENOENT
494
- add_closing( app )
495
- return
496
- end
497
- else
498
- data = ext[ :wbuff ]
499
- from = :wbuff
500
- end
501
- end
502
-
503
- if data.empty?
504
- if ext[ :is_shadow_closed ] && ( ext[ :biggest_shadow_pack_id ] == ext[ :continue_shadow_pack_id ] )
505
- # puts "debug 2-2. all sent && ext.biggest_shadow_pack_id == ext.continue_shadow_pack_id -> add closing app #{ Time.new }"
506
- add_closing( app )
507
- return
508
- end
509
-
510
- @writes.delete( app )
511
- return
512
- end
513
-
514
- begin
515
- written = app.write_nonblock( data )
516
- rescue IO::WaitWritable, Errno::EINTR => e
517
- ext[ from ] = data
518
- return
519
- rescue Exception => e
520
- add_closing( app )
521
- return
522
- end
523
-
524
- data = data[ written..-1 ]
525
- ext[ from ] = data
526
- end
527
-
528
- ##
529
- # write p2
530
- #
531
- def write_p2( p2 )
532
- if @closings.include?( p2 )
533
- close_p2( p2 )
534
- return
535
- end
536
-
537
- now = Time.new
538
- info = @infos[ p2 ]
539
-
540
- # 重传
541
- while info[ :resendings ].any?
542
- app_id, pack_id = info[ :resendings ].shift
543
- ext = info[ :app_exts ][ app_id ]
544
-
545
- if ext
546
- pack = ext[ :wmems ][ pack_id ]
547
-
548
- if pack
549
- send_pack( p2, pack, info[ :p1_addr ] )
550
- ext[ :last_traffic_at ] = now
551
- info[ :last_traffic_at ] = now
552
- return
553
- end
554
- end
555
- end
556
-
557
- # 若写后达到上限,暂停取写前
558
- if info[ :app_exts ].map{ | _, ext | ext[ :wmems ].size }.sum >= WMEMS_LIMIT
559
- unless info[ :paused ]
560
- puts "pause #{ Time.new }"
561
- info[ :paused ] = true
562
- end
563
-
564
- @writes.delete( p2 )
565
- return
566
- end
567
-
568
- # 取写前
569
- if info[ :caches ].any?
570
- app_id, data = info[ :caches ].shift
571
- elsif info[ :chunks ].any?
572
- path = File.join( @p2_chunk_dir, info[ :chunks ].shift )
573
-
574
- begin
575
- data = IO.binread( path )
576
- File.delete( path )
577
- rescue Errno::ENOENT
578
- add_closing( p2 )
579
- return
580
- end
581
-
582
- caches = []
583
-
584
- until data.empty?
585
- app_id, pack_size = data[ 0, 10 ].unpack( 'Q>n' )
586
- caches << [ app_id, data[ 10, pack_size ] ]
587
- data = data[ ( 10 + pack_size )..-1 ]
588
- end
589
-
590
- app_id, data = caches.shift
591
- info[ :caches ] = caches
592
- elsif info[ :wbuffs ].any?
593
- app_id, data = info[ :wbuffs ].shift
594
- else
595
- @writes.delete( p2 )
596
- return
597
- end
598
-
599
- ext = info[ :app_exts ][ app_id ]
600
-
601
- if ext
602
- pack_id = ext[ :biggest_pack_id ] + 1
603
-
604
- if pack_id == 1
605
- data = @hex.encode( data )
606
- end
607
-
608
- pack = "#{ [ app_id, pack_id ].pack( 'Q>Q>' ) }#{ data }"
609
- send_pack( p2, pack, info[ :p1_addr ] )
610
- ext[ :biggest_pack_id ] = pack_id
611
- ext[ :wmems ][ pack_id ] = pack
612
- ext[ :send_ats ][ pack_id ] = now
613
- ext[ :last_traffic_at ] = now
614
- info[ :last_traffic_at ] = now
615
- end
616
- end
617
-
618
- def new_appd
619
- appd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
620
- appd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
621
- appd.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
622
- appd.bind( @appd_sockaddr )
623
- appd.listen( 511 )
624
-
625
- @roles[ appd ] = :appd
626
- add_read( appd )
627
- end
628
-
629
- def new_p2
630
- p2 = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
631
- p2.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
632
- p2.bind( Socket.sockaddr_in( 0, '0.0.0.0' ) )
633
- p2_id = @hex.gen_random_num
634
- p2_info = {
635
- id: p2_id,
636
- waitings: {}, # 还没连上p1,或者还没配上shadow,暂存流量 app_id => buffs[]
637
- wbuffs: [], # 写前缓存 [ app_id, data ]
638
- caches: [], # 块读出缓存 [ app_id, data ]
639
- chunks: [], # 块队列 filename
640
- spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
641
- peer_addr: nil, # 对面地址,连发心跳包打洞,离p2pd近的一方通常会撞死几个直到对面出来
642
- p1_addr: nil, # p1地址
643
- app_exts: {}, # 传输相关 app_id => {}
644
- app_ids: {}, # app_id => shadow_id
645
- shadow_ids: {}, # shadow_id => app_id
646
- fin1s: [], # fin1: app已关闭,等待对面收完流量 app_id
647
- fin2s: [], # fin2: 流量已收完 app_id
648
- paused: false, # 是否暂停写
649
- resendings: [], # 重传队列 [ app_id, pack_id ]
650
- last_traffic_at: nil # 收到有效流量,或者发出流量的时间戳
651
- }
652
-
653
- @p2 = p2
654
- @p2_info = p2_info
655
- @roles[ p2 ] = :p2
656
- @infos[ p2 ] = p2_info
657
- @socks[ p2 ] = p2_id
658
- @sock_ids[ p2_id ] = p2
659
-
660
- send_pack( p2, @title, @p2pd_sockaddr )
661
- add_read( p2 )
662
- loop_send_char( p2 )
663
- end
664
-
665
- def loop_send_char( p2 )
666
- Thread.new do
667
- is_timeout = true
668
-
669
- 20.times do
670
- sleep HEARTBEAT_INTERVAL
671
-
672
- if p2.closed?
673
- is_timeout = false
674
- break
675
- end
676
-
677
- p2_info = @infos[ p2 ]
678
-
679
- if p2_info[ :peer_addr ]
680
- is_timeout = false
681
- break
682
- end
683
-
684
- @mutex.synchronize do
685
- send_pack( p2, [ rand( 128 ) ].pack( 'C' ), @p2pd_sockaddr )
686
- end
687
- end
688
-
689
- if is_timeout
690
- @mutex.synchronize do
691
- p2_id = @socks[ p2 ]
692
- @ctlw.write( [ CTL_CLOSE, p2_id ].pack( 'CQ>' ) )
693
- end
694
- end
695
- end
696
- end
697
-
698
- def loop_punch_peer( p2 )
699
- Thread.new do
700
- 20.times do
701
- break if p2.closed?
702
-
703
- p2_info = @infos[ p2 ]
704
-
705
- @mutex.synchronize do
706
- send_heartbeat( p2, p2_info[ :peer_addr ] )
707
- end
708
-
709
- sleep STATUS_INTERVAL
710
- end
711
-
712
- if !p2.closed?
713
- p2_info = @infos[ p2 ]
714
-
715
- unless p2_info[ :p1_addr ]
716
- @mutex.synchronize do
717
- p2_id = @socks[ p2 ]
718
- @ctlw.write( [ CTL_CLOSE, p2_id ].pack( 'CQ>' ) )
719
- end
720
- end
721
- end
722
- end
723
- end
724
-
725
- def loop_send_heartbeat( p2 )
726
- Thread.new do
727
- loop do
728
- sleep HEARTBEAT_INTERVAL
729
- break if p2.closed?
730
-
731
- @mutex.synchronize do
732
- p2_info = @infos[ p2 ]
733
- send_heartbeat( p2, p2_info[ :p1_addr ] )
734
- end
735
- end
736
- end
737
- end
738
-
739
- def loop_check_expire( p2 )
740
- Thread.new do
741
- loop do
742
- sleep 60
743
- break if p2.closed?
744
-
745
- now = Time.new
746
- p2_info = @infos[ p2 ]
747
-
748
- if now - p2_info[ :last_traffic_at ] > EXPIRE_AFTER
749
- @mutex.synchronize do
750
- p2_id = @socks[ p2 ]
751
- # puts "debug ctlw close p2 #{ p2_id } #{ Time.new } p#{ Process.pid }"
752
- @ctlw.write( [ CTL_CLOSE, p2_id ].pack( 'CQ>' ) )
753
- end
754
-
755
- break
756
- end
757
-
758
- exts = p2_info[ :app_exts ].select{ | _, ext | now - ext[ :created_at ] > 5 }
759
-
760
- if exts.any?
761
- @mutex.synchronize do
762
- exts.each do | app_id, ext |
763
- if ext[ :last_recv_at ].nil? || ( now - ext[ :last_recv_at ] > EXPIRE_AFTER )
764
- # puts "debug ctlw close app #{ app_id } #{ Time.new } p#{ Process.pid }"
765
- @ctlw.write( [ CTL_CLOSE, app_id ].pack( 'CQ>' ) )
766
- end
767
- end
768
- end
769
- end
770
- end
771
- end
772
- end
773
-
774
- def loop_send_status( p2 )
775
- Thread.new do
776
- loop do
777
- sleep STATUS_INTERVAL
778
-
779
- if p2.closed?
780
- # puts "debug p2 is closed, break send status loop #{ Time.new }"
781
- break
782
- end
783
-
784
- p2_info = @infos[ p2 ]
785
-
786
- if p2_info[ :app_exts ].any?
787
- @mutex.synchronize do
788
- now = Time.new
789
-
790
- p2_info[ :app_exts ].each do | app_id, ext |
791
- if ext[ :last_traffic_at ] && ( now - ext[ :last_traffic_at ] < SEND_STATUS_UNTIL )
792
- ctlmsg = [
793
- 0,
794
- APP_STATUS,
795
- app_id,
796
- ext[ :biggest_pack_id ],
797
- ext[ :continue_shadow_pack_id ]
798
- ].pack( 'Q>CQ>Q>Q>' )
799
-
800
- send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
801
- end
802
- end
803
- end
804
- end
805
-
806
- if p2_info[ :paused ] && ( p2_info[ :app_exts ].map{ | _, ext | ext[ :wmems ].size }.sum < RESUME_BELOW )
807
- @mutex.synchronize do
808
- p2_id = @socks[ p2 ]
809
- puts "ctlw resume #{ p2_id } #{ Time.new }"
810
- @ctlw.write( [ CTL_RESUME, p2_id ].pack( 'CQ>' ) )
811
- p2_info[ :paused ] = false
812
- end
813
- end
814
- end
815
- end
816
- end
817
-
818
- def loop_send_fin1( p2, app_id )
819
- Thread.new do
820
- 100.times do
821
- break if p2.closed?
822
-
823
- p2_info = @infos[ p2 ]
824
- break unless p2_info[ :p1_addr ]
825
-
826
- unless p2_info[ :fin1s ].include?( app_id )
827
- # puts "debug break send fin1 loop #{ Time.new }"
828
- break
829
- end
830
-
831
- @mutex.synchronize do
832
- ctlmsg = [
833
- 0,
834
- FIN1,
835
- app_id
836
- ].pack( 'Q>CQ>' )
837
-
838
- # puts "debug send FIN1 #{ app_id } #{ Time.new }"
839
- send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
840
- end
841
-
842
- sleep 1
843
- end
844
- end
845
- end
846
-
847
- def loop_send_fin2( p2, app_id )
848
- Thread.new do
849
- 100.times do
850
- break if p2.closed?
851
-
852
- p2_info = @infos[ p2 ]
853
- break unless p2_info[ :p1_addr ]
854
-
855
- unless p2_info[ :fin2s ].include?( app_id )
856
- # puts "debug break send fin2 loop #{ Time.new }"
857
- break
858
- end
859
-
860
- @mutex.synchronize do
861
- ctlmsg = [
862
- 0,
863
- FIN2,
864
- app_id
865
- ].pack( 'Q>CQ>' )
866
-
867
- # puts "debug send FIN2 #{ app_id } #{ Time.new }"
868
- send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
869
- end
870
-
871
- sleep 1
872
- end
873
- end
874
- end
875
-
876
- def loop_send_a_new_app( app )
877
- Thread.new do
878
- 100.times do
879
- break if app.closed?
880
-
881
- app_info = @infos[ app ]
882
- p2 = app_info[ :p2 ]
883
- break if p2.closed?
884
-
885
- p2_info = @infos[ p2 ]
886
-
887
- if p2_info[ :p1_addr ]
888
- app_id = @socks[ app ]
889
- shadow_id = p2_info[ :app_ids ][ app_id ]
890
-
891
- if shadow_id
892
- # puts "debug break a new app loop #{ Time.new }"
893
- break
894
- end
895
-
896
- @mutex.synchronize do
897
- ctlmsg = [ 0, A_NEW_APP, app_id ].pack( 'Q>CQ>' )
898
- # puts "debug send a new app #{ Time.new }"
899
- send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
900
- end
901
- end
902
-
903
- sleep 1
904
- end
905
- end
906
- end
907
-
908
- def send_heartbeat( p2, target_addr )
909
- ctlmsg = [ 0, HEARTBEAT, rand( 128 ) ].pack( 'Q>CC' )
910
- send_pack( p2, ctlmsg, target_addr )
911
- end
912
-
913
- def send_pack( sock, data, target_sockaddr )
914
- begin
915
- sock.sendmsg( data, 0, target_sockaddr )
916
- rescue IO::WaitWritable, Errno::EINTR => e
917
- puts "sendmsg #{ e.class } #{ Time.new }"
918
- end
919
- end
920
-
921
- def add_read( sock )
922
- return if sock.closed? || @reads.include?( sock )
923
-
924
- @reads << sock
925
- end
926
-
927
- def add_write( sock, data = nil )
928
- return if sock.closed? || @writes.include?( sock )
929
-
930
- @writes << sock
931
- end
932
-
933
- def add_closing( sock )
934
- return if sock.closed? || @closings.include?( sock )
935
-
936
- @reads.delete( sock )
937
- @closings << sock
938
- add_write( sock )
939
- end
940
-
941
- def close_p2( p2 )
942
- info = close_sock( p2 )
943
-
944
- info[ :chunks ].each do | filename |
945
- begin
946
- File.delete( File.join( @p2_chunk_dir, filename ) )
947
- rescue Errno::ENOENT
948
- end
949
- end
950
-
951
- info[ :app_exts ].each{ | _, ext | add_closing( ext[ :app ] ) }
952
- end
953
-
954
- def close_app( app )
955
- info = close_sock( app )
956
- p2 = info[ :p2 ]
957
- return if p2.closed?
958
-
959
- app_id = info[ :id ]
960
- p2_info = @infos[ p2 ]
961
- ext = p2_info[ :app_exts ][ app_id ]
962
- return unless ext
963
-
964
- ext[ :chunks ].each do | filename |
965
- begin
966
- File.delete( File.join( @app_chunk_dir, filename ) )
967
- rescue Errno::ENOENT
968
- end
969
- end
970
-
971
- if ext[ :is_shadow_closed ]
972
- del_app_ext( p2_info, app_id )
973
-
974
- unless p2_info[ :fin2s ].include?( app_id )
975
- # puts "debug 2-3. app.close -> ext.is_shadow_closed ? yes -> del ext -> loop send fin2 #{ Time.new }"
976
- p2_info[ :fin2s ] << app_id
977
- loop_send_fin2( p2, app_id )
978
- end
979
- elsif !p2_info[ :fin1s ].include?( app_id )
980
- # puts "debug 1-1. app.close -> ext.is_shadow_closed ? no -> send fin1 loop #{ Time.new }"
981
- p2_info[ :fin1s ] << app_id
982
- loop_send_fin1( p2, app_id )
983
- end
984
- end
985
-
986
- def close_sock( sock )
987
- sock.close
988
- @reads.delete( sock )
989
- @writes.delete( sock )
990
- @closings.delete( sock )
991
- @roles.delete( sock )
992
- info = @infos.delete( sock )
993
- sock_id = @socks.delete( sock )
994
- @sock_ids.delete( sock_id )
995
-
996
- info
997
- end
998
-
999
- def del_app_ext( p2_info, app_id )
1000
- p2_info[ :waitings ].delete( app_id )
1001
- ext = p2_info[ :app_exts ].delete( app_id )
1002
-
1003
- if ext
1004
- ext[ :chunks ].each do | filename |
1005
- begin
1006
- File.delete( File.join( @app_chunk_dir, filename ) )
1007
- rescue Errno::ENOENT
1008
- end
1009
- end
1010
- end
1011
-
1012
- shadow_id = p2_info[ :app_ids ].delete( app_id )
1013
-
1014
- if shadow_id
1015
- p2_info[ :shadow_ids ].delete( shadow_id )
1016
- end
1017
- end
1018
-
1019
- end
1020
- end
1
+ require 'p2p2/head'
2
+ require 'p2p2/hex'
3
+ require 'p2p2/version'
4
+ require 'socket'
5
+
6
+ ##
7
+ # P2p2::P2 - 内网里的任意应用,访问另一个内网里的应用服务端。p2端。
8
+ #
9
+ # 两套关闭
10
+ # ========
11
+ #
12
+ # 1-1. app.close -> ext.is_shadow_closed ? no -> send fin1 loop
13
+ # 1-2. recv got_fin1 -> break loop
14
+ # 1-3. recv fin2 -> send got_fin2 -> del ext
15
+ #
16
+ # 2-1. recv fin1 -> send got_fin1 -> ext.is_shadow_closed = true
17
+ # 2-2. all sent && ext.biggest_shadow_pack_id == ext.continue_shadow_pack_id -> add closing app
18
+ # 2-3. app.close -> ext.is_shadow_closed ? yes -> del ext -> loop send fin2
19
+ # 2-4. recv got_fin2 -> break loop
20
+ #
21
+ module P2p2
22
+ class P2
23
+
24
+ ##
25
+ # p2pd_host 配对服务器ip
26
+ # p2pd_port 配对服务器端口
27
+ # appd_host 代理地址 不限制访问:'0.0.0.0',或者只允许本地访问:'127.0.0.1'
28
+ # appd_port 代理端口
29
+ # title 约定的房间名
30
+ # app_chunk_dir 文件缓存目录,缓存app来不及写的流量
31
+ # p2_chunk_dir 文件缓存目录,缓存p2来不及写的流量
32
+ #
33
+ def initialize( p2pd_host, p2pd_port, appd_host, appd_port, title, app_chunk_dir = '/tmp', p2_chunk_dir = '/tmp' )
34
+ @p2pd_sockaddr = Socket.sockaddr_in( p2pd_port, p2pd_host )
35
+ @appd_sockaddr = Socket.sockaddr_in( appd_port, appd_host )
36
+ @title = title
37
+ @app_chunk_dir = app_chunk_dir
38
+ @p2_chunk_dir = p2_chunk_dir
39
+ @hex = P2p2::Hex.new
40
+ @mutex = Mutex.new
41
+ @reads = []
42
+ @writes = []
43
+ @closings = []
44
+ @roles = {} # sock => :ctlr / :appd / :app / :p2
45
+ @infos = {} # sock => {}
46
+ @socks = {} # sock => sock_id
47
+ @sock_ids = {} # sock_id => sock
48
+
49
+ ctlr, ctlw = IO.pipe
50
+ @ctlw = ctlw
51
+ @roles[ ctlr ] = :ctlr
52
+ @reads << ctlr
53
+ end
54
+
55
+ def looping
56
+ puts 'looping'
57
+
58
+ new_appd
59
+
60
+ loop do
61
+ rs, ws = IO.select( @reads, @writes )
62
+
63
+ @mutex.synchronize do
64
+ rs.each do | sock |
65
+ case @roles[ sock ]
66
+ when :ctlr
67
+ read_ctlr( sock )
68
+ when :appd
69
+ read_appd( sock )
70
+ when :app
71
+ read_app( sock )
72
+ when :p2
73
+ read_p2( sock )
74
+ end
75
+ end
76
+
77
+ ws.each do | sock |
78
+ case @roles[ sock ]
79
+ when :app
80
+ write_app( sock )
81
+ when :p2
82
+ write_p2( sock )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ rescue Interrupt => e
88
+ puts e.class
89
+ quit!
90
+ end
91
+
92
+ def quit!
93
+ if @p2 && !@p2.closed? && @p2_info[ :p1_addr ]
94
+ ctlmsg = [ 0, P2_FIN ].pack( 'Q>C' )
95
+ send_pack( @p2, ctlmsg, @p2_info[ :p1_addr ] )
96
+ end
97
+
98
+ exit
99
+ end
100
+
101
+ private
102
+
103
+ ##
104
+ # read ctlr
105
+ #
106
+ def read_ctlr( ctlr )
107
+ case ctlr.read( 1 ).unpack( 'C' ).first
108
+ when CTL_CLOSE
109
+ sock_id = ctlr.read( 8 ).unpack( 'Q>' ).first
110
+ sock = @sock_ids[ sock_id ]
111
+
112
+ if sock
113
+ add_closing( sock )
114
+ end
115
+ when CTL_RESUME
116
+ sock_id = ctlr.read( 8 ).unpack( 'Q>' ).first
117
+ sock = @sock_ids[ sock_id ]
118
+
119
+ if sock
120
+ add_write( sock )
121
+ end
122
+ end
123
+ end
124
+
125
+ ##
126
+ # read appd
127
+ #
128
+ def read_appd( appd )
129
+ begin
130
+ app, _ = appd.accept_nonblock
131
+ rescue IO::WaitReadable, Errno::EINTR
132
+ return
133
+ end
134
+
135
+ if @p2.nil? || @p2.closed?
136
+ new_p2
137
+ end
138
+
139
+ app_id = @hex.gen_random_num
140
+ @roles[ app ] = :app
141
+ @infos[ app ] = {
142
+ id: app_id,
143
+ p2: @p2
144
+ }
145
+ @socks[ app ] = app_id
146
+ @sock_ids[ app_id ] = app
147
+
148
+ @p2_info[ :waitings ][ app_id ] = []
149
+ @p2_info[ :app_exts ][ app_id ] = {
150
+ app: app,
151
+ created_at: Time.new,
152
+ last_recv_at: nil, # 上一次收到流量的时间
153
+ wbuff: '', # 写前缓存
154
+ cache: '', # 块读出缓存
155
+ chunks: [], # 块队列,写前达到块大小时结一个块 filename
156
+ spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
157
+ wmems: {}, # 写后缓存 pack_id => data
158
+ send_ats: {}, # 上一次发出时间 pack_id => send_at
159
+ biggest_pack_id: 0, # 发到几
160
+ continue_shadow_pack_id: 0, # 收到几
161
+ pieces: {}, # 跳号包 shadow_pack_id => data
162
+ is_shadow_closed: false, # 对面是否已关闭
163
+ biggest_shadow_pack_id: 0, # 对面发到几
164
+ completed_pack_id: 0, # 完成到几(对面收到几)
165
+ last_traffic_at: nil # 收到有效流量,或者发出流量的时间戳
166
+ }
167
+
168
+ add_read( app )
169
+ loop_send_a_new_app( app )
170
+ end
171
+
172
+ ##
173
+ # read app
174
+ #
175
+ def read_app( app )
176
+ begin
177
+ data = app.read_nonblock( PACK_SIZE )
178
+ rescue IO::WaitReadable, Errno::EINTR
179
+ return
180
+ rescue Exception => e
181
+ add_closing( app )
182
+ return
183
+ end
184
+
185
+ info = @infos[ app ]
186
+ p2 = info[ :p2 ]
187
+
188
+ if p2.closed?
189
+ add_closing( app )
190
+ return
191
+ end
192
+
193
+ app_id = @socks[ app ]
194
+ p2_info = @infos[ p2 ]
195
+ shadow_id = p2_info[ :app_ids ][ app_id ]
196
+
197
+ if p2_info[ :p1_addr ].nil? || shadow_id.nil?
198
+ p2_info[ :waitings ][ app_id ] << data
199
+ return
200
+ end
201
+
202
+ p2_info[ :wbuffs ] << [ app_id, data ]
203
+
204
+ if p2_info[ :wbuffs ].size >= WBUFFS_LIMIT
205
+ p2_id = @socks[ p2 ]
206
+ spring = p2_info[ :chunks ].size > 0 ? ( p2_info[ :spring ] + 1 ) : 0
207
+ filename = "#{ p2_id }.#{ spring }"
208
+ chunk_path = File.join( @p2_chunk_dir, filename )
209
+ IO.binwrite( chunk_path, p2_info[ :wbuffs ].map{ | app_id, data | "#{ [ app_id, data.bytesize ].pack( 'Q>n' ) }#{ data }" }.join )
210
+ p2_info[ :chunks ] << filename
211
+ p2_info[ :spring ] = spring
212
+ p2_info[ :wbuffs ].clear
213
+ end
214
+
215
+ unless p2_info[ :paused ]
216
+ add_write( p2 )
217
+ end
218
+ end
219
+
220
+ ##
221
+ # read p2
222
+ #
223
+ def read_p2( p2 )
224
+ data, addrinfo, rflags, *controls = p2.recvmsg
225
+ sockaddr = addrinfo.to_sockaddr
226
+ now = Time.new
227
+ info = @infos[ p2 ]
228
+ shadow_id = data[ 0, 8 ].unpack( 'Q>' ).first
229
+
230
+ if shadow_id == 0
231
+ case data[ 8 ].unpack( 'C' ).first
232
+ when PEER_ADDR
233
+ return if info[ :peer_addr ] || ( sockaddr != @p2pd_sockaddr )
234
+
235
+ info[ :peer_addr ] = data[ 9..-1 ]
236
+ # puts "debug peer addr #{ Addrinfo.new( info[ :peer_addr ] ).ip_unpack.inspect } #{ Time.new }"
237
+ loop_punch_peer( p2 )
238
+ when HEARTBEAT
239
+ return if info[ :p1_addr ] || ( sockaddr != info[ :peer_addr ] )
240
+
241
+ info[ :p1_addr ] = sockaddr
242
+ # puts "debug p1 addr #{ Addrinfo.new( info[ :p1_addr ] ).ip_unpack.inspect } #{ Time.new }"
243
+ info[ :last_traffic_at ] = now
244
+ loop_send_heartbeat( p2 )
245
+ loop_check_expire( p2 )
246
+ loop_send_status( p2 )
247
+ when PAIRED
248
+ return if sockaddr != info[ :p1_addr ]
249
+
250
+ app_id, shadow_id = data[ 9, 16 ].unpack( 'Q>Q>' )
251
+ return if info[ :app_ids ].include?( app_id )
252
+
253
+ # puts "debug got PAIRED #{ app_id } #{ shadow_id } #{ Time.new }"
254
+ info[ :app_ids ][ app_id ] = shadow_id
255
+ info[ :shadow_ids ][ shadow_id ] = app_id
256
+ buffs = info[ :waitings ][ app_id ]
257
+
258
+ if buffs.any?
259
+ # puts "debug move #{ buffs.size } waiting buffs to wbuffs #{ Time.new } p#{ Process.pid }"
260
+
261
+ buffs.each do | buff |
262
+ info[ :wbuffs ] << [ app_id, buff ]
263
+ end
264
+
265
+ buffs.clear
266
+ add_write( p2 )
267
+ end
268
+ when SHADOW_STATUS
269
+ return if sockaddr != info[ :p1_addr ]
270
+
271
+ shadow_id, biggest_shadow_pack_id, continue_app_pack_id = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
272
+ app_id = info[ :shadow_ids ][ shadow_id ]
273
+ return unless app_id
274
+
275
+ ext = info[ :app_exts ][ app_id ]
276
+ return unless ext
277
+
278
+ # 更新对面发到几
279
+ if biggest_shadow_pack_id > ext[ :biggest_shadow_pack_id ]
280
+ ext[ :biggest_shadow_pack_id ] = biggest_shadow_pack_id
281
+ end
282
+
283
+ # 更新对面收到几,释放写后
284
+ if continue_app_pack_id > ext[ :completed_pack_id ]
285
+ pack_ids = ext[ :wmems ].keys.select { | pack_id | pack_id <= continue_app_pack_id }
286
+
287
+ pack_ids.each do | pack_id |
288
+ ext[ :wmems ].delete( pack_id )
289
+ ext[ :send_ats ].delete( pack_id )
290
+ end
291
+
292
+ ext[ :completed_pack_id ] = continue_app_pack_id
293
+ end
294
+
295
+ if ext[ :is_shadow_closed ] && ( ext[ :biggest_shadow_pack_id ] == ext[ :continue_shadow_pack_id ] )
296
+ add_write( ext[ :app ] )
297
+ return
298
+ end
299
+
300
+ # 发miss
301
+ if !ext[ :app ].closed? && ( ext[ :continue_shadow_pack_id ] < ext[ :biggest_shadow_pack_id ] )
302
+ ranges = []
303
+ curr_pack_id = ext[ :continue_shadow_pack_id ] + 1
304
+
305
+ ext[ :pieces ].keys.sort.each do | pack_id |
306
+ if pack_id > curr_pack_id
307
+ ranges << [ curr_pack_id, pack_id - 1 ]
308
+ end
309
+
310
+ curr_pack_id = pack_id + 1
311
+ end
312
+
313
+ if curr_pack_id <= ext[ :biggest_shadow_pack_id ]
314
+ ranges << [ curr_pack_id, ext[ :biggest_shadow_pack_id ] ]
315
+ end
316
+
317
+ pack_count = 0
318
+ # puts "debug #{ ext[ :continue_shadow_pack_id ] }/#{ ext[ :biggest_shadow_pack_id ] } send MISS #{ ranges.size }"
319
+ ranges.each do | pack_id_begin, pack_id_end |
320
+ if pack_count >= BREAK_SEND_MISS
321
+ puts "break send miss at #{ pack_id_begin } #{ Time.new }"
322
+ break
323
+ end
324
+
325
+ ctlmsg = [
326
+ 0,
327
+ MISS,
328
+ shadow_id,
329
+ pack_id_begin,
330
+ pack_id_end
331
+ ].pack( 'Q>CQ>Q>Q>' )
332
+
333
+ send_pack( p2, ctlmsg, info[ :p1_addr ] )
334
+ pack_count += ( pack_id_end - pack_id_begin + 1 )
335
+ end
336
+ end
337
+ when MISS
338
+ return if sockaddr != info[ :p1_addr ]
339
+
340
+ app_id, pack_id_begin, pack_id_end = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
341
+ ext = info[ :app_exts ][ app_id ]
342
+ return unless ext
343
+
344
+ ( pack_id_begin..pack_id_end ).each do | pack_id |
345
+ send_at = ext[ :send_ats ][ pack_id ]
346
+
347
+ if send_at
348
+ break if now - send_at < STATUS_INTERVAL
349
+
350
+ info[ :resendings ] << [ app_id, pack_id ]
351
+ end
352
+ end
353
+
354
+ add_write( p2 )
355
+ when FIN1
356
+ return if sockaddr != info[ :p1_addr ]
357
+
358
+ shadow_id = data[ 9, 8 ].unpack( 'Q>' ).first
359
+ ctlmsg = [
360
+ 0,
361
+ GOT_FIN1,
362
+ shadow_id
363
+ ].pack( 'Q>CQ>' )
364
+
365
+ # puts "debug 2-1. recv fin1 -> send got_fin1 -> ext.is_shadow_closed = true #{ shadow_id } #{ Time.new }"
366
+ send_pack( p2, ctlmsg, info[ :p1_addr ] )
367
+
368
+ app_id = info[ :shadow_ids ][ shadow_id ]
369
+ return unless app_id
370
+
371
+ ext = info[ :app_exts ][ app_id ]
372
+ return unless ext
373
+
374
+ ext[ :is_shadow_closed ] = true
375
+ when GOT_FIN1
376
+ return if sockaddr != info[ :p1_addr ]
377
+
378
+ # puts "debug 1-2. recv got_fin1 -> break loop #{ Time.new }"
379
+ app_id = data[ 9, 8 ].unpack( 'Q>' ).first
380
+ info[ :fin1s ].delete( app_id )
381
+ when FIN2
382
+ return if sockaddr != info[ :p1_addr ]
383
+
384
+ # puts "debug 1-3. recv fin2 -> send got_fin2 -> del ext #{ Time.new }"
385
+ shadow_id = data[ 9, 8 ].unpack( 'Q>' ).first
386
+ ctlmsg = [
387
+ 0,
388
+ GOT_FIN2,
389
+ shadow_id
390
+ ].pack( 'Q>CQ>' )
391
+
392
+ send_pack( p2, ctlmsg, info[ :p1_addr ] )
393
+
394
+ app_id = info[ :shadow_ids ][ shadow_id ]
395
+ return unless app_id
396
+
397
+ del_app_ext( info, app_id )
398
+ when GOT_FIN2
399
+ return if sockaddr != info[ :p1_addr ]
400
+
401
+ # puts "debug 2-4. recv got_fin2 -> break loop #{ Time.new }"
402
+ app_id = data[ 9, 8 ].unpack( 'Q>' ).first
403
+ info[ :fin2s ].delete( app_id )
404
+ when P1_FIN
405
+ return if sockaddr != info[ :p1_addr ]
406
+ puts "recv p1 fin #{ Time.new }"
407
+ add_closing( p2 )
408
+ end
409
+
410
+ return
411
+ end
412
+
413
+ return if sockaddr != info[ :p1_addr ]
414
+
415
+ app_id = info[ :shadow_ids ][ shadow_id ]
416
+ return unless app_id
417
+
418
+ ext = info[ :app_exts ][ app_id ]
419
+ return if ext.nil? || ext[ :app ].closed?
420
+
421
+ pack_id = data[ 8, 8 ].unpack( 'Q>' ).first
422
+ return if ( pack_id <= ext[ :continue_shadow_pack_id ] ) || ext[ :pieces ].include?( pack_id )
423
+
424
+ data = data[ 16..-1 ]
425
+
426
+ # 解混淆
427
+ if pack_id == 1
428
+ data = @hex.decode( data )
429
+ end
430
+
431
+ # 放进shadow的写前缓存,跳号放碎片缓存
432
+ if pack_id - ext[ :continue_shadow_pack_id ] == 1
433
+ while ext[ :pieces ].include?( pack_id + 1 )
434
+ data << ext[ :pieces ].delete( pack_id + 1 )
435
+ pack_id += 1
436
+ end
437
+
438
+ ext[ :continue_shadow_pack_id ] = pack_id
439
+ ext[ :wbuff ] << data
440
+
441
+ if ext[ :wbuff ].bytesize >= CHUNK_SIZE
442
+ spring = ext[ :chunks ].size > 0 ? ( ext[ :spring ] + 1 ) : 0
443
+ filename = "#{ app_id }.#{ spring }"
444
+ chunk_path = File.join( @app_chunk_dir, filename )
445
+ IO.binwrite( chunk_path, ext[ :wbuff ] )
446
+ ext[ :chunks ] << filename
447
+ ext[ :spring ] = spring
448
+ ext[ :wbuff ].clear
449
+ end
450
+
451
+ add_write( ext[ :app ] )
452
+ ext[ :last_traffic_at ] = now
453
+ info[ :last_traffic_at ] = now
454
+ else
455
+ ext[ :pieces ][ pack_id ] = data
456
+ end
457
+
458
+ ext[ :last_recv_at ] = now
459
+ end
460
+
461
+ ##
462
+ # write app
463
+ #
464
+ def write_app( app )
465
+ if @closings.include?( app )
466
+ close_app( app )
467
+ return
468
+ end
469
+
470
+ info = @infos[ app ]
471
+ p2 = info[ :p2 ]
472
+
473
+ if p2.closed?
474
+ add_closing( app )
475
+ return
476
+ end
477
+
478
+ p2_info = @infos[ p2 ]
479
+ app_id = @socks[ app ]
480
+ ext = p2_info[ :app_exts ][ app_id ]
481
+
482
+ # 取写前
483
+ data = ext[ :cache ]
484
+ from = :cache
485
+
486
+ if data.empty?
487
+ if ext[ :chunks ].any?
488
+ path = File.join( @app_chunk_dir, ext[ :chunks ].shift )
489
+
490
+ begin
491
+ data = IO.binread( path )
492
+ File.delete( path )
493
+ rescue Errno::ENOENT
494
+ add_closing( app )
495
+ return
496
+ end
497
+ else
498
+ data = ext[ :wbuff ]
499
+ from = :wbuff
500
+ end
501
+ end
502
+
503
+ if data.empty?
504
+ if ext[ :is_shadow_closed ] && ( ext[ :biggest_shadow_pack_id ] == ext[ :continue_shadow_pack_id ] )
505
+ # puts "debug 2-2. all sent && ext.biggest_shadow_pack_id == ext.continue_shadow_pack_id -> add closing app #{ Time.new }"
506
+ add_closing( app )
507
+ return
508
+ end
509
+
510
+ @writes.delete( app )
511
+ return
512
+ end
513
+
514
+ begin
515
+ written = app.write_nonblock( data )
516
+ rescue IO::WaitWritable, Errno::EINTR => e
517
+ ext[ from ] = data
518
+ return
519
+ rescue Exception => e
520
+ add_closing( app )
521
+ return
522
+ end
523
+
524
+ data = data[ written..-1 ]
525
+ ext[ from ] = data
526
+ end
527
+
528
+ ##
529
+ # write p2
530
+ #
531
+ def write_p2( p2 )
532
+ if @closings.include?( p2 )
533
+ close_p2( p2 )
534
+ return
535
+ end
536
+
537
+ now = Time.new
538
+ info = @infos[ p2 ]
539
+
540
+ # 重传
541
+ while info[ :resendings ].any?
542
+ app_id, pack_id = info[ :resendings ].shift
543
+ ext = info[ :app_exts ][ app_id ]
544
+
545
+ if ext
546
+ pack = ext[ :wmems ][ pack_id ]
547
+
548
+ if pack
549
+ send_pack( p2, pack, info[ :p1_addr ] )
550
+ ext[ :last_traffic_at ] = now
551
+ info[ :last_traffic_at ] = now
552
+ return
553
+ end
554
+ end
555
+ end
556
+
557
+ # 若写后达到上限,暂停取写前
558
+ if info[ :app_exts ].map{ | _, ext | ext[ :wmems ].size }.sum >= WMEMS_LIMIT
559
+ unless info[ :paused ]
560
+ puts "pause #{ Time.new }"
561
+ info[ :paused ] = true
562
+ end
563
+
564
+ @writes.delete( p2 )
565
+ return
566
+ end
567
+
568
+ # 取写前
569
+ if info[ :caches ].any?
570
+ app_id, data = info[ :caches ].shift
571
+ elsif info[ :chunks ].any?
572
+ path = File.join( @p2_chunk_dir, info[ :chunks ].shift )
573
+
574
+ begin
575
+ data = IO.binread( path )
576
+ File.delete( path )
577
+ rescue Errno::ENOENT
578
+ add_closing( p2 )
579
+ return
580
+ end
581
+
582
+ caches = []
583
+
584
+ until data.empty?
585
+ app_id, pack_size = data[ 0, 10 ].unpack( 'Q>n' )
586
+ caches << [ app_id, data[ 10, pack_size ] ]
587
+ data = data[ ( 10 + pack_size )..-1 ]
588
+ end
589
+
590
+ app_id, data = caches.shift
591
+ info[ :caches ] = caches
592
+ elsif info[ :wbuffs ].any?
593
+ app_id, data = info[ :wbuffs ].shift
594
+ else
595
+ @writes.delete( p2 )
596
+ return
597
+ end
598
+
599
+ ext = info[ :app_exts ][ app_id ]
600
+
601
+ if ext
602
+ pack_id = ext[ :biggest_pack_id ] + 1
603
+
604
+ if pack_id == 1
605
+ data = @hex.encode( data )
606
+ end
607
+
608
+ pack = "#{ [ app_id, pack_id ].pack( 'Q>Q>' ) }#{ data }"
609
+ send_pack( p2, pack, info[ :p1_addr ] )
610
+ ext[ :biggest_pack_id ] = pack_id
611
+ ext[ :wmems ][ pack_id ] = pack
612
+ ext[ :send_ats ][ pack_id ] = now
613
+ ext[ :last_traffic_at ] = now
614
+ info[ :last_traffic_at ] = now
615
+ end
616
+ end
617
+
618
+ def new_appd
619
+ appd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
620
+ appd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
621
+ appd.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
622
+ appd.bind( @appd_sockaddr )
623
+ appd.listen( 511 )
624
+
625
+ @roles[ appd ] = :appd
626
+ add_read( appd )
627
+ end
628
+
629
+ def new_p2
630
+ p2 = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
631
+ p2.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
632
+ p2.bind( Socket.sockaddr_in( 0, '0.0.0.0' ) )
633
+ p2_id = @hex.gen_random_num
634
+ p2_info = {
635
+ id: p2_id,
636
+ waitings: {}, # 还没连上p1,或者还没配上shadow,暂存流量 app_id => buffs[]
637
+ wbuffs: [], # 写前缓存 [ app_id, data ]
638
+ caches: [], # 块读出缓存 [ app_id, data ]
639
+ chunks: [], # 块队列 filename
640
+ spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
641
+ peer_addr: nil, # 对面地址,连发心跳包打洞,离p2pd近的一方通常会撞死几个直到对面出来
642
+ p1_addr: nil, # p1地址
643
+ app_exts: {}, # 传输相关 app_id => {}
644
+ app_ids: {}, # app_id => shadow_id
645
+ shadow_ids: {}, # shadow_id => app_id
646
+ fin1s: [], # fin1: app已关闭,等待对面收完流量 app_id
647
+ fin2s: [], # fin2: 流量已收完 app_id
648
+ paused: false, # 是否暂停写
649
+ resendings: [], # 重传队列 [ app_id, pack_id ]
650
+ last_traffic_at: nil # 收到有效流量,或者发出流量的时间戳
651
+ }
652
+
653
+ @p2 = p2
654
+ @p2_info = p2_info
655
+ @roles[ p2 ] = :p2
656
+ @infos[ p2 ] = p2_info
657
+ @socks[ p2 ] = p2_id
658
+ @sock_ids[ p2_id ] = p2
659
+
660
+ send_pack( p2, @title, @p2pd_sockaddr )
661
+ add_read( p2 )
662
+ loop_send_char( p2 )
663
+ end
664
+
665
+ def loop_send_char( p2 )
666
+ Thread.new do
667
+ is_timeout = true
668
+
669
+ 20.times do
670
+ sleep HEARTBEAT_INTERVAL
671
+
672
+ if p2.closed?
673
+ is_timeout = false
674
+ break
675
+ end
676
+
677
+ p2_info = @infos[ p2 ]
678
+
679
+ if p2_info[ :peer_addr ]
680
+ is_timeout = false
681
+ break
682
+ end
683
+
684
+ @mutex.synchronize do
685
+ send_pack( p2, [ rand( 128 ) ].pack( 'C' ), @p2pd_sockaddr )
686
+ end
687
+ end
688
+
689
+ if is_timeout
690
+ @mutex.synchronize do
691
+ p2_id = @socks[ p2 ]
692
+ @ctlw.write( [ CTL_CLOSE, p2_id ].pack( 'CQ>' ) )
693
+ end
694
+ end
695
+ end
696
+ end
697
+
698
+ def loop_punch_peer( p2 )
699
+ Thread.new do
700
+ 20.times do
701
+ break if p2.closed?
702
+
703
+ p2_info = @infos[ p2 ]
704
+
705
+ @mutex.synchronize do
706
+ send_heartbeat( p2, p2_info[ :peer_addr ] )
707
+ end
708
+
709
+ sleep STATUS_INTERVAL
710
+ end
711
+
712
+ if !p2.closed?
713
+ p2_info = @infos[ p2 ]
714
+
715
+ unless p2_info[ :p1_addr ]
716
+ @mutex.synchronize do
717
+ p2_id = @socks[ p2 ]
718
+ @ctlw.write( [ CTL_CLOSE, p2_id ].pack( 'CQ>' ) )
719
+ end
720
+ end
721
+ end
722
+ end
723
+ end
724
+
725
+ def loop_send_heartbeat( p2 )
726
+ Thread.new do
727
+ loop do
728
+ sleep HEARTBEAT_INTERVAL
729
+ break if p2.closed?
730
+
731
+ @mutex.synchronize do
732
+ p2_info = @infos[ p2 ]
733
+ send_heartbeat( p2, p2_info[ :p1_addr ] )
734
+ end
735
+ end
736
+ end
737
+ end
738
+
739
+ def loop_check_expire( p2 )
740
+ Thread.new do
741
+ loop do
742
+ sleep 60
743
+ break if p2.closed?
744
+
745
+ now = Time.new
746
+ p2_info = @infos[ p2 ]
747
+
748
+ if now - p2_info[ :last_traffic_at ] > EXPIRE_AFTER
749
+ @mutex.synchronize do
750
+ p2_id = @socks[ p2 ]
751
+ # puts "debug ctlw close p2 #{ p2_id } #{ Time.new } p#{ Process.pid }"
752
+ @ctlw.write( [ CTL_CLOSE, p2_id ].pack( 'CQ>' ) )
753
+ end
754
+
755
+ break
756
+ end
757
+
758
+ exts = p2_info[ :app_exts ].select{ | _, ext | now - ext[ :created_at ] > 5 }
759
+
760
+ if exts.any?
761
+ @mutex.synchronize do
762
+ exts.each do | app_id, ext |
763
+ if ext[ :last_recv_at ].nil? || ( now - ext[ :last_recv_at ] > EXPIRE_AFTER )
764
+ # puts "debug ctlw close app #{ app_id } #{ Time.new } p#{ Process.pid }"
765
+ @ctlw.write( [ CTL_CLOSE, app_id ].pack( 'CQ>' ) )
766
+ end
767
+ end
768
+ end
769
+ end
770
+ end
771
+ end
772
+ end
773
+
774
+ def loop_send_status( p2 )
775
+ Thread.new do
776
+ loop do
777
+ sleep STATUS_INTERVAL
778
+
779
+ if p2.closed?
780
+ # puts "debug p2 is closed, break send status loop #{ Time.new }"
781
+ break
782
+ end
783
+
784
+ p2_info = @infos[ p2 ]
785
+
786
+ if p2_info[ :app_exts ].any?
787
+ @mutex.synchronize do
788
+ now = Time.new
789
+
790
+ p2_info[ :app_exts ].each do | app_id, ext |
791
+ if ext[ :last_traffic_at ] && ( now - ext[ :last_traffic_at ] < SEND_STATUS_UNTIL )
792
+ ctlmsg = [
793
+ 0,
794
+ APP_STATUS,
795
+ app_id,
796
+ ext[ :biggest_pack_id ],
797
+ ext[ :continue_shadow_pack_id ]
798
+ ].pack( 'Q>CQ>Q>Q>' )
799
+
800
+ send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
801
+ end
802
+ end
803
+ end
804
+ end
805
+
806
+ if p2_info[ :paused ] && ( p2_info[ :app_exts ].map{ | _, ext | ext[ :wmems ].size }.sum < RESUME_BELOW )
807
+ @mutex.synchronize do
808
+ p2_id = @socks[ p2 ]
809
+ puts "ctlw resume #{ p2_id } #{ Time.new }"
810
+ @ctlw.write( [ CTL_RESUME, p2_id ].pack( 'CQ>' ) )
811
+ p2_info[ :paused ] = false
812
+ end
813
+ end
814
+ end
815
+ end
816
+ end
817
+
818
+ def loop_send_fin1( p2, app_id )
819
+ Thread.new do
820
+ 100.times do
821
+ break if p2.closed?
822
+
823
+ p2_info = @infos[ p2 ]
824
+ break unless p2_info[ :p1_addr ]
825
+
826
+ unless p2_info[ :fin1s ].include?( app_id )
827
+ # puts "debug break send fin1 loop #{ Time.new }"
828
+ break
829
+ end
830
+
831
+ @mutex.synchronize do
832
+ ctlmsg = [
833
+ 0,
834
+ FIN1,
835
+ app_id
836
+ ].pack( 'Q>CQ>' )
837
+
838
+ # puts "debug send FIN1 #{ app_id } #{ Time.new }"
839
+ send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
840
+ end
841
+
842
+ sleep 1
843
+ end
844
+ end
845
+ end
846
+
847
+ def loop_send_fin2( p2, app_id )
848
+ Thread.new do
849
+ 100.times do
850
+ break if p2.closed?
851
+
852
+ p2_info = @infos[ p2 ]
853
+ break unless p2_info[ :p1_addr ]
854
+
855
+ unless p2_info[ :fin2s ].include?( app_id )
856
+ # puts "debug break send fin2 loop #{ Time.new }"
857
+ break
858
+ end
859
+
860
+ @mutex.synchronize do
861
+ ctlmsg = [
862
+ 0,
863
+ FIN2,
864
+ app_id
865
+ ].pack( 'Q>CQ>' )
866
+
867
+ # puts "debug send FIN2 #{ app_id } #{ Time.new }"
868
+ send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
869
+ end
870
+
871
+ sleep 1
872
+ end
873
+ end
874
+ end
875
+
876
+ def loop_send_a_new_app( app )
877
+ Thread.new do
878
+ 100.times do
879
+ break if app.closed?
880
+
881
+ app_info = @infos[ app ]
882
+ p2 = app_info[ :p2 ]
883
+ break if p2.closed?
884
+
885
+ p2_info = @infos[ p2 ]
886
+
887
+ if p2_info[ :p1_addr ]
888
+ app_id = @socks[ app ]
889
+ shadow_id = p2_info[ :app_ids ][ app_id ]
890
+
891
+ if shadow_id
892
+ # puts "debug break a new app loop #{ Time.new }"
893
+ break
894
+ end
895
+
896
+ @mutex.synchronize do
897
+ ctlmsg = [ 0, A_NEW_APP, app_id ].pack( 'Q>CQ>' )
898
+ # puts "debug send a new app #{ Time.new }"
899
+ send_pack( p2, ctlmsg, p2_info[ :p1_addr ] )
900
+ end
901
+ end
902
+
903
+ sleep 1
904
+ end
905
+ end
906
+ end
907
+
908
+ def send_heartbeat( p2, target_addr )
909
+ ctlmsg = [ 0, HEARTBEAT, rand( 128 ) ].pack( 'Q>CC' )
910
+ send_pack( p2, ctlmsg, target_addr )
911
+ end
912
+
913
+ def send_pack( sock, data, target_sockaddr )
914
+ begin
915
+ sock.sendmsg( data, 0, target_sockaddr )
916
+ rescue IO::WaitWritable, Errno::EINTR => e
917
+ puts "sendmsg #{ e.class } #{ Time.new }"
918
+ end
919
+ end
920
+
921
+ def add_read( sock )
922
+ return if sock.closed? || @reads.include?( sock )
923
+
924
+ @reads << sock
925
+ end
926
+
927
+ def add_write( sock, data = nil )
928
+ return if sock.closed? || @writes.include?( sock )
929
+
930
+ @writes << sock
931
+ end
932
+
933
+ def add_closing( sock )
934
+ return if sock.closed? || @closings.include?( sock )
935
+
936
+ @reads.delete( sock )
937
+ @closings << sock
938
+ add_write( sock )
939
+ end
940
+
941
+ def close_p2( p2 )
942
+ info = close_sock( p2 )
943
+
944
+ info[ :chunks ].each do | filename |
945
+ begin
946
+ File.delete( File.join( @p2_chunk_dir, filename ) )
947
+ rescue Errno::ENOENT
948
+ end
949
+ end
950
+
951
+ info[ :app_exts ].each{ | _, ext | add_closing( ext[ :app ] ) }
952
+ end
953
+
954
+ def close_app( app )
955
+ info = close_sock( app )
956
+ p2 = info[ :p2 ]
957
+ return if p2.closed?
958
+
959
+ app_id = info[ :id ]
960
+ p2_info = @infos[ p2 ]
961
+ ext = p2_info[ :app_exts ][ app_id ]
962
+ return unless ext
963
+
964
+ ext[ :chunks ].each do | filename |
965
+ begin
966
+ File.delete( File.join( @app_chunk_dir, filename ) )
967
+ rescue Errno::ENOENT
968
+ end
969
+ end
970
+
971
+ if ext[ :is_shadow_closed ]
972
+ del_app_ext( p2_info, app_id )
973
+
974
+ unless p2_info[ :fin2s ].include?( app_id )
975
+ # puts "debug 2-3. app.close -> ext.is_shadow_closed ? yes -> del ext -> loop send fin2 #{ Time.new }"
976
+ p2_info[ :fin2s ] << app_id
977
+ loop_send_fin2( p2, app_id )
978
+ end
979
+ elsif !p2_info[ :fin1s ].include?( app_id )
980
+ # puts "debug 1-1. app.close -> ext.is_shadow_closed ? no -> send fin1 loop #{ Time.new }"
981
+ p2_info[ :fin1s ] << app_id
982
+ loop_send_fin1( p2, app_id )
983
+ end
984
+ end
985
+
986
+ def close_sock( sock )
987
+ sock.close
988
+ @reads.delete( sock )
989
+ @writes.delete( sock )
990
+ @closings.delete( sock )
991
+ @roles.delete( sock )
992
+ info = @infos.delete( sock )
993
+ sock_id = @socks.delete( sock )
994
+ @sock_ids.delete( sock_id )
995
+
996
+ info
997
+ end
998
+
999
+ def del_app_ext( p2_info, app_id )
1000
+ p2_info[ :waitings ].delete( app_id )
1001
+ ext = p2_info[ :app_exts ].delete( app_id )
1002
+
1003
+ if ext
1004
+ ext[ :chunks ].each do | filename |
1005
+ begin
1006
+ File.delete( File.join( @app_chunk_dir, filename ) )
1007
+ rescue Errno::ENOENT
1008
+ end
1009
+ end
1010
+ end
1011
+
1012
+ shadow_id = p2_info[ :app_ids ].delete( app_id )
1013
+
1014
+ if shadow_id
1015
+ p2_info[ :shadow_ids ].delete( shadow_id )
1016
+ end
1017
+ end
1018
+
1019
+ end
1020
+ end