Netty中使用http协议


1、简介

  协议本身就是一种消息的格式,包含了消息头和消息体,我们在发送消息的时候按照协议中消息头和消息体的样式进行封装,

即可完成协议需要发送的消息内容的构建。Netty是一种高效的网络异步通信框架框架,对现阶段的各种网络协议进行了封装,提

供了各种编码及解码器,我们在使用netty时无需像以往那样过多的去搭建与逻辑无关的框架,仅需要根据自己的需求在pipeline()

里面按照自己的消息处理顺序添加Handler即可,下面就简单的使用netty来发送http消息。

2、实验工具

  idea2020,jdk1.8,maven3.6.1

3、框架搭建

  第一步:首先在pom文件里面引入对应的netty jar包。

 

<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.28.Final</version>
</dependency>

第二步:搭建起netty的服务端基本框架,下面的这部分代码也就是netty的最基本的架构,服务端通信必备的参数,分别是
port(端口号),EventLoopGroup(服务端可以分开定义成boss和worker两个组),然后就是启动助手ServerBootstrap。
准备好了上面的参数之后就可以开始搭建通信架构了,如下面的代码所示,利用启动助手bootstrap绑定group,设定通信模式,
然后对Handler进行逻辑添加,以下的代码基本上都是固定的格式,这里不再赘述,所有的netty基本上都得有下面的模板代码,
不一样的地方主要在Handler上。
public class HttpServer {
public static final int port = 6789; //设置服务端端口
private static EventLoopGroup group = new NioEventLoopGroup();
  // 通过nio方式来接收连接和处理连接

private static ServerBootstrap b = new ServerBootstrap();


/**
* Netty创建全部都是实现自AbstractBootstrap。
* 客户端的是Bootstrap,服务端的则是ServerBootstrap。
**/
public static void main(String[] args) throws Exception {

try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new ServerHandlerInit()); //设置过滤器
// 服务器绑定端口监听
ChannelFuture f = b.bind(port).sync();
System.out.println("服务端启动成功,端口是:"+port);
// 监听服务器关闭监听
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully(); //关闭EventLoopGroup,释放掉所有资源包括创建的线程
}
}
}
第三步:准备好了基本的框架之后就要按照自己的需求添加Handler,在添加handler的时候一定要特别注意一点,就是入站和出站
Handler的顺序,按照从上到下的顺序,入站的和入站的保持一直,出站的和出站的保持一致,我习惯于入站和出站分开放,当然你
也可以将入站和出站的放在一起,只要各自的逻辑顺序正确即可。
public class ServerHandlerInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
/*把应答报文 编码*/
ph.addLast("encoder",new HttpResponseEncoder());
/*把请求报文 解码*/
ph.addLast("decoder",new HttpRequestDecoder());

/*聚合http为一个完整的报文*/
ph.addLast("aggregator",
new HttpObjectAggregator(10*1024*1024));
/*把应答报文 压缩,非必要*/
ph.addLast("compressor",new HttpContentCompressor());
ph.addLast(new BusiHandler());


}
}
我这里的是混合着放的,只需要保证入站和出站各自的顺序正确就好。
消息入站时(也就是从client传到server时),首先应该对原来的http消息进行解码,将字节数据转化为基本的数据类型,
所以添加一个Http的解码器(代码示例中已经备注),解完码后,考虑到消息的半包问题,添加了一个聚合处理器,将分散
的http消息聚合成一个完整的消息,经过了这个入站处理器过后,http消息就已经完美的接并且完成格式转换了,接下来就
是要添加自己的处理器,对接收到的消息进行处理并且发送应答消息。也就时BusiHandler(代码中的)。具体的代码如下:
public class BusiHandler extends ChannelInboundHandlerAdapter {

/**
* 发送的返回值
* @param ctx 返回
* @param context 消息
* @param status 状态
*/
private void send(ChannelHandlerContext ctx, String context,
HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,status,
Unpooled.copiedBuffer(context,CharsetUtil.UTF_8)
);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String result="";
FullHttpRequest httpRequest = (FullHttpRequest)msg;
System.out.println(httpRequest.headers());
try{
//获取路径
String path=httpRequest.uri();
//获取body
String body = httpRequest.content().toString(CharsetUtil.UTF_8);
//获取请求方法
HttpMethod method=httpRequest.method();
System.out.println("接收到:"+method+" 请求");
//如果不是这个路径,就直接返回错误
if(!"/test".equalsIgnoreCase(path)){
result="非法请求!"+path;
send(ctx,result,HttpResponseStatus.BAD_REQUEST);
return;
}

//如果是GET请求
if(HttpMethod.GET.equals(method)){
//接受到的消息,做业务逻辑处理...
System.out.println("body:"+body);
result="GET请求,应答:"+RespConstant.getNews();
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是其他类型请求,如post
if(HttpMethod.POST.equals(method)){
//接受到的消息,做业务逻辑处理...
//....
return;
}

}catch(Exception e){
System.out.println("处理请求失败!");
e.printStackTrace();
}finally{
//释放请求
httpRequest.release();
}
}

/*
* 建立连接时,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
}
}
经过了前面的步骤,我们已经得到了完整的并且是经过了解码的http消息了,到这个处理器,我们就需要将封装在
Http消息中的需要的内容提取出来,可以分别对消息头和消息体进行提取,可以利用里面封装的字段变量完成自己
的逻辑,具体的操作如我代码所示,可以获取到消息头的内容判断请求方式,提取请求体的内容完成自己的逻辑,处
理完之后发送应答消息。ctx.writeAndFlush(msg),就可以将需要回送得消息交给里当前处理器最近得出站处理器。

说到回送应答消息,就还得再说说出站处理器,可以从我上面Handler得摆放代码,一个消息要发送到对端去,借助
http协议,首先应该要通过消息封装,将需要回送得消息封装成标准得http协议格式,然后通过编码器编码,我上面还
添加了一个压缩报文得,可有可无,但是发送端得出站添加了压缩,在接收端一定要解压。

到这里位置,服务端得代码基本上就已经构建完成了,接下来就是客户端代码,其实都差不多,主要还是在Handler得摆放和
处理消息得Handler得定义上面。
第四步:搭建client端得代码
public class HttpClient {
public static final String HOST = "127.0.0.1";
private static final boolean SSL = false;
public void connect(String host, int port) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//ruhan
ch.pipeline().addLast(new HttpClientCodec());
/*聚合http为一个完整的报文*/
ch.pipeline().addLast("aggregator",
new HttpObjectAggregator(10*1024*1024));
/*解压缩*/
ch.pipeline().addLast("decompressor",
new HttpContentDecompressor());
ch.pipeline().addLast(new HttpClientInboundHandler());
}
});

// Start the client.
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}

public static void main(String[] args) throws Exception {
HttpClient client = new HttpClient();
client.connect("127.0.0.1", HttpServer.port);
}
}
关于这里,在启动助手中我多添加了一个参数,b.option(ChannelOption.SO_KEEPALIVE, true);
这个参数得作用是保持TCp得连接。其余处理器得摆放和逻辑都跟服务端差不多,我主要还是说说http消息得封装。
这个看起来代码多,但是主要得功能还是不复杂,主要是完成了Http消息格式得封装而已,封装完成之后使用ctx
将数据扔到出站处理器,通过编码和压缩后就可以发送到对端。

以上就是我个人得使用心得,希望各位高手指导,代码我也上传一份。

 
 
 




 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM