Full Code of beykery/jkcp for AI

master be8039d97b4b cached
14 files
77.0 KB
19.3k tokens
143 symbols
1 requests
Download .txt
Repository: beykery/jkcp
Branch: master
Commit: be8039d97b4b
Files: 14
Total size: 77.0 KB

Directory structure:
gitextract_bdwvhk3j/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
└── src/
    ├── main/
    │   └── java/
    │       └── org/
    │           └── beykery/
    │               └── jkcp/
    │                   ├── Kcp.java
    │                   ├── KcpClient.java
    │                   ├── KcpListerner.java
    │                   ├── KcpOnUdp.java
    │                   ├── KcpServer.java
    │                   ├── KcpThread.java
    │                   └── Output.java
    └── test/
        └── java/
            └── test/
                ├── TestClient.java
                └── TestServer.java

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/target/
.idea/


================================================
FILE: .travis.yml
================================================
language: java
jdk:
  - openjdk7
  - oraclejdk7


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# jkcp

kcp for java . base on netty .

kcp是一种独立于底层通信协议的重传算法,jkcp直接构建于udp之上
并提供方便的编程接口,只需要继承相关的类即可;用户不用关心udp
和kcp的使用细节就能轻松驾驭视频、moba类等需要高速传输环境的应用开发

## 坐标

```xml
  <dependency>
      <groupId>org.beykery</groupId>
      <artifactId>jkcp</artifactId>
      <version>1.3.2</version>
  </dependency>
```

## 使用

请参考src/test/java/test目录下的TestServer和TestClient

## kcp的算法细节请参考

