webSocket 是在H5上提出的规范,它可以解决很多以前http无法做到的事情,比如:
- 每次请求都要携带cookie、一大堆请求头信息
- 只能客户端主动调用服务端方法,不能从服务端往客户端推送数据
服务端
public class WebSocketServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.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();
// 因为webSocket 连接的建立过程是首先通过http的请求建立连接,
// 当请求成功后将协议切换并且升级,所以服务器上要有Http的支持
pipeline.addLast(new HttpServerCodec());
// 数据以块的形式处理
pipeline.addLast(new ChunkedWriteHandler());
// 针对于http的请求 对请求对象进行聚合 也是http开发中最常用的处理器
// 当使用此处理器之后 请求参数的类型就会转换成为FullHttpRequest/FullHttpResponse(根据是接收消息还是发送消息选择)
pipeline.addLast(new HttpObjectAggregator(4096));
// 真正处理webSocket的处理器
// 参数 指定项目路径 相当于web项目中的context-path
// eg: ws://localhost:8899/netty-ws
pipeline.addLast(new WebSocketServerProtocolHandler("/netty-ws"));
// 添加自定义的处理器
pipeline.addLast(new TextWebSocketFrameHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取服务器当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println("收到消息:" + msg.text());
// 向客户端返回消息的时候不能直接使用writeAndFlush 了
// 需要使用WebSocketFrame的子类包装返回消息
ctx.writeAndFlush(new TextWebSocketFrame("服务器:" + now));
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println("连接接入:" + channel.remoteAddress() + " channelId:" + channel.id().asLongText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println("连接断开:" + channel.remoteAddress() + " channelId:" + channel.id().asLongText());
}
}
客户端
因为webSocket 是针对于web服务,所以
我们可以用常用的浏览器来作为客户端,所以我们只用编写一个html文件即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<form onsubmit="return false">
<textarea name="message" style="width:500px;height:200px;"></textarea>
<input type="button" value="发送" onclick="send(this.form.message.value)"/>
<h3>服务器消息</h3>
<textarea name="responseText" id="responseText" style="width:500px;height:300px;">
</textarea>
<input type="button" value="连接" onclick="connectWebSocket()"/>
<input type="button" value="断开" onclick="closeWebSocket()"/>
</form>
<script type="text/javascript">
var webSocket;
function connectWebSocket() {
// 判断浏览器是否支持webSocket
if (window.WebSocket) {
webSocket = new WebSocket("ws://localhost:8899/netty-ws")
webSocket.onopen = function () {
var el = document.getElementById("responseText")
el.value = "成功与服务器建立连接\n"
}
webSocket.onclose = function () {
var el = document.getElementById("responseText")
el.value = el.value + "断开连接\n"
}
webSocket.onmessage = function (event) {
var el = document.getElementById("responseText")
el.value = el.value + event.data + "\n"
}
}
}
function send(msg) {
webSocket && webSocket.readyState == WebSocket.OPEN && webSocket.send(msg)
}
function closeWebSocket() {
webSocket && webSocket.close()
}
</script>
</body>
</html>
实现效果
客户端
服务端
总结
服务端向客户端发送消息
服务端往客户端发送消息,需要将消息使用WebSocketFrame的子类进行包装,否则会发不出去。
// 需要使用WebSocketFrame的子类包装返回消息
ctx.writeAndFlush(new TextWebSocketFrame("服务器:" + now));
针对于webSocket的规范 netty均提供了对于的frame类。
浏览器F12 中的Network中的内容很丰富
在Header后面的Frames里面我们可以看到客户端和服务器交互的所有数据