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