[kcp](https://github.com/skywind3000/kcp)


================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.beykery</groupId>
    <artifactId>jkcp</artifactId>
    <version>1.3.2</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <java.version>17</java.version>
    </properties>
    <name>jkcp</name>
    <description>
        kcp for java .
    </description>
    <url>
        https://github.com/beykery/jkcp
    </url>
    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>
    <developers>
        <developer>
            <name>beykery</name>
            <email>beykery@sina.com</email>
        </developer>
    </developers>
    <scm>
        <connection>scm:git:git@github.com:beykery/jkcp.git</connection>
        <developerConnection>scm:git:git@github.com:beykery/jkcp.git</developerConnection>
        <url>git@github.com:beykery/jkcp.git</url>
    </scm>
    <profiles>
        <profile>
            <id>oss</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.sonatype.plugins</groupId>
                        <artifactId>nexus-staging-maven-plugin</artifactId>
                        <version>1.6.8</version>
                        <extensions>true</extensions>
                        <configuration>
                            <serverId>oss</serverId>
                            <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                            <autoReleaseAfterClose>true</autoReleaseAfterClose>
                        </configuration>
                        <dependencies>
                            <dependency>
                                <groupId>com.thoughtworks.xstream</groupId>
                                <artifactId>xstream</artifactId>
                                <version>1.4.15</version> <!-- apparently this needs to be exactly this version -->
                            </dependency>
                        </dependencies>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>2.4</version>
                        <executions>
                            <execution>
                                <id>attach-sources</id>
                                <goals>
                                    <goal>jar-no-fork</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <version>2.10.3</version>
                        <executions>
                            <execution>
                                <id>attach-javadocs</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                                <configuration>
                                    <additionalparam>-Xdoclint:none</additionalparam>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.6</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
            <distributionManagement>
                <snapshotRepository>
                    <id>oss</id>
                    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
                </snapshotRepository>
                <repository>
                    <id>oss</id>
                    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
                </repository>
            </distributionManagement>
        </profile>
    </profiles>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.118.Final</version>
        </dependency>

        <!--
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.24</version>
          <type>jar</type>
        </dependency>
        -->
    </dependencies>
</project>


================================================
FILE: src/main/java/org/beykery/jkcp/Kcp.java
================================================
/**
 * KCP - A Better ARQ Protocol Implementation
 * skywind3000 (at) gmail.com, 2010-2011
 * Features:
 * + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.
 * + Maximum RTT reduce three times vs tcp.
 * + Lightweight, distributed as a single source file.
 */
package org.beykery.jkcp;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;

import java.util.ArrayDeque;
import java.util.ArrayList;

/**
 * @author beykery
 */
public class Kcp {

    public static final int IKCP_RTO_NDL = 30;  // no delay min rto
    public static final int IKCP_RTO_MIN = 100; // normal min rto
    public static final int IKCP_RTO_DEF = 200;
    public static final int IKCP_RTO_MAX = 60000;
    public static final int IKCP_CMD_PUSH = 81; // cmd: push data
    public static final int IKCP_CMD_ACK = 82; // cmd: ack
    public static final int IKCP_CMD_WASK = 83; // cmd: window probe (ask)
    public static final int IKCP_CMD_WINS = 84; // cmd: window size (tell)
    public static final int IKCP_ASK_SEND = 1;  // need to send IKCP_CMD_WASK
    public static final int IKCP_ASK_TELL = 2;  // need to send IKCP_CMD_WINS
    public static final int IKCP_WND_SND = 32;
    public static final int IKCP_WND_RCV = 32;
    public static final int IKCP_MTU_DEF = 1400;
    public static final int IKCP_ACK_FAST = 3;
    public static final int IKCP_INTERVAL = 100;
    public static final int IKCP_OVERHEAD = 24;
    public static final int IKCP_DEADLINK = 10;
    public static final int IKCP_THRESH_INIT = 2;
    public static final int IKCP_THRESH_MIN = 2;
    public static final int IKCP_PROBE_INIT = 7000;   // 7 secs to probe window size
    public static final int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window

    private int conv;
    private int mtu;
    private int mss;
    private int state;
    private int snd_una;
    private int snd_nxt;
    private int rcv_nxt;
    private int ts_recent;
    private int ts_lastack;
    private int ssthresh;
    private int rx_rttval;
    private int rx_srtt;
    private int rx_rto;
    private int rx_minrto;
    private int snd_wnd;
    private int rcv_wnd;
    private int rmt_wnd;
    private int cwnd;
    private int probe;
    private int current;
    private int interval;
    private int ts_flush;
    private int xmit;
    private int nodelay;
    private int updated;
    private int ts_probe;
    private int probe_wait;
    private final int dead_link;
    private int incr;
    private final ArrayDeque<Segment> snd_queue = new ArrayDeque<>();
    private final ArrayDeque<Segment> rcv_queue = new ArrayDeque<>();
    private final ArrayList<Segment> snd_buf = new ArrayList<>();
    private final ArrayList<Segment> rcv_buf = new ArrayList<>();
    private final ArrayList<Integer> acklist = new ArrayList<>();
    private ByteBuf buffer;
    private int fastresend;
    private int nocwnd;
    private boolean stream;//流模式
    private final Output output;
    private final Object user;//远端地址
    private long nextUpdate;//the next update time.

    private static int _ibound_(int lower, int middle, int upper) {
        return Math.min(Math.max(lower, middle), upper);
    }

    private static int _itimediff(int later, int earlier) {
        return later - earlier;
    }

    private static long _itimediff(long later, long earlier) {
        return later - earlier;
    }

    /**
     * SEGMENT
     */
    class Segment {

        private int conv = 0;
        private byte cmd = 0;
        private int frg = 0;
        private int wnd = 0;
        private int ts = 0;
        private int sn = 0;
        private int una = 0;
        private int resendts = 0;
        private int rto = 0;
        private int fastack = 0;
        private int xmit = 0;
        private ByteBuf data;

        private Segment(int size) {
            if (size > 0) {
                this.data = PooledByteBufAllocator.DEFAULT.buffer(size);
            }
        }

        /**
         * encode a segment into buffer
         *
         * @param buf
         * @return
         */
        private int encode(ByteBuf buf) {
            int off = buf.writerIndex();
            buf.writeIntLE(conv);
            buf.writeByte(cmd);
            buf.writeByte(frg);
            buf.writeShortLE(wnd);
            buf.writeIntLE(ts);
            buf.writeIntLE(sn);
            buf.writeIntLE(una);
            buf.writeIntLE(data == null ? 0 : data.readableBytes());
            return buf.writerIndex() - off;
        }

        /**
         * 释放内存
         */
        private void release() {
            if (this.data != null && data.refCnt() > 0) {
                this.data.release(data.refCnt());
            }
        }
    }

    /**
     * create a new kcpcb
     *
     * @param output
     * @param user
     */
    public Kcp(Output output, Object user) {
        snd_wnd = IKCP_WND_SND;
        rcv_wnd = IKCP_WND_RCV;
        rmt_wnd = IKCP_WND_RCV;
        mtu = IKCP_MTU_DEF;
        mss = mtu - IKCP_OVERHEAD;
        rx_rto = IKCP_RTO_DEF;
        rx_minrto = IKCP_RTO_MIN;
        interval = IKCP_INTERVAL;
        ts_flush = IKCP_INTERVAL;
        ssthresh = IKCP_THRESH_INIT;
        dead_link = IKCP_DEADLINK;
        buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
        this.output = output;
        this.user = user;
    }

    /**
     * check the size of next message in the recv queue
     *
     * @return
     */
    public int peekSize() {
        if (rcv_queue.isEmpty()) {
            return -1;
        }
        Segment seq = rcv_queue.getFirst();
        if (seq.frg == 0) {
            return seq.data.readableBytes();
        }
        if (rcv_queue.size() < seq.frg + 1) {
            return -1;
        }
        int length = 0;
        for (Segment item : rcv_queue) {
            length += item.data.readableBytes();
            if (item.frg == 0) {
                break;
            }
        }
        return length;
    }

    /**
     * user/upper level recv: returns size, returns below zero for EAGAIN
     *
     * @param buffer
     * @return
     */
    public int receive(ByteBuf buffer) {
        if (rcv_queue.isEmpty()) {
            return -1;
        }
        int peekSize = peekSize();
        if (peekSize < 0) {
            return -2;
        }
        boolean recover = rcv_queue.size() >= rcv_wnd;
        // merge fragment.
        int c = 0;
        int len = 0;
        for (Segment seg : rcv_queue) {
            len += seg.data.readableBytes();
            buffer.writeBytes(seg.data);
            c++;
            if (seg.frg == 0) {
                break;
            }
        }
        if (c > 0) {
            for (int i = 0; i < c; i++) {
                rcv_queue.removeFirst().data.release();
            }
        }
        if (len != peekSize) {
            throw new RuntimeException("数据异常.");
        }
        // move available data from rcv_buf -> rcv_queue
        c = 0;
        for (Segment seg : rcv_buf) {
            if (seg.sn == rcv_nxt && rcv_queue.size() < rcv_wnd) {
                rcv_queue.add(seg);
                rcv_nxt++;
                c++;
            } else {
                break;
            }
        }
        if (c > 0) {
            for (int i = 0; i < c; i++) {
                rcv_buf.remove(0);
            }
        }
        // fast recover
        if (rcv_queue.size() < rcv_wnd && recover) {
            // ready to send back IKCP_CMD_WINS in ikcp_flush
            // tell remote my window size
            probe |= IKCP_ASK_TELL;
        }
        return len;
    }

    /**
     * user/upper level send, returns below zero for error
     *
     * @param buffer
     * @return
     */
    public int send(ByteBuf buffer) {
        if (buffer.readableBytes() == 0) {
            return -1;
        }
        // append to previous segment in streaming mode (if possible)
        if (this.stream && !this.snd_queue.isEmpty()) {
            Segment seg = snd_queue.getLast();
            if (seg.data != null && seg.data.readableBytes() < mss) {
                int capacity = mss - seg.data.readableBytes();
                int extend = Math.min(buffer.readableBytes(), capacity);
                seg.data.writeBytes(buffer, extend);
                if (buffer.readableBytes() == 0) {
                    return 0;
                }
            }
        }
        int count;
        if (buffer.readableBytes() <= mss) {
            count = 1;
        } else {
            count = (buffer.readableBytes() + mss - 1) / mss;
        }
        if (count > 255) {
            return -2;
        }
        if (count == 0) {
            count = 1;
        }
        //fragment
        for (int i = 0; i < count; i++) {
            int size = Math.min(buffer.readableBytes(), mss);
            Segment seg = new Segment(size);
            seg.data.writeBytes(buffer, size);
            seg.frg = this.stream ? 0 : count - i - 1;
            snd_queue.add(seg);
        }
        buffer.release();
        return 0;
    }

    /**
     * update ack.
     *
     * @param rtt
     */
    private void update_ack(int rtt) {
        if (rx_srtt == 0) {
            rx_srtt = rtt;
            rx_rttval = rtt / 2;
        } else {
            int delta = rtt - rx_srtt;
            if (delta < 0) {
                delta = -delta;
            }
            rx_rttval = (3 * rx_rttval + delta) / 4;
            rx_srtt = (7 * rx_srtt + rtt) / 8;
            if (rx_srtt < 1) {
                rx_srtt = 1;
            }
        }
        int rto = rx_srtt + Math.max(interval, 4 * rx_rttval);
        rx_rto = _ibound_(rx_minrto, rto, IKCP_RTO_MAX);
    }

    private void shrink_buf() {
        if (snd_buf.size() > 0) {
            snd_una = snd_buf.get(0).sn;
        } else {
            snd_una = snd_nxt;
        }
    }

    private void parse_ack(int sn) {
        if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) {
            return;
        }
        for (int i = 0; i < snd_buf.size(); i++) {
            Segment seg = snd_buf.get(i);
            if (sn == seg.sn) {
                snd_buf.remove(i);
                seg.data.release(seg.data.refCnt());
                break;
            }
            if (_itimediff(sn, seg.sn) < 0) {
                break;
            }
        }
    }

    private void parse_una(int una) {
        int c = 0;
        for (Segment seg : snd_buf) {
            if (_itimediff(una, seg.sn) > 0) {
                c++;
            } else {
                break;
            }
        }
        if (c > 0) {
            for (int i = 0; i < c; i++) {
                Segment seg = snd_buf.remove(0);
                seg.data.release(seg.data.refCnt());
            }
        }
    }

    private void parse_fastack(int sn) {
        if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) {
            return;
        }
        for (Segment seg : this.snd_buf) {
            if (_itimediff(sn, seg.sn) < 0) {
                break;
            } else if (sn != seg.sn) {
                seg.fastack++;
            }
        }
    }

    /**
     * ack append
     *
     * @param sn
     * @param ts
     */
    private void ack_push(int sn, int ts) {
        acklist.add(sn);
        acklist.add(ts);
    }

    private void parse_data(Segment newseg) {
        int sn = newseg.sn;
        if (_itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || _itimediff(sn, rcv_nxt) < 0) {
            newseg.release();
            return;
        }
        int n = rcv_buf.size() - 1;
        int temp = -1;
        boolean repeat = false;
        for (int i = n; i >= 0; i--) {
            Segment seg = rcv_buf.get(i);
            if (seg.sn == sn) {
                repeat = true;
                break;
            }
            if (_itimediff(sn, seg.sn) > 0) {
                temp = i;
                break;
            }
        }
        if (!repeat) {
            rcv_buf.add(temp + 1, newseg);
        } else {
            newseg.release();
        }
        // move available data from rcv_buf -> rcv_queue
        int c = 0;
        for (Segment seg : rcv_buf) {
            if (seg.sn == rcv_nxt && rcv_queue.size() < rcv_wnd) {
                rcv_queue.add(seg);
                rcv_nxt++;
                c++;
            } else {
                break;
            }
        }
        if (0 < c) {
            for (int i = 0; i < c; i++) {
                rcv_buf.remove(0);
            }
        }
    }

    /**
     * when you received a low level packet (eg. UDP packet), call it
     *
     * @param data
     * @return
     */
    public int input(ByteBuf data) {
        int una_temp = snd_una;
        int flag = 0, maxack = 0;
        if (data == null || data.readableBytes() < IKCP_OVERHEAD) {
            return -1;
        }
        while (true) {
            boolean readed = false;
            int ts;
            int sn;
            int len;
            int una;
            int conv_;
            int wnd;
            byte cmd;
            byte frg;
            if (data.readableBytes() < IKCP_OVERHEAD) {
                break;
            }
            conv_ = data.readIntLE();
            if (this.conv != conv_) {
                return -1;
            }
            cmd = data.readByte();
            frg = data.readByte();
            wnd = data.readShortLE();
            ts = data.readIntLE();
            sn = data.readIntLE();
            una = data.readIntLE();
            len = data.readIntLE();
            if (data.readableBytes() < len) {
                return -2;
            }
            switch ((int) cmd) {
                case IKCP_CMD_PUSH:
                case IKCP_CMD_ACK:
                case IKCP_CMD_WASK:
                case IKCP_CMD_WINS:
                    break;
                default:
                    return -3;
            }
            rmt_wnd = wnd & 0x0000ffff;
            parse_una(una);
            shrink_buf();
            switch (cmd) {
                case IKCP_CMD_ACK:
                    if (_itimediff(current, ts) >= 0) {
                        update_ack(_itimediff(current, ts));
                    }
                    parse_ack(sn);
                    shrink_buf();
                    if (flag == 0) {
                        flag = 1;
                        maxack = sn;
                    } else if (_itimediff(sn, maxack) > 0) {
                        maxack = sn;
                    }
                    break;
                case IKCP_CMD_PUSH:
                    if (_itimediff(sn, rcv_nxt + rcv_wnd) < 0) {
                        ack_push(sn, ts);
                        if (_itimediff(sn, rcv_nxt) >= 0) {
                            Segment seg = new Segment(len);
                            seg.conv = conv_;
                            seg.cmd = cmd;
                            seg.frg = frg & 0x000000ff;
                            seg.wnd = wnd;
                            seg.ts = ts;
                            seg.sn = sn;
                            seg.una = una;
                            if (len > 0) {
                                seg.data.writeBytes(data, len);
                                readed = true;
                            }
                            parse_data(seg);
                        }
                    }
                    break;
                case IKCP_CMD_WASK:
                    // ready to send back IKCP_CMD_WINS in Ikcp_flush
                    // tell remote my window size
                    probe |= IKCP_ASK_TELL;
                    break;
                case IKCP_CMD_WINS:
                    // do nothing
                    break;
                default:
                    return -3;
            }
            if (!readed) {
                data.skipBytes(len);
            }
        }
        if (flag != 0) {
            parse_fastack(maxack);
        }
        if (_itimediff(snd_una, una_temp) > 0) {
            if (this.cwnd < this.rmt_wnd) {
                if (this.cwnd < this.ssthresh) {
                    this.cwnd++;
                    this.incr += mss;
                } else {
                    if (this.incr < mss) {
                        this.incr = mss;
                    }
                    this.incr += (mss * mss) / this.incr + (mss / 16);
                    if ((this.cwnd + 1) * mss <= this.incr) {
                        this.cwnd++;
                    }
                }
                if (this.cwnd > this.rmt_wnd) {
                    this.cwnd = this.rmt_wnd;
                    this.incr = this.rmt_wnd * mss;
                }
            }
        }
        return 0;
    }

    private int wnd_unused() {
        if (rcv_queue.size() < rcv_wnd) {
            return rcv_wnd - rcv_queue.size();
        }
        return 0;
    }

    /**
     * force flush
     */
    public void forceFlush() {
        int cur = current;
        int change = 0;
        int lost = 0;
        Segment seg = new Segment(0);
        seg.conv = conv;
        seg.cmd = IKCP_CMD_ACK;
        seg.wnd = wnd_unused();
        seg.una = rcv_nxt;
        // flush acknowledges
        int c = acklist.size() / 2;
        for (int i = 0; i < c; i++) {
            if (buffer.readableBytes() + IKCP_OVERHEAD > mtu) {
                this.output.out(buffer, this, user);
                buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
            }
            seg.sn = acklist.get(i * 2 + 0);
            seg.ts = acklist.get(i * 2 + 1);
            seg.encode(buffer);
        }
        acklist.clear();
        // probe window size (if remote window size equals zero)
        if (rmt_wnd == 0) {
            if (probe_wait == 0) {
                probe_wait = IKCP_PROBE_INIT;
                ts_probe = current + probe_wait;
            } else if (_itimediff(current, ts_probe) >= 0) {
                if (probe_wait < IKCP_PROBE_INIT) {
                    probe_wait = IKCP_PROBE_INIT;
                }
                probe_wait += probe_wait / 2;
                if (probe_wait > IKCP_PROBE_LIMIT) {
                    probe_wait = IKCP_PROBE_LIMIT;
                }
                ts_probe = current + probe_wait;
                probe |= IKCP_ASK_SEND;
            }
        } else {
            ts_probe = 0;
            probe_wait = 0;
        }
        // flush window probing commands
        if ((probe & IKCP_ASK_SEND) != 0) {
            seg.cmd = IKCP_CMD_WASK;
            if (buffer.readableBytes() + IKCP_OVERHEAD > mtu) {
                this.output.out(buffer, this, user);
                buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
            }
            seg.encode(buffer);
        }
        // flush window probing commands
        if ((probe & IKCP_ASK_TELL) != 0) {
            seg.cmd = IKCP_CMD_WINS;
            if (buffer.readableBytes() + IKCP_OVERHEAD > mtu) {
                this.output.out(buffer, this, user);
                buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
            }
            seg.encode(buffer);
        }
        probe = 0;
        // calculate window size
        int cwnd_temp = Math.min(snd_wnd, rmt_wnd);
        if (nocwnd == 0) {
            cwnd_temp = Math.min(cwnd, cwnd_temp);
        }
        // move data from snd_queue to snd_buf
        c = 0;
        for (Segment item : snd_queue) {
            if (_itimediff(snd_nxt, snd_una + cwnd_temp) >= 0) {
                break;
            }
            Segment newseg = item;
            newseg.conv = conv;
            newseg.cmd = IKCP_CMD_PUSH;
            newseg.wnd = seg.wnd;
            newseg.ts = cur;
            newseg.sn = snd_nxt++;
            newseg.una = rcv_nxt;
            newseg.resendts = cur;
            newseg.rto = rx_rto;
            newseg.fastack = 0;
            newseg.xmit = 0;
            snd_buf.add(newseg);
            c++;
        }
        if (c > 0) {
            for (int i = 0; i < c; i++) {
                snd_queue.removeFirst();
            }
        }
        // calculate resent
        int resent = (fastresend > 0) ? fastresend : Integer.MAX_VALUE;
        int rtomin = (nodelay == 0) ? (rx_rto >> 3) : 0;
        // flush data segments
        for (Segment segment : snd_buf) {
            boolean needsend = false;
            if (segment.xmit == 0) {
                needsend = true;
                segment.xmit++;
                segment.rto = rx_rto;
                segment.resendts = cur + segment.rto + rtomin;
            } else if (_itimediff(cur, segment.resendts) >= 0) {
                needsend = true;
                segment.xmit++;
                xmit++;
                if (nodelay == 0) {
                    segment.rto += rx_rto;
                } else {
                    segment.rto += rx_rto / 2;
                }
                segment.resendts = cur + segment.rto;
                lost = 1;
            } else if (segment.fastack >= resent) {
                needsend = true;
                segment.xmit++;
                segment.fastack = 0;
                segment.resendts = cur + segment.rto;
                change++;
            }
            if (needsend) {
                segment.ts = cur;
                segment.wnd = seg.wnd;
                segment.una = rcv_nxt;
                int need = IKCP_OVERHEAD + segment.data.readableBytes();
                if (buffer.readableBytes() + need > mtu) {
                    this.output.out(buffer, this, user);
                    buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
                }
                segment.encode(buffer);
                if (segment.data.readableBytes() > 0) {
                    buffer.writeBytes(segment.data.duplicate());
                }
                if (segment.xmit >= dead_link) {
                    state = -1;
                }
            }
        }
        // flash remain segments
        if (buffer.readableBytes() > 0) {
            this.output.out(buffer, this, user);
            buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
        }
        // update ssthresh
        if (change != 0) {
            int inflight = snd_nxt - snd_una;
            ssthresh = inflight / 2;
            if (ssthresh < IKCP_THRESH_MIN) {
                ssthresh = IKCP_THRESH_MIN;
            }
            cwnd = ssthresh + resent;
            incr = cwnd * mss;
        }
        if (lost != 0) {
            ssthresh = cwnd / 2;
            if (ssthresh < IKCP_THRESH_MIN) {
                ssthresh = IKCP_THRESH_MIN;
            }
            cwnd = 1;
            incr = mss;
        }
        if (cwnd < 1) {
            cwnd = 1;
            incr = mss;
        }
    }

    /**
     * flush pending data
     */
    public void flush() {
        //if (updated != 0)
        {
            forceFlush();
        }
    }

    /**
     * update state (call it repeatedly, every 10ms-100ms), or you can ask
     * ikcp_check when to call it again (without ikcp_input/_send calling).
     *
     * @param current current timestamp in millisec.
     */
    public void update(long current) {
        this.current = (int) current;
        if (updated == 0) {
            updated = 1;
            ts_flush = this.current;
        }
        int slap = _itimediff(this.current, ts_flush);
        if (slap >= 10000 || slap < -10000) {
            ts_flush = this.current;
            slap = 0;
        }
        if (slap >= 0) {
            ts_flush += interval;
            if (_itimediff(this.current, ts_flush) >= 0) {
                ts_flush = this.current + interval;
            }
            flush();
        }
    }

    /**
     * Determine when should you invoke ikcp_update: returns when you should
     * invoke ikcp_update in millisec, if there is no ikcp_input/_send calling.
     * you can call ikcp_update in that time, instead of call update repeatly.
     * Important to reduce unnacessary ikcp_update invoking. use it to schedule
     * ikcp_update (eg. implementing an epoll-like mechanism, or optimize
     * ikcp_update when handling massive kcp connections)
     *
     * @param current
     * @return
     */
    public long check(long current) {
        long cur = current;
        if (updated == 0) {
            return cur;
        }
        long ts_flush_temp = this.ts_flush;
        long tm_packet = 0x7fffffff;
        if (_itimediff(cur, ts_flush_temp) >= 10000 || _itimediff(cur, ts_flush_temp) < -10000) {
            ts_flush_temp = cur;
        }
        if (_itimediff(cur, ts_flush_temp) >= 0) {
            return cur;
        }
        long tm_flush = _itimediff(ts_flush_temp, cur);
        for (Segment seg : snd_buf) {
            long diff = _itimediff(seg.resendts, cur);
            if (diff <= 0) {
                return cur;
            }
            if (diff < tm_packet) {
                tm_packet = diff;
            }
        }
        long minimal = tm_packet < tm_flush ? tm_packet : tm_flush;
        if (minimal >= interval) {
            minimal = interval;
        }
        return cur + minimal;
    }

    /**
     * change MTU size, default is 1400
     *
     * @param mtu
     * @return
     */
    public int setMtu(int mtu) {
        if (mtu < 50 || mtu < IKCP_OVERHEAD) {
            return -1;
        }
        ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);
        this.mtu = mtu;
        mss = mtu - IKCP_OVERHEAD;
        if (buffer != null) {
            buffer.release();
        }
        this.buffer = buf;
        return 0;
    }

    /**
     * conv
     *
     * @param conv
     */
    public void setConv(int conv) {
        this.conv = conv;
    }

    /**
     * conv
     *
     * @return
     */
    public int getConv() {
        return conv;
    }

    /**
     * interval per update
     *
     * @param interval
     * @return
     */
    public int interval(int interval) {
        if (interval > 5000) {
            interval = 5000;
        } else if (interval < 10) {
            interval = 10;
        }
        this.interval = interval;
        return 0;
    }

    /**
     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),
     * 1:enable interval: internal update timer interval in millisec, default is
     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:
     * 0:normal congestion control(default), 1:disable congestion control
     *
     * @param nodelay
     * @param interval
     * @param resend
     * @param nc
     * @return
     */
    public int noDelay(int nodelay, int interval, int resend, int nc) {
        if (nodelay >= 0) {
            this.nodelay = nodelay;
            if (nodelay != 0) {
                rx_minrto = IKCP_RTO_NDL;
            } else {
                rx_minrto = IKCP_RTO_MIN;
            }
        }
        if (interval >= 0) {
            if (interval > 5000) {
                interval = 5000;
            } else if (interval < 10) {
                interval = 10;
            }
            this.interval = interval;
        }
        if (resend >= 0) {
            fastresend = resend;
        }
        if (nc >= 0) {
            nocwnd = nc;
        }
        return 0;
    }

    /**
     * set maximum window size: sndwnd=32, rcvwnd=32 by default
     *
     * @param sndwnd
     * @param rcvwnd
     * @return
     */
    public int wndSize(int sndwnd, int rcvwnd) {
        if (sndwnd > 0) {
            snd_wnd = sndwnd;
        }
        if (rcvwnd > 0) {
            rcv_wnd = rcvwnd;
        }
        return 0;
    }

    /**
     * get how many packet is waiting to be sent
     *
     * @return
     */
    public int waitSnd() {
        return snd_buf.size() + snd_queue.size();
    }

    public void setNextUpdate(long nextUpdate) {
        this.nextUpdate = nextUpdate;
    }

    public long getNextUpdate() {
        return nextUpdate;
    }

    public Object getUser() {
        return user;
    }

    public boolean isStream() {
        return stream;
    }

    public void setStream(boolean stream) {
        this.stream = stream;
    }

    public void setMinRto(int min) {
        rx_minrto = min;
    }

    @Override
    public String toString() {
        return this.user.toString();
    }

    /**
     * 释放内存
     */
    void release() {
        if (buffer.refCnt() > 0) {
            this.buffer.release(buffer.refCnt());
        }
        for (Segment seg : this.rcv_buf) {
            seg.release();
        }
        for (Segment seg : this.rcv_queue) {
            seg.release();
        }
        for (Segment seg : this.snd_buf) {
            seg.release();
        }
        for (Segment seg : this.snd_queue) {
            seg.release();
        }
    }
}


