四、Netty實現webSocket,實現伺服器與瀏覽器(HTML)線上聊天功能
阿新 • • 發佈:2018-11-04
由於http協議有一個缺陷:通訊只能由客戶端發起。如果伺服器有連續的狀態變化,客戶端要獲知只能使用"輪詢":每隔一段時候,就發出一個詢問,瞭解伺服器有沒有新的資訊。輪詢的效率低,非常浪費資源(因為必須不停連線,或者 HTTP 連線始終開啟)。
而websocket就這樣應用而生。
瀏覽器與伺服器之間的長連線,是websocket的支援。WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。而netty他就對websocket通訊做了支援。
websocket除了上面的支援,還有一些其他的特點:
(1)建立在 TCP 協議之上。
(2)與 HTTP 協議有著良好的相容性。預設埠也是80和443,並且握手階段採用 HTTP 協議。
(3)資料格式比較輕量,效能開銷小,通訊高效。
(4)可以傳送文字,二進位制資料。
(5)客戶端可以與任意伺服器通訊。
(6)協議識別符號是ws(如果加密,則為wss),伺服器網址就是 URL。
下面我用netty對websocket的支援來實現一個完整了實現一個線上聊天功能:
伺服器端的程式碼實現:
package com.zhurong.netty.test5; import com.zhurong.netty.test4.NettyServerInitializer; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.net.InetSocketAddress; /** * Description: * User: zhurong * Date: 2018-09-24 23:17 */ public class NettyWebSocketServer { public static void main(String[] args) { //接收連線 EventLoopGroup bossGroup = new NioEventLoopGroup(); //連線傳送給work EventLoopGroup workerGroup = new NioEventLoopGroup(); try { System.out.println("伺服器啟動成功!"); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class). handler(new LoggingHandler(LogLevel.INFO)). childHandler(new WebSocketChannelInitalizer()); ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8000)).sync(); channelFuture.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
package com.zhurong.netty.test5; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; /** * Description: * User: zhurong * Date: 2018-09-24 11:05 */ public class WebSocketChannelInitalizer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(8192)); pipeline.addLast(new WebSocketServerProtocolHandler("/test")); pipeline.addLast(new WebSockethandler()); } }
package com.zhurong.netty.test5;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.EventExecutorGroup;
import java.util.Date;
/**
* Description:
* User: zhurong
* Date: 2018-09-24 11:15
*/
public class WebSockethandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("收到瀏覽器的訊息:"+ msg.text());
ctx.channel().writeAndFlush(new TextWebSocketFrame("伺服器返回訊息:" + new Date()));
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught");
}
}
webapp端的程式碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webSocketTest客戶端</title>
</head>
<script type="text/javascript">
var webSocket;
if(window.WebSocket){
webSocket = new WebSocket("ws://localhost:8000/test");
//客戶端收到伺服器的方法,這個方法就會被回撥
webSocket.onmessage = function (ev) {
var contents = document.getElementById("responseText");
contents.value = contents.value +"\n"+ ev.data;
}
webSocket.onopen = function (ev) {
var contents = document.getElementById("responseText");
contents.value = "與伺服器端的websocket連線建立";
}
webSocket.onclose = function (ev) {
var contents = document.getElementById("responseText");
contents.value = contents.value +"\n"+ "與伺服器端的websocket連線斷開";
}
}else{
alert("該環境不支援websocket")
}
function sendMessage() {
if(window.webSocket){
if(webSocket.readyState == WebSocket.OPEN){
var contents = document.getElementById("message").value;
webSocket.send(contents);
}else{
alert("與伺服器連線尚未建立")
}
}
}
</script>
<body>
<form onsubmit="return false;">
<h4>客戶端輸入:</h4>
<textarea id = "message" name="message" style="width: 200px;height: 100px"></textarea>
<br/>
<input type="button" value="傳送到伺服器" onclick="sendMessage()">
<h4>伺服器返回訊息:</h4>
<textarea id = "responseText" name="message" style="width: 200px;height: 100px"></textarea>
<br/>
<input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="clear data">
</form>
</body>
</html>
伺服器輸出:
這就是一個完整的websocket功能,當然我們其實客戶端直接使用瀏覽器http就可以了,這樣更方便。