内存优化,减轻GC压力
This commit is contained in:
@@ -265,4 +265,4 @@ public class SendPacketSlidingWindow<K, V extends NetworkPacket> implements Clos
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -118,46 +118,50 @@ public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 增加两个 ThreadLocal 静态变量,用于复用 4 字节的长度头
|
||||||
|
private static final ThreadLocal<ByteBuffer> SIZE_WRITE_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(4));
|
||||||
|
private static final ThreadLocal<ByteBuffer> SIZE_READ_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(4));
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writePacket(ByteBuffer kp) throws IOException {
|
public void writePacket(ByteBuffer kp) throws IOException {
|
||||||
ByteBuffer szeWrite=NetworkPacket.bufferAllocator.allocate(4);
|
// 1. 获取并清空缓存的 4 字节 buffer
|
||||||
szeWrite.limit(4);
|
ByteBuffer szeWrite = SIZE_WRITE_BUFFER.get();
|
||||||
//szeWrite.clear();
|
szeWrite.clear();
|
||||||
|
|
||||||
|
// 2. 写入长度并翻转
|
||||||
szeWrite.putInt(kp.limit());
|
szeWrite.putInt(kp.limit());
|
||||||
szeWrite.flip();
|
szeWrite.flip();
|
||||||
KLALBVirtualSocketChannel obj = null;
|
|
||||||
writableChannel.write(new ByteBuffer[] {szeWrite,kp});
|
|
||||||
checkflush();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 3. 发送
|
||||||
|
writableChannel.write(new ByteBuffer[] {szeWrite, kp});
|
||||||
|
checkflush();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer readPacket() throws IOException {
|
public ByteBuffer readPacket() throws IOException {
|
||||||
|
// 1. 获取并清空缓存的 4 字节 buffer
|
||||||
ByteBuffer szeRead=NetworkPacket.bufferAllocator.allocate(4);
|
ByteBuffer szeRead = SIZE_READ_BUFFER.get();
|
||||||
timeoutTimer=System.nanoTime();
|
|
||||||
timerenabled=true;
|
|
||||||
ByteBuffer kp;
|
|
||||||
szeRead.clear();
|
szeRead.clear();
|
||||||
szeRead.limit(4);
|
szeRead.limit(4);
|
||||||
//szeRead.clear();
|
|
||||||
|
timeoutTimer = System.nanoTime();
|
||||||
|
timerenabled = true;
|
||||||
|
ByteBuffer kp;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KNEChannels.readFully(readableChannel,szeRead);
|
// 2. 读取长度头
|
||||||
|
KNEChannels.readFully(readableChannel, szeRead);
|
||||||
szeRead.flip();
|
szeRead.flip();
|
||||||
int size=szeRead.getInt(0);
|
int size = szeRead.getInt(0);
|
||||||
kp=NetworkPacket.bufferAllocator.allocate(size);
|
|
||||||
|
// 3. 分配真正的数据区(这里如果是高频,建议后续也可以池化,不过现在是按需大小分配)
|
||||||
|
kp = NetworkPacket.bufferAllocator.allocate(size);
|
||||||
kp.limit(size);
|
kp.limit(size);
|
||||||
KNEChannels.readFully(readableChannel, kp);
|
KNEChannels.readFully(readableChannel, kp);
|
||||||
kp.flip();
|
kp.flip();
|
||||||
return kp;
|
return kp;
|
||||||
}finally {
|
} finally {
|
||||||
timerenabled=false;
|
timerenabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,14 +252,23 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
checkLock.unlock();
|
checkLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 在类成员变量中定义 ThreadLocal 发送缓冲区
|
||||||
|
private static final ThreadLocal<ByteBuffer> NTP_TX_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(9000));
|
||||||
|
|
||||||
private void sendNTPPacket(NTPv4Packet nv4, SocketAddress target) throws IOException {
|
private void sendNTPPacket(NTPv4Packet nv4, SocketAddress target) throws IOException {
|
||||||
ByteBuffer bbf = ByteBuffer.allocate(65535);
|
// 1. 获取并重置缓冲区
|
||||||
|
ByteBuffer bbf = NTP_TX_BUFFER.get();
|
||||||
|
bbf.clear(); // 重置 position=0, limit=9000
|
||||||
|
|
||||||
|
// 2. 写入数据
|
||||||
nv4.writeToChannel(KNEChannels.newWritableChannel(bbf));
|
nv4.writeToChannel(KNEChannels.newWritableChannel(bbf));
|
||||||
bbf.flip();
|
bbf.flip(); // 切换为读模式,limit 会变成实际写入的长度
|
||||||
|
|
||||||
|
// 3. 发送
|
||||||
DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit());
|
DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit());
|
||||||
dp.setSocketAddress(target);
|
dp.setSocketAddress(target);
|
||||||
dgs.send(dp);
|
dgs.send(dp);
|
||||||
|
|
||||||
if (showPacket)
|
if (showPacket)
|
||||||
System.out.println("NTPv4 TX:" + target + " " + nv4);
|
System.out.println("NTPv4 TX:" + target + " " + nv4);
|
||||||
}
|
}
|
||||||
@@ -379,15 +388,30 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
return sends;
|
return sends;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. 在类成员变量中定义 ThreadLocal 缓冲区
|
||||||
|
private static final ThreadLocal<ByteBuffer> NTP_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(9000));
|
||||||
|
|
||||||
private NTPv4Packet receiveNTPPacket(AtomicReference<InetSocketAddress> isa) throws IOException {
|
private NTPv4Packet receiveNTPPacket(AtomicReference<InetSocketAddress> isa) throws IOException {
|
||||||
ByteBuffer bbf = ByteBuffer.allocate(65535);
|
// 2. 从 ThreadLocal 获取,并重置状态 (position=0, limit=9000)
|
||||||
|
ByteBuffer bbf = NTP_BUFFER.get();
|
||||||
|
bbf.clear();
|
||||||
|
|
||||||
|
// 3. 构造 DatagramPacket
|
||||||
DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit());
|
DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit());
|
||||||
dgs.receive(dp);
|
dgs.receive(dp);
|
||||||
|
|
||||||
isa.set((InetSocketAddress) dp.getSocketAddress());
|
isa.set((InetSocketAddress) dp.getSocketAddress());
|
||||||
|
|
||||||
|
// 4. 关键:限制 ByteBuffer 的读取范围,防止越界读取历史脏数据
|
||||||
|
bbf.limit(dp.getLength());
|
||||||
|
|
||||||
NTPv4Packet nv4 = new NTPv4Packet(context.getClock());
|
NTPv4Packet nv4 = new NTPv4Packet(context.getClock());
|
||||||
|
// 5. 将 Channel 的读取操作限制在 dp.getLength() 长度内
|
||||||
nv4.readFromChannel(KNEChannels.newReadableChannel(bbf), dp.getLength());
|
nv4.readFromChannel(KNEChannels.newReadableChannel(bbf), dp.getLength());
|
||||||
|
|
||||||
if (showPacket)
|
if (showPacket)
|
||||||
System.out.println("NTPv4 RX:" + isa.get() + " " + nv4);
|
System.out.println("NTPv4 RX:" + isa.get() + " " + nv4);
|
||||||
|
|
||||||
return nv4;
|
return nv4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.net.Inet6Address;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -35,6 +36,7 @@ import org.kne.cloud.network.monitor.MonitorData;
|
|||||||
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
||||||
import org.kne.cloud.network.monitor.SpeedAndTrafficMonitorData;
|
import org.kne.cloud.network.monitor.SpeedAndTrafficMonitorData;
|
||||||
import org.kne.cloud.network.monitor.SpeedAndTrafficMonitorDataImpl;
|
import org.kne.cloud.network.monitor.SpeedAndTrafficMonitorDataImpl;
|
||||||
|
import org.kne.io.KNEChannels;
|
||||||
import org.kne.membandboost.MembandBoost;
|
import org.kne.membandboost.MembandBoost;
|
||||||
|
|
||||||
public class KLALBRoutingProtocol extends Thread{
|
public class KLALBRoutingProtocol extends Thread{
|
||||||
@@ -211,20 +213,15 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
while(true) {
|
while(true) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[]ca=MembandBoost.allocateUninitializedByteArray(65535);
|
KLALBRoutingProtocolPacket kp=receivePacket();
|
||||||
DatagramPacket dgp=new DatagramPacket(ca, ca.length);
|
|
||||||
ds.receive(dgp);
|
|
||||||
//System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength())));
|
|
||||||
ByteArrayInputStream bi=new ByteArrayInputStream(dgp.getData(),0,dgp.getLength());
|
|
||||||
KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi));
|
|
||||||
|
|
||||||
switch(kp.getType()) {
|
switch(kp.getType()) {
|
||||||
case KLALBRoutingProtocolPacket.RINFO_REQ:
|
case KLALBRoutingProtocolPacket.RINFO_REQ:
|
||||||
RouterInfo rifr=netmap.get(router.getLocator().getAddress());
|
RouterInfo rifr=netmap.get(router.getLocator().getAddress());
|
||||||
if(debug)
|
if(debug)
|
||||||
System.out.println("get Request from:"+dgp.getSocketAddress());
|
System.out.println("get Request from:"+kp.getFrom());
|
||||||
if(rifr!=null) {
|
if(rifr!=null) {
|
||||||
writePacket( new RouterInfoPacket( rifr,false,-1), dgp.getSocketAddress());
|
writePacket( new RouterInfoPacket( rifr,false,-1), kp.getFrom());
|
||||||
if(debug)
|
if(debug)
|
||||||
System.out.println("response router info:"+rifr);
|
System.out.println("response router info:"+rifr);
|
||||||
}else {
|
}else {
|
||||||
@@ -246,7 +243,7 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
netmap.put(rif.getLocator().getAddress(), rif);
|
netmap.put(rif.getLocator().getAddress(), rif);
|
||||||
noticeUpdate();
|
noticeUpdate();
|
||||||
if(rifp.isFlood()) {
|
if(rifp.isFlood()) {
|
||||||
floodPacket( dgp.getSocketAddress(), rifp);
|
floodPacket( kp.getFrom(), rifp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +256,7 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
break;
|
break;
|
||||||
case KLALBRoutingProtocolPacket.JSON_DATA:
|
case KLALBRoutingProtocolPacket.JSON_DATA:
|
||||||
JsonDataPacket data=(JsonDataPacket) kp;
|
JsonDataPacket data=(JsonDataPacket) kp;
|
||||||
receivers.forEach((v)->{v.accept(dgp.getSocketAddress(),data);});
|
receivers.forEach((v)->{v.accept(kp.getFrom(),data);});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -276,7 +273,32 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static final ThreadLocal<ByteBuffer> LSDB_RX_BUFFER =
|
||||||
|
ThreadLocal.withInitial(() -> ByteBuffer.allocate(65535));
|
||||||
|
|
||||||
|
private KLALBRoutingProtocolPacket receivePacket() throws IOException {
|
||||||
|
// 2. 获取并重置缓冲区
|
||||||
|
ByteBuffer bbf = LSDB_RX_BUFFER.get();
|
||||||
|
bbf.clear();
|
||||||
|
|
||||||
|
// 3. 构造 DatagramPacket(注意这里要传底层 array 和 capacity)
|
||||||
|
DatagramPacket dgp = new DatagramPacket(bbf.array(), bbf.capacity());
|
||||||
|
|
||||||
|
// 4. 阻塞接收
|
||||||
|
ds.receive(dgp);
|
||||||
|
|
||||||
|
// 5. 限制缓冲区范围到实际收到的长度
|
||||||
|
bbf.limit(dgp.getLength());
|
||||||
|
bbf.position(0);
|
||||||
|
|
||||||
|
// 6. 直接用 KNEChannels 包装 ByteBuffer,避免 ByteArrayInputStream
|
||||||
|
KLALBRoutingProtocolPacket kp = KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(
|
||||||
|
KNEChannels.newReadableChannel(bbf)
|
||||||
|
);
|
||||||
|
if(kp!=null)
|
||||||
|
kp.setFrom(dgp.getSocketAddress());
|
||||||
|
return kp;
|
||||||
|
}
|
||||||
private void floodPacket(SocketAddress except,RouterInfoPacket rifp) throws IOException {
|
private void floodPacket(SocketAddress except,RouterInfoPacket rifp) throws IOException {
|
||||||
long asn=rifp.getASN();
|
long asn=rifp.getASN();
|
||||||
List<Neighbor>ln= router.getNeighbors();
|
List<Neighbor>ln= router.getNeighbors();
|
||||||
@@ -304,20 +326,34 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
public void sendJsonPacketToAddress(JsonDataPacket pkt,SocketAddress dest) throws IOException {
|
public void sendJsonPacketToAddress(JsonDataPacket pkt,SocketAddress dest) throws IOException {
|
||||||
writePacket(pkt,dest);
|
writePacket(pkt,dest);
|
||||||
}
|
}
|
||||||
|
// 1. 在类成员变量处定义一个 64KB 的 ThreadLocal 缓冲区
|
||||||
private void writePacket(KLALBRoutingProtocolPacket pkt,SocketAddress dest) throws IOException {
|
// 注意:这里依然使用 65535,因为你说 LSDB 同步可能需要这么大
|
||||||
ByteArrayOutputStream bos=new ByteArrayOutputStream(65535);
|
private static final ThreadLocal<ByteBuffer> LSDB_TX_BUFFER =
|
||||||
KLALBRoutingProtocolPacket.writeKLALBPacketToChannel(Channels.newChannel(bos),pkt);
|
ThreadLocal.withInitial(() -> ByteBuffer.allocate(65535));
|
||||||
byte[]to=bos.toByteArray();
|
|
||||||
DatagramPacket dgpx=new DatagramPacket(to,to.length);
|
private void writePacket(KLALBRoutingProtocolPacket pkt, SocketAddress dest) throws IOException {
|
||||||
|
// 2. 从 ThreadLocal 获取,并清空状态
|
||||||
|
ByteBuffer bbf = LSDB_TX_BUFFER.get();
|
||||||
|
bbf.clear();
|
||||||
|
|
||||||
|
// 3. 使用 KNEChannels 绑定到 ByteBuffer,直接写入
|
||||||
|
// 这避免了 ByteArrayOutputStream 内部的动态扩容和拷贝
|
||||||
|
KLALBRoutingProtocolPacket.writeKLALBPacketToChannel(KNEChannels.newWritableChannel(bbf), pkt);
|
||||||
|
|
||||||
|
// 4. 翻转,准备读取数据
|
||||||
|
bbf.flip();
|
||||||
|
|
||||||
|
// 5. 直接拿底层的 byte[] 和真实的 limit 发送
|
||||||
|
DatagramPacket dgpx = new DatagramPacket(bbf.array(), bbf.limit());
|
||||||
dgpx.setSocketAddress(dest);
|
dgpx.setSocketAddress(dest);
|
||||||
|
|
||||||
sendLock.lock();
|
sendLock.lock();
|
||||||
try {
|
try {
|
||||||
ds.send(dgpx);
|
ds.send(dgpx);
|
||||||
}catch(BindException e) {
|
} catch (BindException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.err.println("Cannot assign:"+dest);
|
System.err.println("Cannot assign:" + dest);
|
||||||
}finally {
|
} finally {
|
||||||
sendLock.unlock();
|
sendLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
@@ -133,4 +134,11 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
|
|||||||
public static void writeKLALBPacketToChannel(WritableByteChannel writableByteChannel,KLALBRoutingProtocolPacket klb) throws IOException {
|
public static void writeKLALBPacketToChannel(WritableByteChannel writableByteChannel,KLALBRoutingProtocolPacket klb) throws IOException {
|
||||||
klb.writeToChannel(writableByteChannel);
|
klb.writeToChannel(writableByteChannel);
|
||||||
}
|
}
|
||||||
|
private transient SocketAddress from;
|
||||||
|
public void setFrom(SocketAddress from) {
|
||||||
|
this.from=from;
|
||||||
|
}
|
||||||
|
public SocketAddress getFrom(){
|
||||||
|
return from;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -204,34 +205,19 @@ public class SRv6Router {
|
|||||||
srListeners.remove(listener);
|
srListeners.remove(listener);
|
||||||
}
|
}
|
||||||
public void sortRouteTabel() {
|
public void sortRouteTabel() {
|
||||||
|
List<Map<IPv6Address, List<RouteItem>>> routeTabel1x = routeTabel1;
|
||||||
|
|
||||||
// 构建前缀长度索引的路由表
|
for (Map<IPv6Address, List<RouteItem>> map : routeTabel1x) {
|
||||||
List<Map<IPv6Address, List<RouteItem>>> routeTabel1x = new ArrayList<Map<IPv6Address, List<RouteItem>>>(
|
// 使用 compute 直接替换
|
||||||
IPv6_BITS + 1);
|
map.replaceAll((key, oldList) -> {
|
||||||
for (int i = 0; i < IPv6_BITS + 1; i++) {
|
if (oldList == null || oldList.isEmpty()) {
|
||||||
routeTabel1x.add(new HashMap<IPv6Address, List<RouteItem>>(1024, 0.5f));
|
return oldList;
|
||||||
|
}
|
||||||
|
List<RouteItem> newList = new ArrayList<>(oldList);
|
||||||
|
Collections.sort(newList);
|
||||||
|
return newList;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
List<RouteItem> routeTabel0x=routeTable0;
|
|
||||||
// 按前缀长度组织路由表
|
|
||||||
for (RouteItem item : routeTabel0x) {
|
|
||||||
IPv6AddressGroup iag = item.getDestination();
|
|
||||||
Map<IPv6Address, List<RouteItem>> map = routeTabel1x.get(iag.getPrefixLength());
|
|
||||||
List<RouteItem> newlist = new ArrayList<RouteItem>();
|
|
||||||
List<RouteItem> oldlist = map.putIfAbsent(iag.getAddress(), newlist);
|
|
||||||
if (oldlist == null) {
|
|
||||||
oldlist = newlist;
|
|
||||||
}
|
|
||||||
oldlist.add(item);
|
|
||||||
}
|
|
||||||
for(Map<IPv6Address, List<RouteItem>> map:routeTabel1x) {
|
|
||||||
for(List<RouteItem>v:map.values()) {
|
|
||||||
Collections.sort(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// System.out.println(routeTabel1x);
|
|
||||||
routeTabel1 = routeTabel1x; // 更新索引路由表
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// 更新路由表
|
// 更新路由表
|
||||||
public void updateRouteTabel() {
|
public void updateRouteTabel() {
|
||||||
@@ -264,7 +250,7 @@ public class SRv6Router {
|
|||||||
ArrayList<Map<IPv6Address, List<RouteItem>>> routeTabel1x = new ArrayList<Map<IPv6Address, List<RouteItem>>>(
|
ArrayList<Map<IPv6Address, List<RouteItem>>> routeTabel1x = new ArrayList<Map<IPv6Address, List<RouteItem>>>(
|
||||||
IPv6_BITS + 1);
|
IPv6_BITS + 1);
|
||||||
for (int i = 0; i < IPv6_BITS + 1; i++) {
|
for (int i = 0; i < IPv6_BITS + 1; i++) {
|
||||||
routeTabel1x.add(new HashMap<IPv6Address, List<RouteItem>>(1024, 0.5f));
|
routeTabel1x.add(new ConcurrentHashMap<IPv6Address, List<RouteItem>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按前缀长度组织路由表
|
// 按前缀长度组织路由表
|
||||||
|
|||||||
Reference in New Issue
Block a user