================================================
FILE: src/main/java/org/beykery/jkcp/KcpClient.java
================================================
/**
 * 测试
 */
package org.beykery.jkcp;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;

import java.net.InetSocketAddress;

/**
 * @author beykery
 */
public abstract class KcpClient implements Output, KcpListerner, Runnable {

    private final DatagramChannel channel;
    private final InetSocketAddress addr;
    private int nodelay;
    private int interval = Kcp.IKCP_INTERVAL;
    private int resend;
    private int nc;
    private int sndwnd = Kcp.IKCP_WND_SND;
    private int rcvwnd = Kcp.IKCP_WND_RCV;
    private int mtu = Kcp.IKCP_MTU_DEF;
    private int conv = (int) (Math.random() * Integer.MAX_VALUE);
    private boolean stream;
    private int minRto = Kcp.IKCP_RTO_MIN;
    private long timeout;
    private KcpOnUdp kcp;
    private volatile boolean running;
    private final Object waitLock = new Object();
    private InetSocketAddress remote;
    private final EventLoopGroup nioEventLoopGroup;

    /**
     * client
     */
    public KcpClient() {
        this(0);
    }

    /**
     * 客户端
     *
     * @param port
     */
    public KcpClient(int port) {
        boolean epoll = Epoll.isAvailable();
        boolean kqueue = KQueue.isAvailable();
        nioEventLoopGroup = epoll ? new EpollEventLoopGroup() : (kqueue ? new KQueueEventLoopGroup() : new NioEventLoopGroup());
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(epoll ? EpollDatagramChannel.class : (kqueue ? KQueueDatagramChannel.class : NioDatagramChannel.class));
        bootstrap.group(nioEventLoopGroup);
        bootstrap.handler(new ChannelInitializer<DatagramChannel>() {

            @Override
            protected void initChannel(DatagramChannel ch) {
                ChannelPipeline cp = ch.pipeline();
                cp.addLast(new ChannelInboundHandlerAdapter() {
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        DatagramPacket dp = (DatagramPacket) msg;
                        KcpClient.this.onReceive(dp);
                    }

                    @Override
                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        KcpClient.this.handleException(cause, null);
                        KcpClient.this.close();
                    }
                });
            }
        });
        ChannelFuture sync = bootstrap.bind(port).syncUninterruptibly();
        channel = (DatagramChannel) sync.channel();
        addr = channel.localAddress();
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                nioEventLoopGroup.shutdownGracefully();
            }
        }));
    }

    /**
     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),
     * 1:enable interval: internal update timer interval in millisec, default is
     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:
     * 0:normal congestion control(default), 1:disable congestion control
     *
     * @param nodelay
     * @param interval
     * @param resend
     * @param nc
     */
    public void noDelay(int nodelay, int interval, int resend, int nc) {
        this.nodelay = nodelay;
        this.interval = interval;
        this.resend = resend;
        this.nc = nc;
    }

    /**
     * set maximum window size: sndwnd=32, rcvwnd=32 by default
     *
     * @param sndwnd
     * @param rcvwnd
     */
    public void wndSize(int sndwnd, int rcvwnd) {
        this.sndwnd = sndwnd;
        this.rcvwnd = rcvwnd;
    }

    /**
     * change MTU size, default is 1400
     *
     * @param mtu
     */
    public void setMtu(int mtu) {
        this.mtu = mtu;
    }

    /**
     * conv
     *
     * @param conv
     */
    public void setConv(int conv) {
        this.conv = conv;
    }

    /**
     * stream mode
     *
     * @param stream
     */
    public void setStream(boolean stream) {
        this.stream = stream;
    }

    public boolean isStream() {
        return stream;
    }

    public void setMinRto(int minRto) {
        this.minRto = minRto;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public long getTimeout() {
        return this.timeout;
    }

    /**
     * 固定连接到一个服务器地址,只会处理此地址的消息
     *
     * @param addr
     */
    public void connect(InetSocketAddress addr) {
        this.remote = addr;
        this.channel.connect(addr);
    }

    @Override
    public void out(ByteBuf msg, Kcp kcp, Object user) {
        DatagramPacket temp = new DatagramPacket(msg, (InetSocketAddress) user, this.addr);
        this.channel.writeAndFlush(temp);
    }

    @Override
    public void handleClose(KcpOnUdp kcp) {
        this.close();
    }

    /**
     * 收到服务器消息
     *
     * @param dp
     */
    private void onReceive(DatagramPacket dp) {
        if (this.kcp != null && this.running) {
            this.kcp.input(dp.content());
            synchronized (this.waitLock) {
                this.waitLock.notify();
            }
        } else {
            dp.release();
        }
    }

    /**
     * 关掉
     */
    public void close() {
        if (this.running) {
            this.running = false;
        }
    }

    /**
     * 发送消息
     *
     * @param bb
     */
    public void send(ByteBuf bb) {
        if (this.kcp != null) {
            this.kcp.send(bb);
            synchronized (this.waitLock) {
                this.waitLock.notify();
            }
        }
    }

    /**
     * 开启线程处理kcp状态
     */
    public void start() {
        if (!this.running) {
            this.running = true;
            this.kcp = new KcpOnUdp(this, remote, addr, this);
            this.kcp.noDelay(nodelay, interval, resend, nc);
            this.kcp.wndSize(sndwnd, rcvwnd);
            this.kcp.setTimeout(timeout);
            this.kcp.setMtu(mtu);
            this.kcp.setConv(conv);
            this.kcp.setStream(stream);
            this.kcp.setMinRto(minRto);
            Thread t = new Thread(this);
            t.setName("kcp client thread");
            t.start();
        }
    }

    @Override
    public void run() {
        long start, end;
        while (running) {
            start = System.currentTimeMillis();
            if (kcp.isClosed()) {
                this.running = false;
                continue;
            }
            kcp.update();
            end = System.currentTimeMillis();
            if (end - start < interval) {
                synchronized (waitLock) {
                    try {
                        waitLock.wait(this.interval - end + start);
                    } catch (InterruptedException ex) {
                    }
                }
            }
        }
        this.release();
        nioEventLoopGroup.shutdownGracefully();
        this.channel.close();
    }

    /**
     * 释放内存
     */
    private void release() {
        this.kcp.release();
    }

}


