1. 程式人生 > 實用技巧 >Netty中使用http協議

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
將資料扔到出站處理器,通過編碼和壓縮後就可以傳送到對端。

以上就是我個人得使用心得,希望各位高手指導,程式碼我也上傳一份。