当前位置: 首页 > 后端技术 > Java

Netty系列:一口多用,使用同一个端口运行不同的协议

时间:2023-04-01 20:43:52 Java

介绍上一篇我们介绍了在同一个netty程序中支持多种不同的服务。它的逻辑很简单,就是在一个主程序中启动多个子程序,每个子程序通过一个BootStrap绑定不同的端口,从而达到访问不同端口时访问不同服务的目的。然而,虽然多端口差异化程度高,但使用起来仍然不方便。是否可以只使用一个端口来统一不同的协议服务?今天给大家介绍下netty中如何使用同一个端口运行不同的协议。这种方法称为端口统一。SocksPortUnificationServerHandler在讲解自定义端口统一之前,先看看netty自带的端口统一,比如SocksPortUnificationServerHandler。我们知道SOCKS主要有3个协议,分别是SOCKS4、SOCKS4a和SOCKS5。它们属于同一个协议的不同版本,所以一定不能使用不同的端口,需要对同一个端口进行版本判断。具体来说,SocksPortUnificationServerHandler继承自ByteToMessageDecoder,表示将ByteBuf转换为对应的Socks对象。那么他是如何区分不同版本的呢?在decode方法中传入待解码的ByteBuf,先获取其readerIndex:intreaderIndex=in.readerIndex();我们知道SOCKS协议的第一个字节代表的是版本,所以从ByteBuf中读取第一个字节作为版本号:byteversionVal=in.getByte(readerIndex);有了版本号,就可以按不同的版本号来处理了。具体来说,对于SOCKS4a,需要添加Socks4ServerEncoder和Socks4ServerDecoder:caseSOCKS4a:logKnownVersion(ctx,version);p.addAfter(ctx.name(),null,Socks4ServerEncoder.INSTANCE);p.addAfter(ctx.name(),null,newSocks4ServerDecoder());休息;对于SOCKS5,需要添加Socks5ServerEncoder和Socks5InitialRequestDecoder两个编码器和解码器:caseSOCKS5:logKnownVersion(ctx,version);p.addAfter(ctx.name(),null,socks5encoder);p.addAfter(ctx.name(),null,newSocks5InitialRequestDecoder());休息;这样,一个端口的统一就完成了。思路是通过从同一个端口传入的ByteBuf的第一个字节来判断对应的SOCKS版本号,从而处理不同的SOCKS版本。自定义PortUnificationServerHandler在此示例中,我们将创建一个自定义端口统一以同时接收HTTP请求和gzip请求。在此之前,我们先来看看这两个协议的魔咒。也就是说,如果我们得到一个ByteBuf,我们怎么知道它传输的是HTTP协议还是gzip文件呢?首先看HTTP协议,这里我们默认是HTTP1.1,对于HTTP1.1请求协议,下面是一个例子:GET/HTTP/1.1Host:www.flydean.comHTTP请求的第一个字是methodoftheHTTPrequest具体来说有八个方法,分别是:OPTIONS返回服务器对特定资源支持的HTTP请求方法。也可以通过向Web服务器发送“*”请求来测试服务器的功能。HEAD向服务器请求一个与GET请求一致的响应,只是不会返回响应体。这种方法可以在不必传输整个响应内容的情况下获取包含在响应头中的元信息。GET向特定资源发出请求。注意:GET方法不应用于“副作用”操作,例如在Web应用程序中。原因之一是GET可能会被网络蜘蛛等随机访问。POST将数据提交到指定的资源以处理请求(例如提交表单或上传文件)。数据包含在请求正文中。POST请求可能会导致创建新资源和/或修改现有资源。PUT将其最新内容上传到指定的资源位置。DELETE请求服务器删除Request-URI标识的资源。TRACE回显服务器收到的请求,主要用于测试或诊断。CONNECTHTTP/1.1协议是为可以更改管道连接的代理服务器保留的。那么区分这八种方法需要多少字节呢?可以看出一个字节是不够的,因为我们有POST和PUT,它们的第一个字节是P。所以应该用2个字节作为magicword。对于gzip协议,它还有一种特殊的格式,gzip的前10个字节是header,第一个字节是0x1f,第二个字节是0x8b。这样我们也可以用两个字节来区分gzip协议。这样,我们的handler逻辑就出来了。先从byteBuf中取出前两个字节,然后判断是HTTP请求还是gzip请求:privatebooleanisGzip(intmagic1,intmagic2){returnmagic1==31&&magic2==139;}privatestaticbooleanisHttp(intmagic1,intmagic2){returnmagic1=='G'&&magic2=='E'||//获取magic1=='P'&&magic2=='O'||//POSTmagic1=='P'&&magic2=='U'||//PUTmagic1=='H'&&magic2=='E'||//头magic1=='O'&&magic2=='P'||//OPTIONSmagic1=='P'&&magic2=='A'||//补丁magic1=='D'&&magic2=='E'||//删除magic1=='T'&&magic2=='R'||//跟踪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",newHttpRequestDecoder());p.addLast("encoder",newHttpResponseEncoder());p.addLast("compressor",newHttpContentCompressor());编码解码后添加的摘要,如果想要自定义一些操作,只需要添加一个自定义对应的messagehandler就可以了,非常方便,本文中的例子可以参考:learn-netty4本文已经收录在http://www.flydean。com/38-netty-cust-port-unification/最通俗的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!