================================================
FILE: src/main/java/org/beykery/jkcp/KcpListerner.java
================================================
/**
 *
 */
package org.beykery.jkcp;

import io.netty.buffer.ByteBuf;

/**
 *
 * @author beykery
 */
public interface KcpListerner {

    /**
     * kcp message
     *
     * @param bb the data
     * @param kcp
     */
    public void handleReceive(ByteBuf bb, KcpOnUdp kcp);

    /**
     *
     * kcp异常,之后此kcp就会被关闭
     *
     * @param ex 异常
     * @param kcp 发生异常的kcp,null表示非kcp错误
     */
    public void handleException(Throwable ex, KcpOnUdp kcp);

    /**
     * 关闭
     *
     * @param kcp
     */
    public void handleClose(KcpOnUdp kcp);
}


================================================
FILE: src/main/java/org/beykery/jkcp/KcpOnUdp.java
================================================
/**
 * udp for kcp
 */
package org.beykery.jkcp;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author beykery
 */
public class KcpOnUdp {

    private final Kcp kcp;//kcp的状态
    private final Queue<ByteBuf> received;//输入
    private final Queue<ByteBuf> sendList;
    private long timeout;//超时设定
    private long lastTime;//上次超时检查时间
    private int errcode;//错误代码
    private final KcpListerner listerner;
    private volatile boolean needUpdate;
    private volatile boolean closed;
    private String sessionId;
    private final Map<Object, Object> session;
    private final InetSocketAddress remote;//远程地址
    private final InetSocketAddress local;//本地

