Netty5入門(3),netty5入門
一、樣本介紹
樣本取自《基於Netty5.0進階案例一之NettyWebsocket》,和《Netty inAction》中11章的例子一樣,這個例子通過WebSocket實現了一個聊天室的群發功能。但後者的例子我沒本事跑通。
建立一個Maven項目,項目名稱叫NettyWebSocket,具體過程請參考前一貼。別忘了在pom.xml中加入netty5.0的依賴。
在項目中建立4個class:
4個類的代碼你可以從後面的內容中找到,這裡先不考慮代碼的問題。用“複製>>粘貼”將代碼原樣拷貝到這4個源檔案中再說。
注意,如果原始碼中缺少import語句,請自行fixed一下。
在NettyServe.java上右鍵,Run As >> Java Application,運行服務端代碼。此時控制台將輸出:
服務端開啟等待用戶端串連 ... ...
然後在磁碟上建立一個index.html檔案,檔案內容你也可以在後面的內容中找到。在Finder中雙擊index.html檔案,用Safari開啟。
在Dock欄上右擊Safari表徵圖,選擇“建立視窗”,開啟另一個Safari視窗。然後將第一個Safari視窗中的地址複製到第二個Safari視窗地址欄中,斷行符號。
將兩個視窗並列,你可以看到在一個視窗中輸入的聊天訊息,在另一個視窗中會即時得到重新整理,顯然是服務端通過WebSocket同時向所有串連的用戶端進行了推送:
在服務端控制台中也會有輸出:
測試完畢,我們下面再來介紹代碼。
注意,如果使用Safari測試,當你關閉Safari,服務端會輸出“用戶端與服務端串連關閉”。如果使用Chrome測試,當你關閉Chrome時,服務端會拋出一個“UnsupportedOperationException”異常。
二、Global.java
這個類很簡單,就是定義了一個全域變數ChannelGroup group,這樣在後面的其它類(主要是MyWebSocketServerHandler)中就不用定義group了,直接使用就行了。
原始碼(如果你已經複製/粘貼過原始碼了,請跳過):
public class Global {
public staticChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
三、ChildChannelHandler.java
這個類實現了ChannelInitializer,即Channel與ChannelHandler的綁定,也就是向pipeline中填入各個ChannelHandler。
原始碼(如果你已經複製/粘貼過原始碼了,請跳過):
public classChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel e) throws Exception {
e.pipeline().addLast("http-codec",new HttpServerCodec());
e.pipeline().addLast("aggregator",new HttpObjectAggregator(65536));
e.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
e.pipeline().addLast("handler",newMyWebSocketServerHandler());
}
}
四、MyWebSocketServerHandler.java
伺服器所有的商務邏輯被放到這裡,當然也包括我們的WebSocket群發。
原始碼(如果你已經複製/粘貼過原始碼了,請跳過):
public class MyWebSocketServerHandlerextends
SimpleChannelInboundHandler<Object>{
private static final Logger logger = Logger
.getLogger(WebSocketServerHandshaker.class.getName());
private WebSocketServerHandshaker handshaker;
@Override
public voidchannelActive(ChannelHandlerContext ctx) throws Exception {
// 添加
Global.group.add(ctx.channel());
System.out.println("用戶端與服務端串連開啟");
}
@Override
public voidchannelInactive(ChannelHandlerContext ctx) throws Exception {
// 移除
Global.group.remove(ctx.channel());
System.out.println("用戶端與服務端串連關閉");
}
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, ((FullHttpRequest) msg));
} else if (msg instanceofWebSocketFrame) {
handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
@Override
public voidchannelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
private voidhandlerWebSocketFrame(ChannelHandlerContext ctx,
WebSocketFrameframe) {
// 判斷是否關閉鏈路的指令
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame
.retain());
return;
}
// 判斷是否ping訊息
else if (frame instanceofPingWebSocketFrame) {
ctx.channel().write(
new PongWebSocketFrame(frame.content().retain()));
return;
}
// 本常式僅支援簡訊,不支援二進位訊息
else if (!(frame instanceofTextWebSocketFrame)) {
System.out.println("本常式僅支援簡訊,不支援二進位訊息");
throw newUnsupportedOperationException(String.format(
"%s frame types notsupported", frame.getClass().getName()));
}
// 返回應答訊息
Stringrequest = ((TextWebSocketFrame) frame).text();
System.out.println("服務端收到:" + request);
if (logger.isLoggable(Level.FINE)) {
logger
.fine(String.format("%s received %s", ctx.channel(),
request));
}
TextWebSocketFrametws = new TextWebSocketFrame(new Date().toString()
+ctx.channel().id() + ":" + request);
// 群發
Global.group.writeAndFlush(tws);
// 返回【誰發的發給誰】
// ctx.channel().writeAndFlush(tws);
}
private voidhandleHttpRequest(ChannelHandlerContext ctx,
FullHttpRequestreq) {
if (!req.getDecoderResult().isSuccess()
||(!"websocket".equals(req.headers().get("Upgrade")))) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
WebSocketServerHandshakerFactorywsFactory = new WebSocketServerHandshakerFactory(
"ws://localhost:7397/websocket", null, false);
handshaker = wsFactory.newHandshaker(req);
if (handshaker == null) {
WebSocketServerHandshakerFactory
.sendUnsupportedWebSocketVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), req);
}
}
private static void sendHttpResponse(ChannelHandlerContext ctx,
FullHttpRequestreq, DefaultFullHttpResponse res) {
// 返回應答給用戶端
if (res.getStatus().code()!= 200) {
ByteBufbuf = Unpooled.copiedBuffer(res.getStatus().toString(),
CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
}
// 如果是非Keep-Alive,關閉串連
ChannelFuturef = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
private static boolean isKeepAlive(FullHttpRequest req) {
return false;
}
@Override
public voidexceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}