一个基于udp协议的低延迟,带宽利用低的可靠传输协议。
以下是看码思路。
先跑起来看看,借助example覆写了一个回声用例:
首先看init:一个用config初始化的函数。
KcpClient kcpClient = new KcpClient();
kcpClient.init(channelConfig);
十分眼熟的netty标准实现方式,根据配置文件选择channel等。
其中NioDatagramChannel指定用udp传输
public void init(ChannelConfig channelConfig) {
if(channelConfig.isUseConvChannel()){
int convIndex = 0;
if(channelConfig.getFecAdapt()!=null){
convIndex+= Fec.fecHeaderSizePlus2;
}
channelManager = new ClientConvChannelManager(convIndex);
}else{
channelManager = new ClientAddressChannelManager();
}
this.iMessageExecutorPool = channelConfig.getiMessageExecutorPool();
nioEventLoopGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());
hashedWheelTimer = new HashedWheelTimer(new TimerThreadFactory(),1, TimeUnit.MILLISECONDS);
bootstrap = new Bootstrap();
bootstrap.channel(NioDatagramChannel.class);
bootstrap.group(nioEventLoopGroup);
bootstrap.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel ch) {
ChannelPipeline cp = ch.pipeline();
if(channelConfig.isCrc32Check()){
Crc32Encode crc32Encode = new Crc32Encode();
Crc32Decode crc32Decode = new Crc32Decode();
cp.addLast(crc32Encode);
cp.addLast(crc32Decode);
}
cp.addLast(new ClientChannelHandler(channelManager));
}
});
Runtime.getRuntime().addShutdownHook(new Thread(() -> stop()));
}
cp.addLast(new ClientChannelHandler(channelManager));通过织入实现了对获取的udp包的缓存,以实现kcp的操作(超时重传、滑动窗口、分包等)
package kcp;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.DatagramPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by JinMiao
* 2019-06-26.
*/
public class ClientChannelHandler extends ChannelInboundHandlerAdapter {
static final Logger logger = LoggerFactory.getLogger(ClientChannelHandler.class);
private IChannelManager channelManager;
public ClientChannelHandler(IChannelManager channelManager) {
this.channelManager = channelManager;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error("",cause);
//SocketAddress socketAddress = ctx.channel().localAddress();
//Ukcp ukcp = ukcpMap.get(socketAddress);
//ukcp.getKcpListener().handleException(cause,ukcp);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object object) {
DatagramPacket msg = (DatagramPacket) object;
Ukcp ukcp = this.channelManager.get(msg);
if(ukcp!=null){
ukcp.read(msg.content());
}
}
}
HashedWheelTimer时间轮实现超时重传等功能。
package kcp;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import threadPool.IMessageExecutor;
import threadPool.ITask;
import java.util.concurrent.TimeUnit;
/**
* Created by JinMiao
* 2018/10/24.
*/
public class ScheduleTask implements ITask, Runnable, TimerTask {
private final IMessageExecutor messageExecutor;
private final Ukcp ukcp;
private final HashedWheelTimer hashedWheelTimer;
public ScheduleTask(IMessageExecutor messageExecutor, Ukcp ukcp, HashedWheelTimer hashedWheelTimer) {
this.messageExecutor = messageExecutor;
this.ukcp = ukcp;
this.hashedWheelTimer = hashedWheelTimer;
}
//flush策略
//1,在send调用后检查缓冲区如果可以发送直接调用update得到时间并存在ukcp内
//2,定时任务到了检查ukcp的时间和自己的定时 如果可以发送则直接发送 时间延后则重新定时
//定时任务发送成功后检测缓冲区 是否触发发送时间
//3,读时间触发后检测检测缓冲区触发写事件
//问题: 精准大量的flush触发会导致ack重复发送 流量增大? 不会的 ack只会发送一次
@Override
public void execute() {
try {
final Ukcp ukcp = this.ukcp;
long now = System.currentTimeMillis();
//判断连接是否关闭
if (ukcp.getTimeoutMillis() != 0 && now - ukcp.getTimeoutMillis() > ukcp.getLastRecieveTime()) {
ukcp.internalClose();
}
if (!ukcp.isActive()) {
return;
}
long timeLeft = ukcp.getTsUpdate() - now;
//判断执行时间是否到了
if (timeLeft > 0) {
hashedWheelTimer.newTimeout(this,timeLeft, TimeUnit.MILLISECONDS);
return;
}
long next = ukcp.flush(now);
hashedWheelTimer.newTimeout(this,next, TimeUnit.MILLISECONDS);
//检测写缓冲区 如果能写则触发写事件
if (!ukcp.getWriteBuffer().isEmpty() && ukcp.canSend(false))
{
ukcp.notifyWriteEvent();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void run() {
this.messageExecutor.execute(this);
}
@Override
public void run(Timeout timeout) {
run();
}
}
看源码之前还是得先理解其实现用的底层,比如netty,从netty入手知道netty的数据处理一般都是ChannelPipeline.addlast()加入,便能快速理清思路了。
博客介绍了一个基于UDP协议的低延迟、低带宽利用的可靠传输协议。借助example覆写回声用例,采用netty标准实现方式,用NioDatagramChannel指定UDP传输,通过织入实现对UDP包缓存以完成kcp操作,还用时间轮实现超时重传,建议从netty入手理清源码思路。
224

被折叠的 条评论
为什么被折叠?