    /**
     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),
     * 1:enable interval: internal update timer interval in millisec, default is
     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:
     * 0:normal congestion control(default), 1:disable congestion control
     *
     * @param nodelay
     * @param interval
     * @param resend
     * @param nc
     */
    public void noDelay(int nodelay, int interval, int resend, int nc) {
        this.kcp.noDelay(nodelay, interval, resend, nc);
    }

    /**
     * set maximum window size: sndwnd=32, rcvwnd=32 by default
     *
     * @param sndwnd
     * @param rcvwnd
     */
    public void wndSize(int sndwnd, int rcvwnd) {
        this.kcp.wndSize(sndwnd, rcvwnd);
    }

    /**
     * change MTU size, default is 1400
     *
     * @param mtu
     */
    public void setMtu(int mtu) {
        this.kcp.setMtu(mtu);
    }

    /**
     * conv
     *
     * @param conv
     */
    public void setConv(int conv) {
        this.kcp.setConv(conv);
    }

    /**
     * stream模式
     *
     * @param stream
     */
    public void setStream(boolean stream) {
        this.kcp.setStream(stream);
    }

    /**
     * 流模式
     *
     * @return
     */
    public boolean isStream() {
        return this.kcp.isStream();
    }

    /**
     * rto设置
     *
     * @param rto
     */
    public void setMinRto(int rto) {
        this.kcp.setMinRto(rto);
    }

    /**
     * kcp for udp
     *
     * @param out       输出接口
     * @param remote    远程地址
     * @param local     本地地址
     * @param listerner 监听
     */
    public KcpOnUdp(Output out, InetSocketAddress remote, InetSocketAddress local, KcpListerner listerner) {
        this.listerner = listerner;
        kcp = new Kcp(out, remote);
        received = new LinkedBlockingQueue<>();
        sendList = new LinkedBlockingQueue<>();
        this.session = new HashMap<>();
        this.remote = remote;
        this.local = local;
    }

    /**
     * send data to addr
     *
     * @param bb
     */
    public void send(ByteBuf bb) {
        if (!closed) {
            this.sendList.add(bb);
            this.needUpdate = true;
        }
    }

    /**
     * update one kcp
     *
     */
    void update() {
        //input
        while (!this.received.isEmpty()) {
            ByteBuf dp = this.received.remove();
            errcode = kcp.input(dp);
            dp.release();
            if (errcode != 0) {
                this.closed = true;
                this.listerner.handleException(new IllegalStateException("input error : " + errcode), this);
                return;
            }
        }
        //receive
        int len;
        while ((len = kcp.peekSize()) > 0) {
            ByteBuf bb = PooledByteBufAllocator.DEFAULT.buffer(len);
            int n = kcp.receive(bb);
            if (n > 0) {
                this.listerner.handleReceive(bb, this);
            } else {
                bb.release();
            }
        }
        //send
        while (!this.sendList.isEmpty()) {
            ByteBuf bb = sendList.remove();
            errcode = this.kcp.send(bb);
            if (errcode != 0) {
                this.closed = true;
                this.listerner.handleException(new IllegalStateException("send error : " + errcode), this);
                return;
            }
        }
        //update kcp status
        if (this.needUpdate) {
            kcp.flush();
            this.needUpdate = false;
        }
        long cur = System.currentTimeMillis();
        if (cur >= kcp.getNextUpdate()) {
            kcp.update(cur);
            kcp.setNextUpdate(kcp.check(cur));
        }
        //check timeout
        if (closed || (this.timeout > 0 && lastTime > 0 && System.currentTimeMillis() - lastTime > this.timeout)) {
            this.closed = true;
            this.listerner.handleClose(this);
            this.release();
        }
    }

    /**
     * 输入
     *
     * @param content
     */
    void input(ByteBuf content) {
        if (!this.closed) {
            this.received.add(content);
            this.needUpdate = true;
            this.lastTime = System.currentTimeMillis();
        } else {
            content.release();
        }
    }

    public boolean isClosed() {
        return closed;
    }

    public void close() {
        this.closed = true;
    }

    public Kcp getKcp() {
        return kcp;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public long getTimeout() {
        return timeout;
    }

    @Override
    public String toString() {
        return "local: " + local + " remote: " + remote;
    }

    /**
     * session id
     *
     * @return
     */
    public String getSessionId() {
        return sessionId;
    }

    /**
     * session id
     *
     * @param sessionId
     */
    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    /**
     * session map
     *
     * @return
     */
    public Map<Object, Object> getSessionMap() {
        return session;
    }

    /**
     * session k v
     *
     * @param k
     * @return
     */
    public Object getSession(Object k) {
        return this.session.get(k);
    }

    /**
     * session k v
     *
     * @param k
     * @param v
     * @return
     */
    public Object setSession(Object k, Object v) {
        return this.session.put(k, v);
    }

    /**
     * contains key
     *
     * @param k
     * @return
     */
    public boolean containsSessionKey(Object k) {
        return this.session.containsKey(k);
    }

    /**
     * contains value
     *
     * @param v
     * @return
     */
    public boolean containsSessionValue(Object v) {
        return this.session.containsValue(v);
    }

    /**
     * 立即更新?
     *
     * @return
     */
    boolean needUpdate() {
        return this.needUpdate;
    }

    /**
     * 监听器
     *
     * @return
     */
    public KcpListerner getListerner() {
        return listerner;
    }

    /**
     * 本地地址
     *
     * @return
     */
    public InetSocketAddress getLocal() {
        return local;
    }

    /**
     * 远程地址
     *
     * @return
     */
    public InetSocketAddress getRemote() {
        return remote;
    }

    /**
     * 释放内存
     */
    void release() {
        this.kcp.release();
        for (ByteBuf item : this.received) {
            item.release();
        }
        for (ByteBuf item : this.sendList) {
            item.release();
        }
    }

}


================================================
FILE: src/main/java/org/beykery/jkcp/KcpServer.java
================================================
/**
 * kcp服务器
 */
package org.beykery.jkcp;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author beykery
 */
public abstract class KcpServer implements Output, KcpListerner {
    private final AtomicLong counter = new AtomicLong(0);
    private final List<Channel> channels;
    private InetSocketAddress localAddress;
    private int nodelay;
    private int interval = Kcp.IKCP_INTERVAL;
    private int resend;
    private int nc;
    private int sndwnd = Kcp.IKCP_WND_SND;
    private int rcvwnd = Kcp.IKCP_WND_RCV;
    private int mtu = Kcp.IKCP_MTU_DEF;
    private boolean stream;
    private int minRto = Kcp.IKCP_RTO_MIN;
    private KcpThread[] workers;
    private volatile boolean running;
    private long timeout;

