proxynt 2.0.26__tar.gz → 2.0.32__tar.gz

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.
Files changed (102) hide show
  1. {proxynt-2.0.26 → proxynt-2.0.32}/PKG-INFO +1 -1
  2. {proxynt-2.0.26 → proxynt-2.0.32}/client/tcp_forward_client.py +12 -30
  3. {proxynt-2.0.26 → proxynt-2.0.32}/client/udp_forward_client.py +11 -37
  4. {proxynt-2.0.26 → proxynt-2.0.32}/common/speed_limit.py +2 -1
  5. {proxynt-2.0.26 → proxynt-2.0.32}/constant/message_type_constnat.py +1 -4
  6. {proxynt-2.0.26 → proxynt-2.0.32}/constant/system_constant.py +1 -1
  7. {proxynt-2.0.26 → proxynt-2.0.32}/proxynt.egg-info/PKG-INFO +1 -1
  8. {proxynt-2.0.26 → proxynt-2.0.32}/proxynt.egg-info/SOURCES.txt +0 -2
  9. {proxynt-2.0.26 → proxynt-2.0.32}/run_client.py +18 -86
  10. {proxynt-2.0.26 → proxynt-2.0.32}/server/tcp_forward_client.py +8 -16
  11. {proxynt-2.0.26 → proxynt-2.0.32}/server/udp_forward_client.py +4 -11
  12. {proxynt-2.0.26 → proxynt-2.0.32}/server/websocket_handler.py +39 -195
  13. proxynt-2.0.26/client/data_connection_manager.py +0 -250
  14. proxynt-2.0.26/server/session_manager.py +0 -288
  15. {proxynt-2.0.26 → proxynt-2.0.32}/LICENSE +0 -0
  16. {proxynt-2.0.26 → proxynt-2.0.32}/MANIFEST.in +0 -0
  17. {proxynt-2.0.26 → proxynt-2.0.32}/__init__.py +0 -0
  18. {proxynt-2.0.26 → proxynt-2.0.32}/client/__init__.py +0 -0
  19. {proxynt-2.0.26 → proxynt-2.0.32}/client/abstract_tunnel.py +0 -0
  20. {proxynt-2.0.26 → proxynt-2.0.32}/client/clear_nonce_task.py +0 -0
  21. {proxynt-2.0.26 → proxynt-2.0.32}/client/heart_beat_task.py +0 -0
  22. {proxynt-2.0.26 → proxynt-2.0.32}/client/kcp_tunnel_impl.py +0 -0
  23. {proxynt-2.0.26 → proxynt-2.0.32}/client/n4_tunnel_manager.py +0 -0
  24. {proxynt-2.0.26 → proxynt-2.0.32}/client/quic_tunnel_impl.py +0 -0
  25. {proxynt-2.0.26 → proxynt-2.0.32}/client/tunnel_protocol.py +0 -0
  26. {proxynt-2.0.26 → proxynt-2.0.32}/common/__init__.py +0 -0
  27. {proxynt-2.0.26 → proxynt-2.0.32}/common/cert_utils.py +0 -0
  28. {proxynt-2.0.26 → proxynt-2.0.32}/common/crypto/__init__.py +0 -0
  29. {proxynt-2.0.26 → proxynt-2.0.32}/common/crypto/table.py +0 -0
  30. {proxynt-2.0.26 → proxynt-2.0.32}/common/encrypt_utils.py +0 -0
  31. {proxynt-2.0.26 → proxynt-2.0.32}/common/kcp.py +0 -0
  32. {proxynt-2.0.26 → proxynt-2.0.32}/common/logger_factory.py +0 -0
  33. {proxynt-2.0.26 → proxynt-2.0.32}/common/n4_protocol.py +0 -0
  34. {proxynt-2.0.26 → proxynt-2.0.32}/common/n4_punch.py +0 -0
  35. {proxynt-2.0.26 → proxynt-2.0.32}/common/nat_serialization.py +0 -0
  36. {proxynt-2.0.26 → proxynt-2.0.32}/common/pool.py +0 -0
  37. {proxynt-2.0.26 → proxynt-2.0.32}/common/register_append_data.py +0 -0
  38. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/__init__.py +0 -0
  39. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_abnf.py +0 -0
  40. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_app.py +0 -0
  41. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_cookiejar.py +0 -0
  42. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_core.py +0 -0
  43. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_exceptions.py +0 -0
  44. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_handshake.py +0 -0
  45. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_http.py +0 -0
  46. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_logging.py +0 -0
  47. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_socket.py +0 -0
  48. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_ssl_compat.py +0 -0
  49. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_url.py +0 -0
  50. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_utils.py +0 -0
  51. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/_wsdump.py +0 -0
  52. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/__init__.py +0 -0
  53. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/echo-server.py +0 -0
  54. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/test_abnf.py +0 -0
  55. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/test_app.py +0 -0
  56. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/test_cookiejar.py +0 -0
  57. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/test_http.py +0 -0
  58. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/test_url.py +0 -0
  59. {proxynt-2.0.26 → proxynt-2.0.32}/common/websocket/tests/test_websocket.py +0 -0
  60. {proxynt-2.0.26 → proxynt-2.0.32}/constant/__init__.py +0 -0
  61. {proxynt-2.0.26 → proxynt-2.0.32}/context/__init__.py +0 -0
  62. {proxynt-2.0.26 → proxynt-2.0.32}/context/context_utils.py +0 -0
  63. {proxynt-2.0.26 → proxynt-2.0.32}/entity/__init__.py +0 -0
  64. {proxynt-2.0.26 → proxynt-2.0.32}/entity/client_config_entity.py +0 -0
  65. {proxynt-2.0.26 → proxynt-2.0.32}/entity/message/__init__.py +0 -0
  66. {proxynt-2.0.26 → proxynt-2.0.32}/entity/message/message_entity.py +0 -0
  67. {proxynt-2.0.26 → proxynt-2.0.32}/entity/message/push_config_entity.py +0 -0
  68. {proxynt-2.0.26 → proxynt-2.0.32}/entity/message/tcp_over_websocket_message.py +0 -0
  69. {proxynt-2.0.26 → proxynt-2.0.32}/entity/server_config_entity.py +0 -0
  70. {proxynt-2.0.26 → proxynt-2.0.32}/exceptions/__init__.py +0 -0
  71. {proxynt-2.0.26 → proxynt-2.0.32}/exceptions/duplicated_name.py +0 -0
  72. {proxynt-2.0.26 → proxynt-2.0.32}/exceptions/invalid_password.py +0 -0
  73. {proxynt-2.0.26 → proxynt-2.0.32}/exceptions/replay_error.py +0 -0
  74. {proxynt-2.0.26 → proxynt-2.0.32}/exceptions/signature_error.py +0 -0
  75. {proxynt-2.0.26 → proxynt-2.0.32}/p2ptest/__init__.py +0 -0
  76. {proxynt-2.0.26 → proxynt-2.0.32}/p2ptest/client.py +0 -0
  77. {proxynt-2.0.26 → proxynt-2.0.32}/p2ptest/n4.py +0 -0
  78. {proxynt-2.0.26 → proxynt-2.0.32}/proxynt.egg-info/dependency_links.txt +0 -0
  79. {proxynt-2.0.26 → proxynt-2.0.32}/proxynt.egg-info/entry_points.txt +0 -0
  80. {proxynt-2.0.26 → proxynt-2.0.32}/proxynt.egg-info/requires.txt +0 -0
  81. {proxynt-2.0.26 → proxynt-2.0.32}/proxynt.egg-info/top_level.txt +0 -0
  82. {proxynt-2.0.26 → proxynt-2.0.32}/run_server.py +0 -0
  83. {proxynt-2.0.26 → proxynt-2.0.32}/server/__init__.py +0 -0
  84. {proxynt-2.0.26 → proxynt-2.0.32}/server/admin_http_handler.py +0 -0
  85. {proxynt-2.0.26 → proxynt-2.0.32}/server/n4.py +0 -0
  86. {proxynt-2.0.26 → proxynt-2.0.32}/server/n4_signal_service.py +0 -0
  87. {proxynt-2.0.26 → proxynt-2.0.32}/server/task/__init__.py +0 -0
  88. {proxynt-2.0.26 → proxynt-2.0.32}/server/task/check_cookie_task.py +0 -0
  89. {proxynt-2.0.26 → proxynt-2.0.32}/server/task/clear_nonce_task.py +0 -0
  90. {proxynt-2.0.26 → proxynt-2.0.32}/server/task/heart_beat_task.py +0 -0
  91. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/__init__.py +0 -0
  92. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/base.html +0 -0
  93. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/css/fonts/element-icons.woff +0 -0
  94. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/css/index.css +0 -0
  95. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/ele_index.html +0 -0
  96. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/js/axios.min.js +0 -0
  97. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/js/index.js +0 -0
  98. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/js/vue.min.js +0 -0
  99. {proxynt-2.0.26 → proxynt-2.0.32}/server/template/login.html +0 -0
  100. {proxynt-2.0.26 → proxynt-2.0.32}/setup.cfg +0 -0
  101. {proxynt-2.0.26 → proxynt-2.0.32}/setup.py +0 -0
  102. {proxynt-2.0.26 → proxynt-2.0.32}/test_exchange.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proxynt
