Netty WebSocket 協議
1. 前言
上一節,我們主要講解了 Http 協議,本節我們來講解 Netty 支持的另外一個 WebSocket 協議,WebSocket 是 HTML5 開始支持的全雙工協議。
在真實的開發當中,其實 WebSocket 使用非常的廣泛,特別是在目前前端技術越來越完善的情況下,很多應用都是基于前端技術去實現,比如:APP、小程序等主流應用,前端的技術完全可以開發出類似原生技術一樣的產品,并且開發效率上比原生更加的快、用戶體驗更好。
這些應用涉及通信、聊天、推送等業務,則可以使用 WebSocket 來實現,因此,WebSocket 已經是目前非常主流的瀏覽器和服務端建立長連接的通信技術。
WebSocket 協議架構圖如下所示:
2. 學習目的
Netty 雖然可以開發客戶端和服務端的應用,但是 Netty 和 WebSocket 結合則是另外一個不同的應用方向。
應用方向一: 純后端的應用,比如:框架、中間件通信等,則完全可以使用 Netty 來開發客戶端和服務端,并且雙方通過 TCP 協議來進行通信。
應用方向二: 前端(瀏覽器)和服務端之間通信,并且想實現類似長連接的效果,那么 WebSocket 和 Netty 則是主流,并且這部分的應用場景非常的廣泛和運用非常的多。
因此,我們在學習完本章內容之后,我們就可以掌握這類的需求的開發(比如:聊天、消息推送),自己可以嘗試去開發一款小程序在線聊天系統。
當真實的開發當中,也許很多人直接使用 Spring 封裝的 WebSocket 或者其它第三方的框架(比如:netty-socketio)去實現。本節我們主要講解原生 Netty 來開發 WebSocket 協議格式的數據請求,畢竟會用和知道知其所以然還是有區別的。
3. WebSocket 的 API
創建 WebSocket 對象,第一個參數 url, 指定連接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。
var Socket = new WebSocket(url, [protocol] );
WebSocket 事件
事件 | 事件處理程序 | 描述 |
---|---|---|
open | Socket.onopen | 連接建立時觸發 |
message | Socket.onmessage | 客戶端接收服務端數據時觸發 |
error | Socket.onerror | 通信發生錯誤時觸發 |
close | Socket.onclose | 連接關閉時觸發 |
4. 環境搭建
本節,我們主要開發一個 Demo,主要功能如下:
- 前端頁面(html)啟動的時候,連接 WebSocket 服務端;
- 服務端往前端每隔 5s 推送一條消息。
環境搭建步驟:
- 創建一個 Maven 項目;
- 導入 Netty 坐標。
實例:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
5. 代碼實現
5.1. Netty 主啟動類
public class MyWebSocketServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//因為基于http協議,使用http的編碼和解碼器
pipeline.addLast(new HttpServerCodec());
//是以塊方式寫,添加ChunkedWriteHandler處理器
pipeline.addLast(new ChunkedWriteHandler());
//http數據在傳輸過程中是分段, HttpObjectAggregator ,就是可以將多個段聚合
pipeline.addLast(new HttpObjectAggregator(8192));
//將 http協議升級為 ws協議 , 保持長連接
pipeline.addLast(new WebSocketServerProtocolHandler("/hello2"));
//自定義的handler ,處理業務邏輯
pipeline.addLast(new MyWebSocketHandler());
}
});
//啟動服務器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
代碼說明:
- WebSocket 比 TCP 和 Http 協議都稍微復雜有點,它其實是 TCP 和 Http 協議的結合,首先是連接之前發送的是 Http 協議請求,但是新增 Http 所沒有的附加頭信息
Upgrade: WebSocket
表明是一個申請協議升級的 Http 請求。其次,真正建立連接之后,其實底層是 TCP 協議長連接; HttpServerCodec
將請求和應答消息解碼為 HTTP 消息;ChunkedWriteHandler
向客戶端發送 HTML5 文件;- http 數據在傳輸過程中是分段,當瀏覽器發送大量數據時,就會發出多次 http 請求,
HttpObjectAggregator
可以將 HTTP 消息的多個部分合成一條完整的 HTTP 消息; WebSocketServerProtocolHandler
定義了 WebSocket 的對外暴露地址。
5.2 WebSocket 業務類
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
private SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//1.獲取Channel通道
final Channel channel=ctx.channel();
//2.創建一個定時線程池
ScheduledExecutorService ses=Executors.newScheduledThreadPool(1);
//3.一秒鐘之后只需,并且每隔5秒往瀏覽器發送數據
ses.scheduleWithFixedDelay(new Runnable() {
public void run() {
String sendTime=format.format(new Date());
channel.writeAndFlush(new TextWebSocketFrame("推送時間=" + sendTime));
}
},1,5, TimeUnit.SECONDS);
}
//接受瀏覽器消息
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("收到消息 " + msg.text());
}
//當web客戶端連接后,觸發方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
}
//當web客戶端斷開后,觸發方法
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
}
}
代碼說明:
其實 WebSocket 對于的 Handler 跟我們普通業務的 Handler 沒有什么區別,這里主要使用定時線程池定時往瀏覽器推送消息,這個是傳統的 Http+Ajax 請求無法實現的逆向推送效果。
5.3. 前端代碼
function WebSocketTest() {
if ("WebSocket" in window) {
var ws = new WebSocket("ws://localhost:7000/hello2");
//發送數據
ws.onopen = function() {
ws.send("發送數據");
};
//接受數據
ws.onmessage = function(evt) {
var received_msg = evt.data;
console.log(">>>"+received_msg)
};
//監聽連接關閉
ws.onclose = function() {
alert("連接已關閉...");
};
} else {
alert("您的瀏覽器不支持 WebSocket!");
}
}
5.4 測試效果
瀏覽器控制臺每隔 5s 鐘就能收到服務端推送過來的消息,如下圖所示:
6. 小結
- 我們需要了解 WebSocket 的應用方向,主要是前端和服務端建立長連接通信;
- 掌握 WebSocket 幾個事件監聽方法,open ()、message ()、error ()、close ();
- 掌握 Netty 編寫的服務端寫法,主要是幾個類 HttpServerCodec、ChunkedWriteHandler、HttpObjectAggregator、WebSocketServerProtocolHandler,Handler 業務跟普通業務沒啥區別。