    /**
     * server
     *
     * @param port
     * @param workerSize
     */
    public KcpServer(int port, int workerSize) {
        boolean epoll = Epoll.isAvailable();
        boolean kqueue = KQueue.isAvailable();
        int bonds = (epoll || kqueue) ? Runtime.getRuntime().availableProcessors() : 1;
        channels = new ArrayList<>(bonds);
        final EventLoopGroup group = epoll ? new EpollEventLoopGroup() : (kqueue ? new KQueueEventLoopGroup() : new NioEventLoopGroup());
        if (port <= 0 || workerSize <= 0) {
            throw new IllegalArgumentException("参数非法");
        }
        this.workers = new KcpThread[workerSize];
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(epoll ? EpollDatagramChannel.class : (kqueue ? KQueueDatagramChannel.class : NioDatagramChannel.class));
        bootstrap.group(group);
        bootstrap.option(ChannelOption.SO_BROADCAST, true)
                .option(ChannelOption.SO_RCVBUF, 1024 * 1024);
        if (epoll || kqueue) {
            bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);
        }
        bootstrap.handler(new ChannelInitializer<Channel>() {

            @Override
            protected void initChannel(Channel ch) {
                ChannelPipeline cp = ch.pipeline();
                cp.addLast(new KcpServer.UdpHandler());
            }
        });

        for (int i = 0; i < bonds; i++) {
            try {
                ChannelFuture f = bootstrap.bind(port).await();
                Channel channel = f.channel();
                localAddress = (InetSocketAddress) channel.localAddress();
                channels.add(channel);
            } catch (InterruptedException e) {
                throw new RuntimeException("init failed . can not bind channel to port .");
            }
        }
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                group.shutdownGracefully();
            }
        }));
    }

    /**
     * 开始
     */
    public void start() {
        if (!this.running) {
            this.running = true;
            for (int i = 0; i < this.workers.length; i++) {
                workers[i] = new KcpThread(this, this, localAddress);
                workers[i].setName("kcp thread " + i);
                workers[i].wndSize(sndwnd, rcvwnd);
                workers[i].noDelay(nodelay, interval, resend, nc);
                workers[i].setMtu(mtu);
                workers[i].setTimeout(timeout);
                workers[i].setMinRto(minRto);
                workers[i].setStream(stream);
                workers[i].start();
            }
        }
    }

    /**
     * close
     *
     * @return
     */
    public void close() {
        if (this.running) {
            this.running = false;
            for (KcpThread kt : this.workers) {
                kt.close();
            }
            this.workers = null;
            for (Channel c : channels) {
                c.close();
            }
        }
    }

    /**
     * kcp call
     *
     * @param msg
     * @param kcp
     * @param user
     */
    @Override
    public void out(ByteBuf msg, Kcp kcp, Object user) {
        DatagramPacket temp = new DatagramPacket(msg, (InetSocketAddress) user, this.localAddress);
        Channel channel = channel();
        channel.writeAndFlush(temp);
    }

    /**
     * select one channel
     *
     * @return
     */
    private Channel channel() {
        long cur = counter.getAndAdd(1);
        return channels.get((int) (cur % channels.size()));
    }

    /**
     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),
     * 1:enable interval: internal update timer interval in millisec, default is
     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:
     * 0:normal congestion control(default), 1:disable congestion control
     *
     * @param nodelay
     * @param interval
     * @param resend
     * @param nc
     */
    public void noDelay(int nodelay, int interval, int resend, int nc) {
        this.nodelay = nodelay;
        this.interval = interval;
        this.resend = resend;
        this.nc = nc;
    }

    /**
     * set maximum window size: sndwnd=32, rcvwnd=32 by default
     *
     * @param sndwnd
     * @param rcvwnd
     */
    public void wndSize(int sndwnd, int rcvwnd) {
        this.sndwnd = sndwnd;
        this.rcvwnd = rcvwnd;
    }

    /**
     * change MTU size, default is 1400
     *
     * @param mtu
     */
    public void setMtu(int mtu) {
        this.mtu = mtu;
    }

    /**
     * stream mode
     *
     * @param stream
     */
    public void setStream(boolean stream) {
        this.stream = stream;
    }

    public boolean isStream() {
        return stream;
    }

    public void setMinRto(int minRto) {
        this.minRto = minRto;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public long getTimeout() {
        return this.timeout;
    }

    /**
     * 发送
     *
     * @param bb
     * @param ku
     */
    public void send(ByteBuf bb, KcpOnUdp ku) {
        ku.send(bb);
    }

    /**
     * receive DatagramPacket
     *
     * @param dp
     */
    private void onReceive(DatagramPacket dp) {
        if (this.running) {
            InetSocketAddress sender = dp.sender();
            int hash = sender.hashCode();
            hash = hash < 0 ? -hash : hash;
            this.workers[hash % workers.length].input(dp);
        } else {
            dp.release();
        }
    }

    /**
     * handler
     */
    public class UdpHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            DatagramPacket dp = (DatagramPacket) msg;
            KcpServer.this.onReceive(dp);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            KcpServer.this.handleException(cause, null);
        }
    }
}


================================================
FILE: src/main/java/org/beykery/jkcp/KcpThread.java
================================================
/**
 * 维护kcp状态的线程
 */
package org.beykery.jkcp;

import io.netty.buffer.ByteBuf;
import io.netty.channel.socket.DatagramPacket;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author beykery
 */
public class KcpThread extends Thread {

    private final Output out;
    private final LinkedBlockingQueue<DatagramPacket> inputs;
    private volatile boolean running;
    private final Map<InetSocketAddress, KcpOnUdp> kcps;
    private final KcpListerner listerner;
    private int nodelay;
    private int interval = Kcp.IKCP_INTERVAL;
    private int resend;
    private int nc;
    private int sndwnd = Kcp.IKCP_WND_SND;
    private int rcvwnd = Kcp.IKCP_WND_RCV;
    private int mtu = Kcp.IKCP_MTU_DEF;
    private boolean stream;
    private int minRto = Kcp.IKCP_RTO_MIN;
    private long timeout;//idle
    private final Object lock;//锁
    private final InetSocketAddress local;//本地地址

    /**
     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),
     * 1:enable interval: internal update timer interval in millisec, default is
     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:
     * 0:normal congestion control(default), 1:disable congestion control
     *
     * @param nodelay
     * @param interval
     * @param resend
     * @param nc
     */
    public void noDelay(int nodelay, int interval, int resend, int nc) {
        this.nodelay = nodelay;
        this.interval = interval;
        this.resend = resend;
        this.nc = nc;
    }

    /**
     * set maximum window size: sndwnd=32, rcvwnd=32 by default
     *
     * @param sndwnd
     * @param rcvwnd
     */
    public void wndSize(int sndwnd, int rcvwnd) {
        this.sndwnd = sndwnd;
        this.rcvwnd = rcvwnd;
    }

    /**
     * change MTU size, default is 1400
     *
     * @param mtu
     */
    public void setMtu(int mtu) {
        this.mtu = mtu;
    }

    /**
     * kcp工作线程
     *
     * @param out
     * @param listerner
     * @param local
     */
    public KcpThread(Output out, KcpListerner listerner, InetSocketAddress local) {
        this.out = out;
        this.listerner = listerner;
        inputs = new LinkedBlockingQueue<>();
        kcps = new HashMap<>();
        this.lock = new Object();
        this.local = local;
    }

    /**
     * 开启线程
     */
    @Override
    public synchronized void start() {
        if (!this.running) {
            this.running = true;
            super.start();
        }
    }

    /**
     * 关闭线程
     */
    public void close() {
        this.running = false;
    }

    @Override
    public void run() {
        while (this.running) {
            long st = System.currentTimeMillis();
            //input
            while (!this.inputs.isEmpty()) {
                DatagramPacket dp = this.inputs.remove();
                ByteBuf content = dp.content();
                KcpOnUdp ku = this.kcps.get(dp.sender());
                if (ku == null) {
                    ku = new KcpOnUdp(this.out, dp.sender(), local, this.listerner);//初始化
                    ku.noDelay(nodelay, interval, resend, nc);
                    ku.wndSize(sndwnd, rcvwnd);
                    ku.setMtu(mtu);
                    // conv应该在客户端第一次建立时获取
                    int conv = content.getIntLE(0);
                    ku.setConv(conv);
                    ku.setMinRto(minRto);
                    ku.setStream(stream);
                    ku.setTimeout(timeout);
                    this.kcps.put(dp.sender(), ku);
                }
                ku.input(content);
            }
            //update
            KcpOnUdp temp = null;
            for (KcpOnUdp ku : this.kcps.values()) {
                if (ku.isClosed()) {
                    temp = ku;
                } else {
                    ku.update();
                }
            }
            if (temp != null)//删掉过时的kcp
            {
                this.kcps.remove((InetSocketAddress) temp.getKcp().getUser());
            }
            if (inputs.isEmpty())//如果输入为空则考虑wait
            {
                long end = System.currentTimeMillis();
                if (end - st < this.interval) {
                    synchronized (this.lock) {
                        try {
                            lock.wait(interval - end + st);
                        } catch (InterruptedException e) {
                        }
                    }
                }
            }
        }
        release();
    }

