--- /dev/null
+package net.excentrics.bandWidth;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * \82±\82Ì\83N\83\89\83X\82Í\83X\83\8d\83b\83g\83\8a\83\93\83O\82ð\90¬\8c÷\82³\82¹\82é\82½\82ß\93à\95\94\93I\82É\8eg\97p\82³\82ê\82é\81B
+ *
+ * @author senju
+ *
+ */
+class BandWidthController {
+ private final static long defaultCycle = 100; // \83f\83t\83H\83\8b\83g\82Ì\83T\83C\83N\83\8b\8e\9e\8aÔ\81B\83~\83\8a\95b\82Å\90Ý\92è\81B
+
+ private final static int defaultSpeedCount = 70;
+
+ private static final int min_cycle = 10; // \83T\83C\83N\83\8b\82ª\83I\81[\83o\81[\83\89\83C\83h\82³\82ê\82é\8dÛ\82Ì\8dÅ\8f¬\82Ì\92l
+
+ private static final int defaultAdjust = 10;
+ private double bandWidth = 0;
+
+ private int bytePerCycle = 0;
+
+ private long cycle;
+
+ private long startTime;
+
+ private long sentSize; //startTime\8e\9e\93_\82©\82ç\8c»\8dÝ\82Ü\82Å\91\97\82ç\82ê\82½\83f\81[\83^\82Ì\97Ê\82ðbyte\82Å\8e¦\82·\81B
+
+ private int wellSentCount; //\8ew\92è\82µ\82½\83T\83C\83Y\92Ê\82è\82Ì\83f\81[\83^\82ª\91\97\82ç\82ê\82½\89ñ\90\94\81B
+ //\88ê\93x\82Å\82à\81i\83V\83X\83e\83\80\82Ì\91\97\90M\83o\83b\83t\83@\82ª\88ì\82ê\82½\82È\82Ç\82Ì\97\9d\97R\82Å\81jpostWrite\82Ì
+ //writtenSize\82ªpreWrite\82Ì\8ew\92è\82µ\82½\92l\82Æ\88á\82Á\82Ä\82¢\82½\82ç\83J\83E\83\93\83g\82Í\83\8a\83Z\83b\83g\82³\82ê\82é\81B
+
+ private long maySentSize; //\91O\89ñpreWrite\82ª\95Ô\82µ\82½\81u\91\97\90M\82³\82ê\82é\82×\82«\83T\83C\83Y\81v
+
+ private long totalSpeed; // startTime\8e\9e\93_\82©\82ç\8c»\8dÝ\82Ü\82Å\82Ì\91\97\8fo\91¬\93x\82ð\95\\82·\81B(bit per second)
+
+ private int adjustmentCount = defaultAdjust;
+
+ private LinkedList<Integer> sizeList;
+
+ private int speedCount = defaultSpeedCount; // \8c»\8dÝ\82Ì\93]\91\97\91¬\93x\82ð\92²\82×\82é\8e\9e\82É\89½\8cÂ\82ÌsizeList\93à\82Ì\97v\91f\82ð\8eg\82¤\82©\81B
+
+ private LinkedList<Long> timeList;
+
+ public BandWidthController() {
+ timeList = new LinkedList<Long>();
+ sizeList = new LinkedList<Integer>();
+ cycle = defaultCycle;
+ setBytePerCycle();
+ }
+
+ /**
+ * @return \83X\83\8d\83b\83g\83\8a\83\93\83O\82ð\90¬\8c÷\82³\82¹\82é\82½\82ß\82É\8f\91\82«\8d\9e\82Ü\82ê\82é\82×\82«\83T\83C\83Y\82ð\83o\83C\83g\82Å\95Ô\82·\81B \82Ü\82½\81A\8e\9f\82Ì\8f\91\82«\8d\9e\82Ý\82Ü\82Å\82Ì\8aú\8aÔ\82ð\83~\83\8a\95b\82Å\95Ô\82·\81B
+ * \8f\91\82«\8d\9e\82Þ\83T\83C\83Y\82É\90§\8cÀ\82ª\95K\97v\82È\82¢\8fê\8d\87\82Í-1\82ð\95Ô\82·\81B
+ */
+private SizeAndInterval calculationSizeForWrite(int bufferSize) {
+ if (bandWidth == 0)
+ return new SizeAndInterval(-1, 0);
+ if (timeList.isEmpty())
+ return new SizeAndInterval(bytePerCycle, cycle);
+
+ double currentBps = getCurrentBps();
+
+
+ System.err.println();
+ System.err.println("bandwidth(bps): " + bandWidth);
+ System.err.println("currentBps(bps): " + currentBps);
+ System.err.println("bandwidth(Kbps): " + bandWidth / 1000);
+ System.err.println("currentBps(Kbps): " + currentBps / 1000);
+ System.err.println("bandwidth(Mbps): " + bandWidth / 1000 / 1000);
+ System.err.println("currentBps(Mbps): " + currentBps / 1000 / 1000);
+ System.err.println("wellsent: " + wellSentCount);
+ System.err.println("totalspeed: " + totalSpeed);
+
+
+ // \8c»\8dÝ\82Ì\91¬\93x\82ªbandWidth\82æ\82è\8fo\82Ä\82¢\82é\8fê\8d\87\82Í\8f\91\82«\8d\9e\82Ü\82È\82¢\81B
+ if (currentBps > bandWidth)
+ return new SizeAndInterval(0, cycle);
+
+ // \82»\82¤\82Å\82È\82\81A\83o\83b\83t\83@\82Ì\83T\83C\83Y\82à\8f\\95ª\82È\8fê\8d\87\82Í\8f\91\82«\8d\9e\82Þ\81B
+ if (bufferSize >= bytePerCycle){
+ if(wellSentCount >= adjustmentCount){
+ wellSentCount = 0;
+ /*\90\94\89ñ\82É\88ê\93x\81A\8c»\8dÝ\82Ì\91¬\93x\82¾\82¯\82Å\82È\82\81A\83g\81[\83^\83\8b\82Ì\93]\91\97\91¬\93x\82©\82ç
+ * \92²\90®\82ð\8ds\82¤\81B
+ */
+ if(totalSpeed / bandWidth < 1){ //\90§\8cÀ\91¬\93x\82Ì9\8a\84\82ð\89º\89ñ\82Á\82Ä\82¢\82½\82ç
+ return new SizeAndInterval(bytePerCycle,0);
+ }
+ }
+ return new SizeAndInterval(bytePerCycle, cycle);
+ }
+
+ // \83o\83b\83t\83@\81[\83T\83C\83Y\82ª\91«\82è\82È\82¢\8fê\8d\87\81Acycle\82ð\83I\81[\83o\81[\83\89\83C\83h\82·\82é\81B
+ double rate = bufferSize / bytePerCycle;
+ return new SizeAndInterval(bytePerCycle, Math.max((int) (cycle * rate),min_cycle));
+ }
+
+ public double getBandWidth() {
+ return bandWidth;
+ }
+
+ private double getCurrentBps() {
+ // speedCount\8cÂ\82Ì\83T\83C\83Y\82Ì\8d\87\8cv\82ð\8b\81\82ß\81A\8c»\8dÝ\82Ì\91¬\93x\82ð\8b\81\82ß\82é\81B
+ int firstIndex = Math.max(sizeList.size() - speedCount, 0);
+ ListIterator li = sizeList.listIterator(firstIndex);
+ int sizeInByte = 0;
+ while (li.hasNext()) {
+ Integer s = (Integer) li.next();
+ sizeInByte += s.intValue();
+ }
+ long startTime = timeList.get(firstIndex);
+ long currentTime = System.currentTimeMillis();
+ System.err.println("real interval: "
+ + (timeList.getLast() - currentTime));
+ long intervalInMillis = currentTime - startTime;
+ assert intervalInMillis >= 0;
+ intervalInMillis = Math.max(1, intervalInMillis);
+ double intervalInSecond = intervalInMillis / 1000.0;
+ double currentBps = sizeInByte * 8 / intervalInSecond;
+ return currentBps;
+ }
+
+ /**
+ *
+ * @param bufferSize
+ * \8eg\97p\82µ\82Ä\82¢\82é\83o\83b\83t\83@\82Ì\83T\83C\83Y
+ * @return \8f\91\82«\8d\9e\82Þ\82×\82«\83T\83C\83Y(byte)\81A\82¨\82æ\82Ñ\8e\9f\89ñ\8cÄ\82Ñ\8fo\82µ\82Ü\82Å\82Ì\8aÔ\8au(\83~\83\8a\95b)\82ð\95Ô\82·
+ */
+ public SizeAndInterval preWrite(int bufferSize) {
+ SizeAndInterval si = calculationSizeForWrite(bufferSize);
+ timeList.add(System.currentTimeMillis());
+ if(timeList.size() == 1){
+ startTime = timeList.get(0);
+ }
+ // TODO: \83f\83o\83b\83O\97p
+ System.err.println(si);
+
+ maySentSize = si.getSize();
+ return si;
+
+ }
+
+ public long getCycle() {
+ return cycle;
+ }
+
+ /**
+ * @param writtenSize
+ * \8eÀ\8dÛ\82É\8f\91\82«\8d\9e\82Ü\82ê\82½\83T\83C\83Y
+ * @throws AsymmetricalCallException
+ * preWrite\82ð\8cÄ\82Ñ\8fo\82·\91O\82ÉpostWrite\82ª\8cÄ\82Î\82ê\82½\8e\9e
+ */
+ public void postWrite(int writtenSize) throws AsymmetricalCallException {
+
+ sentSize += writtenSize;
+ if(timeList.size() > 0){
+ long elapsedTime = (timeList.getLast() - startTime )/ 1000;
+ if(elapsedTime > 0)
+ totalSpeed = sentSize * 8/ elapsedTime;
+ }
+
+ if(writtenSize == maySentSize)
+ wellSentCount++;
+ else
+ wellSentCount = 0;
+
+ sizeList.add(writtenSize);
+ if (timeList.size() > 100) {
+ for (int i = 0; i < 20; i++) {
+ timeList.remove();
+ sizeList.remove();
+ }
+ }
+ if (sizeList.size() != timeList.size()) {
+ throw new AsymmetricalCallException(
+ "preWrite and postWrite called asymmetric.");
+ }
+ }
+
+ /**
+ * @param bandWidth
+ * \91Ñ\88æ\82ðbps(bit per second) \82Å\97^\82¦\82é\81B0\82ª\97^\82¦\82ç\82ê\82½\8fê\8d\87\91Ñ\88æ\90§\8cÀ\82ð\8ds\82í\82È\82¢\81B
+ */
+ public void setBandWidth(double bandWidth) {
+ this.bandWidth = bandWidth;
+ sizeList.clear();
+ timeList.clear();
+ setBytePerCycle();
+ }
+
+ private void setBytePerCycle() {
+ bytePerCycle = (int) (bandWidth / 1000 * cycle / 8);
+ }
+ public void setCycle(long cycle) {
+ this.cycle = cycle;
+ setBytePerCycle();
+ }
+
+ public long getTotalSpeed() {
+ return totalSpeed;
+ }
+}
--- /dev/null
+package net.excentrics.bandWidth;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+/**
+ * <p>java.nio.SocketChannel\82Ì\91\97\90M\91Ñ\88æ\82ð\90§\8cÀ\82·\82é\82½\82ß\82Ì\83N\83\89\83X\82Å\82·\81B
+ * <p>\91S\82Ä\82Ì\83\81\83\\83b\83h\82Í\83X\83\8c\83b\83h\83Z\81[\83t\82Å\82Í<b>\82 \82è\82Ü\82¹\82ñ\81B</b>
+ * <p>\8eó\90M\82Ì\91Ñ\88æ\82ð\90§\8cÀ\82·\82é\8b@\94\\82Í\8c»\8dÝ\8eÀ\91\95\82³\82ê\82Ä\82¢\82Ü\82¹\82ñ\81B
+ * <p>\8eg\82¢\95û
+ * <pre>
+ * ByteBuffer buf = ByteBuffer.allocate(BUFSIZE);
+ * SocketChannel sc = serverSocketChannel.accept();
+ * //\90§\8cÀ\91¬\93x\82ðbandWidth\82É\90Ý\92è
+ * SocketChannelWriter scw = new SocketChannelWriter(sc,bandWidth);
+ * while(//\83o\83b\83t\83@\81[\82É\83f\81[\83^\82ð\93Ç\82Ý\8d\9e\82Þ){
+ * long nextTime = scw.write(buf);
+ * sleep(nextTime);
+ * }
+ * </pre>
+ *
+ *
+ * @version 0.9
+ * @author senju
+ *
+ */
+
+
+public class SocketChannelWriter{
+
+ private SocketChannel channel;
+
+ private BandWidthController c;
+
+ /**
+ * SocketChannelWriter\82ð\90¶\90¬\82µ\82Ü\82·\81B
+ * @param channel \83f\81[\83^\82Ì\91\97\90M\82ð\8ds\82¤\83`\83\83\83l\83\8b
+ * @param bandWidth \90§\8cÀ\91¬\93x(bit per second)
+ */
+ public SocketChannelWriter(SocketChannel channel, double bandWidth) {
+ this.channel = channel;
+ c = new BandWidthController();
+ c.setBandWidth(bandWidth);
+ }
+
+ /**\90§\8cÀ\91¬\93x\82ð\95Ô\82µ\82Ü\82·\81B
+ * @return \8c»\8dÝ\90Ý\92è\82³\82ê\82Ä\82¢\82é\90§\8cÀ\91¬\93x(bit per second)
+ */
+ public double getBandWidth() {
+ return c.getBandWidth();
+ }
+
+ /**\90§\8cÀ\91¬\93x\82ð\95Ï\8dX\82µ\82Ü\82·\81B
+ * @param bandWidth
+ */
+ public void setBandWidth(double bandWidth) {
+ c.setBandWidth(bandWidth);
+ }
+
+
+ /**<p>\91Ñ\88æ\90§\8cÀ\82ð\82©\82¯\82½\91\97\8fo\82ð\8ds\82¢\82Ü\82·\81B
+ * <p>\82±\82Ì\83\81\83\\83b\83h\8cÄ\82Ñ\8fo\82µ\82ª\83u\83\8d\83b\83N\82³\82ê\82é\82±\82Æ\82Í\82 \82è\82Ü\82¹\82ñ\81B
+ * <p>\96ß\82è\92l\82Í\96³\8e\8b\82µ\82Ä\82à\91Ñ\88æ\90§\8cÀ\82Í\8ds\82í\82ê\82Ü\82·\82ª\81A\95s\97v\82È\82b\82o\82t\83\8a\83\\81[\83X\82Ì\8fÁ\94ï\82ð
+ * \94ð\82¯\82é\82½\82ß\82É\82Í\96ß\82è\92l\82ð\97\98\97p\82µ\82Ä\82\82¾\82³\82¢\81B
+ *
+ * @param buffer \91\97\8fo\82·\82é\83f\81[\83^\82ª\93ü\82Á\82½\83o\83b\83t\83@\81B
+ * <p>\90§\8cÀ\91¬\93x\82ðbyte\92P\88Ê\82É\95Ï\8dX\82µ\82½\8e\9e\82Ì10\95ª\82Ì\82P\88È\8fã\82Ì\83T\83C\83Y\82ª\90\84\8f§\82³\82ê\82Ü\82·\81B
+ * <p>\82Â\82Ü\82ègetBandWidth() / 8 / 10 \88È\8fã\82Å\82·\81B
+ *
+ * @return \8e\9f\89ñ\82±\82Ì\83\81\83\\83b\83h\82ð\8cÄ\82Ñ\8fo\82·\82Ü\82Å\82Ì\8e\9e\8aÔ\82ð\83~\83\8a\95b\82Å\95Ô\82µ\82Ü\82·\81B<br>\82Â\82Ü\82è
+ * \81u\96ß\82è\92l\83~\83\8a\95b\81v\8co\89ß\82µ\82Ä\82©\82ç\91±\82«\82Ì\83f\81[\83^\82ð\91\97\8fo\82µ\82Ä\82\82¾\82³\82¢\81B
+ * <p>\82±\82Ì\96ß\82è\92l\82Ì\8e\9e\8aÔ\82ª\8co\89ß\82µ\82È\82¢\82¤\82¿\82É\8dÄ\82Ñ\82±\82Ì\83\81\83\\83b\83h\82ð\8cÄ\82Ñ\8fo\82µ\82Ä\82à
+ * \82¨\82»\82ç\820\83o\83C\83g\82Ì\91\97\8fo\82ª\8ds\82í\82ê\82Ü\82·\81B\82æ\82Á\82Ä\93K\90Ø\82É\91Ñ\88æ\90§\8cÀ\82Í\8ds\82í\82ê\82Ü\82·\82ª\82b\82o\82t
+ * \83\8a\83\\81[\83X\82ð\96³\91Ê\82É\8fÁ\94ï\82·\82é\82¾\82¯\82È\82Ì\82Å\81A(write()\82Í\8cÄ\82Ñ\8fo\82³\82ê\82é\82Æ\95K\82¸\81A\89½\83o\83C\83g\91\97\8fo
+ * \82·\82é\82×\82«\82©\82Ì\8cv\8eZ\82ð\8ds\82¢\82Ü\82·\81B)\82±\82Ì\8e\9e\8aÔ\82Ì\8aÔ\91\97\8fo\83X\83\8c\83b\83h\82ðsleep()\82³\82¹\82é\82È\82è
+ * \82Ì\8f\88\97\9d\82ð\8ds\82¤\82±\82Æ\82ª\90\84\8f§\82³\82ê\82Ü\82·\81B
+ *
+ *
+ * @throws IOException\81@SocketChannel\82ª\97á\8aO\82ð\94\90¶\82³\82¹\82½\8fê\8d\87\81B
+ */
+ public long write(ByteBuffer buffer) throws IOException {
+ int writtenSize = 0;
+ int size = buffer.remaining();
+ SizeAndInterval si = c.preWrite(buffer.capacity());
+ try {
+ if (si.getSize() == 0)
+ c.postWrite(0);
+ else if (si.getSize() >= size || si.getSize() == -1) {
+ writtenSize = channel.write(buffer);
+ c.postWrite(writtenSize);
+ } else {
+ int limit = buffer.limit();
+ buffer.limit(buffer.position() + si.getSize());
+ writtenSize = channel.write(buffer);
+
+ c.postWrite(writtenSize);
+ buffer.limit(limit);
+ }
+ } catch (AsymmetricalCallException e) {
+ e.printStackTrace();
+ }
+
+ return si.getInterval();
+ }
+
+}