Netty实现对Websocket的支持
一、WebSocket的简介及优势
WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
首先可以看下HTTP协议的有哪些不好的地方:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
为了解决这些痛点,WebSocket就应运而生了。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。
二、WebSocket客户端API
2.1 WebSocket 构造函数
var Socket=new WebSocket("ws://localhost:20000/web");
2.2 WebSocket 属性
以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:
属性 | 描述 |
---|---|
Socket.readyState | 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。 |
Socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
2.3 WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
2.4 WebSocket 方法
以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
三、WebSocket客户端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Netty WebSocket</title> </head> <body> <br> <script type="text/javascript"> var websocket; if(!window.WebSocket){ window.WebSocket=window.MozWebSocket; } if(window.WebSocket){ websocket=new WebSocket("ws://localhost:20000/web"); websocket.onmessage=function (event) { console.log("websocket接受消息"+event.data); var text=document.getElementById('responseText'); text.value=""; text.value=event.data; } websocket.onopen=function (event) { console.log("websocket打开"); var text=document.getElementById('responseText'); text.value=""; text.value="打开websocket服务正常"; } websocket.onclose=function (event) { console.log("websocket关闭"); var text=document.getElementById('responseText'); text.value=""; text.value="关闭websocket服务"; } websocket.onerror=function (event) { console.log("websocket异常"); var text=document.getElementById('responseText'); text.value=""; text.value="websocket服务异常"; } }else{ alert("你的浏览器不支持WebSocket"); } function send(message) { if(websocket){ if(websocket.readyState==WebSocket.OPEN){ console.log("通过websocket发送消息"); websocket.send(message); } }else{ alert("未建立websocket连接"); } } </script> <form onsubmit="return false;"> <input type="text" name="message" value="Netty实践"/> <br><br> <input type="button" value="发送消息" onclick="send (this.form.message.value)"/> <h3>应答消息</h3> <textarea id="responseText" style="width: 500px;height: 300px;"></textarea> </form> </body> </html>
四、netty服务端代码
4.1 netty启动类
public class NettyServer { public static void main(String[] args) throws Exception{ //服务器启动 new NettyServer().start(20000); } public void start(int port) throws Exception{ //用于监听连接的线程组 EventLoopGroup bossGroup=new NioEventLoopGroup(); //用于发送接收消息的线程组 EventLoopGroup workGroup=new NioEventLoopGroup(); try{ //启动类引导程序 ServerBootstrap b=new ServerBootstrap(); //绑定两个线程组 b.group(bossGroup,workGroup); //设置非阻塞,用它来建立新accept的连接,用于构造serverSocketChannel的工厂类 b.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel){ ChannelPipeline channelPipeline=channel.pipeline(); // HttpServerCodec:将请求和应答消息解码为HTTP消息 channelPipeline.addLast(new HttpServerCodec()); // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息 channelPipeline.addLast(new HttpObjectAggregator(65536)); // ChunkedWriteHandler:向客户端发送HTML5文件 channelPipeline.addLast(new ChunkedWriteHandler()); //在管道中添加自己实现的Handler处理类 channelPipeline.addLast(new WebsocketServerHandler()); } }); Channel channel=b.bind(port).sync().channel(); System.out.println("服务器启动端口:"+port); channel.closeFuture().sync(); }finally { workGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } }
4.2 netty的业务处理的Handler类
public class WebsocketServerHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker; @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception { //传统的http接入 if(o instanceof FullHttpRequest){ handleHttpRequest(channelHandlerContext,(FullHttpRequest) o); } //webSocket接入 else if(o instanceof WebSocketFrame){ handleWebsocketFrame(channelHandlerContext,(WebSocketFrame) o); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){ //构造握手响应返回 WebSocketServerHandshakerFactory webSocketServerHandshakerFactory=new WebSocketServerHandshakerFactory("ws://localhost:20000/web",null,false); handshaker=webSocketServerHandshakerFactory.newHandshaker(req); handshaker.handshake(ctx.channel(),req); } private void handleWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){ //判断是否是链路关闭消息 if(frame instanceof CloseWebSocketFrame){ handshaker.close(ctx.channel(),(CloseWebSocketFrame) frame.retain()); return; } //判断是否是ping消息 if(frame instanceof PingWebSocketFrame){ ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } //文本消息处理 String request=((TextWebSocketFrame)frame).text(); System.out.println("接受的信息是:"+request); String date=new Date().toString(); //将接收消息写回给客户端 ctx.channel().write(new TextWebSocketFrame("现在时刻:"+date+"发送了:"+request)); } }
五、运行成功截图
参考资料: