基于Netty的网络编程项目实战
·
Netty介绍与相关基础知识
- Netty提供异步的事件驱动的网络应用程序框架和工具,用以快速开发高性能,高可靠性的网络服务器和客户端程序。
- Netty提供了简单易用的API
- 基于事件驱动的编程方式来编写网络通信程序
- 更高吞吐量
- 学习难度低
BIO、NIO、AIO 介绍与区别
-
阻塞与非阻塞

-
同步和异步

-
BIO 同步阻塞IO,block io IO操作时会阻塞线程,并发处理能力低。
-
NIO 同步非阻塞IO ,NIO是对BIO的改进,基于Reactor模型。
-
AIO(NIO2.0) 异步非阻塞IO 完成了客户端请求处理,在通知服务器去启动线程进行处理。
Netty Reactor模型 -单线程模型、多线程模型、主从多线程模型
-
单线程模型

-
Reactor 多线程模型

但是如果并发仍然很大,Reactor 仍然无法处理大量的客户端请求 -
Reactor 主从多线程模型
这种线程模型是 Netty 推荐使用的线程模型,
这种模型适用于高并发场景,一组线程池接收请求,一组线程池处理 IO。
Netty - 基于 web socket 简单聊天 DEMO 实现
- 导入依赖
<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) {
//创建两个线程池
NioEventLoopGroup mainGroup = new NioEventLoopGroup();
NioEventLoopGroup subGroup = new NioEventLoopGroup();
try {
//创建Netty服务器启动对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
//初始化容器启动对象
serverBootstrap
//指定使用上面创建的两个线程池
.group(mainGroup, subGroup)
//指定Netty通道类型
.channel(NioServerSocketChannel.class)
//指定通道初始化器用来加载 当Channel收到事件后
//进行业务处理
.childHandler(new WebSocketChanelInitailizer());
//绑定服务器端口,已同步的方式启动服务器
ChannelFuture future = serverBootstrap.bind(9090).sync();
//等待服务器关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//关闭服务器
mainGroup.shutdownGracefully();
subGroup.shutdownGracefully();
}
}
}
/**
* 通道初始化器,用来加载通道处理器(ChanelHandler)
*
* @Author chenppeng
* @Date 2021-06-03 10:58
* @Version 1.0
*/
@SuppressWarnings("all")
public class WebSocketChanelInitailizer extends ChannelInitializer<SocketChannel> {
/**
* 初始化通道,这个方法主要是加载对应的ChannelHandler
*
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道,将一个一个的ChannelHandler加载到管道中
ChannelPipeline pipeline = socketChannel.pipeline();
//添加一个http的编解码器
pipeline.addLast(new HttpServerCodec());
//添加一个用户支持大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//添加一个聚合器,这个聚合器主要是将HttpMessage聚合成FullHttpRequest/Response
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
//需要指定接受请求的路由
//必须使用以ws后缀结尾的url才能访问
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//添加自定义hanlder
pipeline.addLast(new ChatHandler());
}
}
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
/**
* 用户保存所有的客户端连接
*/
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 时间格式解析器
*/
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd hh:MM:ss");
/**
* 当Channel中有新的事件消息会自动调用
*
* @param channelHandlerContext
* @param textWebSocketFrame
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
//当接受到数据后 会自动调用
//获取客户端发送过来的文本消息
String text = textWebSocketFrame.text();
System.out.println("接受到的消息数据为:" + text);
for (Channel client : clients) {
//将消息发送给所有的客户端
client.writeAndFlush(new TextWebSocketFrame(simpleDateFormat.format(new Date()) + ":" + text));
}
}
/**
* 当有新的客户端li连接服务器之后,会自动调用这个方法
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//将新的通道加入到clients
clients.add(ctx.channel());
}
}
- 编写前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在线聊天室</title>
</head>
<body>
<label for="message"><input type="text" id="message"></label>
<input type="button" value="发送消息" onclick="SendMessage()">
接受到的消息
<p id="server_message" style="background-color: #AAAAAA"></p>
<script>
let websocket = null;
//判断当前浏览器是否支持web socket
if (window.WebSocket) {
//连接到服务器并发送消息
websocket = new WebSocket("ws://127.0.0.1:9090/ws");
websocket.onopen = function () {
console.log("建立连接");
};
websocket.onclose = function () {
console.log("断开连接")
};
websocket.onmessage = function (e) {
console.log("接受到服务器的消息:" + e.data);
document.getElementById("server_message").innerHTML += e.data + "<br>";
}
} else {
alert("当前浏览器不支持 web socket")
}
function SendMessage() {
let message = document.getElementById("message");
websocket.send(message.value);
}
</script>
</body>
</html>
MUI、HTML5+、HBuilder 介绍
MUI 是一个轻量级的前端框架。MUI 以 iOS 平台 UI 为基础,补充部分 Android 平台特有的 UI 控件。MUI 不依赖任何第三方 JS 库,压缩后的 JS 和 CSS 文件仅有 100+K 和 60+K,可以根据自己的需要,自定义去下载对应的模块。并且 MUI 编写的前端,可以打包成 APK 和 IPA 安装文件,在手机端运行。也就是,编写一套代码,就可以在 Android、
IOS 下运行。
H5+提供了对 HTML5 的增强,提供了 40WAPI 给程序员使用。使用 H5+ API 可以轻松开发二维码扫描、摄像头、地图位置、消息推送等功能
- HBuilder 下载地址 http://www.dcloud.io/
用户登录功能


- 后台业务代码
@Override
public User login(String username, String password) {
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
TbUserExample tbUserExample = new TbUserExample();
TbUserExample.Criteria criteria = tbUserExample.createCriteria();
criteria.andUsernameEqualTo(username);
List<TbUser> userList = tbUserMapper.selectByExample(tbUserExample);
if (userList != null && userList.size() == 1) {
String pwd = DigestUtils.md5DigestAsHex(password.getBytes());
if (pwd.equals(userList.get(0).getPassword())) {
User user = new User();
BeanUtils.copyProperties(userList.get(0), user);
return user;
}
}
}
return null;
}
注册功能


- 后台业务代码
@Override
public void register(TbUser tbUser) {
//判断用户在数据库是否存在
TbUserExample example = new TbUserExample();
TbUserExample.Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(tbUser.getUsername());
List<TbUser> userList = tbUserMapper.selectByExample(example);
if (userList != null && userList.size() > 0) {
throw new RuntimeException("注册失败");
}
//将用户信息保存到数据库中
tbUser.setId(idWorker.nextId());
tbUser.setPassword(DigestUtils.md5DigestAsHex(tbUser.getPassword().getBytes()));
tbUser.setPicSmall("");
tbUser.setPicNormal("l");
tbUser.setNickname(tbUser.getUsername());
tbUser.setCreatetime(new Date());
tbUserMapper.insert(tbUser);
}
FastDFS 文件服务器介绍与搭建
FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
- springboot整合fastdfs 上传用户头像
- application.properties配置文件
#整合FASTDFS
fdfs.soTimeout=1501
fdfs.connectTimeout=601
#缩略图生成参数
fdfs.thumbImage.width=150
fdfs.thumbImage.height=150
#TrackerList参数,支持多个
fdfs.trackerList[0]=192.168.25.135:22122
#HTTP URL
fdfs.httpurl=http://192.168.25.135/
上传头像业务层
@Override
public User upload(MultipartFile file, String userid) {
try {
//返回的fastdfs中的url路径,这个路径不带http://192.168.1.133/..
String url = fastDFSClient.uploadFile(file);
//在fastDFS上传的时候,会自动生成一个缩略图 文件名_150*150,后缀
String[] fileNameList = url.split("\\.");
String fileName = fileNameList[0];
String ext = fileNameList[1];
String picSmallUrl = fileName + "_150x150." + ext;
String prefix = environment.getProperty("fdfs.httpurl");
TbUser tbUser = tbUserMapper.selectByPrimaryKey(userid);
//设置头像大图片
tbUser.setPicNormal(prefix + url);
//设置头像小图片
tbUser.setPicSmall(prefix + picSmallUrl);
//将新的头像url更新到数据库里面
tbUserMapper.updateByPrimaryKey(tbUser);
User user = new User();
BeanUtils.copyProperties(tbUser, user);
return user;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
更多推荐





所有评论(0)