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
org.beykeryjkcp1.3.2
```
## 使用
请参考src/test/java/test目录下的TestServer和TestClient
## kcp的算法细节请参考
[kcp](https://github.com/skywind3000/kcp)
================================================
FILE: pom.xml
================================================
4.0.0org.beykeryjkcp1.3.2jarUTF-8UTF-8171717jkcp
kcp for java .
https://github.com/beykery/jkcp
The Apache Software License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0.txtbeykerybeykery@sina.comscm:git:git@github.com:beykery/jkcp.gitscm:git:git@github.com:beykery/jkcp.gitgit@github.com:beykery/jkcp.gitossorg.sonatype.pluginsnexus-staging-maven-plugin1.6.8trueosshttps://oss.sonatype.org/truecom.thoughtworks.xstreamxstream1.4.15org.apache.maven.pluginsmaven-source-plugin2.4attach-sourcesjar-no-forkorg.apache.maven.pluginsmaven-javadoc-plugin2.10.3attach-javadocsjar-Xdoclint:noneorg.apache.maven.pluginsmaven-gpg-plugin1.6sign-artifactsverifysignosshttps://oss.sonatype.org/content/repositories/snapshotsosshttps://oss.sonatype.org/service/local/staging/deploy/maven2io.nettynetty-all4.1.118.Final
================================================
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 snd_queue = new ArrayDeque<>();
private final ArrayDeque rcv_queue = new ArrayDeque<>();
private final ArrayList snd_buf = new ArrayList<>();
private final ArrayList rcv_buf = new ArrayList<>();
private final ArrayList 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() {
@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 received;//输入
private final Queue 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