    /**
     * 收到输入
     */
    void input(DatagramPacket dp) {
        if (this.running) {
            this.inputs.add(dp);
            synchronized (this.lock) {
                lock.notify();
            }
        } else {
            dp.release();
        }
    }

    /**
     * stream mode
     *
     * @param stream
     */
    public void setStream(boolean stream) {
        this.stream = stream;
    }

    public boolean isStream() {
        return stream;
    }

    public void setMinRto(int minRto) {
        this.minRto = minRto;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public long getTimeout() {
        return timeout;
    }

    /**
     * 释放所有内存
     */
    private void release() {
        for (DatagramPacket dp : this.inputs) {
            dp.release();
        }
        this.inputs.clear();
        for (KcpOnUdp ku : this.kcps.values()) {
            ku.close();
            ku.release();
        }
    }

}


================================================
FILE: src/main/java/org/beykery/jkcp/Output.java
================================================
/**
 * out
 */
package org.beykery.jkcp;

import io.netty.buffer.ByteBuf;

/**
 * @author beykery
 */
public interface Output {

    /**
     * kcp的底层输出
     *
     * @param msg  消息
     * @param kcp  kcp对象
     * @param user 远端地址
     */
    void out(ByteBuf msg, Kcp kcp, Object user);
}


================================================
FILE: src/test/java/test/TestClient.java
================================================
/**
 * 客户端
 */
package test;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.util.ResourceLeakDetector;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.beykery.jkcp.Kcp;
import org.beykery.jkcp.KcpClient;
import org.beykery.jkcp.KcpOnUdp;

/**
 * @author beykery
 */
public class TestClient extends KcpClient {

    @Override
    public void handleReceive(ByteBuf bb, KcpOnUdp kcp) {
        String content = bb.toString(Charset.forName("utf-8"));
        System.out.println("conv:" + kcp.getKcp().getConv() + " recv:" + content + " kcp-->" + kcp);
        ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(2048);
        buf.writeBytes(content.getBytes(Charset.forName("utf-8")));
        kcp.send(buf);
        bb.release();
    }

    /**
     * kcp异常,之后此kcp就会被关闭
     *
     * @param ex
     * @param kcp
     */
    @Override
    public void handleException(Throwable ex, KcpOnUdp kcp) {
        System.out.println(ex);
    }

    @Override
    public void handleClose(KcpOnUdp kcp) {
        super.handleClose(kcp);
        System.out.println("服务器离开:" + kcp);
        System.out.println("waitSnd:" + kcp.getKcp().waitSnd());
    }

    @Override
    public void out(ByteBuf msg, Kcp kcp, Object user) {
        super.out(msg, kcp, user);
    }

    /**
     * tcpdump udp port 2225 -x -vv -s0 -w 1112.pcap
     *
     * @param args
     * @throws java.lang.InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
        TestClient tc = new TestClient();
        tc.noDelay(1, 20, 2, 1);
        tc.setMinRto(10);
        tc.wndSize(32, 32);
        tc.setTimeout(10 * 1000);
        tc.setMtu(512);
        // tc.setConv(121106);//默认conv随机

        tc.connect(new InetSocketAddress("localhost", 2222));
        tc.start();
        String content = "sdfkasd你好。。。。。。。";
        ByteBuf bb = PooledByteBufAllocator.DEFAULT.buffer(1500);
        bb.writeBytes(content.getBytes(Charset.forName("utf-8")));
        tc.send(bb);
    }
}


================================================
FILE: src/test/java/test/TestServer.java
================================================
/**
 * 测试
 */
package test;

import io.netty.buffer.ByteBuf;

import java.nio.charset.Charset;

import org.beykery.jkcp.KcpOnUdp;
import org.beykery.jkcp.KcpServer;

/**
 * @author beykery
 */
public class TestServer extends KcpServer {

    public TestServer(int port, int workerSize) {
        super(port, workerSize);
    }

    @Override
    public void handleReceive(ByteBuf bb, KcpOnUdp kcp) {
        if (c == 0) {
            start = System.currentTimeMillis();
        }
        c++;
        String content = bb.toString(Charset.forName("utf-8"));
        System.out.println("msg:" + content + " kcp--> " + kcp);
        if (c < 10000) {
            kcp.send(bb);//echo
        } else {
            System.out.println("cost:" + (System.currentTimeMillis() - start));
            kcp.close();
        }
    }

    @Override
    public void handleException(Throwable ex, KcpOnUdp kcp) {
        System.out.println(ex);
    }

    @Override
    public void handleClose(KcpOnUdp kcp) {
        System.out.println("客户端离开:" + kcp);
        System.out.println("waitSnd:" + kcp.getKcp().waitSnd());
    }

