本篇文章是延續(xù)上一篇Netty文章,因此推薦先去看上一篇文章Netty(一),當(dāng)然對(duì)Netty有一定認(rèn)識(shí)略過。開始利用Netty創(chuàng)建一個(gè)簡單的服務(wù)器
先上代碼,運(yùn)行后,再講解!
NettyServer
package com.tanoak.demo3.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* @author tanoak@qq.com
* @date 2018/7/1 0:45
* @Desc
*/
public class HttpServer {
public void start(final int port) throws Exception {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup woker = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap.channel(NioServerSocketChannel.class)
.group(boss, woker)
//測試鏈接的狀態(tài)
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 用來初始化服務(wù)端可連接隊(duì)列,服務(wù)端處理客戶端連接請(qǐng)求是順序處理的;指定隊(duì)列的大小
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//重點(diǎn) 添加HttpServer
ch.pipeline().addLast("http-decoder",new HttpServerCodec());
//添加自定義的ChannelHandler
ch.pipeline().addLast(new HttpServerHandler());
}
});
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
woker.shutdownGracefully();
}
}
public static void main(String[] args) {
try{
System.out.println("服務(wù)器正在啟動(dòng)中");
new HttpServer().start(8080);
}catch (Exception e){
System.out.println("服務(wù)器啟動(dòng)失敗");
e.printStackTrace();
}
}
}
這里有幾個(gè)基本的概念。
Channel — Socket ;
基本的 I/O 操作(bind()、connect()、read()和 write())依賴于底層網(wǎng)絡(luò)傳輸所提
供的原語。在基于 Java 的網(wǎng)絡(luò)編程中,其基本的構(gòu)造是 class Socket。Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 類的復(fù)雜性
EventLoop — 控制流、多線程處理、并發(fā);
EventLoop 定義了 Netty 的核心抽象,用于處理連接的生命周期中所發(fā)生的事件
- 一個(gè) EventLoopGroup 包含一個(gè)或者多個(gè) EventLoop;
- 一個(gè) EventLoop 在它的生命周期內(nèi)只和一個(gè) Thread 綁定;
- 所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理;
- 一個(gè) Channel 在它的生命周期內(nèi)只注冊(cè)于一個(gè) EventLoop;
- 一個(gè) EventLoop 可能會(huì)被分配給一個(gè)或多個(gè) Channel。
ChannelFuture — 異步通知
Netty 中所有的 I/O 操作都是異步的。因?yàn)橐粋€(gè)操作可能不會(huì)立即返回,所以我們需要一種用于在之后的某個(gè)時(shí)間點(diǎn)確定其結(jié)果的方法。為此,Netty 提供了ChannelFuture 接口
ChannelHandler
Netty 的主要組件是 ChannelHandler,它充當(dāng)了所有處理入站和出站數(shù)據(jù)的應(yīng)用程序邏輯的容器
ChannelPipeline
ChannelPipeline 提供了 ChannelHandler 鏈的容器,并定義了用于在該鏈上傳播入站
和出站事件流的 API
ChannelOption 部分參數(shù)
- ChannelOption.SO_BACKLOG
? 用來初始化服務(wù)端可連接隊(duì)列,服務(wù)端處理客戶端連接請(qǐng)求是順序處理的,同一時(shí)間只能處理一個(gè)客戶端連接,多個(gè)客戶端時(shí),服務(wù)端將不能處理的客戶端連接請(qǐng)求放在隊(duì)列中等待處理,backlog參數(shù)指定了隊(duì)列的大小
- ChannelOption.SO_REUSEADDR
? 對(duì)應(yīng)于套接字選項(xiàng)中的SO_REUSEADDR,這個(gè)參數(shù)表示允許重復(fù)使用本地地址和端口,該參數(shù)允許共用該端口。
- 、ChannelOption.SO_KEEPALIVE
? 對(duì)應(yīng)于套接字選項(xiàng)中的SO_KEEPALIVE,該參數(shù)用于設(shè)置TCP連接,當(dāng)設(shè)置該選項(xiàng)以后,連接會(huì)測試鏈接的狀態(tài),可能長時(shí)間沒有數(shù)據(jù)交流的連接。當(dāng)設(shè)置該選項(xiàng)以后,如果在兩小時(shí)內(nèi)沒有數(shù)據(jù)的通信時(shí),TCP會(huì)自動(dòng)發(fā)送一個(gè)活動(dòng)探測數(shù)據(jù)報(bào)文。
ChannelOption參數(shù)詳解:傳送門
有了這些基本的概念后我們就開始ChannelHandler的編寫,這里是使用它的子類
ChannelHandler
package com.tanoak.demo3.server;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author 656443534@qq.com
* @date 2018/7/1 0:12
* @Desc
*/
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private static String yes = "<h1> this is yes Page </h1>";
private static String helloPage = "<h1> this is Hello wolrd page </h1>";
private String error = "<h1>404</h1>訪問入徑不存在";
private static Map<String, String> mapUrl = new HashMap<>();
static {
mapUrl.put("hello",helloPage) ;
mapUrl.put("yes",yes) ;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端連上了...");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
boolean keepaLive = HttpUtil.isKeepAlive(request);
System.out.println("訪問的方式是:" + request.method()+"類型");
System.out.println("訪問的URI:" + request.uri());
//獲取訪問的url
String uri = request.uri().replace("/", "").trim();
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
if(mapUrl.get(uri)!=null){
response.content().writeBytes(mapUrl.get(uri).getBytes());
}else{
response.content().writeBytes(error.getBytes());
}
//重定向處理
if (response.status().equals(HttpResponseStatus.FOUND)) {
response.headers().set(HttpHeaderNames.LOCATION, "https://www.baidu.com/");
}
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
if (keepaLive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.writeAndFlush(response);
} else {
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
}
/**
* 有異常拋出時(shí)會(huì)調(diào)用。
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
/**
* 并且關(guān)閉該 Channel
*/
System.out.println("發(fā)生了異常");
cause.printStackTrace();
ctx.close() ;
System.out.println("已關(guān)閉ChannelHandlerContext");
}
}
可以看到主要的業(yè)務(wù)邏輯集中在channelRead(ChannelHandlerContext ctx, Object msg) ;這個(gè)方法主要還是使用Netty封裝好的一些方法,指定Http的版本,狀態(tài)碼和accrpt



致此一個(gè)簡單的請(qǐng)求響應(yīng)的服務(wù)器就完成了,如理解有誤,請(qǐng)指正,謝謝?。?!