3
- Version: 2.0.26
3
+ Version: 2.0.32
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/sazima/proxynt
6
6
  License: UNKNOWN
@@ -146,9 +146,6 @@ class TcpForwardClient:
146
146
  # N4 Tunnel Manager for P2P data transfer
147
147
  self.tunnel_manager = None
148
148
 
149
- # 多连接支持:DataConnectionManager(由 run_client 注入)
150
- self.data_conn_manager = None
151
-
152
149
  # Pending data buffer: uid -> list of (data, timestamp)
153
150
  # Used to buffer P2P data that arrives before connection is established
154
151
  self.pending_data: Dict[bytes, list] = {}
@@ -303,16 +300,22 @@ class TcpForwardClient:
303
300
  except OSError:
304
301
  recv = b''
305
302
 
303
+ # --- 发送端限速:在发送前等待 ---
304
+ if data.speed_limiter and recv:
305
+ wait_time = data.speed_limiter.acquire(len(recv))
306
+ if wait_time > 0:
307
+ time.sleep(wait_time)
308
+
306
309
  # --- 无缝切换逻辑 ---
307
310
  if self.tunnel_manager:
308
311
  # 尝试走 P2P 隧道
309
312
  if self.tunnel_manager.send_data(connection.uid, recv):
310
313
  # 如果发送成功返回 True,逻辑结束
311
- if not recv: # 本地连接关闭,通知隧道
314
+ if not recv: # 本地连接关闭,通知隧道
312
315
  self.close_connection(each)
313
316
  return
314
317
 
315
- # --- 回退/初始逻辑:走 WebSocket ---
318
+ # --- 回退/初始逻辑:走 WebSocket ---
316
319
  send_message: MessageEntity = {
317
320
  'type_': MessageTypeConstant.WEBSOCKET_OVER_TCP,
318
321
  'data': {
@@ -323,21 +326,9 @@ class TcpForwardClient:
323
326
  }
324
327
  }
325
328
 
326
- # 发送端限速:在发送前等待
327
- if data.speed_limiter and recv:
328
- wait_time = data.speed_limiter.acquire(len(recv))
329
- if wait_time > 0:
330
- time.sleep(wait_time)
331
-
332
- # 多连接模式:优先使用数据连接
333
- message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
334
- if self.data_conn_manager and self.data_conn_manager.is_ready():
335
- if not self.data_conn_manager.send_by_uid(connection.uid, message_bytes):
336
- # 数据连接发送失败,回退到控制连接
337
- connection.sender.enqueue_message(message_bytes)
338
- else:
339
- # 单连接模式:使用控制连接
340
- connection.sender.enqueue_message(message_bytes)
329
+ connection.sender.enqueue_message(
330
+ NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
331
+ )
341
332
 
342
333
  if not recv:
343
334
  try:
@@ -496,16 +487,7 @@ class TcpForwardClient:
496
487
  }
497
488
  }
498
489
  start_time = time.time()
499
- message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
500
-
501
- # 多连接模式:优先使用数据连接
502
- if self.data_conn_manager and self.data_conn_manager.is_ready():
503
- if not self.data_conn_manager.send_by_uid(connection.uid, message_bytes):
504
- # 数据连接发送失败,回退到控制连接
505
- self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
506
- else:
507
- # 单连接模式
508
- self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
490
+ self.ws.send(NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support), websocket.ABNF.OPCODE_BINARY)
509
491
  LoggerFactory.get_logger().debug(f'Send to websocket cost time {time.time() - start_time}')
510
492
 
511
493
  def send_by_uid(self, uid: bytes, msg: bytes):
@@ -53,9 +53,6 @@ class UdpForwardClient:
53
53
  self.c2c_listeners: Dict[str, socket.socket] = {} # rule_name → listener socket
54
54
  self.c2c_uid_to_rule: Dict[bytes, str] = {} # UID → rule_name
55
55
 
56
- # 多连接支持:DataConnectionManager(由 run_client 注入)
57
- self.data_conn_manager = None
58
-
59
56
  def set_running(self, running: bool):
60
57
  """Set running state"""
61
58
  self.running = running
@@ -134,9 +131,6 @@ class UdpForwardClient:
134
131
  protocol = rule['protocol']
135
132
  speed_limit = rule.get('speed_limit', 0.0)
136
133
 
137
- # 创建限速器
138
- speed_limiter = SpeedLimiter(speed_limit) if speed_limit > 0 else None
139
-
140
134
  # Check if using direct mode (target_ip + target_port) or service mode (target_service)
141
135
  use_direct_mode = 'target_ip' in rule and 'target_port' in rule
142
136
 
@@ -211,22 +205,10 @@ class UdpForwardClient:
211
205
  'ip_port': f"{source_addr[0]}:{source_addr[1]}"
212
206
  }
213
207
  }
214
-
215
- # 发送端限速:在发送前等待
216
- if speed_limiter and data:
217
- wait_time = speed_limiter.acquire(len(data))
218
- if wait_time > 0:
219
- time.sleep(wait_time)
220
-
221
- message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
222
- # 多连接模式:优先使用数据连接
223
- if self.data_conn_manager and self.data_conn_manager.is_ready():
224
- if not self.data_conn_manager.send_by_uid(uid, message_bytes):
225
- # 数据连接发送失败,回退到控制连接
226
- self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
227
- else:
228
- # 单连接模式
229
- self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
208
+ self.ws.send(
209
+ NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support),
210
+ websocket.ABNF.OPCODE_BINARY
211
+ )
230
212
  LoggerFactory.get_logger().debug(f'C2C UDP data forwarded: {rule_name} UID: {uid.hex()}, len: {len(data)}')
231
213
 
232
214
  except OSError as e:
@@ -264,6 +246,12 @@ class UdpForwardClient:
264
246
 
265
247
  def _handle_udp_data(self, conn: UdpSocketConnection, data: bytes, addr):
266
248
  """Handle received UDP data"""
249
+ # --- 发送端限速:在发送前等待 ---
250
+ if conn.speed_limiter and data:
251
+ wait_time = conn.speed_limiter.acquire(len(data))
252
+ if wait_time > 0:
253
+ time.sleep(wait_time)
254
+
267
255
  # Construct UDP message and send to public server via WebSocket
268
256
  send_message: MessageEntity = {
269
257
  'type_': MessageTypeConstant.WEBSOCKET_OVER_UDP,
@@ -279,21 +267,7 @@ class UdpForwardClient:
279
267
  LoggerFactory.get_logger().debug(f'Sending UDP to WebSocket, uid: {conn.uid}, len: {len(data)}')
280
268
 
281
269
  try:
282
- # 发送端限速:在发送前等待
283
- if conn.speed_limiter and data:
284
- wait_time = conn.speed_limiter.acquire(len(data))
285
- if wait_time > 0:
286
- time.sleep(wait_time)
287
-
288
- message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
289
- # 多连接模式:优先使用数据连接
290
- if self.data_conn_manager and self.data_conn_manager.is_ready():
291
- if not self.data_conn_manager.send_by_uid(conn.uid, message_bytes):
292
- # 数据连接发送失败,回退到控制连接
293
- self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
294
- else:
295
- # 单连接模式
296
- self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
270
+ self.ws.send(NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support), websocket.ABNF.OPCODE_BINARY)
297
271
  except Exception as e:
298
272
  LoggerFactory.get_logger().error(f"Failed to send UDP to WebSocket: {e}")
299
273
 
@@ -1,5 +1,6 @@
1
1
  import time
2
2
  import threading
3
+ from typing import Tuple
3
4
 
4
5
 
5
6
  class SpeedLimiter:
@@ -75,7 +76,7 @@ class SpeedLimiter:
75
76
  """兼容旧接口,等同于 acquire 但不返回等待时间"""
76
77
  self.acquire(data_len)
77
78
 
78
- def is_exceed(self):
79
+ def is_exceed(self) -> Tuple[bool, float]:
79
80
  """兼容旧接口,返回 (是否超速, 剩余量)"""
80
81
  if self.max_speed <= 0:
81
82
  return False, 0
@@ -28,7 +28,4 @@ class MessageTypeConstant:
28
28
  P2P_PRE_CONNECT = 'g'
29
29
  P2P_EXCHANGE = 'h' # UDP exchange to establish NAT mapping
30
30
  P2P_PEER_INFO = 'i' # Server sends peer's actual UDP port
31
- P2P_PUNCH_REQUEST = 'j' # Request to initiate P2P hole punching
32
-
33
- # 多连接支持
34
- JOIN_SESSION = 'k' # 数据连接加入会话(携带 session_token)
31
+ P2P_PUNCH_REQUEST = 'j' # Request to initiate P2P hole punching
@@ -15,7 +15,7 @@ class SystemConstant:
15
15
 
16
16
  COOKIE_EXPIRE_SECONDS = 3600 * 24
17
17
 
18
- VERSION = '2.0.26'
18
+ VERSION = '2.0.32'
19
19
 
20
20
  GITHUB = 'https://github.com/sazima/proxynt'
21
21
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proxynt
3
- Version: 2.0.26
3
+ Version: 2.0.32
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/sazima/proxynt
6
6
  License: UNKNOWN
@@ -8,7 +8,6 @@ setup.py
8
8
  ./client/__init__.py
9
9
  ./client/abstract_tunnel.py
10
10
  ./client/clear_nonce_task.py
11
- ./client/data_connection_manager.py
12
11
  ./client/heart_beat_task.py
13
12
  ./client/kcp_tunnel_impl.py
14
13
  ./client/n4_tunnel_manager.py
@@ -75,7 +74,6 @@ setup.py
75
74
  ./server/admin_http_handler.py
76
75
  ./server/n4.py
77
76
  ./server/n4_signal_service.py
78
- ./server/session_manager.py
79
77
  ./server/tcp_forward_client.py
80
78
  ./server/udp_forward_client.py
81
79
  ./server/websocket_handler.py
@@ -49,7 +49,6 @@ from client.udp_forward_client import UdpForwardClient
49
49
  from client.heart_beat_task import HeatBeatTask
50
50
  from client.tcp_forward_client import TcpForwardClient
51
51
  from client.n4_tunnel_manager import N4TunnelManager, get_pair_key
52
- from client.data_connection_manager import DataConnectionManager
53
52
  from common import websocket
54
53
  from common.logger_factory import LoggerFactory
55
54
  from common.nat_serialization import NatSerialization
@@ -152,19 +151,6 @@ class WebsocketClient:
152
151
  self.public_ip: str = None
153
152
  self.public_port: int = None
154
153
 
155
- # 多连接支持
156
- self.multi_connection_enabled: bool = config_data.get('multi_connection', True)
157
- self.num_data_channels: int = config_data.get('num_data_channels', 4)
158
- server_url = config_data['server']['url']
159
- if self.compress_support:
160
- sep = '&' if '?' in server_url else '?'
161
- server_url += sep + 'c=' + json.dumps(self.compress_support)
162
- self.data_conn_manager: DataConnectionManager = DataConnectionManager(
163
- server_url=server_url,
164
- compress_support=self.compress_support,
165
- on_message_callback=self._on_data_connection_message
166
- )
167
-
168
154
  # Get server info for N4 signaling
169
155
  server_url = config_data['server']['url']
170
156
  parsed = urlparse(server_url)
@@ -223,7 +209,14 @@ class WebsocketClient:
223
209
  self.tunnel_manager.register_uid(uid, source_client)
224
210
  LoggerFactory.get_logger().info(f'Registered UID {uid.hex()} to peer {source_client}')
225
211
 
226
- self.forward_client.create_socket(name, uid, data['ip_port'], name_to_speed_limiter.get(name))
212
+ # 优先使用消息中携带的限速配置(C2C场景),否则使用本地配置
213
+ speed_limit = data.get('speed_limit', 0.0)
214
+ if speed_limit > 0:
215
+ speed_limiter = SpeedLimiter(speed_limit)
216
+ else:
217
+ speed_limiter = name_to_speed_limiter.get(name)
218
+
219
+ self.forward_client.create_socket(name, uid, data['ip_port'], speed_limiter)
227
220
 
228
221
  # Handle UDP messages
229
222
  elif msg_type == MessageTypeConstant.WEBSOCKET_OVER_UDP:
@@ -240,7 +233,15 @@ class WebsocketClient:
240
233
  data: TcpOverWebsocketMessage = message_data['data']
241
234
  uid = data['uid']
242
235
  name = data['name']
243
- self.udp_forward_client.create_udp_socket(name, uid, data['ip_port'], name_to_speed_limiter.get(name))
236
+
237
+ # 优先使用消息中携带的限速配置(C2C场景),否则使用本地配置
238
+ speed_limit = data.get('speed_limit', 0.0)
239
+ if speed_limit > 0:
240
+ speed_limiter = SpeedLimiter(speed_limit)
241
+ else:
242
+ speed_limiter = name_to_speed_limiter.get(name)
243
+
244
+ self.udp_forward_client.create_udp_socket(name, uid, data['ip_port'], speed_limiter)
244
245
 
245
246
  # Handle Heartbeat / Config
246
247
  elif msg_type == MessageTypeConstant.PING:
@@ -254,20 +255,6 @@ class WebsocketClient:
254
255
  if d.get('speed_limit'):
255
256
  name_to_speed_limiter[d['name']] = SpeedLimiter(d['speed_limit'])
256
257
 
257
- # 多连接支持:如果服务端返回了 session_token,建立数据连接
258
- session_token = push_config.get('session_token')
259
- if session_token and self.multi_connection_enabled:
260
- LoggerFactory.get_logger().info(
261
- f'Multi-connection enabled, establishing {self.num_data_channels} data connections'
262
- )
263
- self.data_conn_manager.setup_data_connections(
264
- session_token=session_token,
265
- num_channels=self.num_data_channels
266
- )
267
- # 将 DataConnectionManager 注入到 forward_client
268
- self.forward_client.data_conn_manager = self.data_conn_manager
269
- self.udp_forward_client.data_conn_manager = self.data_conn_manager
270
-
271
258
  # Setup Listeners
272
259
  c2c_rules = push_config.get('client_to_client_rules', [])
273
260
  if c2c_rules:
@@ -360,52 +347,6 @@ class WebsocketClient:
360
347
  # Start hole punching with peer info
361
348
  self.tunnel_manager.receive_peer_info(peer_name, peer_ip, peer_port)
362
349
 
363
- def _on_data_connection_message(self, message_data: MessageEntity):
364
- """处理数据连接收到的消息(和控制连接消息处理逻辑相同)"""
365
- try:
366
- msg_type = message_data['type_']
367
-
368
- # Handle TCP messages
369
- if msg_type == MessageTypeConstant.WEBSOCKET_OVER_TCP:
370
- data: TcpOverWebsocketMessage = message_data['data']
371
- uid = data['uid']
372
- name = data['name']
373
- b = data['data']
374
-
375
- create_result = self.forward_client.create_socket(name, uid, data['ip_port'], name_to_speed_limiter.get(name))
376
- if create_result:
377
- self.forward_client.send_by_uid(uid, b)
378
-
379
- # Handle UDP messages
380
- elif msg_type == MessageTypeConstant.WEBSOCKET_OVER_UDP:
381
- data: TcpOverWebsocketMessage = message_data['data']
382
- uid = data['uid']
383
- name = data['name']
384
- b = data['data']
385
- create_result = self.udp_forward_client.create_udp_socket(name, uid, data['ip_port'], name_to_speed_limiter.get(name))
386
- if create_result:
387
- self.udp_forward_client.send_by_uid(uid, b)
388
-
389
- # Handle connection confirmed/failed (for C2C)
390
- elif msg_type == MessageTypeConstant.CONNECT_CONFIRMED:
391
- data: TcpOverWebsocketMessage = message_data['data']
392
- uid = data['uid']
393
- # C2C 连接确认,暂无特殊处理
394
- LoggerFactory.get_logger().debug(f'C2C connection confirmed: {uid.hex()}')
395
-
396
- elif msg_type == MessageTypeConstant.CONNECT_FAILED:
397
- data: TcpOverWebsocketMessage = message_data['data']
398
- uid = data['uid']
399
- LoggerFactory.get_logger().info(f'C2C connection failed: {uid.hex()}')
400
- # 关闭本地连接
401
- conn = self.forward_client.uid_to_socket_connection.get(uid)
402
- if conn:
403
- self.forward_client.close_connection(conn.socket)
404
-
405
- except Exception as e:
406
- LoggerFactory.get_logger().error(f'Data connection message handling error: {e}')
407
- LoggerFactory.get_logger().error(traceback.format_exc())
408
-
409
350
  def _on_p2p_data_received(self, uid: bytes, data: bytes):
410
351
  """Callback when data comes from P2P Tunnel"""
411
352
  self.forward_client.send_by_uid(uid, data)
@@ -471,10 +412,6 @@ class WebsocketClient:
471
412
  self.forward_client.close()
472
413
  self.udp_forward_client.close()
473
414
 
474
- # Stop data connections (will be re-established after PUSH_CONFIG response)
475
- if self.data_conn_manager:
476
- self.data_conn_manager.stop_all()
477
-
478
415
  # Restart tunnel manager
479
416
  if self.tunnel_manager:
480
417
  self.tunnel_manager.stop()
@@ -496,9 +433,7 @@ class WebsocketClient:
496
433
  'config_list': push_client_data,
497
434
  "client_name": client_name,
498
435
  'version': SystemConstant.VERSION,
499
- 'p2p_supported': True,
500
- 'multi_connection': self.multi_connection_enabled,
501
- 'num_data_channels': self.num_data_channels
436
+ 'p2p_supported': True
502
437
  }
