一、WebSocket 简介
百度百科
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 通信协议于 2011 年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
二、有了 HTTP 为什么还需要 WebSocket?
因为 HTTP 有一个缺陷,就是只能从客户端发起请求,无法从服务器发起请求,所以诞生了 WebSocket 请求
如下
以上为 WebSocket 请求 URI
二、需求说明
Http 协议是无状态的,浏览器和服务器间的请求响应一次,下一次会重新创建连接
要求:实现基于 WebSocket 的长连接的全双工的交互
改变 Http 协议多次请求的约束,实现长连接,服务器可以发送消息给浏览器
客户端浏览器和服务器会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器也可感知
三、需求分析
⌚服务器与浏览器相互感知
当客户端浏览器上线后,服务器可以感知到并提示用户上线,继承 SimpleChannelInboundHandler 类并重写 handlerAdded
即可感知用户上线
⌚服务器发送消息给客户端
当连接成功后,服务器重写 channelRead0 方法并通过全局上下文 ctx.channel().writeAndFlush 方法发送消息
注意:这里不可以直接写字符串发送,要封装成 TextWebSocketFrame
⌚客户端发送消息给服务器
客户端发送消息给服务器通过 WebSocket 的 send 方法发送即可
四、效果图
五、核心源码
❇️前端源码
WebSocket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script>
var socket;
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:7000/hello2");
socket.onmessage = function(ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + ev.data;
document.getElementById("msg").value = '';
}
//连接开启
socket.onopen = function(ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "已开启连接...";
}
//连接关闭
socket.onclose = function(ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "已关闭连接...";
}
} else {
alert("您的浏览器不支持WebSocket!");
}
//发送消息到浏览器
function send(msg) {
//判断websocket是否创建好
if (!socket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(msg);
} else {
alert("连接未开启!")
}
}
</script>
<body>
<form onsubmit="return false">
<div class="msgDiv">
<h2>客户端</h2>
<textarea name="msg" id="msg" style="width: 400px; height: 300px;"></textarea> 
<input type="button" value="发送消息" onclick="send(this.form.msg.value)">
</div>
<div class="msgDiv">
<h2>服务器内容</h2>
<textarea id="responseText" style="width: 400px; height: 300px;"></textarea> 
<input type="button" value="清空内容" onclick="document.getElementById('responseText').value = ''">
</div>
</form>
</body>
</html>
<style>
.msgDiv{
float: left;
margin: 20px;
}
</style>
复制代码
❇️后端源码
MyWebSocketServer
package com.wanshi.netty.websocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class MyWebSocketServer {
public static void main(String[] args) {
//创建2个线程组
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器监听
ServerBootstrap serverBootstrap = new ServerBootstrap();
//添加前置参数
serverBootstrap.group(boosGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.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 HttpObjectAggregator(8192));
pipeline.addLast(new WebSocketServerProtocolHandler("/hello2"));
pipeline.addLast(new MyWebSocketServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
复制代码
核心业务处理器 MyWebSocketServerHandler
package com.wanshi.netty.websocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
public class MyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("服务器接受消息:" + msg.text());
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间:" + sdf.format(new Date()) + " -- 消息:" + msg.text()));
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded被调用:" + ctx.channel().id().asLongText());
System.out.println("handlerAdded被调用:" + ctx.channel().id().asShortText());
System.out.println(sdf.format(new Date()) + " 【服务器】用户已连接!");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved被调用:" + ctx.channel().id().asLongText());
System.out.println(sdf.format(new Date()) + " 【服务器】用户已断开连接");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("发生异常:" + cause.getMessage());
ctx.close();
}
}
复制代码
⛲小结
以上就是【Bug 终结者】对 Netty 进阶 -- WebSocket 长连接开发简单的理解,WebSocket 建立再 TCP 之上,可以实现浏览器与服务器的通信,和 Http 具有很好的兼容性,数据格式比较轻量,性能开销小,通信高效。可以随时与服务器通信,WebSocket 有很多的好处,多了解,去慢慢掌握,相信你很快就可以掌握~
如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!
评论