netty源码详细讲解(基于Netty网络编程项目实战课程1)
netty源码详细讲解(基于Netty网络编程项目实战课程1)(3)更高的吞吐量(2)基于事件驱动的编程方式来编写网络通信程序“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括 FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。(1)Netty 提供了简单易用的API
一 基于 Netty 网络编程项目实战课程1项目介绍2Netty 介绍与相关基础知识2.1Netty 介绍
简介
Netty 是由 JBOSS 提供的一个 java 开源框架。Netty 提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于 NIO 的客户、服务器端编程框架,使用 Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty 相当于简化和流线化了网络应用的编程开发过程, 例如:基于 TCP 和 UDP 的 socket 服务开发。
“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括 FTP、SMTP、HTTP
等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
(1)Netty 提供了简单易用的API
(2)基于事件驱动的编程方式来编写网络通信程序
(3)更高的吞吐量
(4)学习难度低
同步和异步
主要是指的数据的请求方式
同步和异步是指访问数据的一种机制
BIO
同步阻塞 IO,Block IO,IO 操作时会阻塞线程,并发处理能力低。
我们熟知的 Socket 编程就是 BIO,一个 socket 连接一个处理线程(这个线程负责这个 Socket 连接的一系列数据传输操作)。阻塞的原因在于:操作系统允许的线程数量是有限的,多个 socket 申请与服务端建立连接时,服务端不能提供相应数量的处理线程,没有分配到处理线程的连接就会阻塞等待或被拒绝。
NIO
同步非阻塞 IO,None-Block IO
NIO 是对 BIO 的改进,基于 Reactor 模型。我们知道,一个 socket 连接只有在特点时候才会发生数据传输 IO 操作, 大部分时间这个“数据通道”是空闲的,但还是占用着线程。NIO 作出的改进就是“一个请求一个线程”,在连接到服务端的众多 socket 中,只有需要进行 IO 操作的才能获取服务端的处理线程进行 IO。这样就不会因为线程不够用而限制了 socket 的接入。
AIO(NIO 2.0)
异步非阻塞 IO
这种 IO 模型是由操作系统先完成了客户端请求处理再通知服务器去启动线程进行处理。AIO 也称 NIO2.0,在 JDK7开始支持。
2.3Netty Reactor 模型 - 单线程模型、多线程模型、主从多线程模型介绍
2.3.1单线程模型
用户发起 IO 请求到 Reactor 线程
Ractor 线程将用户的 IO 请求放入到通道,然后再进行后续处理
处理完成后,Reactor 线程重新获得控制权,继续其他客户端的处理
这种模型一个时间点只有一个任务在执行,这个任务执行完了,再去执行下一个任务。
1.但单线程的 Reactor 模型每一个用户事件都在一个线程中执行:
2.性能有极限,不能处理成百上千的事件
3.当负荷达到一定程度时,性能将会下降
4.某一个事件处理器发生故障,不能继续处理其他事件
2.3.2Reactor 多线程模型
Reactor 多线程模型是由一组 NIO 线程来处理 IO 操作(之前是单个线程),所以在请求处理上会比上一中模型效率更高,可以处理更多的客户端请求。
这种模式使用多个线程执行多个任务,任务可以同时执行
但是如果并发仍然很大,Reactor 仍然无法处理大量的客户端请求
2.3.3Reactor 主从多线程模型
这种线程模型是 Netty 推荐使用的线程模型
这种模型适用于高并发场景,一组线程池接收请求,一组线程池处理 IO。
2.4Netty - 基于 web socket 简单聊天 DEMO 实现
后端编写
导入依赖
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <target>1.8</target> <source>1.8</source> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.15.Final</version> </dependency> </dependencies>
编写 Netty Server
public class WebsocketServer { public static void main(String[] args) throws InterruptedException { // 初始化主线程池(boss 线程池) NioEventLoopGroup mainGroup = new NioEventLoopGroup(); // 初始化从线程池(worker 线程池) NioEventLoopGroup subGroup = new NioEventLoopGroup(); try { // 创建服务器启动器 ServerBootstrap b = new ServerBootstrap(); // 指定使用主线程池和从线程池 b.group(mainGroup subGroup) // 指定使用 Nio 通道类型 .channel(NioServerSocketChannel.class) // 指定通道初始化器加载通道处理器 .childHandler(new WsServerInitializer()); // 绑定端口号启动服务器,并等待服务器启动 // ChannelFuture 是 Netty 的回调消息 ChannelFuture future = b.bind(9090).sync(); // 等待服务器 socket 关闭 future.channel().closeFuture().sync(); } finally { // 优雅关闭 boos 线程池和 worker 线程池mainGroup.shutdownGracefully(); subGroup.shutdownGracefully(); } } }
编写通道初始化器
public class WsServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline();
// ------------------
// 用于支持 Http 协议
// websocket 基于 http 协议,需要有 http 的编解码器
pipeline.addLast(new HttpServerCodec());
// 对写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
// 添加对 HTTP 请求和响应的聚合器:只要使用 Netty 进行 Http 编程都需要使用
// 对 HttpMessage 进行聚合,聚合成 FullHttpRequest 或者 FullHttpResponse
// 在 netty 编程中都会使用到 Handler
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
// ---------支持 Web Socket -----------------
// websocket 服务器处理的协议,用于指定给客户端连接访问的路由: /ws
// 本 handler 会帮你处理一些握手动作: handshaking(close ping pong) ping pong = 心跳
// 对于 websocket 来讲,都是以 frames 进行传输的,不同的数据类型对应的 frames 也不
同
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// 添 加 自 定 义 的 handler pipeline.addLast(new ChatHandler());
}
}
编写处理消息的 ChannelHandler
/**
*处理消息的handler
*TextWebSocketFrame: 在netty 中,是用于为 websocket 专门处理文本的对象,frame 是消息的载体
*/
public class ChatHandler extends
SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用于记录和管理所有客户端的Channel
private static ChannelGroup clients = new
DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx TextWebSocketFrame msg) throws Exception {
// 获取从客户端传输过来的消息
String text = msg.text(); System.out.println("接收到的数据:" text);
// 将接收到消息发送到所有客户端
for(Channel channel : clients) {
// 注意所有的 websocket 数据都应该以 TextWebSocketFrame 进行封装
channel.writeAndFlush(new TextWebSocketFrame("[服务器接收到消息:]"
LocalDateTime.now() " 消息为:" text));
}
}
/**
*当客户端连接服务端之后(打开连接)
*获取客户端的 channel,并且放入到 ChannelGroup 中去进行管理
*@param ctx
*@throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 将 channel 添加到客户端
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 当触发handlerRemoved,ChannelGroup 会自动移除对应客户端的 channel
//clients.remove(ctx.channel());
// asLongText()——唯一的 ID
// asShortText()—— 短 ID( 有 可 能 会 重 复 ) System.out.println("客户端断开 channel 对应的长 id 为:"
ctx.channel().id().asLongText());
System.out.println("客户端断开 channel 对应的短 id 为:" ctx.channel().id().asShortText());
}
}
2.5websocket 以及前端代码编写
WebSocket protocol 是HTML5 一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP 请求完成。Websocket 是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手, 握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。
前端编写
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div>发送消息</div> <input type="text" id="msgContent" /> <input type="button" value="点击发送" onclick="CHAT.chat()"/> <div>接收消息:</div> <div id="recMsg" style="background-color: gainsboro;"></div> <script type="application/javascript"> window.CHAT = { socket: null init: function() { // 判断浏览器是否支持 websocket if(window.WebSocket) { // 支持 WebScoekt // 连接创建 socket,注意要添加 ws 后缀 CHAT.socket = new WebSocket("ws://127.0.0.1:9001/ws"); CHAT.socket.onopen = function() { console.log("连接建立成功"); }; CHAT.socket.onclose = function() { console.log("连接关闭") }; CHAT.socket.onerror = function() { console.log("发生错误"); }; CHAT.socket.onmessage = function(e) { console.log("接收到消息:" e.data); var recMsg = document.getElementById("recMsg"); var html = recMsg.innerHTML; recMsg.innerHTML = html "<br/>" e.data; }; } else { alert("浏览器不支持 websocket 协议"); } } chat: function() { var msg = document.getElementById("msgContent"); CHAT.socket.send(msg.value); } } CHAT.init(); </script> </body> </html>
2.6MUI、HTML5 、HBuilder 介绍
MUI 介绍
http://dev.dcloud.net.cn/mui/ MUI 是一个轻量级的前端框架。MUI 以 iOS 平台 UI 为基础,补充部分 Android 平台特有的 UI 控件。MUI 不依赖任何第三方 JS 库,压缩后的 JS 和 CSS 文件仅有 100 K 和 60 K,可以根据自己的需要,自定义去下载对应的模块。并且 MUI 编写的前端,可以打包成 APK 和 IPA 安装文件,在手机端运行。也就是,编写一套代码,就可以在 Android、IOS 下运行。 API 地址:http://dev.dcloud.net.cn/mui/ui/
H5
H5 提供了对 HTML5 的增强,提供了 40WAPI 给程序员使用。使用 H5 API 可以轻松开发二维码扫描、摄像头、地图位置、消息推送等功能
HBuilder
前端开发工具。本次项目所有的前端使用 HBuilder 开发。在项目开发完后,也会使用 HBuilder 来进行打包Android/IOS 的安装包。
http://www.dcloud.io/
2.7MUI 前端开发2.7.1创建项目/页面/添加 MUI 元素
创建 MUI 移动 App 项目
页面创建,添加组件
<header class="mui-bar mui-bar-nav">
<h1 class="mui-title">登录页面</h1>
</header>
<div class="mui-content">
<form class="mui-input-group">
<div class="mui-input-row">
<label>用户名</label>
<input type="text" class="mui-input-clear" placeholder="请输入用户名">
</div>
<div class="mui-input-row">
<label>密码</label>
<input type="password" class="mui-input-password" placeholder="请输入密
码">
</div>
<div class="mui-button-row">
<button type="button" class="mui-btn mui-btn-primary">确认</button>
<button type="button" class="mui-btn mui-btn-danger">取消</button>
</div>
</form>
</div>
http://dev.dcloud.net.cn/mui/ui/#accordion
2.7.2获取页面元素/添加点击事件
获取页面元素
mui.plusReady(function() { // 使用document.getElementById来获取Input组件数据var username = document.getElementById("username"); var password = document.getElementById("password"); var confirm = document.getElementById("confirm"); // 绑定事件 confirm.addEventListener("tap" function() { alert("按下按钮"); }); });
批量绑定页面元素的点击事件
mui(".mui-table-view").on('tap' '.mui-table-view-cell' function(){ });
使用原生 JS 的事件绑定方式
// 绑定事件 confirm.addEventListener("tap" function() { alert("按下按钮"); });
2.7.3发起ajax 请求
前端
当我们点击确认按钮的时候,将用户名和密码发送给后端服务器
// 发送ajax请求 mui.ajax('http://192.168.1.106:9000/login' { data: { username: username.value password: password.value } dataType: 'JSON' //服务器返回json格式数据type: 'post' //HTTP请求类型 timeout: 10000 //超时时间设置为10秒; headers: { 'Content-Type': 'application/json' } success: function(data) { // 可以使用console.log打印数据,一般用于调试 console.log(data); } error: function(xhr type errorThrown) { // 异 常 处 理 ; console.log(type); } });
后端
基于 SpringBoot 编写一个 web 应用,主要是用于接收ajax 请求,响应一些数据到前端
@RestController public class LoginController { @RequestMapping("/login") public Map login(@RequestBody User user) { System.out.println(user); Map map = new HashMap<String Object>(); if("tom".equals(user.getUsername()) && "123".equals(user.getPassword())) { map.put("success" true); map.put("message" "登录成功"); } else { map.put("success" false); map.put("message" "登录失败,请检查用户名和密码是否输入正确"); } return map; } }
2.7.4字符串转 JSON 对象以及 JSON 对象转字符串
将 JSON 对象转换为字符串
// 使用JSON.stringify可以将JSON对象转换为String字符串 console.log(JSON.stringify(data));
将字符串转换为JSON 对象
2.7.5页面跳转
mui.openWindow({ url: 'login_succss.html' id:'login_succss.html' });
2.7.6App 客户端缓存操作
大量的 App 很多时候都需要将服务器端响应的数据缓存到手机 App 本地。
http://www.html5plus.org/doc/zh_cn/storage.html
在 App 中缓存的数据,就是以 key-value 键值对来存放的。
将数据放入到本地缓存中
var user = { username: username.value password: password.value } // 将对象数据放入到缓存中,需要转换为字符串 plus.storage.setItem("user" JSON.stringify(user));
从本地缓存中读取数据
// 从storage本地缓存中获取对应的数据 var userStr = plus.storage.getItem("user"); 3构建项目
3.1项目功能需求、技术架构介绍
功能需求
登录/注册个人信息
搜索添加好友
好友聊天
技术架构
前端
开发工具:HBuilder 框架:MUI、H5
后端
开发工具:IDEA 框架:Spring Boot、MyBatis、Spring MVC、FastDFS、Netty 数据库:mysql
3.2使用模拟器进行测试
安装附件中的夜神 Android 模拟器(nox_setup_v6.2.3.8_full.exe)
双击桌面图标启动模拟器
安装后找到模拟器的安装目录
到命令行中执行以下命令
nox_adb connect 127.0.0.1:62001
nox_adb devices
进入到 Hbuilder 安装目录下的 tools/adbs 目录
切换到命令行中执行以下命令
adb connect 127.0.0.1:62001 adb devices
打开 HBuilder 开始调试
3.3前端 - HBuilder 前端项目导入
将资料中的 heima-chat.zip 解压,并导入到 HBuilder 中。
3.4后端 - 导入数据库/SpringBoot 项目/MyBatis 逆向工程
导入数据库
将资料中的 hchat.sql 脚本在开发工具中执行
数据库表结构介绍
tb_user 用户表
tb_friend 朋友表
tb_friend_req 申请好友表
tb_chat_record 聊天记录表
使用 MyBatis 逆向工程生成代码
将资料中的 generatorSqlmapCustom 项目导入到 IDEA 中,并配置项目所使用的 JDK
创建 Spring Boot 项目拷贝资料 pom.xml 依赖
拷贝资料中的application.properties 配置文件
3.5后端 - Spring Boot 整合 Netty 搭建后台
spring boot 整合 Netty
导入资料中配置文件中的 spring-netty 文件夹中的 java 文件
启动 Spring Boot,导入HTML 页面 使用浏览器打开测试 Netty 是否整合成功