修复内存泄露

This commit is contained in:
Administrator
2023-03-16 16:06:39 +08:00
parent 2d6122480a
commit cc1df750ae
10 changed files with 181 additions and 76 deletions
+13 -3
View File
@@ -1,4 +1,4 @@
{ {
"name" : "KNECloud", "name" : "KNECloud",
"services" : [ { "services" : [ {
"name" : "KNE官网", "name" : "KNE官网",
@@ -12,14 +12,24 @@
"name" : "服务器远程桌面", "name" : "服务器远程桌面",
"protocol" : "RDP", "protocol" : "RDP",
"localaddress" : "127.0.0.1:3389" "localaddress" : "127.0.0.1:3389"
}, {
"name" : "MC",
"protocol" : "Minecraft",
"localaddress" : "127.0.0.1:25565"
} ], } ],
"tunnels" : [ { "tunnels" : [ {
"name" : "Openfrp-直连线路",
"remoteaddress" : "127.0.0.1:4566",
"frpc" : [ "[common]", "server_addr = 180.76.147.250", "server_port = 8120", "tcp_mux = true", "protocol = tcp", "dns_server = 223.5.5.5", "user = bb28264e0abf3bcbec3a2180c5a279b8", "token = I2KMo1HvRxuvurv2", "[ts1xx]", "type = xtcp", "role = visitor", "server_name = zl1", "bind_addr = 127.0.0.1", "bind_port = 4566", "sk = knecloud" ]
}, {
"name" : "Openfrp-直连线路", "name" : "Openfrp-直连线路",
"remoteaddress" : "127.0.0.1:4566" "remoteaddress" : "127.0.0.1:4566"
}, { }, {
"name" : "Openfrp-直连线路", "name" : "Openfrp-直连线路",
"remoteaddress" : "127.0.0.1:4566", "remoteaddress" : "127.0.0.1:4566"
"frpc" : [ "[common]", "server_addr = 180.76.147.250", "server_port = 8120", "tcp_mux = true", "protocol = tcp", "dns_server = 223.5.5.5", "user = bb28264e0abf3bcbec3a2180c5a279b8", "token = I2KMo1HvRxuvurv2", "[ts1xx]", "type = xtcp", "role = visitor", "server_name = zl1", "bind_addr = 127.0.0.1", "bind_port = 4566", "sk = knecloud" ] }, {
"name" : "Openfrp-直连线路",
"remoteaddress" : "127.0.0.1:4566"
},{ },{
"name" : "NULL-宿迁联通", "name" : "NULL-宿迁联通",
"remoteaddress" : "153.36.240.12:65529" "remoteaddress" : "153.36.240.12:65529"
+2 -1
View File
@@ -5,8 +5,9 @@ public class Consts {
public static final long PINGTIMENS=1000000000L; public static final long PINGTIMENS=1000000000L;
public static final int SO_TIMEOUT = 5000; public static final int SO_TIMEOUT = 5000;
public static final long SN_KEEP = 60000000000L; public static final long SN_KEEP = 60000000000L;
public static final int MAX_RESEND = 100; public static final int MAX_RESEND = 10;
public static final long UACK_TIME = 5000000000L; public static final long UACK_TIME = 5000000000L;
public static final int CONNECT_TIMEOUT=10000; public static final int CONNECT_TIMEOUT=10000;
public static final double MAX_QUEUE_TIME=500; public static final double MAX_QUEUE_TIME=500;
public static final long KEEP_TIME = 60000000000L;
} }
@@ -17,6 +17,7 @@ import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.kne.cloud.network.mport.ServiceElement; import org.kne.cloud.network.mport.ServiceElement;
import org.kne.cloud.network.mport.ThreadTool; import org.kne.cloud.network.mport.ThreadTool;
@@ -35,43 +36,44 @@ public class IOThreadManager {
} }
klc.getLocaltcps().put(tc.getCuid(), tc); klc.getLocaltcps().put(tc.getCuid(), tc);
if(klc.getRemotetcps().isEmpty()) { // checkRemotes();
System.out.println("所有线路已断开,请检查网络");
Collection<LocalTCPConnection> c=klc.getLocaltcps().values();
for (Iterator<LocalTCPConnection> iterator = c.iterator(); iterator.hasNext();) {
LocalTCPConnection object =iterator.next();
object.close();
}
}
AtomicBoolean AB=new AtomicBoolean(true); AtomicBoolean AB=new AtomicBoolean(true);
try { try {
if (syn) {
KLALBBlock sbk = new KLALBBlock();
sbk.cuid = tc.getCuid();
sbk.number = 0;
sbk.command = 2;
sbk.lservice = tc.getServiceElement().name;
sbk.lipport = tc.getServiceElement().ipport;
klc.submitDataBlockNoDelay(sbk);
}
//AtomicReference<Thread>tn=new AtomicReference<>();
//AtomicReference<Thread>ltn=new AtomicReference<>();
Thread lt = ThreadTool.makeVThreadIfSupport("数据包计时重发线程", () -> { Thread lt = ThreadTool.makeVThreadIfSupport("数据包计时重发线程", () -> {
try { try {
while (AB.get()) { while (AB.get()) {
klc.outputTimer(tc); klc.outputTimer(tc);
/*if(!checkRemotes()) {
AB.set(false);
}*/
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace();
AB.set(false);
//tn.get().interrupt();
//ltn.get().interrupt();
tc.getOutputcache().clear();
tc.close();
KLALBBlock sbk = new KLALBBlock();
sbk.cuid = tc.getCuid();
sbk.number = 0;
sbk.command = 3;
klc.submitDataBlockNoDelay(sbk);
} catch (IOException e) {
e.printStackTrace();
} }
}); });
Thread.sleep(50); //Thread.sleep(500);
Thread ls = ThreadTool.makeVThreadIfSupport("本地发送线程", () -> { Thread ls = ThreadTool.makeVThreadIfSupport("本地发送线程", () -> {
try { try {
while (AB.get()) { while (true) {
KLALBBlock rd=klc.getDataBlock(tc); KLALBBlock rd=klc.getDataBlock(tc);
if(rd.data==null) { if(rd.data==null) {
break; break;
@@ -84,6 +86,7 @@ public class IOThreadManager {
AB.set(false); AB.set(false);
lt.interrupt(); lt.interrupt();
tc.getOutputcache().clear(); tc.getOutputcache().clear();
tc.close();
KLALBBlock sbk = new KLALBBlock(); KLALBBlock sbk = new KLALBBlock();
sbk.cuid = tc.getCuid(); sbk.cuid = tc.getCuid();
sbk.number = 0; sbk.number = 0;
@@ -97,12 +100,14 @@ public class IOThreadManager {
} }
} }
}); });
//tn.set(ls);
Thread lr = ThreadTool.makeVThreadIfSupport("本地接收线程", () -> { Thread lr = ThreadTool.makeVThreadIfSupport("本地接收线程", () -> {
try { try {
while (AB.get()) { while (true) {
KLALBBlock ks = tc.packBlock(); KLALBBlock ks = tc.packBlock();
klc.putDataBlock(tc, ks); klc.putDataBlock(tc, ks);
if (ks.data == null) { if (ks.data == null) {
klc.waitOutput(tc);
break; break;
} }
} }
@@ -112,6 +117,7 @@ public class IOThreadManager {
ls.interrupt(); ls.interrupt();
lt.interrupt(); lt.interrupt();
tc.getOutputcache().clear(); tc.getOutputcache().clear();
tc.close();
KLALBBlock sbk = new KLALBBlock(); KLALBBlock sbk = new KLALBBlock();
sbk.cuid = tc.getCuid(); sbk.cuid = tc.getCuid();
sbk.number = 0; sbk.number = 0;
@@ -125,22 +131,34 @@ public class IOThreadManager {
} }
} }
}); });
//ltn.set(lr);
tc.setRSTHook((t)->{ tc.setRSTHook((t)->{
//System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxx");
//System.exit(123);
AB.set(false); AB.set(false);
ls.interrupt(); ls.interrupt();
lt.interrupt(); lt.interrupt();
tc.getOutputcache().clear(); tc.getOutputcache().clear();
tc.close();
}); });
if (syn) {
KLALBBlock sbk = new KLALBBlock();
sbk.cuid = tc.getCuid();
sbk.number = 0;
sbk.command = 2;
sbk.lservice = tc.getServiceElement().name;
sbk.lipport = tc.getServiceElement().ipport;
klc.submitAckBlockNoDelay(sbk,1);
}
ls.start(); ls.start();
lr.start(); lr.start();
lt.start(); lt.start();
//System.out.println(klc.getLocaltcps());
lr.join(); lr.join();
ls.join();
ls.interrupt(); ls.interrupt();
klc.waitOutput(tc);
lt.interrupt(); lt.interrupt();
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@@ -190,20 +208,30 @@ public class IOThreadManager {
klc.submitDataBlock(tmp); klc.submitDataBlock(tmp);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} }
} }
// checkRemotes();
}
}
/*private boolean checkRemotes() {
boolean b=true;
if(klc.getRemotetcps().isEmpty()) { if(klc.getRemotetcps().isEmpty()) {
System.out.println("所有线路已断开,请检查网络"); System.out.println("所有线路已断开");
b=false;
Collection<LocalTCPConnection> c=klc.getLocaltcps().values(); Collection<LocalTCPConnection> c=klc.getLocaltcps().values();
for (Iterator<LocalTCPConnection> iterator = c.iterator(); iterator.hasNext();) { for (Iterator<LocalTCPConnection> iterator = c.iterator(); iterator.hasNext();) {
LocalTCPConnection object =iterator.next(); LocalTCPConnection object =iterator.next();
object.close(); object.close();
} }
} }
return b;
} }
} */
public void close() { public void close() {
isopen = false; isopen = false;
Collection<LocalTCPConnection>ltcc= klc.getLocaltcps().values(); Collection<LocalTCPConnection>ltcc= klc.getLocaltcps().values();
@@ -14,7 +14,7 @@ public class KLALBBlock implements Comparable<KLALBBlock>{
public volatile int cuid;//用于识别数据包的stream ID号 public volatile int cuid;//用于识别数据包的stream ID号
public volatile long number;//数据包的编号,用于排序 public volatile long number;//数据包的编号,用于排序
public volatile byte[]data;//数据内容 public volatile byte[]data;//数据内容
public volatile int command;//功能标记 0=PING 1=PONG 2=SYN 3=RST public volatile int command;//功能标记 0=PING 1=PONG 2=SYN 3=RST 4=KEEP
public volatile long pingtime;//PING计时器 public volatile long pingtime;//PING计时器
public volatile int cacheused; public volatile int cacheused;
+48 -20
View File
@@ -45,7 +45,7 @@ public class KLALBCore {
private volatile int cacheblocks; private volatile int cacheblocks;
private Map<Integer, LocalTCPConnection> localtcps = Collections.synchronizedMap(new HashMap<>()); private Map<Integer, LocalTCPConnection> localtcps = new ConcurrentHashMap<>();
private List<RemoteTCPConnection> remotetcps = new Vector<>(); private List<RemoteTCPConnection> remotetcps = new Vector<>();
private Predicate<KLALBBlock>acceptSYN; private Predicate<KLALBBlock>acceptSYN;
@@ -194,8 +194,7 @@ public class KLALBCore {
}else if(brc.command==1) { }else if(brc.command==1) {
}else if(brc.command==2) { }else if(brc.command==2) {
if(!localtcps.containsKey(brc.cuid)) { if(!localtcps.containsKey(brc.cuid)) {
localtcps.put(brc.cuid, null);
try {
if(acceptSYN==null||!acceptSYN.test(brc)) { if(acceptSYN==null||!acceptSYN.test(brc)) {
KLALBBlock rst=new KLALBBlock(); KLALBBlock rst=new KLALBBlock();
rst.cuid=brc.cuid; rst.cuid=brc.cuid;
@@ -203,27 +202,24 @@ public class KLALBCore {
rst.command=3; rst.command=3;
submitDataBlockNoDelay(rst); submitDataBlockNoDelay(rst);
} }
}finally {
localtcps.remove(brc.cuid, null);
}
} }
}else if(brc.command==3) { }else if(brc.command==3) {
LocalTCPConnection ltc=localtcps.get(brc.cuid); LocalTCPConnection ltc=localtcps.get(brc.cuid);
if(ltc!=null) { if(ltc!=null) {
ltc.close(); //ltc.close();
ltc.getRSTHook().accept(ltc); ltc.getRSTHook().accept(ltc);
} }
} }
}else if(brc.number>0){ }else if(brc.number>0){
LocalTCPConnection ltcs=localtcps.get(brc.cuid); LocalTCPConnection ltcs=localtcps.get(brc.cuid);
if(ltcs!=null) { if(ltcs!=null) {
ltcs.getSendDeque().add(brc);
KLALBBlock ack=new KLALBBlock(); KLALBBlock ack=new KLALBBlock();
ack.cuid=brc.cuid; ack.cuid=brc.cuid;
ack.number=-brc.number; ack.number=-brc.number;
ack.cacheused=ltcs.getSendDeque().size(); ack.cacheused=ltcs.getSendDeque().size();
submitAckBlockNoDelay(ack,3); submitAckBlockNoDelay(ack,3);
ltcs.getSendDeque().add(brc);
} }
@@ -251,6 +247,12 @@ public class KLALBCore {
return b.number == nx; return b.number == nx;
});*/ });*/
ltc.setPeerCacheUsed(brc.cacheused); ltc.setPeerCacheUsed(brc.cacheused);
}else {
KLALBBlock sbk = new KLALBBlock();
sbk.cuid = brc.cuid;
sbk.number = 0;
sbk.command = 3;
submitDataBlockNoDelay(sbk);
} }
int cuid=brc.cuid; int cuid=brc.cuid;
synchronized (remotetcps) { synchronized (remotetcps) {
@@ -318,12 +320,14 @@ public class KLALBCore {
tc.setDelay((tc.getDelay()*10+delay)/11); tc.setDelay((tc.getDelay()*10+delay)/11);
} }
public void submitDataBlock(KLALBBlock ks) throws InterruptedException { public void submitDataBlock(KLALBBlock ks) throws InterruptedException, IOException {
submitDataBlock(ks,1); submitDataBlock(ks,1);
} }
public void submitDataBlock(KLALBBlock kb,int i) throws InterruptedException { public void submitDataBlock(KLALBBlock kb,int i) throws InterruptedException, IOException {
int ni=Math.min(i, remotetcps.size()); int ni=Math.min(i, remotetcps.size());
while(true) { while(true) {
if(remotetcps.isEmpty())
throw new IOException("发送错误");
synchronized (remotetcps) { synchronized (remotetcps) {
sortRemoteTCPByPrediction(); sortRemoteTCPByPrediction();
for (Iterator<RemoteTCPConnection> iterator = remotetcps.iterator(); iterator.hasNext();) { for (Iterator<RemoteTCPConnection> iterator = remotetcps.iterator(); iterator.hasNext();) {
@@ -374,8 +378,10 @@ public class KLALBCore {
} }
} }
} }
public void putDataBlock(LocalTCPConnection tc, KLALBBlock ks) throws InterruptedException { public void putDataBlock(LocalTCPConnection tc, KLALBBlock ks) throws InterruptedException, IOException {
while (true) { while (true) {
if(remotetcps.isEmpty())
throw new IOException("发送错误");
boolean b; boolean b;
synchronized (tc.getOutputcache()) { synchronized (tc.getOutputcache()) {
b=!tc.getOutputcache().isEmpty()&&(tc.getOutputcount()-tc.getOutputcache().get(0).number>cacheblocks); b=!tc.getOutputcache().isEmpty()&&(tc.getOutputcount()-tc.getOutputcache().get(0).number>cacheblocks);
@@ -402,13 +408,15 @@ public class KLALBCore {
}*/ }*/
submitDataBlock(ks); submitDataBlock(ks);
tc.getOutputcache().add(ks); tc.getOutputcache().add(ks);
while( tc.getPeerCacheUsed()>5000) { while( tc.getPeerCacheUsed()>2*cacheblocks) {
if(remotetcps.isEmpty())
throw new IOException("发送错误");
Thread.sleep(1); Thread.sleep(1);
} }
} }
private volatile long uackt=System.nanoTime(); //private volatile long uackt=System.nanoTime();
public void outputTimer(LocalTCPConnection tc) throws InterruptedException { public void outputTimer(LocalTCPConnection tc) throws InterruptedException, IOException {
List<KLALBBlock> l=tc.getOutputcache(); List<KLALBBlock> l=tc.getOutputcache();
synchronized( l) { synchronized( l) {
for (int i = 0; i < l.size(); i++) { for (int i = 0; i < l.size(); i++) {
@@ -434,28 +442,48 @@ public class KLALBCore {
if(block.resend>=Consts.MAX_RESEND) { if(block.resend>=Consts.MAX_RESEND) {
l.remove(i); l.remove(i);
i--; i--;
throw new IOException("重传失败:"+block);
} }
} }
} }
} }
long curr=System.nanoTime(); long curr=System.nanoTime();
if(curr-uackt>Consts.UACK_TIME) { if(tc.getSendDeque().size()<cacheblocks&&curr-tc.uackt>Consts.UACK_TIME) {
uackt=curr; tc.uackt=curr;
KLALBBlock ack=new KLALBBlock(); KLALBBlock ack=new KLALBBlock();
ack.cuid=tc.getCuid(); ack.cuid=tc.getCuid();
ack.number=Long.MIN_VALUE; ack.number=Long.MIN_VALUE;
ack.cacheused=tc.getSendDeque().size(); ack.cacheused=tc.getSendDeque().size();
submitAckBlockNoDelay(ack,3); submitAckBlockNoDelay(ack,3);
} }
/*if(tc.getSendDeque().size()<cacheblocks&&curr-tc.ukeept>Consts.KEEP_TIME) {
tc.ukeept=curr;
KLALBBlock keep=new KLALBBlock();
keep.cuid=tc.getCuid();
keep.number=0;
keep.command=4;
submitDataBlockNoDelay(keep);
}*/
Thread.sleep(10); Thread.sleep(10);
} }
public KLALBBlock getDataBlock(LocalTCPConnection tc) throws InterruptedException { public KLALBBlock getDataBlock(LocalTCPConnection tc) throws InterruptedException, IOException {
return tc.getSendDeque().take(); KLALBBlock recv=null;
while(true) {
if(remotetcps.isEmpty())
throw new IOException("发送错误");
recv=tc.getSendDeque().poll();
if(recv!=null)
break;
Thread.sleep(1);
}
return recv;
} }
public void waitOutput(LocalTCPConnection tc) throws InterruptedException { public void waitOutput(LocalTCPConnection tc) throws InterruptedException, IOException {
while(tc.getOutputcache().size()>0) { while(tc.getOutputcache().size()>0) {
if(remotetcps.isEmpty())
throw new IOException("发送错误");
Thread.sleep(10); Thread.sleep(10);
} }
} }
@@ -22,7 +22,7 @@ import org.kne.cloud.network.mport.ThreadTool;
public class KLALBServer { public class KLALBServer {
Map<UUID, IOThreadManager> whm=new WeakHashMap<UUID, IOThreadManager>(); Map<UUID, IOThreadManager> whm=Collections.synchronizedMap( new WeakHashMap<UUID, IOThreadManager>());
public KLALBServer(int port,Supplier<String> gjso) throws IOException { public KLALBServer(int port,Supplier<String> gjso) throws IOException {
TCPListener tcpl=new TCPListener(port); TCPListener tcpl=new TCPListener(port);
tcpl.setCon((s)->{ tcpl.setCon((s)->{
@@ -50,9 +50,34 @@ public class KLALBServer {
tcc.handshake(10); tcc.handshake(10);
IOThreadManager nx = null; IOThreadManager nx=whm.computeIfAbsent(uid, (kuid)->{
synchronized (tcpl) { IOThreadManager iom=new IOThreadManager();
if(whm.containsKey(uid)) { iom.getCore().setAcceptSYN((b)->{
try {
STJson stj=new STJson(gjso.get());
if(!stj.checkSafety(b.lipport)) {
System.out.println("未授权的请求:"+b.lipport);
return false;
}
Socket soc=new Socket(b.lipport.getIp(),b.lipport.getPort());
LocalTCPConnection ltc=new LocalTCPConnection(soc, b.cuid);
ThreadTool.makeVThreadIfSupport("局域网连接监视线程", ()->{
try {
iom.handleLocal(ltc, false);
}finally {
ltc.close();
}
}).start();
return true;
} catch (IOException e) {
System.out.println("连接本地服务"+b.lipport+"失败,请检查你的本地服务");
e.printStackTrace();
}
return false;
});
return iom;
});
/*if(whm.containsKey(uid)) {
nx=whm.get(uid); nx=whm.get(uid);
}else { }else {
nx=new IOThreadManager(); nx=new IOThreadManager();
@@ -81,8 +106,7 @@ public class KLALBServer {
return false; return false;
}); });
whm.put(uid, nx); whm.put(uid, nx);
} }*/
}
nx.handleRemote(tcc); nx.handleRemote(tcc);
}catch(IOException e) { }catch(IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -25,10 +25,12 @@ private volatile long inputcount = 1;
private volatile long outputcount = 1; private volatile long outputcount = 1;
public LocalTCPConnection(Socket s) throws IOException { public LocalTCPConnection(Socket s) throws IOException {
super(s); super(s);
s.setKeepAlive(true);
cuid=sng.getAndIncrement(); cuid=sng.getAndIncrement();
} }
public LocalTCPConnection(Socket s,int uid) throws IOException { public LocalTCPConnection(Socket s,int uid) throws IOException {
super(s); super(s);
s.setKeepAlive(true);
cuid=uid; cuid=uid;
} }
public int getCuid() { public int getCuid() {
@@ -95,7 +97,10 @@ private volatile long outputcount = 1;
return sendDeque; return sendDeque;
} }
private volatile int pcu=0; private volatile int pcu=0;
private Consumer<LocalTCPConnection> hook; private volatile Consumer<LocalTCPConnection> hook;
public volatile long uackt=System.nanoTime();
public volatile long ukeept=System.nanoTime();
public void setPeerCacheUsed(int cacheused) { public void setPeerCacheUsed(int cacheused) {
pcu= cacheused; pcu= cacheused;
} }
@@ -331,6 +331,9 @@ public class RemoteTCPConnection extends TCPConnection {
if(v==-1) { if(v==-1) {
throw new EOFException(); throw new EOFException();
} }
if(v!=0) {
throw new StreamCorruptedException();
}
} }
} }
@@ -15,7 +15,7 @@ public class TCPConnection {
protected Socket connect; protected volatile Socket connect;
protected DataInputStream din; protected DataInputStream din;
protected DataOutputStream dout; protected DataOutputStream dout;
@@ -43,13 +43,15 @@ public class TCPConnection {
e1.printStackTrace(); e1.printStackTrace();
} }
try { try {
if (connect != null) if (connect != null) {
connect.close(); connect.close();
}
} catch (IOException e) { } catch (IOException e) {
// TODO 自动生成的 catch 块 // TODO 自动生成的 catch 块
e.printStackTrace(); e.printStackTrace();
} }
connect = null; //connect = null;
} }
public TCPConnection( Socket soc) throws IOException { public TCPConnection( Socket soc) throws IOException {
+5 -1
View File
@@ -19,6 +19,7 @@ import javax.swing.JScrollBar;
public class YScrollPane extends JPanel { public class YScrollPane extends JPanel {
private JPanel settings; private JPanel settings;
private JScrollBar scrollBar;
public YScrollPane(int contentx){ public YScrollPane(int contentx){
this(contentx,BorderLayout.EAST); this(contentx,BorderLayout.EAST);
} }
@@ -26,7 +27,7 @@ public class YScrollPane extends JPanel {
setLayout(new BorderLayout(0, 0)); setLayout(new BorderLayout(0, 0));
setBackground(Color.WHITE); setBackground(Color.WHITE);
JScrollBar scrollBar = new JScrollBar(); scrollBar = new JScrollBar();
add(scrollBar,d); add(scrollBar,d);
JPanel panel = new JPanel(); JPanel panel = new JPanel();
@@ -125,5 +126,8 @@ settings.addContainerListener(new ContainerListener() {
public JPanel getView() { public JPanel getView() {
return settings; return settings;
} }
public JScrollBar getScrollBar() {
return scrollBar;
}
} }