前言:
就如前文所講述的, 聊天室往往是最基本的網絡編程的學習案例. 本文以WebSocket為底層協議, 實現一個簡單的基於web客戶端的Echo服務.
服務器采用Netty 4.x來實現, 源於其對websocket的超強支持, 基於卓越的性能和穩定.
本系列的文章鏈接如下:
1). websocket協議和javascript版的api
要點提示:
Netty作為高性能網絡編程框架, 其所有的網絡IO操作皆為異步方式驅動. 而其核心的概念之一: ChannelHandler. 由一組ChannelHandler構成了ChannelPipeline, 決定了其編解碼(Codec)/數據流(DataFlow)/業務處理(Logic Handler)的具體行為.
ChannelHanlder的自由組合和清晰的職責划分, 讓Netty更加的靈活和重要.

WebSocket協議包括握手和數據傳輸這兩個階段. 前者的握手是基於HTTP/HTTPS協議的, 而后者的數據傳輸則基於TCP的雙向通訊模式. 數據以Frame的方式來組織和交互.
本文不是Netty的學習文章, 這邊就略為帶過, 具體見后邊的解釋代碼.
服務端:
基於之上的要點提要, 我們迅速來進行服務端的代碼編寫.
使用netty 4.x版本, 其maven的依賴配置如下:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.29.Final</version>
</dependency>
服務端的代碼如下:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// pipeline的設置, 參看下面
}
});
ChannelFuture f = serverBootstrap.bind(8123).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
注: 這邊是主體的服務器配置和啟動代碼, 其一如既然的簡潔.
核心的pipeline設置代碼如下所示:
ChannelPipeline cp = socketChannel.pipeline();
// *) 支持http協議的解析
cp.addLast(new HttpServerCodec());
cp.addLast(new HttpObjectAggregator(65535));
// *) 對於大文件支持 chunked方式寫
cp.addLast(new ChunkedWriteHandler());
// *) 對websocket協議的處理--握手處理, ping/pong心跳, 關閉
cp.addLast(new WebSocketServerProtocolHandler("/echoserver"));
// *) 對TextWebSocketFrame的處理
cp.addLast(new SimpleChannelInboundHandler<TextWebSocketFrame>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// *) echo 邏輯
ctx.writeAndFlush(new TextWebSocketFrame(msg.text()));
}
});
注: HttpServerCodec和HttpObjectAggregator已經幫我們封裝好了WebSocket的握手FullHttpRequest/FullHttpResponse包和各類數據Frame包. WebSocketServerProtocolHandler隱藏了握手的細節處理, 以及心跳處理和關閉響應. 多個ChannelHanlder的疊加和WebSocket協議本身的復雜是密切先關的.
客戶端:
這邊只是個演示項目, 因此盡量簡潔地去實現.
<div style="margin:0 auto; width: 800px;">
<textarea id="taMessages" style="width: 360px; height: 200px;" readonly ></textarea>
<br />
<input id="btnMessage" type="text" style="float:left; width:300px;" />
<input id="btnSend" type="button" value="Send" disabled="disabled" onclick="sendMessage();"/>
</div>
<script>
/* 注意瀏覽器js的執行順序 */
var wsServer = 'ws://localhost:8123/echoserver'; //服務器地址
var websocket = new WebSocket(wsServer); //創建WebSocket對象
websocket.onopen = function(evt) {
document.getElementById("btnSend").disabled = false;
}
websocket.onmessage = function(evt) {
document.getElementById("taMessages").value += evt.data;
}
websocket.onclose = function(evt) {
}
websocket.onerror = function(evt) {
}
function sendMessage() {
var message = document.getElementById('btnMessage').value;
if ( websocket.readyState == WebSocket.OPEN ) {
websocket.send(message);
}
document.getElementById('btnMessage').value = '';
}
</script>
注: 發送數據到服務端, 然后把服務端返回的數據追加到文本區域中.
效果:
在chrome瀏覽器中, 效果如下:

點擊send按鈕后, 經服務器返回其消息.

消息在大文本區域中展示. 看來Echo服務一切正常.
其實這是個悲傷的故事, 你覺得呢?
寫在最后:
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.
