[
  {
    "path": ".gitignore",
    "content": "/target/\n.idea/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\njdk:\n  - openjdk7\n  - oraclejdk7\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# jkcp\r\n\r\nkcp for java . base on netty .\r\n\r\nkcp是一种独立于底层通信协议的重传算法，jkcp直接构建于udp之上\r\n并提供方便的编程接口，只需要继承相关的类即可；用户不用关心udp\r\n和kcp的使用细节就能轻松驾驭视频、moba类等需要高速传输环境的应用开发\r\n\r\n## 坐标\r\n\r\n```xml\r\n  <dependency>\r\n      <groupId>org.beykery</groupId>\r\n      <artifactId>jkcp</artifactId>\r\n      <version>1.3.2</version>\r\n  </dependency>\r\n```\r\n\r\n## 使用\r\n\r\n请参考src/test/java/test目录下的TestServer和TestClient\r\n\r\n## kcp的算法细节请参考\r\n\r\n[kcp](https://github.com/skywind3000/kcp)\r\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>org.beykery</groupId>\n    <artifactId>jkcp</artifactId>\n    <version>1.3.2</version>\n    <packaging>jar</packaging>\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <maven.compiler.source>17</maven.compiler.source>\n        <maven.compiler.target>17</maven.compiler.target>\n        <java.version>17</java.version>\n    </properties>\n    <name>jkcp</name>\n    <description>\n        kcp for java .\n    </description>\n    <url>\n        https://github.com/beykery/jkcp\n    </url>\n    <licenses>\n        <license>\n            <name>The Apache Software License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n        </license>\n    </licenses>\n    <developers>\n        <developer>\n            <name>beykery</name>\n            <email>beykery@sina.com</email>\n        </developer>\n    </developers>\n    <scm>\n        <connection>scm:git:git@github.com:beykery/jkcp.git</connection>\n        <developerConnection>scm:git:git@github.com:beykery/jkcp.git</developerConnection>\n        <url>git@github.com:beykery/jkcp.git</url>\n    </scm>\n    <profiles>\n        <profile>\n            <id>oss</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.sonatype.plugins</groupId>\n                        <artifactId>nexus-staging-maven-plugin</artifactId>\n                        <version>1.6.8</version>\n                        <extensions>true</extensions>\n                        <configuration>\n                            <serverId>oss</serverId>\n                            <nexusUrl>https://oss.sonatype.org/</nexusUrl>\n                            <autoReleaseAfterClose>true</autoReleaseAfterClose>\n                        </configuration>\n                        <dependencies>\n                            <dependency>\n                                <groupId>com.thoughtworks.xstream</groupId>\n                                <artifactId>xstream</artifactId>\n                                <version>1.4.15</version> <!-- apparently this needs to be exactly this version -->\n                            </dependency>\n                        </dependencies>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-source-plugin</artifactId>\n                        <version>2.4</version>\n                        <executions>\n                            <execution>\n                                <id>attach-sources</id>\n                                <goals>\n                                    <goal>jar-no-fork</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-javadoc-plugin</artifactId>\n                        <version>2.10.3</version>\n                        <executions>\n                            <execution>\n                                <id>attach-javadocs</id>\n                                <goals>\n                                    <goal>jar</goal>\n                                </goals>\n                                <configuration>\n                                    <additionalparam>-Xdoclint:none</additionalparam>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-gpg-plugin</artifactId>\n                        <version>1.6</version>\n                        <executions>\n                            <execution>\n                                <id>sign-artifacts</id>\n                                <phase>verify</phase>\n                                <goals>\n                                    <goal>sign</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n            <distributionManagement>\n                <snapshotRepository>\n                    <id>oss</id>\n                    <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n                </snapshotRepository>\n                <repository>\n                    <id>oss</id>\n                    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n                </repository>\n            </distributionManagement>\n        </profile>\n    </profiles>\n    <dependencies>\n        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n            <version>4.1.118.Final</version>\n        </dependency>\n\n        <!--\n        <dependency>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n          <version>1.7.24</version>\n          <type>jar</type>\n        </dependency>\n        -->\n    </dependencies>\n</project>\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/Kcp.java",
    "content": "/**\n * KCP - A Better ARQ Protocol Implementation\n * skywind3000 (at) gmail.com, 2010-2011\n * Features:\n * + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.\n * + Maximum RTT reduce three times vs tcp.\n * + Lightweight, distributed as a single source file.\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAllocator;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\n\n/**\n * @author beykery\n */\npublic class Kcp {\n\n    public static final int IKCP_RTO_NDL = 30;  // no delay min rto\n    public static final int IKCP_RTO_MIN = 100; // normal min rto\n    public static final int IKCP_RTO_DEF = 200;\n    public static final int IKCP_RTO_MAX = 60000;\n    public static final int IKCP_CMD_PUSH = 81; // cmd: push data\n    public static final int IKCP_CMD_ACK = 82; // cmd: ack\n    public static final int IKCP_CMD_WASK = 83; // cmd: window probe (ask)\n    public static final int IKCP_CMD_WINS = 84; // cmd: window size (tell)\n    public static final int IKCP_ASK_SEND = 1;  // need to send IKCP_CMD_WASK\n    public static final int IKCP_ASK_TELL = 2;  // need to send IKCP_CMD_WINS\n    public static final int IKCP_WND_SND = 32;\n    public static final int IKCP_WND_RCV = 32;\n    public static final int IKCP_MTU_DEF = 1400;\n    public static final int IKCP_ACK_FAST = 3;\n    public static final int IKCP_INTERVAL = 100;\n    public static final int IKCP_OVERHEAD = 24;\n    public static final int IKCP_DEADLINK = 10;\n    public static final int IKCP_THRESH_INIT = 2;\n    public static final int IKCP_THRESH_MIN = 2;\n    public static final int IKCP_PROBE_INIT = 7000;   // 7 secs to probe window size\n    public static final int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window\n\n    private int conv;\n    private int mtu;\n    private int mss;\n    private int state;\n    private int snd_una;\n    private int snd_nxt;\n    private int rcv_nxt;\n    private int ts_recent;\n    private int ts_lastack;\n    private int ssthresh;\n    private int rx_rttval;\n    private int rx_srtt;\n    private int rx_rto;\n    private int rx_minrto;\n    private int snd_wnd;\n    private int rcv_wnd;\n    private int rmt_wnd;\n    private int cwnd;\n    private int probe;\n    private int current;\n    private int interval;\n    private int ts_flush;\n    private int xmit;\n    private int nodelay;\n    private int updated;\n    private int ts_probe;\n    private int probe_wait;\n    private final int dead_link;\n    private int incr;\n    private final ArrayDeque<Segment> snd_queue = new ArrayDeque<>();\n    private final ArrayDeque<Segment> rcv_queue = new ArrayDeque<>();\n    private final ArrayList<Segment> snd_buf = new ArrayList<>();\n    private final ArrayList<Segment> rcv_buf = new ArrayList<>();\n    private final ArrayList<Integer> acklist = new ArrayList<>();\n    private ByteBuf buffer;\n    private int fastresend;\n    private int nocwnd;\n    private boolean stream;//流模式\n    private final Output output;\n    private final Object user;//远端地址\n    private long nextUpdate;//the next update time.\n\n    private static int _ibound_(int lower, int middle, int upper) {\n        return Math.min(Math.max(lower, middle), upper);\n    }\n\n    private static int _itimediff(int later, int earlier) {\n        return later - earlier;\n    }\n\n    private static long _itimediff(long later, long earlier) {\n        return later - earlier;\n    }\n\n    /**\n     * SEGMENT\n     */\n    class Segment {\n\n        private int conv = 0;\n        private byte cmd = 0;\n        private int frg = 0;\n        private int wnd = 0;\n        private int ts = 0;\n        private int sn = 0;\n        private int una = 0;\n        private int resendts = 0;\n        private int rto = 0;\n        private int fastack = 0;\n        private int xmit = 0;\n        private ByteBuf data;\n\n        private Segment(int size) {\n            if (size > 0) {\n                this.data = PooledByteBufAllocator.DEFAULT.buffer(size);\n            }\n        }\n\n        /**\n         * encode a segment into buffer\n         *\n         * @param buf\n         * @return\n         */\n        private int encode(ByteBuf buf) {\n            int off = buf.writerIndex();\n            buf.writeIntLE(conv);\n            buf.writeByte(cmd);\n            buf.writeByte(frg);\n            buf.writeShortLE(wnd);\n            buf.writeIntLE(ts);\n            buf.writeIntLE(sn);\n            buf.writeIntLE(una);\n            buf.writeIntLE(data == null ? 0 : data.readableBytes());\n            return buf.writerIndex() - off;\n        }\n\n        /**\n         * 释放内存\n         */\n        private void release() {\n            if (this.data != null && data.refCnt() > 0) {\n                this.data.release(data.refCnt());\n            }\n        }\n    }\n\n    /**\n     * create a new kcpcb\n     *\n     * @param output\n     * @param user\n     */\n    public Kcp(Output output, Object user) {\n        snd_wnd = IKCP_WND_SND;\n        rcv_wnd = IKCP_WND_RCV;\n        rmt_wnd = IKCP_WND_RCV;\n        mtu = IKCP_MTU_DEF;\n        mss = mtu - IKCP_OVERHEAD;\n        rx_rto = IKCP_RTO_DEF;\n        rx_minrto = IKCP_RTO_MIN;\n        interval = IKCP_INTERVAL;\n        ts_flush = IKCP_INTERVAL;\n        ssthresh = IKCP_THRESH_INIT;\n        dead_link = IKCP_DEADLINK;\n        buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n        this.output = output;\n        this.user = user;\n    }\n\n    /**\n     * check the size of next message in the recv queue\n     *\n     * @return\n     */\n    public int peekSize() {\n        if (rcv_queue.isEmpty()) {\n            return -1;\n        }\n        Segment seq = rcv_queue.getFirst();\n        if (seq.frg == 0) {\n            return seq.data.readableBytes();\n        }\n        if (rcv_queue.size() < seq.frg + 1) {\n            return -1;\n        }\n        int length = 0;\n        for (Segment item : rcv_queue) {\n            length += item.data.readableBytes();\n            if (item.frg == 0) {\n                break;\n            }\n        }\n        return length;\n    }\n\n    /**\n     * user/upper level recv: returns size, returns below zero for EAGAIN\n     *\n     * @param buffer\n     * @return\n     */\n    public int receive(ByteBuf buffer) {\n        if (rcv_queue.isEmpty()) {\n            return -1;\n        }\n        int peekSize = peekSize();\n        if (peekSize < 0) {\n            return -2;\n        }\n        boolean recover = rcv_queue.size() >= rcv_wnd;\n        // merge fragment.\n        int c = 0;\n        int len = 0;\n        for (Segment seg : rcv_queue) {\n            len += seg.data.readableBytes();\n            buffer.writeBytes(seg.data);\n            c++;\n            if (seg.frg == 0) {\n                break;\n            }\n        }\n        if (c > 0) {\n            for (int i = 0; i < c; i++) {\n                rcv_queue.removeFirst().data.release();\n            }\n        }\n        if (len != peekSize) {\n            throw new RuntimeException(\"数据异常.\");\n        }\n        // move available data from rcv_buf -> rcv_queue\n        c = 0;\n        for (Segment seg : rcv_buf) {\n            if (seg.sn == rcv_nxt && rcv_queue.size() < rcv_wnd) {\n                rcv_queue.add(seg);\n                rcv_nxt++;\n                c++;\n            } else {\n                break;\n            }\n        }\n        if (c > 0) {\n            for (int i = 0; i < c; i++) {\n                rcv_buf.remove(0);\n            }\n        }\n        // fast recover\n        if (rcv_queue.size() < rcv_wnd && recover) {\n            // ready to send back IKCP_CMD_WINS in ikcp_flush\n            // tell remote my window size\n            probe |= IKCP_ASK_TELL;\n        }\n        return len;\n    }\n\n    /**\n     * user/upper level send, returns below zero for error\n     *\n     * @param buffer\n     * @return\n     */\n    public int send(ByteBuf buffer) {\n        if (buffer.readableBytes() == 0) {\n            return -1;\n        }\n        // append to previous segment in streaming mode (if possible)\n        if (this.stream && !this.snd_queue.isEmpty()) {\n            Segment seg = snd_queue.getLast();\n            if (seg.data != null && seg.data.readableBytes() < mss) {\n                int capacity = mss - seg.data.readableBytes();\n                int extend = Math.min(buffer.readableBytes(), capacity);\n                seg.data.writeBytes(buffer, extend);\n                if (buffer.readableBytes() == 0) {\n                    return 0;\n                }\n            }\n        }\n        int count;\n        if (buffer.readableBytes() <= mss) {\n            count = 1;\n        } else {\n            count = (buffer.readableBytes() + mss - 1) / mss;\n        }\n        if (count > 255) {\n            return -2;\n        }\n        if (count == 0) {\n            count = 1;\n        }\n        //fragment\n        for (int i = 0; i < count; i++) {\n            int size = Math.min(buffer.readableBytes(), mss);\n            Segment seg = new Segment(size);\n            seg.data.writeBytes(buffer, size);\n            seg.frg = this.stream ? 0 : count - i - 1;\n            snd_queue.add(seg);\n        }\n        buffer.release();\n        return 0;\n    }\n\n    /**\n     * update ack.\n     *\n     * @param rtt\n     */\n    private void update_ack(int rtt) {\n        if (rx_srtt == 0) {\n            rx_srtt = rtt;\n            rx_rttval = rtt / 2;\n        } else {\n            int delta = rtt - rx_srtt;\n            if (delta < 0) {\n                delta = -delta;\n            }\n            rx_rttval = (3 * rx_rttval + delta) / 4;\n            rx_srtt = (7 * rx_srtt + rtt) / 8;\n            if (rx_srtt < 1) {\n                rx_srtt = 1;\n            }\n        }\n        int rto = rx_srtt + Math.max(interval, 4 * rx_rttval);\n        rx_rto = _ibound_(rx_minrto, rto, IKCP_RTO_MAX);\n    }\n\n    private void shrink_buf() {\n        if (snd_buf.size() > 0) {\n            snd_una = snd_buf.get(0).sn;\n        } else {\n            snd_una = snd_nxt;\n        }\n    }\n\n    private void parse_ack(int sn) {\n        if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) {\n            return;\n        }\n        for (int i = 0; i < snd_buf.size(); i++) {\n            Segment seg = snd_buf.get(i);\n            if (sn == seg.sn) {\n                snd_buf.remove(i);\n                seg.data.release(seg.data.refCnt());\n                break;\n            }\n            if (_itimediff(sn, seg.sn) < 0) {\n                break;\n            }\n        }\n    }\n\n    private void parse_una(int una) {\n        int c = 0;\n        for (Segment seg : snd_buf) {\n            if (_itimediff(una, seg.sn) > 0) {\n                c++;\n            } else {\n                break;\n            }\n        }\n        if (c > 0) {\n            for (int i = 0; i < c; i++) {\n                Segment seg = snd_buf.remove(0);\n                seg.data.release(seg.data.refCnt());\n            }\n        }\n    }\n\n    private void parse_fastack(int sn) {\n        if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) {\n            return;\n        }\n        for (Segment seg : this.snd_buf) {\n            if (_itimediff(sn, seg.sn) < 0) {\n                break;\n            } else if (sn != seg.sn) {\n                seg.fastack++;\n            }\n        }\n    }\n\n    /**\n     * ack append\n     *\n     * @param sn\n     * @param ts\n     */\n    private void ack_push(int sn, int ts) {\n        acklist.add(sn);\n        acklist.add(ts);\n    }\n\n    private void parse_data(Segment newseg) {\n        int sn = newseg.sn;\n        if (_itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || _itimediff(sn, rcv_nxt) < 0) {\n            newseg.release();\n            return;\n        }\n        int n = rcv_buf.size() - 1;\n        int temp = -1;\n        boolean repeat = false;\n        for (int i = n; i >= 0; i--) {\n            Segment seg = rcv_buf.get(i);\n            if (seg.sn == sn) {\n                repeat = true;\n                break;\n            }\n            if (_itimediff(sn, seg.sn) > 0) {\n                temp = i;\n                break;\n            }\n        }\n        if (!repeat) {\n            rcv_buf.add(temp + 1, newseg);\n        } else {\n            newseg.release();\n        }\n        // move available data from rcv_buf -> rcv_queue\n        int c = 0;\n        for (Segment seg : rcv_buf) {\n            if (seg.sn == rcv_nxt && rcv_queue.size() < rcv_wnd) {\n                rcv_queue.add(seg);\n                rcv_nxt++;\n                c++;\n            } else {\n                break;\n            }\n        }\n        if (0 < c) {\n            for (int i = 0; i < c; i++) {\n                rcv_buf.remove(0);\n            }\n        }\n    }\n\n    /**\n     * when you received a low level packet (eg. UDP packet), call it\n     *\n     * @param data\n     * @return\n     */\n    public int input(ByteBuf data) {\n        int una_temp = snd_una;\n        int flag = 0, maxack = 0;\n        if (data == null || data.readableBytes() < IKCP_OVERHEAD) {\n            return -1;\n        }\n        while (true) {\n            boolean readed = false;\n            int ts;\n            int sn;\n            int len;\n            int una;\n            int conv_;\n            int wnd;\n            byte cmd;\n            byte frg;\n            if (data.readableBytes() < IKCP_OVERHEAD) {\n                break;\n            }\n            conv_ = data.readIntLE();\n            if (this.conv != conv_) {\n                return -1;\n            }\n            cmd = data.readByte();\n            frg = data.readByte();\n            wnd = data.readShortLE();\n            ts = data.readIntLE();\n            sn = data.readIntLE();\n            una = data.readIntLE();\n            len = data.readIntLE();\n            if (data.readableBytes() < len) {\n                return -2;\n            }\n            switch ((int) cmd) {\n                case IKCP_CMD_PUSH:\n                case IKCP_CMD_ACK:\n                case IKCP_CMD_WASK:\n                case IKCP_CMD_WINS:\n                    break;\n                default:\n                    return -3;\n            }\n            rmt_wnd = wnd & 0x0000ffff;\n            parse_una(una);\n            shrink_buf();\n            switch (cmd) {\n                case IKCP_CMD_ACK:\n                    if (_itimediff(current, ts) >= 0) {\n                        update_ack(_itimediff(current, ts));\n                    }\n                    parse_ack(sn);\n                    shrink_buf();\n                    if (flag == 0) {\n                        flag = 1;\n                        maxack = sn;\n                    } else if (_itimediff(sn, maxack) > 0) {\n                        maxack = sn;\n                    }\n                    break;\n                case IKCP_CMD_PUSH:\n                    if (_itimediff(sn, rcv_nxt + rcv_wnd) < 0) {\n                        ack_push(sn, ts);\n                        if (_itimediff(sn, rcv_nxt) >= 0) {\n                            Segment seg = new Segment(len);\n                            seg.conv = conv_;\n                            seg.cmd = cmd;\n                            seg.frg = frg & 0x000000ff;\n                            seg.wnd = wnd;\n                            seg.ts = ts;\n                            seg.sn = sn;\n                            seg.una = una;\n                            if (len > 0) {\n                                seg.data.writeBytes(data, len);\n                                readed = true;\n                            }\n                            parse_data(seg);\n                        }\n                    }\n                    break;\n                case IKCP_CMD_WASK:\n                    // ready to send back IKCP_CMD_WINS in Ikcp_flush\n                    // tell remote my window size\n                    probe |= IKCP_ASK_TELL;\n                    break;\n                case IKCP_CMD_WINS:\n                    // do nothing\n                    break;\n                default:\n                    return -3;\n            }\n            if (!readed) {\n                data.skipBytes(len);\n            }\n        }\n        if (flag != 0) {\n            parse_fastack(maxack);\n        }\n        if (_itimediff(snd_una, una_temp) > 0) {\n            if (this.cwnd < this.rmt_wnd) {\n                if (this.cwnd < this.ssthresh) {\n                    this.cwnd++;\n                    this.incr += mss;\n                } else {\n                    if (this.incr < mss) {\n                        this.incr = mss;\n                    }\n                    this.incr += (mss * mss) / this.incr + (mss / 16);\n                    if ((this.cwnd + 1) * mss <= this.incr) {\n                        this.cwnd++;\n                    }\n                }\n                if (this.cwnd > this.rmt_wnd) {\n                    this.cwnd = this.rmt_wnd;\n                    this.incr = this.rmt_wnd * mss;\n                }\n            }\n        }\n        return 0;\n    }\n\n    private int wnd_unused() {\n        if (rcv_queue.size() < rcv_wnd) {\n            return rcv_wnd - rcv_queue.size();\n        }\n        return 0;\n    }\n\n    /**\n     * force flush\n     */\n    public void forceFlush() {\n        int cur = current;\n        int change = 0;\n        int lost = 0;\n        Segment seg = new Segment(0);\n        seg.conv = conv;\n        seg.cmd = IKCP_CMD_ACK;\n        seg.wnd = wnd_unused();\n        seg.una = rcv_nxt;\n        // flush acknowledges\n        int c = acklist.size() / 2;\n        for (int i = 0; i < c; i++) {\n            if (buffer.readableBytes() + IKCP_OVERHEAD > mtu) {\n                this.output.out(buffer, this, user);\n                buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n            }\n            seg.sn = acklist.get(i * 2 + 0);\n            seg.ts = acklist.get(i * 2 + 1);\n            seg.encode(buffer);\n        }\n        acklist.clear();\n        // probe window size (if remote window size equals zero)\n        if (rmt_wnd == 0) {\n            if (probe_wait == 0) {\n                probe_wait = IKCP_PROBE_INIT;\n                ts_probe = current + probe_wait;\n            } else if (_itimediff(current, ts_probe) >= 0) {\n                if (probe_wait < IKCP_PROBE_INIT) {\n                    probe_wait = IKCP_PROBE_INIT;\n                }\n                probe_wait += probe_wait / 2;\n                if (probe_wait > IKCP_PROBE_LIMIT) {\n                    probe_wait = IKCP_PROBE_LIMIT;\n                }\n                ts_probe = current + probe_wait;\n                probe |= IKCP_ASK_SEND;\n            }\n        } else {\n            ts_probe = 0;\n            probe_wait = 0;\n        }\n        // flush window probing commands\n        if ((probe & IKCP_ASK_SEND) != 0) {\n            seg.cmd = IKCP_CMD_WASK;\n            if (buffer.readableBytes() + IKCP_OVERHEAD > mtu) {\n                this.output.out(buffer, this, user);\n                buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n            }\n            seg.encode(buffer);\n        }\n        // flush window probing commands\n        if ((probe & IKCP_ASK_TELL) != 0) {\n            seg.cmd = IKCP_CMD_WINS;\n            if (buffer.readableBytes() + IKCP_OVERHEAD > mtu) {\n                this.output.out(buffer, this, user);\n                buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n            }\n            seg.encode(buffer);\n        }\n        probe = 0;\n        // calculate window size\n        int cwnd_temp = Math.min(snd_wnd, rmt_wnd);\n        if (nocwnd == 0) {\n            cwnd_temp = Math.min(cwnd, cwnd_temp);\n        }\n        // move data from snd_queue to snd_buf\n        c = 0;\n        for (Segment item : snd_queue) {\n            if (_itimediff(snd_nxt, snd_una + cwnd_temp) >= 0) {\n                break;\n            }\n            Segment newseg = item;\n            newseg.conv = conv;\n            newseg.cmd = IKCP_CMD_PUSH;\n            newseg.wnd = seg.wnd;\n            newseg.ts = cur;\n            newseg.sn = snd_nxt++;\n            newseg.una = rcv_nxt;\n            newseg.resendts = cur;\n            newseg.rto = rx_rto;\n            newseg.fastack = 0;\n            newseg.xmit = 0;\n            snd_buf.add(newseg);\n            c++;\n        }\n        if (c > 0) {\n            for (int i = 0; i < c; i++) {\n                snd_queue.removeFirst();\n            }\n        }\n        // calculate resent\n        int resent = (fastresend > 0) ? fastresend : Integer.MAX_VALUE;\n        int rtomin = (nodelay == 0) ? (rx_rto >> 3) : 0;\n        // flush data segments\n        for (Segment segment : snd_buf) {\n            boolean needsend = false;\n            if (segment.xmit == 0) {\n                needsend = true;\n                segment.xmit++;\n                segment.rto = rx_rto;\n                segment.resendts = cur + segment.rto + rtomin;\n            } else if (_itimediff(cur, segment.resendts) >= 0) {\n                needsend = true;\n                segment.xmit++;\n                xmit++;\n                if (nodelay == 0) {\n                    segment.rto += rx_rto;\n                } else {\n                    segment.rto += rx_rto / 2;\n                }\n                segment.resendts = cur + segment.rto;\n                lost = 1;\n            } else if (segment.fastack >= resent) {\n                needsend = true;\n                segment.xmit++;\n                segment.fastack = 0;\n                segment.resendts = cur + segment.rto;\n                change++;\n            }\n            if (needsend) {\n                segment.ts = cur;\n                segment.wnd = seg.wnd;\n                segment.una = rcv_nxt;\n                int need = IKCP_OVERHEAD + segment.data.readableBytes();\n                if (buffer.readableBytes() + need > mtu) {\n                    this.output.out(buffer, this, user);\n                    buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n                }\n                segment.encode(buffer);\n                if (segment.data.readableBytes() > 0) {\n                    buffer.writeBytes(segment.data.duplicate());\n                }\n                if (segment.xmit >= dead_link) {\n                    state = -1;\n                }\n            }\n        }\n        // flash remain segments\n        if (buffer.readableBytes() > 0) {\n            this.output.out(buffer, this, user);\n            buffer = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n        }\n        // update ssthresh\n        if (change != 0) {\n            int inflight = snd_nxt - snd_una;\n            ssthresh = inflight / 2;\n            if (ssthresh < IKCP_THRESH_MIN) {\n                ssthresh = IKCP_THRESH_MIN;\n            }\n            cwnd = ssthresh + resent;\n            incr = cwnd * mss;\n        }\n        if (lost != 0) {\n            ssthresh = cwnd / 2;\n            if (ssthresh < IKCP_THRESH_MIN) {\n                ssthresh = IKCP_THRESH_MIN;\n            }\n            cwnd = 1;\n            incr = mss;\n        }\n        if (cwnd < 1) {\n            cwnd = 1;\n            incr = mss;\n        }\n    }\n\n    /**\n     * flush pending data\n     */\n    public void flush() {\n        //if (updated != 0)\n        {\n            forceFlush();\n        }\n    }\n\n    /**\n     * update state (call it repeatedly, every 10ms-100ms), or you can ask\n     * ikcp_check when to call it again (without ikcp_input/_send calling).\n     *\n     * @param current current timestamp in millisec.\n     */\n    public void update(long current) {\n        this.current = (int) current;\n        if (updated == 0) {\n            updated = 1;\n            ts_flush = this.current;\n        }\n        int slap = _itimediff(this.current, ts_flush);\n        if (slap >= 10000 || slap < -10000) {\n            ts_flush = this.current;\n            slap = 0;\n        }\n        if (slap >= 0) {\n            ts_flush += interval;\n            if (_itimediff(this.current, ts_flush) >= 0) {\n                ts_flush = this.current + interval;\n            }\n            flush();\n        }\n    }\n\n    /**\n     * Determine when should you invoke ikcp_update: returns when you should\n     * invoke ikcp_update in millisec, if there is no ikcp_input/_send calling.\n     * you can call ikcp_update in that time, instead of call update repeatly.\n     * Important to reduce unnacessary ikcp_update invoking. use it to schedule\n     * ikcp_update (eg. implementing an epoll-like mechanism, or optimize\n     * ikcp_update when handling massive kcp connections)\n     *\n     * @param current\n     * @return\n     */\n    public long check(long current) {\n        long cur = current;\n        if (updated == 0) {\n            return cur;\n        }\n        long ts_flush_temp = this.ts_flush;\n        long tm_packet = 0x7fffffff;\n        if (_itimediff(cur, ts_flush_temp) >= 10000 || _itimediff(cur, ts_flush_temp) < -10000) {\n            ts_flush_temp = cur;\n        }\n        if (_itimediff(cur, ts_flush_temp) >= 0) {\n            return cur;\n        }\n        long tm_flush = _itimediff(ts_flush_temp, cur);\n        for (Segment seg : snd_buf) {\n            long diff = _itimediff(seg.resendts, cur);\n            if (diff <= 0) {\n                return cur;\n            }\n            if (diff < tm_packet) {\n                tm_packet = diff;\n            }\n        }\n        long minimal = tm_packet < tm_flush ? tm_packet : tm_flush;\n        if (minimal >= interval) {\n            minimal = interval;\n        }\n        return cur + minimal;\n    }\n\n    /**\n     * change MTU size, default is 1400\n     *\n     * @param mtu\n     * @return\n     */\n    public int setMtu(int mtu) {\n        if (mtu < 50 || mtu < IKCP_OVERHEAD) {\n            return -1;\n        }\n        ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer((mtu + IKCP_OVERHEAD) * 3);\n        this.mtu = mtu;\n        mss = mtu - IKCP_OVERHEAD;\n        if (buffer != null) {\n            buffer.release();\n        }\n        this.buffer = buf;\n        return 0;\n    }\n\n    /**\n     * conv\n     *\n     * @param conv\n     */\n    public void setConv(int conv) {\n        this.conv = conv;\n    }\n\n    /**\n     * conv\n     *\n     * @return\n     */\n    public int getConv() {\n        return conv;\n    }\n\n    /**\n     * interval per update\n     *\n     * @param interval\n     * @return\n     */\n    public int interval(int interval) {\n        if (interval > 5000) {\n            interval = 5000;\n        } else if (interval < 10) {\n            interval = 10;\n        }\n        this.interval = interval;\n        return 0;\n    }\n\n    /**\n     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),\n     * 1:enable interval: internal update timer interval in millisec, default is\n     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:\n     * 0:normal congestion control(default), 1:disable congestion control\n     *\n     * @param nodelay\n     * @param interval\n     * @param resend\n     * @param nc\n     * @return\n     */\n    public int noDelay(int nodelay, int interval, int resend, int nc) {\n        if (nodelay >= 0) {\n            this.nodelay = nodelay;\n            if (nodelay != 0) {\n                rx_minrto = IKCP_RTO_NDL;\n            } else {\n                rx_minrto = IKCP_RTO_MIN;\n            }\n        }\n        if (interval >= 0) {\n            if (interval > 5000) {\n                interval = 5000;\n            } else if (interval < 10) {\n                interval = 10;\n            }\n            this.interval = interval;\n        }\n        if (resend >= 0) {\n            fastresend = resend;\n        }\n        if (nc >= 0) {\n            nocwnd = nc;\n        }\n        return 0;\n    }\n\n    /**\n     * set maximum window size: sndwnd=32, rcvwnd=32 by default\n     *\n     * @param sndwnd\n     * @param rcvwnd\n     * @return\n     */\n    public int wndSize(int sndwnd, int rcvwnd) {\n        if (sndwnd > 0) {\n            snd_wnd = sndwnd;\n        }\n        if (rcvwnd > 0) {\n            rcv_wnd = rcvwnd;\n        }\n        return 0;\n    }\n\n    /**\n     * get how many packet is waiting to be sent\n     *\n     * @return\n     */\n    public int waitSnd() {\n        return snd_buf.size() + snd_queue.size();\n    }\n\n    public void setNextUpdate(long nextUpdate) {\n        this.nextUpdate = nextUpdate;\n    }\n\n    public long getNextUpdate() {\n        return nextUpdate;\n    }\n\n    public Object getUser() {\n        return user;\n    }\n\n    public boolean isStream() {\n        return stream;\n    }\n\n    public void setStream(boolean stream) {\n        this.stream = stream;\n    }\n\n    public void setMinRto(int min) {\n        rx_minrto = min;\n    }\n\n    @Override\n    public String toString() {\n        return this.user.toString();\n    }\n\n    /**\n     * 释放内存\n     */\n    void release() {\n        if (buffer.refCnt() > 0) {\n            this.buffer.release(buffer.refCnt());\n        }\n        for (Segment seg : this.rcv_buf) {\n            seg.release();\n        }\n        for (Segment seg : this.rcv_queue) {\n            seg.release();\n        }\n        for (Segment seg : this.snd_buf) {\n            seg.release();\n        }\n        for (Segment seg : this.snd_queue) {\n            seg.release();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpClient.java",
    "content": "/**\n * 测试\n */\npackage org.beykery.jkcp;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.*;\nimport io.netty.channel.epoll.Epoll;\nimport io.netty.channel.epoll.EpollDatagramChannel;\nimport io.netty.channel.epoll.EpollEventLoopGroup;\nimport io.netty.channel.kqueue.KQueue;\nimport io.netty.channel.kqueue.KQueueDatagramChannel;\nimport io.netty.channel.kqueue.KQueueEventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.DatagramChannel;\nimport io.netty.channel.socket.DatagramPacket;\nimport io.netty.channel.socket.nio.NioDatagramChannel;\n\nimport java.net.InetSocketAddress;\n\n/**\n * @author beykery\n */\npublic abstract class KcpClient implements Output, KcpListerner, Runnable {\n\n    private final DatagramChannel channel;\n    private final InetSocketAddress addr;\n    private int nodelay;\n    private int interval = Kcp.IKCP_INTERVAL;\n    private int resend;\n    private int nc;\n    private int sndwnd = Kcp.IKCP_WND_SND;\n    private int rcvwnd = Kcp.IKCP_WND_RCV;\n    private int mtu = Kcp.IKCP_MTU_DEF;\n    private int conv = (int) (Math.random() * Integer.MAX_VALUE);\n    private boolean stream;\n    private int minRto = Kcp.IKCP_RTO_MIN;\n    private long timeout;\n    private KcpOnUdp kcp;\n    private volatile boolean running;\n    private final Object waitLock = new Object();\n    private InetSocketAddress remote;\n    private final EventLoopGroup nioEventLoopGroup;\n\n    /**\n     * client\n     */\n    public KcpClient() {\n        this(0);\n    }\n\n    /**\n     * 客户端\n     *\n     * @param port\n     */\n    public KcpClient(int port) {\n        boolean epoll = Epoll.isAvailable();\n        boolean kqueue = KQueue.isAvailable();\n        nioEventLoopGroup = epoll ? new EpollEventLoopGroup() : (kqueue ? new KQueueEventLoopGroup() : new NioEventLoopGroup());\n        Bootstrap bootstrap = new Bootstrap();\n        bootstrap.channel(epoll ? EpollDatagramChannel.class : (kqueue ? KQueueDatagramChannel.class : NioDatagramChannel.class));\n        bootstrap.group(nioEventLoopGroup);\n        bootstrap.handler(new ChannelInitializer<DatagramChannel>() {\n\n            @Override\n            protected void initChannel(DatagramChannel ch) {\n                ChannelPipeline cp = ch.pipeline();\n                cp.addLast(new ChannelInboundHandlerAdapter() {\n                    @Override\n                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n                        DatagramPacket dp = (DatagramPacket) msg;\n                        KcpClient.this.onReceive(dp);\n                    }\n\n                    @Override\n                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n                        KcpClient.this.handleException(cause, null);\n                        KcpClient.this.close();\n                    }\n                });\n            }\n        });\n        ChannelFuture sync = bootstrap.bind(port).syncUninterruptibly();\n        channel = (DatagramChannel) sync.channel();\n        addr = channel.localAddress();\n        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {\n            @Override\n            public void run() {\n                nioEventLoopGroup.shutdownGracefully();\n            }\n        }));\n    }\n\n    /**\n     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),\n     * 1:enable interval: internal update timer interval in millisec, default is\n     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:\n     * 0:normal congestion control(default), 1:disable congestion control\n     *\n     * @param nodelay\n     * @param interval\n     * @param resend\n     * @param nc\n     */\n    public void noDelay(int nodelay, int interval, int resend, int nc) {\n        this.nodelay = nodelay;\n        this.interval = interval;\n        this.resend = resend;\n        this.nc = nc;\n    }\n\n    /**\n     * set maximum window size: sndwnd=32, rcvwnd=32 by default\n     *\n     * @param sndwnd\n     * @param rcvwnd\n     */\n    public void wndSize(int sndwnd, int rcvwnd) {\n        this.sndwnd = sndwnd;\n        this.rcvwnd = rcvwnd;\n    }\n\n    /**\n     * change MTU size, default is 1400\n     *\n     * @param mtu\n     */\n    public void setMtu(int mtu) {\n        this.mtu = mtu;\n    }\n\n    /**\n     * conv\n     *\n     * @param conv\n     */\n    public void setConv(int conv) {\n        this.conv = conv;\n    }\n\n    /**\n     * stream mode\n     *\n     * @param stream\n     */\n    public void setStream(boolean stream) {\n        this.stream = stream;\n    }\n\n    public boolean isStream() {\n        return stream;\n    }\n\n    public void setMinRto(int minRto) {\n        this.minRto = minRto;\n    }\n\n    public void setTimeout(long timeout) {\n        this.timeout = timeout;\n    }\n\n    public long getTimeout() {\n        return this.timeout;\n    }\n\n    /**\n     * 固定连接到一个服务器地址,只会处理此地址的消息\n     *\n     * @param addr\n     */\n    public void connect(InetSocketAddress addr) {\n        this.remote = addr;\n        this.channel.connect(addr);\n    }\n\n    @Override\n    public void out(ByteBuf msg, Kcp kcp, Object user) {\n        DatagramPacket temp = new DatagramPacket(msg, (InetSocketAddress) user, this.addr);\n        this.channel.writeAndFlush(temp);\n    }\n\n    @Override\n    public void handleClose(KcpOnUdp kcp) {\n        this.close();\n    }\n\n    /**\n     * 收到服务器消息\n     *\n     * @param dp\n     */\n    private void onReceive(DatagramPacket dp) {\n        if (this.kcp != null && this.running) {\n            this.kcp.input(dp.content());\n            synchronized (this.waitLock) {\n                this.waitLock.notify();\n            }\n        } else {\n            dp.release();\n        }\n    }\n\n    /**\n     * 关掉\n     */\n    public void close() {\n        if (this.running) {\n            this.running = false;\n        }\n    }\n\n    /**\n     * 发送消息\n     *\n     * @param bb\n     */\n    public void send(ByteBuf bb) {\n        if (this.kcp != null) {\n            this.kcp.send(bb);\n            synchronized (this.waitLock) {\n                this.waitLock.notify();\n            }\n        }\n    }\n\n    /**\n     * 开启线程处理kcp状态\n     */\n    public void start() {\n        if (!this.running) {\n            this.running = true;\n            this.kcp = new KcpOnUdp(this, remote, addr, this);\n            this.kcp.noDelay(nodelay, interval, resend, nc);\n            this.kcp.wndSize(sndwnd, rcvwnd);\n            this.kcp.setTimeout(timeout);\n            this.kcp.setMtu(mtu);\n            this.kcp.setConv(conv);\n            this.kcp.setStream(stream);\n            this.kcp.setMinRto(minRto);\n            Thread t = new Thread(this);\n            t.setName(\"kcp client thread\");\n            t.start();\n        }\n    }\n\n    @Override\n    public void run() {\n        long start, end;\n        while (running) {\n            start = System.currentTimeMillis();\n            if (kcp.isClosed()) {\n                this.running = false;\n                continue;\n            }\n            kcp.update();\n            end = System.currentTimeMillis();\n            if (end - start < interval) {\n                synchronized (waitLock) {\n                    try {\n                        waitLock.wait(this.interval - end + start);\n                    } catch (InterruptedException ex) {\n                    }\n                }\n            }\n        }\n        this.release();\n        nioEventLoopGroup.shutdownGracefully();\n        this.channel.close();\n    }\n\n    /**\n     * 释放内存\n     */\n    private void release() {\n        this.kcp.release();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpListerner.java",
    "content": "/**\n *\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n *\n * @author beykery\n */\npublic interface KcpListerner {\n\n    /**\n     * kcp message\n     *\n     * @param bb the data\n     * @param kcp\n     */\n    public void handleReceive(ByteBuf bb, KcpOnUdp kcp);\n\n    /**\n     *\n     * kcp异常，之后此kcp就会被关闭\n     *\n     * @param ex 异常\n     * @param kcp 发生异常的kcp，null表示非kcp错误\n     */\n    public void handleException(Throwable ex, KcpOnUdp kcp);\n\n    /**\n     * 关闭\n     *\n     * @param kcp\n     */\n    public void handleClose(KcpOnUdp kcp);\n}\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpOnUdp.java",
    "content": "/**\n * udp for kcp\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAllocator;\n\nimport java.net.InetSocketAddress;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.concurrent.LinkedBlockingQueue;\n\n/**\n * @author beykery\n */\npublic class KcpOnUdp {\n\n    private final Kcp kcp;//kcp的状态\n    private final Queue<ByteBuf> received;//输入\n    private final Queue<ByteBuf> sendList;\n    private long timeout;//超时设定\n    private long lastTime;//上次超时检查时间\n    private int errcode;//错误代码\n    private final KcpListerner listerner;\n    private volatile boolean needUpdate;\n    private volatile boolean closed;\n    private String sessionId;\n    private final Map<Object, Object> session;\n    private final InetSocketAddress remote;//远程地址\n    private final InetSocketAddress local;//本地\n\n    /**\n     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),\n     * 1:enable interval: internal update timer interval in millisec, default is\n     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:\n     * 0:normal congestion control(default), 1:disable congestion control\n     *\n     * @param nodelay\n     * @param interval\n     * @param resend\n     * @param nc\n     */\n    public void noDelay(int nodelay, int interval, int resend, int nc) {\n        this.kcp.noDelay(nodelay, interval, resend, nc);\n    }\n\n    /**\n     * set maximum window size: sndwnd=32, rcvwnd=32 by default\n     *\n     * @param sndwnd\n     * @param rcvwnd\n     */\n    public void wndSize(int sndwnd, int rcvwnd) {\n        this.kcp.wndSize(sndwnd, rcvwnd);\n    }\n\n    /**\n     * change MTU size, default is 1400\n     *\n     * @param mtu\n     */\n    public void setMtu(int mtu) {\n        this.kcp.setMtu(mtu);\n    }\n\n    /**\n     * conv\n     *\n     * @param conv\n     */\n    public void setConv(int conv) {\n        this.kcp.setConv(conv);\n    }\n\n    /**\n     * stream模式\n     *\n     * @param stream\n     */\n    public void setStream(boolean stream) {\n        this.kcp.setStream(stream);\n    }\n\n    /**\n     * 流模式\n     *\n     * @return\n     */\n    public boolean isStream() {\n        return this.kcp.isStream();\n    }\n\n    /**\n     * rto设置\n     *\n     * @param rto\n     */\n    public void setMinRto(int rto) {\n        this.kcp.setMinRto(rto);\n    }\n\n    /**\n     * kcp for udp\n     *\n     * @param out       输出接口\n     * @param remote    远程地址\n     * @param local     本地地址\n     * @param listerner 监听\n     */\n    public KcpOnUdp(Output out, InetSocketAddress remote, InetSocketAddress local, KcpListerner listerner) {\n        this.listerner = listerner;\n        kcp = new Kcp(out, remote);\n        received = new LinkedBlockingQueue<>();\n        sendList = new LinkedBlockingQueue<>();\n        this.session = new HashMap<>();\n        this.remote = remote;\n        this.local = local;\n    }\n\n    /**\n     * send data to addr\n     *\n     * @param bb\n     */\n    public void send(ByteBuf bb) {\n        if (!closed) {\n            this.sendList.add(bb);\n            this.needUpdate = true;\n        }\n    }\n\n    /**\n     * update one kcp\n     *\n     */\n    void update() {\n        //input\n        while (!this.received.isEmpty()) {\n            ByteBuf dp = this.received.remove();\n            errcode = kcp.input(dp);\n            dp.release();\n            if (errcode != 0) {\n                this.closed = true;\n                this.listerner.handleException(new IllegalStateException(\"input error : \" + errcode), this);\n                return;\n            }\n        }\n        //receive\n        int len;\n        while ((len = kcp.peekSize()) > 0) {\n            ByteBuf bb = PooledByteBufAllocator.DEFAULT.buffer(len);\n            int n = kcp.receive(bb);\n            if (n > 0) {\n                this.listerner.handleReceive(bb, this);\n            } else {\n                bb.release();\n            }\n        }\n        //send\n        while (!this.sendList.isEmpty()) {\n            ByteBuf bb = sendList.remove();\n            errcode = this.kcp.send(bb);\n            if (errcode != 0) {\n                this.closed = true;\n                this.listerner.handleException(new IllegalStateException(\"send error : \" + errcode), this);\n                return;\n            }\n        }\n        //update kcp status\n        if (this.needUpdate) {\n            kcp.flush();\n            this.needUpdate = false;\n        }\n        long cur = System.currentTimeMillis();\n        if (cur >= kcp.getNextUpdate()) {\n            kcp.update(cur);\n            kcp.setNextUpdate(kcp.check(cur));\n        }\n        //check timeout\n        if (closed || (this.timeout > 0 && lastTime > 0 && System.currentTimeMillis() - lastTime > this.timeout)) {\n            this.closed = true;\n            this.listerner.handleClose(this);\n            this.release();\n        }\n    }\n\n    /**\n     * 输入\n     *\n     * @param content\n     */\n    void input(ByteBuf content) {\n        if (!this.closed) {\n            this.received.add(content);\n            this.needUpdate = true;\n            this.lastTime = System.currentTimeMillis();\n        } else {\n            content.release();\n        }\n    }\n\n    public boolean isClosed() {\n        return closed;\n    }\n\n    public void close() {\n        this.closed = true;\n    }\n\n    public Kcp getKcp() {\n        return kcp;\n    }\n\n    public void setTimeout(long timeout) {\n        this.timeout = timeout;\n    }\n\n    public long getTimeout() {\n        return timeout;\n    }\n\n    @Override\n    public String toString() {\n        return \"local: \" + local + \" remote: \" + remote;\n    }\n\n    /**\n     * session id\n     *\n     * @return\n     */\n    public String getSessionId() {\n        return sessionId;\n    }\n\n    /**\n     * session id\n     *\n     * @param sessionId\n     */\n    public void setSessionId(String sessionId) {\n        this.sessionId = sessionId;\n    }\n\n    /**\n     * session map\n     *\n     * @return\n     */\n    public Map<Object, Object> getSessionMap() {\n        return session;\n    }\n\n    /**\n     * session k v\n     *\n     * @param k\n     * @return\n     */\n    public Object getSession(Object k) {\n        return this.session.get(k);\n    }\n\n    /**\n     * session k v\n     *\n     * @param k\n     * @param v\n     * @return\n     */\n    public Object setSession(Object k, Object v) {\n        return this.session.put(k, v);\n    }\n\n    /**\n     * contains key\n     *\n     * @param k\n     * @return\n     */\n    public boolean containsSessionKey(Object k) {\n        return this.session.containsKey(k);\n    }\n\n    /**\n     * contains value\n     *\n     * @param v\n     * @return\n     */\n    public boolean containsSessionValue(Object v) {\n        return this.session.containsValue(v);\n    }\n\n    /**\n     * 立即更新？\n     *\n     * @return\n     */\n    boolean needUpdate() {\n        return this.needUpdate;\n    }\n\n    /**\n     * 监听器\n     *\n     * @return\n     */\n    public KcpListerner getListerner() {\n        return listerner;\n    }\n\n    /**\n     * 本地地址\n     *\n     * @return\n     */\n    public InetSocketAddress getLocal() {\n        return local;\n    }\n\n    /**\n     * 远程地址\n     *\n     * @return\n     */\n    public InetSocketAddress getRemote() {\n        return remote;\n    }\n\n    /**\n     * 释放内存\n     */\n    void release() {\n        this.kcp.release();\n        for (ByteBuf item : this.received) {\n            item.release();\n        }\n        for (ByteBuf item : this.sendList) {\n            item.release();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpServer.java",
    "content": "/**\n * kcp服务器\n */\npackage org.beykery.jkcp;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.*;\nimport io.netty.channel.epoll.Epoll;\nimport io.netty.channel.epoll.EpollChannelOption;\nimport io.netty.channel.epoll.EpollDatagramChannel;\nimport io.netty.channel.epoll.EpollEventLoopGroup;\nimport io.netty.channel.kqueue.KQueue;\nimport io.netty.channel.kqueue.KQueueDatagramChannel;\nimport io.netty.channel.kqueue.KQueueEventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.DatagramPacket;\nimport io.netty.channel.socket.nio.NioDatagramChannel;\n\nimport java.net.InetSocketAddress;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * @author beykery\n */\npublic abstract class KcpServer implements Output, KcpListerner {\n    private final AtomicLong counter = new AtomicLong(0);\n    private final List<Channel> channels;\n    private InetSocketAddress localAddress;\n    private int nodelay;\n    private int interval = Kcp.IKCP_INTERVAL;\n    private int resend;\n    private int nc;\n    private int sndwnd = Kcp.IKCP_WND_SND;\n    private int rcvwnd = Kcp.IKCP_WND_RCV;\n    private int mtu = Kcp.IKCP_MTU_DEF;\n    private boolean stream;\n    private int minRto = Kcp.IKCP_RTO_MIN;\n    private KcpThread[] workers;\n    private volatile boolean running;\n    private long timeout;\n\n    /**\n     * server\n     *\n     * @param port\n     * @param workerSize\n     */\n    public KcpServer(int port, int workerSize) {\n        boolean epoll = Epoll.isAvailable();\n        boolean kqueue = KQueue.isAvailable();\n        int bonds = (epoll || kqueue) ? Runtime.getRuntime().availableProcessors() : 1;\n        channels = new ArrayList<>(bonds);\n        final EventLoopGroup group = epoll ? new EpollEventLoopGroup() : (kqueue ? new KQueueEventLoopGroup() : new NioEventLoopGroup());\n        if (port <= 0 || workerSize <= 0) {\n            throw new IllegalArgumentException(\"参数非法\");\n        }\n        this.workers = new KcpThread[workerSize];\n        Bootstrap bootstrap = new Bootstrap();\n        bootstrap.channel(epoll ? EpollDatagramChannel.class : (kqueue ? KQueueDatagramChannel.class : NioDatagramChannel.class));\n        bootstrap.group(group);\n        bootstrap.option(ChannelOption.SO_BROADCAST, true)\n                .option(ChannelOption.SO_RCVBUF, 1024 * 1024);\n        if (epoll || kqueue) {\n            bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);\n        }\n        bootstrap.handler(new ChannelInitializer<Channel>() {\n\n            @Override\n            protected void initChannel(Channel ch) {\n                ChannelPipeline cp = ch.pipeline();\n                cp.addLast(new KcpServer.UdpHandler());\n            }\n        });\n\n        for (int i = 0; i < bonds; i++) {\n            try {\n                ChannelFuture f = bootstrap.bind(port).await();\n                Channel channel = f.channel();\n                localAddress = (InetSocketAddress) channel.localAddress();\n                channels.add(channel);\n            } catch (InterruptedException e) {\n                throw new RuntimeException(\"init failed . can not bind channel to port .\");\n            }\n        }\n        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {\n            @Override\n            public void run() {\n                group.shutdownGracefully();\n            }\n        }));\n    }\n\n    /**\n     * 开始\n     */\n    public void start() {\n        if (!this.running) {\n            this.running = true;\n            for (int i = 0; i < this.workers.length; i++) {\n                workers[i] = new KcpThread(this, this, localAddress);\n                workers[i].setName(\"kcp thread \" + i);\n                workers[i].wndSize(sndwnd, rcvwnd);\n                workers[i].noDelay(nodelay, interval, resend, nc);\n                workers[i].setMtu(mtu);\n                workers[i].setTimeout(timeout);\n                workers[i].setMinRto(minRto);\n                workers[i].setStream(stream);\n                workers[i].start();\n            }\n        }\n    }\n\n    /**\n     * close\n     *\n     * @return\n     */\n    public void close() {\n        if (this.running) {\n            this.running = false;\n            for (KcpThread kt : this.workers) {\n                kt.close();\n            }\n            this.workers = null;\n            for (Channel c : channels) {\n                c.close();\n            }\n        }\n    }\n\n    /**\n     * kcp call\n     *\n     * @param msg\n     * @param kcp\n     * @param user\n     */\n    @Override\n    public void out(ByteBuf msg, Kcp kcp, Object user) {\n        DatagramPacket temp = new DatagramPacket(msg, (InetSocketAddress) user, this.localAddress);\n        Channel channel = channel();\n        channel.writeAndFlush(temp);\n    }\n\n    /**\n     * select one channel\n     *\n     * @return\n     */\n    private Channel channel() {\n        long cur = counter.getAndAdd(1);\n        return channels.get((int) (cur % channels.size()));\n    }\n\n    /**\n     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),\n     * 1:enable interval: internal update timer interval in millisec, default is\n     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:\n     * 0:normal congestion control(default), 1:disable congestion control\n     *\n     * @param nodelay\n     * @param interval\n     * @param resend\n     * @param nc\n     */\n    public void noDelay(int nodelay, int interval, int resend, int nc) {\n        this.nodelay = nodelay;\n        this.interval = interval;\n        this.resend = resend;\n        this.nc = nc;\n    }\n\n    /**\n     * set maximum window size: sndwnd=32, rcvwnd=32 by default\n     *\n     * @param sndwnd\n     * @param rcvwnd\n     */\n    public void wndSize(int sndwnd, int rcvwnd) {\n        this.sndwnd = sndwnd;\n        this.rcvwnd = rcvwnd;\n    }\n\n    /**\n     * change MTU size, default is 1400\n     *\n     * @param mtu\n     */\n    public void setMtu(int mtu) {\n        this.mtu = mtu;\n    }\n\n    /**\n     * stream mode\n     *\n     * @param stream\n     */\n    public void setStream(boolean stream) {\n        this.stream = stream;\n    }\n\n    public boolean isStream() {\n        return stream;\n    }\n\n    public void setMinRto(int minRto) {\n        this.minRto = minRto;\n    }\n\n    public void setTimeout(long timeout) {\n        this.timeout = timeout;\n    }\n\n    public long getTimeout() {\n        return this.timeout;\n    }\n\n    /**\n     * 发送\n     *\n     * @param bb\n     * @param ku\n     */\n    public void send(ByteBuf bb, KcpOnUdp ku) {\n        ku.send(bb);\n    }\n\n    /**\n     * receive DatagramPacket\n     *\n     * @param dp\n     */\n    private void onReceive(DatagramPacket dp) {\n        if (this.running) {\n            InetSocketAddress sender = dp.sender();\n            int hash = sender.hashCode();\n            hash = hash < 0 ? -hash : hash;\n            this.workers[hash % workers.length].input(dp);\n        } else {\n            dp.release();\n        }\n    }\n\n    /**\n     * handler\n     */\n    public class UdpHandler extends ChannelInboundHandlerAdapter {\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            DatagramPacket dp = (DatagramPacket) msg;\n            KcpServer.this.onReceive(dp);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            KcpServer.this.handleException(cause, null);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/KcpThread.java",
    "content": "/**\n * 维护kcp状态的线程\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.socket.DatagramPacket;\n\nimport java.net.InetSocketAddress;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.LinkedBlockingQueue;\n\n/**\n * @author beykery\n */\npublic class KcpThread extends Thread {\n\n    private final Output out;\n    private final LinkedBlockingQueue<DatagramPacket> inputs;\n    private volatile boolean running;\n    private final Map<InetSocketAddress, KcpOnUdp> kcps;\n    private final KcpListerner listerner;\n    private int nodelay;\n    private int interval = Kcp.IKCP_INTERVAL;\n    private int resend;\n    private int nc;\n    private int sndwnd = Kcp.IKCP_WND_SND;\n    private int rcvwnd = Kcp.IKCP_WND_RCV;\n    private int mtu = Kcp.IKCP_MTU_DEF;\n    private boolean stream;\n    private int minRto = Kcp.IKCP_RTO_MIN;\n    private long timeout;//idle\n    private final Object lock;//锁\n    private final InetSocketAddress local;//本地地址\n\n    /**\n     * fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) nodelay: 0:disable(default),\n     * 1:enable interval: internal update timer interval in millisec, default is\n     * 100ms resend: 0:disable fast resend(default), 1:enable fast resend nc:\n     * 0:normal congestion control(default), 1:disable congestion control\n     *\n     * @param nodelay\n     * @param interval\n     * @param resend\n     * @param nc\n     */\n    public void noDelay(int nodelay, int interval, int resend, int nc) {\n        this.nodelay = nodelay;\n        this.interval = interval;\n        this.resend = resend;\n        this.nc = nc;\n    }\n\n    /**\n     * set maximum window size: sndwnd=32, rcvwnd=32 by default\n     *\n     * @param sndwnd\n     * @param rcvwnd\n     */\n    public void wndSize(int sndwnd, int rcvwnd) {\n        this.sndwnd = sndwnd;\n        this.rcvwnd = rcvwnd;\n    }\n\n    /**\n     * change MTU size, default is 1400\n     *\n     * @param mtu\n     */\n    public void setMtu(int mtu) {\n        this.mtu = mtu;\n    }\n\n    /**\n     * kcp工作线程\n     *\n     * @param out\n     * @param listerner\n     * @param local\n     */\n    public KcpThread(Output out, KcpListerner listerner, InetSocketAddress local) {\n        this.out = out;\n        this.listerner = listerner;\n        inputs = new LinkedBlockingQueue<>();\n        kcps = new HashMap<>();\n        this.lock = new Object();\n        this.local = local;\n    }\n\n    /**\n     * 开启线程\n     */\n    @Override\n    public synchronized void start() {\n        if (!this.running) {\n            this.running = true;\n            super.start();\n        }\n    }\n\n    /**\n     * 关闭线程\n     */\n    public void close() {\n        this.running = false;\n    }\n\n    @Override\n    public void run() {\n        while (this.running) {\n            long st = System.currentTimeMillis();\n            //input\n            while (!this.inputs.isEmpty()) {\n                DatagramPacket dp = this.inputs.remove();\n                ByteBuf content = dp.content();\n                KcpOnUdp ku = this.kcps.get(dp.sender());\n                if (ku == null) {\n                    ku = new KcpOnUdp(this.out, dp.sender(), local, this.listerner);//初始化\n                    ku.noDelay(nodelay, interval, resend, nc);\n                    ku.wndSize(sndwnd, rcvwnd);\n                    ku.setMtu(mtu);\n                    // conv应该在客户端第一次建立时获取\n                    int conv = content.getIntLE(0);\n                    ku.setConv(conv);\n                    ku.setMinRto(minRto);\n                    ku.setStream(stream);\n                    ku.setTimeout(timeout);\n                    this.kcps.put(dp.sender(), ku);\n                }\n                ku.input(content);\n            }\n            //update\n            KcpOnUdp temp = null;\n            for (KcpOnUdp ku : this.kcps.values()) {\n                if (ku.isClosed()) {\n                    temp = ku;\n                } else {\n                    ku.update();\n                }\n            }\n            if (temp != null)//删掉过时的kcp\n            {\n                this.kcps.remove((InetSocketAddress) temp.getKcp().getUser());\n            }\n            if (inputs.isEmpty())//如果输入为空则考虑wait\n            {\n                long end = System.currentTimeMillis();\n                if (end - st < this.interval) {\n                    synchronized (this.lock) {\n                        try {\n                            lock.wait(interval - end + st);\n                        } catch (InterruptedException e) {\n                        }\n                    }\n                }\n            }\n        }\n        release();\n    }\n\n    /**\n     * 收到输入\n     */\n    void input(DatagramPacket dp) {\n        if (this.running) {\n            this.inputs.add(dp);\n            synchronized (this.lock) {\n                lock.notify();\n            }\n        } else {\n            dp.release();\n        }\n    }\n\n    /**\n     * stream mode\n     *\n     * @param stream\n     */\n    public void setStream(boolean stream) {\n        this.stream = stream;\n    }\n\n    public boolean isStream() {\n        return stream;\n    }\n\n    public void setMinRto(int minRto) {\n        this.minRto = minRto;\n    }\n\n    public void setTimeout(long timeout) {\n        this.timeout = timeout;\n    }\n\n    public long getTimeout() {\n        return timeout;\n    }\n\n    /**\n     * 释放所有内存\n     */\n    private void release() {\n        for (DatagramPacket dp : this.inputs) {\n            dp.release();\n        }\n        this.inputs.clear();\n        for (KcpOnUdp ku : this.kcps.values()) {\n            ku.close();\n            ku.release();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/beykery/jkcp/Output.java",
    "content": "/**\n * out\n */\npackage org.beykery.jkcp;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * @author beykery\n */\npublic interface Output {\n\n    /**\n     * kcp的底层输出\n     *\n     * @param msg  消息\n     * @param kcp  kcp对象\n     * @param user 远端地址\n     */\n    void out(ByteBuf msg, Kcp kcp, Object user);\n}\n"
  },
  {
    "path": "src/test/java/test/TestClient.java",
    "content": "/**\n * 客户端\n */\npackage test;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAllocator;\nimport io.netty.util.ResourceLeakDetector;\n\nimport java.net.InetSocketAddress;\nimport java.nio.charset.Charset;\n\nimport org.beykery.jkcp.Kcp;\nimport org.beykery.jkcp.KcpClient;\nimport org.beykery.jkcp.KcpOnUdp;\n\n/**\n * @author beykery\n */\npublic class TestClient extends KcpClient {\n\n    @Override\n    public void handleReceive(ByteBuf bb, KcpOnUdp kcp) {\n        String content = bb.toString(Charset.forName(\"utf-8\"));\n        System.out.println(\"conv:\" + kcp.getKcp().getConv() + \" recv:\" + content + \" kcp-->\" + kcp);\n        ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(2048);\n        buf.writeBytes(content.getBytes(Charset.forName(\"utf-8\")));\n        kcp.send(buf);\n        bb.release();\n    }\n\n    /**\n     * kcp异常，之后此kcp就会被关闭\n     *\n     * @param ex\n     * @param kcp\n     */\n    @Override\n    public void handleException(Throwable ex, KcpOnUdp kcp) {\n        System.out.println(ex);\n    }\n\n    @Override\n    public void handleClose(KcpOnUdp kcp) {\n        super.handleClose(kcp);\n        System.out.println(\"服务器离开:\" + kcp);\n        System.out.println(\"waitSnd:\" + kcp.getKcp().waitSnd());\n    }\n\n    @Override\n    public void out(ByteBuf msg, Kcp kcp, Object user) {\n        super.out(msg, kcp, user);\n    }\n\n    /**\n     * tcpdump udp port 2225 -x -vv -s0 -w 1112.pcap\n     *\n     * @param args\n     * @throws java.lang.InterruptedException\n     */\n    public static void main(String[] args) throws InterruptedException {\n        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);\n        TestClient tc = new TestClient();\n        tc.noDelay(1, 20, 2, 1);\n        tc.setMinRto(10);\n        tc.wndSize(32, 32);\n        tc.setTimeout(10 * 1000);\n        tc.setMtu(512);\n        // tc.setConv(121106);//默认conv随机\n\n        tc.connect(new InetSocketAddress(\"localhost\", 2222));\n        tc.start();\n        String content = \"sdfkasd你好。。。。。。。\";\n        ByteBuf bb = PooledByteBufAllocator.DEFAULT.buffer(1500);\n        bb.writeBytes(content.getBytes(Charset.forName(\"utf-8\")));\n        tc.send(bb);\n    }\n}\n"
  },
  {
    "path": "src/test/java/test/TestServer.java",
    "content": "/**\n * 测试\n */\npackage test;\n\nimport io.netty.buffer.ByteBuf;\n\nimport java.nio.charset.Charset;\n\nimport org.beykery.jkcp.KcpOnUdp;\nimport org.beykery.jkcp.KcpServer;\n\n/**\n * @author beykery\n */\npublic class TestServer extends KcpServer {\n\n    public TestServer(int port, int workerSize) {\n        super(port, workerSize);\n    }\n\n    @Override\n    public void handleReceive(ByteBuf bb, KcpOnUdp kcp) {\n        if (c == 0) {\n            start = System.currentTimeMillis();\n        }\n        c++;\n        String content = bb.toString(Charset.forName(\"utf-8\"));\n        System.out.println(\"msg:\" + content + \" kcp--> \" + kcp);\n        if (c < 10000) {\n            kcp.send(bb);//echo\n        } else {\n            System.out.println(\"cost:\" + (System.currentTimeMillis() - start));\n            kcp.close();\n        }\n    }\n\n    @Override\n    public void handleException(Throwable ex, KcpOnUdp kcp) {\n        System.out.println(ex);\n    }\n\n    @Override\n    public void handleClose(KcpOnUdp kcp) {\n        System.out.println(\"客户端离开:\" + kcp);\n        System.out.println(\"waitSnd:\" + kcp.getKcp().waitSnd());\n    }\n\n    private static long start;\n    private static int c = 0;\n\n    /**\n     * 测试\n     *\n     * @param args\n     */\n    public static void main(String[] args) {\n        TestServer s = new TestServer(2222, 1);\n        s.noDelay(1, 10, 2, 1);\n        s.setMinRto(10);\n        s.wndSize(64, 64);\n        s.setTimeout(10 * 1000);\n        s.setMtu(512);\n        s.start();\n    }\n}\n"
  }
]