    private static long start;
    private static int c = 0;

    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        TestServer s = new TestServer(2222, 1);
        s.noDelay(1, 10, 2, 1);
        s.setMinRto(10);
        s.wndSize(64, 64);
        s.setTimeout(10 * 1000);
        s.setMtu(512);
        s.start();
    }
}
Download .txt
gitextract_bdwvhk3j/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
└── src/
    ├── main/
    │   └── java/
    │       └── org/
    │           └── beykery/
    │               └── jkcp/
    │                   ├── Kcp.java
    │                   ├── KcpClient.java
    │                   ├── KcpListerner.java
    │                   ├── KcpOnUdp.java
    │                   ├── KcpServer.java
    │                   ├── KcpThread.java
    │                   └── Output.java
    └── test/
        └── java/
            └── test/
                ├── TestClient.java
                └── TestServer.java
Download .txt
SYMBOL INDEX (143 symbols across 9 files)

FILE: src/main/java/org/beykery/jkcp/Kcp.java
  class Kcp (line 20) | public class Kcp {
    method _ibound_ (line 86) | private static int _ibound_(int lower, int middle, int upper) {
    method _itimediff (line 90) | private static int _itimediff(int later, int earlier) {
    method _itimediff (line 94) | private static long _itimediff(long later, long earlier) {
    class Segment (line 101) | class Segment {
      method Segment (line 116) | private Segment(int size) {
      method encode (line 128) | private int encode(ByteBuf buf) {
      method release (line 144) | private void release() {
    method Kcp (line 157) | public Kcp(Output output, Object user) {
    method peekSize (line 179) | public int peekSize() {
    method receive (line 206) | public int receive(ByteBuf buffer) {
    method send (line 265) | public int send(ByteBuf buffer) {
    method update_ack (line 310) | private void update_ack(int rtt) {
    method shrink_buf (line 329) | private void shrink_buf() {
    method parse_ack (line 337) | private void parse_ack(int sn) {
    method parse_una (line 354) | private void parse_una(int una) {
    method parse_fastack (line 371) | private void parse_fastack(int sn) {
    method ack_push (line 390) | private void ack_push(int sn, int ts) {
    method parse_data (line 395) | private void parse_data(Segment newseg) {
    method input (line 444) | public int input(ByteBuf data) {
    method wnd_unused (line 564) | private int wnd_unused() {
    method forceFlush (line 574) | public void forceFlush() {
    method flush (line 743) | public void flush() {
    method update (line 756) | public void update(long current) {
    method check (line 787) | public long check(long current) {
    method setMtu (line 823) | public int setMtu(int mtu) {
    method setConv (line 842) | public void setConv(int conv) {
    method getConv (line 851) | public int getConv() {
    method interval (line 861) | public int interval(int interval) {
    method noDelay (line 883) | public int noDelay(int nodelay, int interval, int resend, int nc) {
    method wndSize (line 916) | public int wndSize(int sndwnd, int rcvwnd) {
    method waitSnd (line 931) | public int waitSnd() {
    method setNextUpdate (line 935) | public void setNextUpdate(long nextUpdate) {
    method getNextUpdate (line 939) | public long getNextUpdate() {
    method getUser (line 943) | public Object getUser() {
    method isStream (line 947) | public boolean isStream() {
    method setStream (line 951) | public void setStream(boolean stream) {
    method setMinRto (line 955) | public void setMinRto(int min) {
    method toString (line 959) | @Override
    method release (line 967) | void release() {

FILE: src/main/java/org/beykery/jkcp/KcpClient.java
  class KcpClient (line 25) | public abstract class KcpClient implements Output, KcpListerner, Runnable {
    method KcpClient (line 49) | public KcpClient() {
    method KcpClient (line 58) | public KcpClient(int port) {
    method noDelay (line 107) | public void noDelay(int nodelay, int interval, int resend, int nc) {
    method wndSize (line 120) | public void wndSize(int sndwnd, int rcvwnd) {
    method setMtu (line 130) | public void setMtu(int mtu) {
    method setConv (line 139) | public void setConv(int conv) {
    method setStream (line 148) | public void setStream(boolean stream) {
    method isStream (line 152) | public boolean isStream() {
    method setMinRto (line 156) | public void setMinRto(int minRto) {
    method setTimeout (line 160) | public void setTimeout(long timeout) {
    method getTimeout (line 164) | public long getTimeout() {
    method connect (line 173) | public void connect(InetSocketAddress addr) {
    method out (line 178) | @Override
    method handleClose (line 184) | @Override
    method onReceive (line 194) | private void onReceive(DatagramPacket dp) {
    method close (line 208) | public void close() {
    method send (line 219) | public void send(ByteBuf bb) {
    method start (line 231) | public void start() {
    method run (line 248) | @Override
    method release (line 276) | private void release() {

FILE: src/main/java/org/beykery/jkcp/KcpListerner.java
  type KcpListerner (line 12) | public interface KcpListerner {
    method handleReceive (line 20) | public void handleReceive(ByteBuf bb, KcpOnUdp kcp);
    method handleException (line 29) | public void handleException(Throwable ex, KcpOnUdp kcp);
    method handleClose (line 36) | public void handleClose(KcpOnUdp kcp);

FILE: src/main/java/org/beykery/jkcp/KcpOnUdp.java
  class KcpOnUdp (line 18) | public class KcpOnUdp {
    method noDelay (line 45) | public void noDelay(int nodelay, int interval, int resend, int nc) {
    method wndSize (line 55) | public void wndSize(int sndwnd, int rcvwnd) {
    method setMtu (line 64) | public void setMtu(int mtu) {
    method setConv (line 73) | public void setConv(int conv) {
    method setStream (line 82) | public void setStream(boolean stream) {
    method isStream (line 91) | public boolean isStream() {
    method setMinRto (line 100) | public void setMinRto(int rto) {
    method KcpOnUdp (line 112) | public KcpOnUdp(Output out, InetSocketAddress remote, InetSocketAddres...
    method send (line 127) | public void send(ByteBuf bb) {
    method update (line 138) | void update() {
    method input (line 194) | void input(ByteBuf content) {
    method isClosed (line 204) | public boolean isClosed() {
    method close (line 208) | public void close() {
    method getKcp (line 212) | public Kcp getKcp() {
    method setTimeout (line 216) | public void setTimeout(long timeout) {
    method getTimeout (line 220) | public long getTimeout() {
    method toString (line 224) | @Override
    method getSessionId (line 234) | public String getSessionId() {
    method setSessionId (line 243) | public void setSessionId(String sessionId) {
    method getSessionMap (line 252) | public Map<Object, Object> getSessionMap() {
    method getSession (line 262) | public Object getSession(Object k) {
    method setSession (line 273) | public Object setSession(Object k, Object v) {
    method containsSessionKey (line 283) | public boolean containsSessionKey(Object k) {
    method containsSessionValue (line 293) | public boolean containsSessionValue(Object v) {
    method needUpdate (line 302) | boolean needUpdate() {
    method getListerner (line 311) | public KcpListerner getListerner() {
    method getLocal (line 320) | public InetSocketAddress getLocal() {
    method getRemote (line 329) | public InetSocketAddress getRemote() {
    method release (line 336) | void release() {

FILE: src/main/java/org/beykery/jkcp/KcpServer.java
  class KcpServer (line 28) | public abstract class KcpServer implements Output, KcpListerner {
    method KcpServer (line 51) | public KcpServer(int port, int workerSize) {
    method start (line 99) | public void start() {
    method close (line 121) | public void close() {
    method out (line 141) | @Override
    method channel (line 153) | private Channel channel() {
    method noDelay (line 169) | public void noDelay(int nodelay, int interval, int resend, int nc) {
    method wndSize (line 182) | public void wndSize(int sndwnd, int rcvwnd) {
    method setMtu (line 192) | public void setMtu(int mtu) {
    method setStream (line 201) | public void setStream(boolean stream) {
    method isStream (line 205) | public boolean isStream() {
    method setMinRto (line 209) | public void setMinRto(int minRto) {
    method setTimeout (line 213) | public void setTimeout(long timeout) {
    method getTimeout (line 217) | public long getTimeout() {
    method send (line 227) | public void send(ByteBuf bb, KcpOnUdp ku) {
    method onReceive (line 236) | private void onReceive(DatagramPacket dp) {
    class UdpHandler (line 250) | public class UdpHandler extends ChannelInboundHandlerAdapter {
      method channelRead (line 252) | @Override
      method exceptionCaught (line 258) | @Override

FILE: src/main/java/org/beykery/jkcp/KcpThread.java
  class KcpThread (line 17) | public class KcpThread extends Thread {
    method noDelay (line 48) | public void noDelay(int nodelay, int interval, int resend, int nc) {
    method wndSize (line 61) | public void wndSize(int sndwnd, int rcvwnd) {
    method setMtu (line 71) | public void setMtu(int mtu) {
    method KcpThread (line 82) | public KcpThread(Output out, KcpListerner listerner, InetSocketAddress...
    method start (line 94) | @Override
    method close (line 105) | public void close() {
    method run (line 109) | @Override
    method input (line 165) | void input(DatagramPacket dp) {
    method setStream (line 181) | public void setStream(boolean stream) {
    method isStream (line 185) | public boolean isStream() {
    method setMinRto (line 189) | public void setMinRto(int minRto) {
    method setTimeout (line 193) | public void setTimeout(long timeout) {
    method getTimeout (line 197) | public long getTimeout() {
    method release (line 204) | private void release() {

FILE: src/main/java/org/beykery/jkcp/Output.java
  type Output (line 11) | public interface Output {
    method out (line 20) | void out(ByteBuf msg, Kcp kcp, Object user);

FILE: src/test/java/test/TestClient.java
  class TestClient (line 20) | public class TestClient extends KcpClient {
    method handleReceive (line 22) | @Override
    method handleException (line 38) | @Override
    method handleClose (line 43) | @Override
    method out (line 50) | @Override
    method main (line 61) | public static void main(String[] args) throws InterruptedException {

FILE: src/test/java/test/TestServer.java
  class TestServer (line 16) | public class TestServer extends KcpServer {
    method TestServer (line 18) | public TestServer(int port, int workerSize) {
    method handleReceive (line 22) | @Override
    method handleException (line 38) | @Override
    method handleClose (line 43) | @Override
    method main (line 57) | public static void main(String[] args) {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
  {
    "path": ".gitignore",
    "chars": 16,
    "preview": "/target/\n.idea/\n"
  },
  {
    "path": ".travis.yml",
    "chars": 48,
    "preview": "language: java\njdk:\n  - openjdk7\n  - oraclejdk7\n"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 440,
    "preview": "# jkcp\r\n\r\nkcp for java . base on netty .\r\n\r\nkcp是一种独立于底层通信协议的重传算法,jkcp直接构建于udp之上\r\n并提供方便的编程接口,只需要继承相关的类即可;用户不用关心udp\r\n和kcp的"
  },
  {
    "path": "pom.xml",
    "chars": 5684,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/Kcp.java",
    "chars": 28863,
    "preview": "/**\n * KCP - A Better ARQ Protocol Implementation\n * skywind3000 (at) gmail.com, 2010-2011\n * Features:\n * + Average RTT"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpClient.java",
    "chars": 7545,
    "preview": "/**\n * 测试\n */\npackage org.beykery.jkcp;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBuf;\nimport io."
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpListerner.java",
    "chars": 551,
    "preview": "/**\n *\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n *\n * @author beykery\n */\npublic interface Kc"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpOnUdp.java",
    "chars": 7406,
    "preview": "/**\n * udp for kcp\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAl"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpServer.java",
    "chars": 7492,
    "preview": "/**\n * kcp服务器\n */\npackage org.beykery.jkcp;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBuf;\nimport"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpThread.java",
    "chars": 5536,
    "preview": "/**\n * 维护kcp状态的线程\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.socket.Datagram"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/Output.java",
    "chars": 290,
    "preview": "/**\n * out\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * @author beykery\n */\npublic interface O"
  },
  {
    "path": "src/test/java/test/TestClient.java",
    "chars": 2142,
    "preview": "/**\n * 客户端\n */\npackage test;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAllocator;\nimport io.n"
  },
  {
    "path": "src/test/java/test/TestServer.java",
    "chars": 1481,
    "preview": "/**\n * 测试\n */\npackage test;\n\nimport io.netty.buffer.ByteBuf;\n\nimport java.nio.charset.Charset;\n\nimport org.beykery.jkcp."
  }
]

About this extraction

This page contains the full source code of the beykery/jkcp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (77.0 KB), approximately 19.3k tokens, and a symbol index with 143 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!