503
438
  message: MessageEntity = {
504
439
  'type_': MessageTypeConstant.PUSH_CONFIG,
@@ -522,9 +457,6 @@ class WebsocketClient:
522
457
  LoggerFactory.get_logger().info(f'WS Closed: {a}, {b}')
523
458
  self.heart_beat_task.is_running = False
524
459
  self.forward_client.close()
525
- # Stop data connections
526
- if self.data_conn_manager:
527
- self.data_conn_manager.stop_all()
528
460
  # Stop tunnel manager
529
461
  if self.tunnel_manager:
530
462
  self.tunnel_manager.stop()
@@ -21,7 +21,6 @@ from common.speed_limit import SpeedLimiter
21
21
  from constant.message_type_constnat import MessageTypeConstant
22
22
  from context.context_utils import ContextUtils
23
23
  from entity.message.message_entity import MessageEntity
24
- from server.session_manager import SessionManager
25
24
 
26
25
 
27
26
  class PublicSocketServer:
@@ -151,6 +150,12 @@ class TcpForwardClient:
151
150
  LoggerFactory.get_logger().error(f'Close error: {traceback.format_exc()}')
152
151
  return
153
152
 
153
+ # --- 发送端限速:在发送前等待 ---
154
+ if data.speed_limiter and recv:
155
+ wait_time = data.speed_limiter.acquire(len(recv))
156
+ if wait_time > 0:
157
+ time.sleep(wait_time)
158
+
154
159
  if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
155
160
  LoggerFactory.get_logger().debug(f'send to ws uid: {socket_connection.uid}, len: {len(recv)}')
156
161
  send_message: MessageEntity = {
@@ -169,22 +174,9 @@ class TcpForwardClient:
169
174
  except (OSError, ValueError, KeyError):
170
175
  LoggerFactory.get_logger().error(f'close error: {traceback.format_exc()}')
171
176
  try:
172
- # 发送端限速:在发送前等待
173
- if data.speed_limiter and recv:
174
- wait_time = data.speed_limiter.acquire(len(recv))
175
- if wait_time > 0:
176
- time.sleep(wait_time)
177
-
178
- # 使用多连接:根据 UID 选择数据通道
179
- control_handler = socket_connection.socket_server.websocket_handler
180
- session = SessionManager.get_instance().get_session_by_handler(control_handler)
181
- if session:
182
- target_handler = session.get_data_handler(socket_connection.uid)
183
- else:
184
- target_handler = control_handler
185
- is_compress = target_handler.compress_support
177
+ is_compress = socket_connection.socket_server.websocket_handler.compress_support
186
178
  self.tornado_loop.add_callback(
187
- partial(target_handler.write_message, NatSerialization.dumps(send_message, ContextUtils.get_password(), is_compress)), True)
179
+ partial(socket_connection.socket_server.websocket_handler.write_message, NatSerialization.dumps(send_message, ContextUtils.get_password(), is_compress)), True)
188
180
  except Exception:
189
181
  LoggerFactory.get_logger().error(traceback.format_exc())
190
182
 
@@ -17,7 +17,6 @@ from common.speed_limit import SpeedLimiter
17
17
  from constant.message_type_constnat import MessageTypeConstant
18
18
  from context.context_utils import ContextUtils
19
19
  from entity.message.message_entity import MessageEntity
20
- from server.session_manager import SessionManager
21
20
 
22
21
 
23
22
  class UdpEndpoint:
@@ -204,7 +203,7 @@ class UdpForwardClient:
204
203
 
205
204
  uid = server.add_endpoint(address)
206
205
 
207
- # 发送端限速:在发送前等待
206
+ # --- 发送端限速:在发送前等待 ---
208
207
  if server.speed_limiter and data:
209
208
  wait_time = server.speed_limiter.acquire(len(data))
210
209
  if wait_time > 0:
@@ -224,16 +223,10 @@ class UdpForwardClient:
224
223
  if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
225
224
  LoggerFactory.get_logger().debug(f"Send UDP data to WebSocket, uid: {uid}, len: {len(data)}")
226
225
 
227
- # 使用多连接:根据 UID 选择数据通道
228
- control_handler = server.websocket_handler
229
- session = SessionManager.get_instance().get_session_by_handler(control_handler)
230
- if session:
231
- target_handler = session.get_data_handler(uid)
232
- else:
233
- target_handler = control_handler
234
- is_compress = target_handler.compress_support
226
+ # Schedule WebSocket send operation in the async loop
227
+ is_compress = server.websocket_handler.compress_support
235
228
  self.tornado_loop.add_callback(
236
- partial(target_handler.write_message, NatSerialization.dumps(send_message, ContextUtils.get_password(), is_compress)), True)
229
+ partial(server.websocket_handler.write_message, NatSerialization.dumps(send_message, ContextUtils.get_password(), is_compress)), True)
237
230
 
238
231
  async def register_udp_server(self, port: int, name: str, ip_port: str, websocket_handler, speed_limit_size: float):
239
232
  """Register a UDP server"""