慕课网《Netty入门之WebSocket初体验》学习总结
时间:2018年04月11日星期三
说明:本文部分内容均来自慕课网。@慕课网:https://www.imooc.com
教学源码:https://github.com/zccodere/study-imooc
学习源码:https://github.com/zccodere/study-imooc
第一章:课程介绍
1-1 课程介绍
什么是Netty
高性能、事件驱动、异步非阻塞的IO Java开源框架
基于NIO的客户端,服务端编程框架
非常可靠的稳定性和伸缩性
Netty使用场景
高性能领域:游戏、大数据、分布式计算
多线程并发领域:多线程模型、主从多线程模型
异步通信领域:异步非阻塞,主动获取或通过通知机制来得到结果
课程提纲
IO通信:BIO、伪异步IO、NIO、AIO通信
Netty入门:原生NIO的缺陷、Netty的优势
WebSocket入门:什么是WebSocket、如何建立连接、生命周期及关闭
Netty实现WebSocket通信案例
课程要求
有一定的Java基础
有一定的IO编程基础
了解Java的BIO、伪异步IO、NIO和AIO
第二章:IO通信
2-1 IO通信
BIO通信
一个线程负责连接
一请求一应答
缺乏弹性伸缩能力
BIO通信模型
伪异步IO通信
线程池负责连接
M请求N应答
线程池阻塞
伪异步IO通信模型
NIO通信
缓冲区Buffer
通道Channel
多路复用器Selector
AIO通信
连接注册读写事件和回调函数
读写方法异步
主动通知程序
四种IO对比
原生NIO的缺陷
类库和API繁杂
入门门槛高
工作量和难度大
JDK NIO存在BUG
Netty的优势
API使用简单,定制能力强,可以通过ChannelHandler对框架进行灵活的扩展
入门门槛低,功能强大,预制了多种编解码功能,支持多种主流协议
性能高,通过与其他的业界主流的NIO框架对比,Netty的综合性能最优
Netty比较成熟稳定,Netty修复了JDK NIO所有发现的BUG
第四章:WebSocket入门
4-1 WebSocket入门
什么是WebSocket
H5协议规范:H5提出的协议规范
握手机制:使客户端与服务器能够建立类似TCP的连接,方便通信
解决客户端与服务端实时通信而产生的技术:基于TCP的协议
WebSocket的优点
节省通信开销:以前使用轮询,开销较大
服务器主动传送数据给客户端:任意时刻,相互传送数据
实时通信:可以彼此相互推送信息
WebSocket建立连接
客户端发起握手请求
服务器响应请求
连接建立
WebSocket生命周期
打开事件:发生新的连接时调用、在端点上建立连接时且在任何事件之前
消息事件:接收WebSocket对话中,另一端发送的消息
错误事件:在进行连接或端点发生错误时产生
关闭事件:表示WebSocket端点的连接关闭
WebSocket关闭连接
服务器关闭底层TCP连接
客户端发起TCP Close
第五章:通信案例
5-1 通信案例
基于Netty实现WebSocket通信案例
功能介绍
Netty开发服务端
Html实现客户端
实现服务端与客户端的实时交互
代码编写
1.创建名为netty-websocket的maven工程pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myimooc</groupId>
<artifactId>netty-websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.编写NettyConfig类
package com.myimooc.netty.websocket;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
* <br>
* 标题: Netty 全局配置类<br>
* 描述: 存储整个工程的全局配置<br>
*
* @author zc
* @date 2018/04/11
*/
public class NettyConfig {
/**
* 存储每一个客户端接入进来时的 Channel
*/
public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
3.编写MyWebSocketHandler类
package com.myimooc.netty.websocket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import java.util.Date;
/**
* <br>
* 标题: 处理客户端WebSocket请求的核心业务处理类<br>
* 描述: 接收/处理/响应 客户端websocket请求的核心业务处理类<br>
*
* @author zc
* @date 2018/04/11
*/
public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {
private WebSocketServerHandshaker handshaker;
private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket";
/**
* 服务端处理客户端websocket请求的核心方法
*/
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
// 处理客户端向服务端发起http握手请求的业务
FullHttpRequest request = (FullHttpRequest) msg;
this.handHttpRequest(ctx, request);
} else if (msg instanceof WebSocketFrame) {
// 处理websocket连接的业务
WebSocketFrame frame = (WebSocketFrame) msg;
this.handWebSocketFrame(ctx, frame);
}
}
/**
* 处理客户端与服务端之前的websocket业务
*/
private void handWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
if (frame instanceof CloseWebSocketFrame){
// 如果是关闭websocket的指令
handshaker.close(ctx.channel(),(CloseWebSocketFrame)frame.retain());
}
if (frame instanceof PingWebSocketFrame){
// 如果是ping消息
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
if (!(frame instanceof TextWebSocketFrame)){
// 如果不是文本消息,则抛出异常
System.out.println("目前暂不支持二进制消息");
throw new RuntimeException("【"+this.getClass().getName()+"】不支持二进制消息");
}
// 获取客户端向服务端发送的文本消息
String request = ((TextWebSocketFrame) frame).text();
System.out.println("服务端收到客户端的消息=====>>>" + request);
// 将客户端发给服务端的消息返回给客户端
TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + "====>>>" + request);
// 群发,服务端向每个连接上来的客户端群发消息
NettyConfig.group.writeAndFlush(tws);
}
/**
* 处理客户端向服务端发起http握手请求的业务
*/
private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
if (!request.getDecoderResult().isSuccess() || !("websocket").equals(request.headers().get("Upgrade"))) {
// 不是websocket握手请求时
this.sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false);
handshaker = wsFactory.newHandshaker(request);
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), request);
}
}
/**
* 服务端向客户端响应消息
*/
private void sendHttpResponse(ChannelHandlerContext ctc, FullHttpMessage request, DefaultFullHttpResponse response) {
if (response.getStatus().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8);
response.content().writeBytes(buf);
buf.release();
}
// 服务端向客户端发送数据
ChannelFuture future = ctc.channel().writeAndFlush(response);
if (response.getStatus().code() != 200) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
/**
* 工程出现异常时调用
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* 客户端与服务端创建连接时调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyConfig.group.add(ctx.channel());
System.out.println("客户端与服务端连接开启...");
}
/**
* 客户端与服务端断开连接时调用
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyConfig.group.remove(ctx.channel());
System.out.println("客户端与服务端连接关闭...");
}
/**
* 服务端接收客户端发送过来的数据结束之后调用
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
4.编写MyWebSocketChannelHandler类
package com.myimooc.netty.websocket;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* <br>
* 标题: 初始化连接时的各个组件<br>
* 描述: 初始化连接时的各个组件<br>
*
* @author zc
* @date 2018/04/11
*/
public class MyWebSocketChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 将请求和应答消息解码为HTTP消息
ch.pipeline().addLast("http-codec",new HttpServerCodec());
// 将HTTP消息的多个部分合成一条完整的HTTP消息
ch.pipeline().addLast("aggregator",new HttpObjectAggregator(65536));
// 向客户端发送HTML5文件
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
ch.pipeline().addLast("handler",new MyWebSocketHandler());
}
}
5.编写AppStart类
package com.myimooc.netty.websocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
/**
* <br>
* 标题: 程序入口<br>
* 描述: 启动应用<br>
*
* @author zc
* @date 2018/04/11
*/
public class AppStart {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new MyWebSocketChannelHandler());
System.out.println("服务端开启等待客户端连接...");
Channel channel = serverBootstrap.bind(8888).sync().channel();
channel.closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
// 优雅的退出程序
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
6.编写websocket.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="content-type" content="text/html"/>
<title>WebSocket客户端</title>
<script type="text/javascript">
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:8888/websocket");
socket.onmessage = function (event) {
var ta = document.getElementById("responseContent");
ta.value += event.data + "\r\n";
};
socket.onopen = function (event) {
var ta = document.getElementById("responseContent");
ta.value = "您当前的浏览器支持 WebSocket,请进行后续操作\r\n";
};
socket.onclose = function (event) {
var ta = document.getElementById("responseContent");
ta.value = "";
ta.value = "WebSocket 连接已近关闭\r\n";
};
} else {
alert("您的浏览器不支持 WebSocket");
}
function send(msg) {
if (!window.WebSocket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(msg);
} else {
alert("WebSocket连接没有建立成功");
}
}
</script>
</head>
<body>
<form onSubmit="return false;">
<input type="text" name="msg" value=""/>
<br/>
<br/>
<input type="button" value="发送WebSocket请求消息" onclick="send(this.form.msg.value)"/>
<hr color="red"/>
<h2>客户端接收到服务端返回的应答消息</h2>
<textarea id="responseContent" ></textarea>
</form>
</body>
</html>
第六章:课程总结
6-1 课程总结
课程总结
课程介绍
IO通信:四种IO通信
Netty入门:原生NIO的缺点,Netty的优点
WebSocket入门:WebSocket的优点,如何使用
通信案例:Netty实现WebSocket通信案例
热门评论
可以参考一下:Netty基于websocket案例演示