前言:
就如前文所讲述的, 聊天室往往是最基本的网络编程的学习案例. 本文以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服务一切正常.
其实这是个悲伤的故事, 你觉得呢?
写在最后:
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.