szhnet / kcp-netty

Java implementation of KCP based on Netty. 基于netty实现的java版kcp。
MIT License
329 stars 110 forks source link

咨询关于conv值和stream模式的问题 #6

Open yellowb opened 6 years ago

yellowb commented 6 years ago

刚接触KCP,从它的首页找到kcp-netty这个项目,使用上有几个疑惑:

1. conv值的具体用法

读了KCP的文档,个人理解conv是供服务器端识别客户端的一个标识符,毕竟UDP是非面向连接的,不知道我理解对不对?我看example里的代码EchoClientHandler和EchoServerHandler里都在channelActive函数中写死了conv的值:

kcpCh.conv(EchoClient.CONV); // set conv

而且Client和Server的conv值需要一样,否则传输数据时Server会报conv不一致的异常。这里我有个疑惑,假设有多个Client,conv值都不同,那么Server端如何在channelActive函数中知道每个Client的conv值?因为example中是hard code的

2.关于stream模式的问题,如何发送大数据

读代码时看到接收缓冲区是设定成512B

public static final int FIXED_RECV_BYTEBUF_ALLOCATOR_SIZE = 512;

测试结果是如果往channel中flush超过(512 - kcp header)字节的数据时,就会被切成多个fragment发送到对端,我看官方网页和腾讯的一篇文章,只要打开stream模式:

option(UkcpChannelOption.UKCP_STREAM, true)

对端接收到fragments后会拼装成一个完整的数据提供给应用层(不知道理解对不对)。不过我本地测试结果显示对端收到的还是一个个fragment。不过如果连续flush多个很小的数据(比如2字节),对端有时候能在一次channelRead中读到多个发送过来的数据,也就是发送方的多个小包在接收方变成一个大包了(关闭stream模式时不会有这种现象,接收方每次channelRead只能读到一次发送的数据)

请问这个行为是跟KCP的stream模式行为一致的吗?那么传输大块数据(比如64KB)时,最佳实践应该怎样?

szhnet commented 6 years ago
  1. conv是会话id。conv需要外部进行指定。例子中是hard code。实际使用中,可以客户端与服务器端进行协商。
  2. 包模式是会保留数据包的边界。会收到所有分片后才返回给外部。流模式没有这些。现在kcp-netty在包模式下,并没有将所有分片合成到一个ByteBuf里,而是将每个分片的ButeBuf都调用了一次channelRead,并在最后调用了一次channelReadComplete,以此来表示一个数据包的边界。我考虑之后可以增加一个开关,用来控制是否将数据合并到一个ByteBuf中。
caoli5288 commented 6 years ago

conv写死,serverchannel里根据clientsocket进行管理在大部分场景下是没有问题的。 client端用一个socket去handle多个kcp会话这种做法估计不存在。

yellowb commented 6 years ago

Hi @szhnet

conv是会话id。conv需要外部进行指定。例子中是hard code。实际使用中,可以客户端与服务器端进行协商

请问关于“协商”,是在什么时候/什么地方进行?因为我看demo里都是在channelActive函数中set的,那么在channelActive之前Server/Client应该怎么协商?

Hi @caoli5288

你的意思是指Server和所有Cilent都写死一个conv是没问题的吗?

caoli5288 commented 6 years ago

@yellowb 在kcp连接建立之前进行协商,用tcp,或者raw udp,或者其他协议如http。

仅限于kcp-netty的实现,conv写死是没问题的,因为kcp-netty根据channel.remoteAddress()保存会话。

szhnet commented 6 years ago

是的,可以在外部用tcp或者udp进行协商。 另外,kcp-netty也确实根据channel.remoteAddress()保存了会话。

dingzhichao commented 5 years ago

=

是的,可以在外部用tcp或者udp进行协商。

@szhnet 作者你好,请问能给出udp协商的 例子吗

exceptionplayer commented 5 years ago

Hi @szhnet

