不想多说什么,深刻的体会到学习方法的重要性,还有心态的问题,是否你能够沉得住气!耐的下心~这个过程感觉自己也是蛮拼的了~ 不过还好!终于让本姑娘有所收获了!!!
如果最开始你去了解Netty你会发现真尼玛难!进程,线程,并发,NIo,IO,这么多鬼玩意。。。。首先我们先从宏观去来接他
Netty是客户端于服务端进行通信的一个网络应用程序框架,他快速开发并且可以实现高性能协议和异步事件驱动。这些话是不是特别的高大尚啊~ 让我用一句大白话解决:他就是小明和小红两个人通过电话进行交流,并且这个过程中小红还可以让雪芬打电话。就是这么个玩意。。。
Netty为什么现在这么好用并且这么流行,其实就是历史发展不断的更新不断的让我们程序员好干活~
Netty是jboss提供的一个jar包。这个jar包主要是针对的socket进行编程的。
1、IO:由于系统的扩大这个时候我们进行分模块话进行管理,然后使用RPC进行不同服务器之间远程通信,最开始的是时候我们使用的IO进行编程,但这种方式的编程也就是一个线程对应一个socket,并且是阻塞模式下的,阻塞的意思就是干这件事情的不能去做另外一件事情,所以非常的消耗资源。由于这些原因我们开发了另一种叫做NIO
2、NIO:NIO的重点突出就是在非阻塞下进行通信,他再设计上进行了优化,一个线程可以select选择多个socket,所以这个时候就方便了很多,但是出现了另一种问题,就是NIO从实质上并没有解决高并发的问题,所以这个时候Netty就出现了
3、Netty:Netty是基于NIO,他可以自己扩展自己的协议,并且是一个高并发的客户端和服务端的一个框架
NIO是面向缓冲区的,IO是面向的数据流
同类技术处了是NIO,还有一个比Netty出现早的框架Mina框架
他简化网络应用的编程过程开发,例如UDP和TCP的socket服务开发,他功能比较强大,并且提供了编码好解码的能力,并且很多著名的框架都是底层采用Netty,比如Dubbo,服务提供者和消费者之间的通信。淘宝的消息中间件 RocketMQ 的消息生产者和消息消费者之间
这个Demo我大概研究了3天的时间,中途的时候有坚持不住的感觉,实在是没有接触过!但是通过我不懈努力,让我给搞定了!!!!!
我们项目主要是针对的是服务端,但是由于设备还没有下来得原因我只能自己模拟客户端,然后让他们直接进行通信,首先看服务端。
首先说一下大体的过程
1有一个boostrap服务者
2有两个干活的,也就是处理IO的,一个是boss用于监听端口,一个work干活的
3配置UDP管道,UDP管道主要使用的是NioDatagramChannel
4然后使用pipeline进行配置,可以实现编码和解码等功能,然后寻找到我们的执行的处理的Handler类
5配置我们的服务,例如选择是广播和单播,读和写缓存
6绑定端口,获取通道
7优雅的关闭服务器
/**
* 服务监听、初始化类
* */
public class UdpServer {
//打印日志
public static final Logger log = Logger.getLogger(UdpServer.class);
//给管道抽象出接口,给Channel更多的能力和配置,例如Channel的状态,参数,IO操作
//使用ChannelPipeline实现自定义IO
public static Channel channel;
public static void run() throws Exception {
//启动服务
EventLoopGroup workerGroup = new NioEventLoopGroup();
//优化使用的线程
final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
try {
Bootstrap b = new Bootstrap();//udp不能使用ServerBootstrap
b.group(workerGroup).channel(NioDatagramChannel.class)//设置UDP通道
//设置udp的管道工厂
.handler(new ChannelInitializer(){
//NioDatagramChannel标志着是UDP格式的
@Override
protected void initChannel(NioDatagramChannel ch)
throws Exception {
// TODO Auto-generated method stub
//创建一个执行Handler的容器
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//执行具体的处理器
pipeline.addLast(group,"handler",new UdpServerHandler());//消息处理器
}
})//初始化处理器
//true / false 多播模式(UDP适用),可以向多个主机发送消息
.option(ChannelOption.SO_BROADCAST, true)// 支持广播
.option(ChannelOption.SO_RCVBUF, 2048 * 1024)// 设置UDP读缓冲区为2M
.option(ChannelOption.SO_SNDBUF, 1024 * 1024);// 设置UDP写缓冲区为1M
// 绑定端口,开始接收进来的连接 ,绑定的端口9999
ChannelFuture f = b.bind(Constants.PORT).sync();
//获取channel通道
channel=f.channel();
System.out.println("UDP Server 启动!");
// 等待服务器 socket 关闭 。
// 这不会发生,可以优雅地关闭服务器。
f.channel().closeFuture().sync();
}finally {
// 优雅退出 释放线程池资源
group.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
编写的Handler主要是实现SimpleChannelInboundHandler,重写他里面的方法,主要做的事情就是连接,获取消息,发送消息,主要介绍下面几个方法
channelActive:表示连接成功
channelRead0:等消息触发的时候走这个方法,主要是获取客户端发来的消息
exceptionCaught()捕获异常消息
writeAndFlush()回馈给客户端的消息
DatagramPacket:表示消息容器,里面包含了发送的消息和接受的消息
//1通过content()来获取消息
//2通过sender()来获取发送者的消息
//3recipient()来获取接受者的消息
public class UdpServerHandler extends SimpleChannelInboundHandler{
//打印日志
public static final Logger log = Logger.getLogger(UdpServerHandler.class);
//在事件循环中执行
private EventExecutor executor ;
//客户端与服务器端连接成功的时候触发
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("UDP通道已经连接");
System.out.println("UDP通道已经连接");
super.channelActive(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception
{
// 读取收到的数据
ByteBuf buf = (ByteBuf) packet.copy().content();
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, CharsetUtil.UTF_8);
System.out.println("【NOTE】>>>>>> 收到客户端的数据:"+body);
// 回复一条信息给客户端
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("Hello,我是Server,我的时间戳是"+System.currentTimeMillis()
, CharsetUtil.UTF_8)
, packet.sender())).sync();
}
//消息没有结束的时候触发
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
//捕获异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
//logger.log(Level.INFO, "AuthServerInitHandler exceptionCaught");
log.error("UdpServerHandler exceptionCaught"+cause.getMessage());
System.out.println("UdpServerHandler exceptionCaught"+cause.getMessage());
cause.printStackTrace();
ctx.close();
}
客户端和服务端非常的相似,但是客户端要比服务端少一个工作者,并且还需要监听服务端的ip和端口,然后客户端自己也是要一个端口的,哈哈
//创建于服务端就是连接操作,创建线程
EventLoopGroup group = new NioEventLoopGroup();
try {
//开始客户端的服务,和管道的设置
Bootstrap b = new Bootstrap();
b.group(group).channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST,true)
.option(ChannelOption.SO_RCVBUF, 2048 * 1024)// 设置UDP读缓冲区为2M
.option(ChannelOption.SO_SNDBUF, 1024 * 1024)// 设置UDP写缓冲区为1M
.handler(new UdpServerHandler());
//服务端绑定的管道的端口
Channel ch = b.bind(8888).sync().channel();
// 向网段类所有机器广播发UDP,这是想客户端发送内容
ch.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("我是在客户端写的!!!", CharsetUtil.UTF_8),
//地址
new InetSocketAddress(
"localhost",port
))).sync();
//如果超过长时间则表示超时
if(!ch.closeFuture().await(15000)){
System.out.println("查询超时!!!");
}
}
finally {
//优雅的关闭释放内存
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception{
int port = 9999;
if(args.length>0){
try{
port = Integer.parseInt(args[0]);
}
catch (NumberFormatException e){
e.printStackTrace();
}
}
new UdpClient().run(port);
}
public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
//接受服务端发送的内容
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
String body = msg.content().toString(CharsetUtil.UTF_8);
System.out.println(body+"这是服务端发送的内容");
//这里接收到服务端发送的1内容
}
//捕获异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
其实我感觉一定要耐心去看,把自己扎到代码堆里,浸泡几天,就OK了。不错的经历哦,后续还要接着完善,加油!