关于java:netty系列之一口多用使用同一端口运行不同协议

9次阅读

共计 3236 个字符,预计需要花费 9 分钟才能阅读完成。

简介

在之前的文章中,咱们介绍了在同一个 netty 程序中反对多个不同的服务,它的逻辑很简略,就是在一个主程序中启动多个子程序,每个子程序通过一个 BootStrap 来绑定不同的端口,从而达到拜访不同端口就拜访了不同服务的目标。

然而多个端口尽管区分度够高,然而应用起来还是有诸多不便,那么有没有可能只用一个端口来对立不同的协定服务呢?

明天给大家介绍一下在 netty 中应用同一端口运行不同协定的办法, 这种办法叫做 port unification。

SocksPortUnificationServerHandler

在解说自定义 port unification 之前,咱们来看下 netty 自带的 port unification,比方 SocksPortUnificationServerHandler。

咱们晓得 SOCKS 的次要协定有 3 中,别离是 SOCKS4、SOCKS4a 和 SOCKS5,他们属于同一种协定的不同版本,所以必定不能应用不同的端口,须要在同一个端口中进行版本的判断。

具体而言,SocksPortUnificationServerHandler 继承自 ByteToMessageDecoder,示意是将 ByteBuf 转换成为对应的 Socks 对象。

那他是怎么辨别不同版本的呢?

在 decode 办法中,传入了要解码的 ByteBuf in,首先取得它的 readerIndex:

int readerIndex = in.readerIndex();

咱们晓得 SOCKS 协定的第一个字节示意的是版本,所以从 in ByteBuf 中读取第一个字节作为版本号:

byte versionVal = in.getByte(readerIndex);

有了版本号就能够通过不同的版本号进行解决,具体而言,对于 SOCKS4a,须要增加 Socks4ServerEncoder 和 Socks4ServerDecoder:

case SOCKS4a:
            logKnownVersion(ctx, version);
            p.addAfter(ctx.name(), null, Socks4ServerEncoder.INSTANCE);
            p.addAfter(ctx.name(), null, new Socks4ServerDecoder());
            break;

对于 SOCKS5 来说,须要增加 Socks5ServerEncoder 和 Socks5InitialRequestDecoder 两个编码和解码器:

case SOCKS5:
            logKnownVersion(ctx, version);
            p.addAfter(ctx.name(), null, socks5encoder);
            p.addAfter(ctx.name(), null, new Socks5InitialRequestDecoder());
            break;

这样,一个 port unification 就实现了,其思路就是通过传入的同一个端口的 ByteBuf 的首字节,来判断对应的 SOCKS 的版本号,从而针对不同的 SOCKS 版本进行解决。

自定义 PortUnificationServerHandler

在本例中,咱们将会创立一个自定义的 Port Unification,用来同时接管 HTTP 申请和 gzip 申请。

在这之前,咱们先看一下两个协定的 magic word,也就是说咱们拿到一个 ByteBuf,怎么可能晓得这个是一个 HTTP 协定,还是传输的一个 gzip 文件呢?

先看下 HTTP 协定,这里咱们默认是 HTTP1.1, 对于 HTTP1.1 的申请协定,上面是一个例子:

GET / HTTP/1.1
Host: www.flydean.com

HTTP 申请的第一个单词就是 HTTP 申请的办法名,具体而言有八种办法,别离是:

OPTIONS
返回服务器针对特定资源所反对的 HTTP 申请办法。也能够利用向 Web 服务器发送 ’*’ 的申请来测试服务器的功能性。
HEAD
向服务器索要与 GET 申请相一致的响应,只不过响应体将不会被返回。这一办法能够在不用传输整个响应内容的状况下,就能够获取蕴含在响应音讯头中的元信息。
GET
向特定的资源发出请求。留神:GET 办法不该当被用于产生“副作用”的操作中,例如在 Web Application 中。其中一个起因是 GET 可能会被网络蜘蛛等随便拜访。
POST
向指定资源提交数据进行解决申请(例如提交表单或者上传文件)。数据被蕴含在申请体中。POST 申请可能会导致新的资源的建设和 / 或已有资源的批改。
PUT
向指定资源地位上传其最新内容。
DELETE
申请服务器删除 Request-URI 所标识的资源。
TRACE
回显服务器收到的申请,次要用于测试或诊断。
CONNECT
HTTP/1.1 协定中预留给可能将连贯改为管道形式的代理服务器。

那么须要几个字节来辨别这八个办法呢?能够看到一个字节是不够的,因为咱们有 POST 和 PUT,他们的第一个字节都是 P。所以应该应用 2 个字节来作为 magic word。

对于 gzip 协定来说,它也有非凡的格局,其中 gzip 的前 10 个字节是 header,其中第一个字节是 0x1f,第二个字节是 0x8b。

这样咱们用两个字节也能辨别 gzip 协定。

这样,咱们的 handler 逻辑就进去了。首先从 byteBuf 中取出前两个字节,而后对其进行判断,辨别出是 HTTP 申请还是 gzip 申请:

    private boolean isGzip(int magic1, int magic2) {return magic1 == 31 && magic2 == 139;}

    private static boolean isHttp(int magic1, int magic2) {
        return
                magic1 == 'G' && magic2 == 'E' || // GET
                        magic1 == 'P' && magic2 == 'O' || // POST
                        magic1 == 'P' && magic2 == 'U' || // PUT
                        magic1 == 'H' && magic2 == 'E' || // HEAD
                        magic1 == 'O' && magic2 == 'P' || // OPTIONS
                        magic1 == 'P' && magic2 == 'A' || // PATCH
                        magic1 == 'D' && magic2 == 'E' || // DELETE
                        magic1 == 'T' && magic2 == 'R' || // TRACE
                        magic1 == 'C' && magic2 == 'O';   // CONNECT
    }

对应的,咱们还须要对其增加相应的编码和解码器,对于 gzip 来说,netty 提供了 ZlibCodecFactory:

p.addLast("gzipEncoder", ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
p.addLast("gzipDecoder", ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));

对于 HTTP 来说,netty 也提供了 HttpRequestDecoder 和 HttpResponseEncoder 还有 HttpContentCompressor 来对 HTTP 音讯进行编码解码和压缩。

p.addLast("decoder", new HttpRequestDecoder());
p.addLast("encoder", new HttpResponseEncoder());
p.addLast("compressor", new HttpContentCompressor());

总结

增加了编码和解码器之后,如果你想自定义一些操作,只须要再增加自定义的对应的音讯 handler 即可,十分的不便。

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/38-netty-cust-port-unification/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0