conv是会话id。conv需要外部进行指定。例子中是hard code。实际使用中,可以客户端与服务器端进行协商

请问关于“协商”,是在什么时候/什么地方进行?因为我看demo里都是在channelActive函数中set的,那么在channelActive之前Server/Client应该怎么协商?

Hi @caoli5288

你的意思是指Server和所有Cilent都写死一个conv是没问题的吗?

我也有同样的疑问,看例子中是channelActive的时候设置了conv,但在实际应用中,此时服务端并不知道这个链接对应的客户端的conv的值,想问下楼主解决了这个问题了吗?

caoli5288 commented 5 years ago

@medusar

服务端并不知道这个链接对应的客户端的conv的值

你可以把这个值存在channel的metadata里。虽然我并不知道这么做有什么用。前文我已经解释过了,kcp-netty根据channel.remoteAddress()保存会话,事实上你在使用tcp时也是根据remoteAddress,存在conv只是因为udp本身并没有会话这种逻辑上的概念,你可以事实上复用端口开多个会话,但是为什么要这么做呢?

exceptionplayer commented 5 years ago

@caoli5288 恩,可能暂时我们也不会使用conv这个字段

exceptionplayer commented 5 years ago

发现有个UkcpChannelOption.UKCP_AUTO_SET_CONV参数,服务端设置该参数,可以不需要提前设置conv,直接读取客户端传递过来的conv

gdpencil commented 5 years ago

服务端用childOption(UkcpChannelOption.UKCP_AUTO_SET_CONV, true),客户端随机一个数值conv。只要客户端前后两次端口值和conv值不是一样即没问题。两个随机值都相同的可能性太小,理论上没问题。

szhnet commented 5 years ago

我这边使用时是通过tcp来进行协商的,conv是服务器端分配的,通过tcp告诉客户端,所以不会重复。把UkcpChannelOption.UKCP_AUTO_SET_CONV设置为true,就是为了先暂时接受客户端传过来的conv,然后消息传到上层,由服务器端上层逻辑判断conv是否合法(也就是判断conv是否与之前分配的一致)。

exceptionplayer commented 5 years ago

@szhnet 恩,感觉你这种方式才是正确的。也是KCP文档里推荐的方式。但是目前我们客户端觉得这样需要一个KCP一个TCP两套逻辑,比较复杂,所以就没有搞,只用了一套方案。

TXYH1 commented 3 years ago

@caoli5288 大佬,有一个小问题。只根据remoteAddress标识会话是不是无法区分同一端口的前后两次连接。或者是旧报文的影响。就像是tcp即使有socket四元组后,也还是需要通过三次握手协商seq 仅限于kcp-netty的实现,conv写死是没问题的,因为kcp-netty根据channel.remoteAddress()保存会话。

TXYH1 commented 3 years ago

我简单了解过一点kcp-go的实现。 看起来也是通过remoteaddr来保存一条会话。在此基础再比较conv的值是否变化来判断连接状态。 conv则单纯由客户端随机生成,没有协商过程。这是一种最佳实践吗?请问目前比较流行怎么用呢

caoli5288 commented 3 years ago

RE: @TXYH1

只根据remoteAddress标识会话是不是无法区分同一端口的前后两次连接

所以为什么要复用同一个udp端口前后发起两次连接?

conv则单纯由客户端随机生成,没有协商过程。这是一种最佳实践吗?

这跟写死conv一样,都是讨巧的做法。最规范的做法是在kcp连接发起前,通过别的协议(如tcp)协商出一个conv出来,但规范不代表最佳,最佳实践这东西就看场景见仁见智了。

TXYH1 commented 3 years ago

@caoli5288 感谢大佬的回复

所以为什么要复用同一个udp端口前后发起两次连接 我只是感觉会不会存在一些特殊情况,比如在一条链接结束后,又用同一个端口重新建立了一条,或者断开重连了,同一时间只有一条链接,但是前后是不同两条链接了,避免旧包的影响啥的。