今天的目标是记录一个框架的内容。 看了看列表,就从netty入手吧! 计划学习netty还是大致从这三个方面入手:
1.Netty有什么用? 2.netty怎么用? 3. netty的大致原理?
由于文档是英文的,虽然我可以用插件一键翻译。 介于我并不赶时间,因此便手动翻译吧哈哈哈!
注: netty官网并没有给出maven的依赖。 因此这里给出maven的依赖参考:
1.写一个disard服务器。 (世界上最简单的协议不是hello world! 而是discard. 这是一种丢掉所有接收到的数据且不做任何响应的协议。)
package com.automannn.example;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
* @author [email protected]
* @time 2018/11/26 14:01
*/
/*
* 该类继承自 信道绑定处理适配器,它是信道保定处理器的一个实现类。
* 信道处理器提供了多种我们能够覆盖的事件处理方法
* 这里只继承信道绑定处理适配器而不是实现处理器就足够了
* */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
/*
* 我们覆盖了这个 channelRead()方法。
* 这个方法在接收到消息时调用,无论合适从客户端接收到消息。
* 在这个例子中,接收到的消息是 字节缓冲(字节数组)
* 为了实现discard协议,处理器必须忽略接收到的消息。
* bytebuf是一个引用计数对象,它必须明确地被释放,通过release()方法。
* 请记住 释放任何经由这个处理器的引用计数对象并不是处理器的责任。通常通过 ReferenceCountUtil.release()完成
* */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
try{
while (in.isReadable()){
System.out.print((char) in.readByte());
System.out.println(" you typed one char just now!");
System.out.flush();
}
}finally {
ReferenceCountUtil.release(msg);
}
}
/*
* 当一个异常被netty产生便会调用以下方法。 通常可用于响应消息。
* */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
package com.automannn.example;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author [email protected]
* @time 2018/11/26 14:50
*/
public class DiscardServer {
private int port;
public DiscardServer(int port){
this.port = port;
}
public void run() throws Exception{
//Nio事件循环组 是一个 能够处理I/O操作的多线程的事件循环
//netty提供了不同的事件循环组,是西安了不同种类的传输,我们正在实现的是服务端应用,因此有两个循环组
//第一个叫做boss的组,用于接收进入的连接。
//第二个通常叫做worker,处理接受的连接的交通情况,一旦boss接受了连接并且注册这个连接给worker后。
//有多少个线程被使用,以及他们如何被映射到创建的信道,取决于事件循环组的实现和构造器配置
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//serverBootstrap是一个用于设置服务器的辅助类。 你可以直接设置信道。
//但是请注意,这是一个枯燥的过程,我们通常不必这样做
//这里,我们指定了用NioServerSocketChannel这个类,他可以用于实例新的信道去接受进入的请求
//ChannelInitializer是一个特殊的处理器,它的目的是帮助用户去配置一个新的信道。 它更像你想要去配置新信道的
// 的管道,通过加入一些处理器是实现你的联网应用。
//你也可以指定参数去定制这个信道实现。 我们正在写一个TCP/IP服务器,所以我们可以设置sokcet选项。
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
}).option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
package com.automannn.example;
/**
* @author [email protected]
* @time 2018/11/26 14:00
*/
public class Main {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length>0){
port = Integer.parseInt(args[0]);
}
new DiscardServer(port).run();
}
}
运行测试:
打开命令行:输入命令: telnet localhst 8080; 然后键入数据:
2.写一个echo服务器。
package com.automannn.example.echo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @author [email protected]
* @time 2018/11/26 16:20
*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.write("->");
ctx.write(msg);
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
修改discard服务器的处理器实现:
运行测试:
每个字符被打印了两遍。 其中第一个是命令行本身所显示的。 另一个是回写的。 通过断点调试的时候可以证明。 此外,不知道为什么第一个字符不符合这个规律。 因为它只显示了一遍。
3.写一个时间服务器。
package com.automannn.example.time;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @author [email protected]
* @time 2018/11/26 16:51
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
/*
* channelActive()方法将一个链接建立并且生成通道的时候被执行。 在当前方法中我们写入了一个32位整型数据代表当前时间
*
* */
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
//为了发送消息,我们需要分配一个新的缓冲,它将装载这个信息。 我们将要发送写入一个32位的整型数据,因此,我们需要
// 至少4字节容量的ByteBuf。 获得当前的ByteBuf分配器通过alloc()方法,然偶分配
final ByteBuf time = ctx.alloc().buffer(4);
//ByteBuf 具有两个指针,一个用于读,一个用于写。 这样对我们来说就方便了很多。因为没有了flip操作
time.writeInt((int)(System.currentTimeMillis()/1000L + 2208988800L));
//一个ChannelFuture 代表了一个还没有发发生的I/O操作。 这意味着,任何请求操作都可能还没有形成。因为在netty
// 中,所有的操作都是异步的。
final ChannelFuture f = ctx.writeAndFlush(time);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
assert f == future;
ctx.close();
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
修改discard服务的处理类:
运行测试,我们需要用到两个环境:
1.unix体系操作系统; 2.rdate命令。
注意,此时我将main中的port改为了37,因为这是rdate服务器的默认端口。 具体修改端口的命令试了一些没有成功。于是就只好这样。
为了解决这个问题,下面将实现一个time客户端。
4.写一个时间客户端。
package com.automannn.example.timeClient;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @author [email protected]
* @time 2018/11/26 18:38
*/
public class TimeClient {
private int port;
private String host;
public TimeClient(int port,String host){
this.port = port;
this.host = host;
}
public void execute() throws InterruptedException {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE,true);
b.handler(new ChannelInitializer() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture f = b.connect(host,port).sync();
f.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new TimeClient(8080,"localhost").execute();
}
}
package com.automannn.example.timeClient;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* @author [email protected]
* @time 2018/11/26 18:38
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf m = (ByteBuf) msg;
try {
long currentTimeMillis = (m.readUnsignedInt()-2208988800L)*1000L;
System.out.println(new Date(currentTimeMillis));
ctx.close();
}finally {
m.release();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
运行测试:
精力问题,今天累了。 参考协议如何工作。地址在:这里。 具体的应用层代码如何实现我有以下一些疑惑:
1.服务端工作线程和何时被启动? 怎样启动? 如何释放?
2.服务端主线程如何运行?线程间如何如何调度?
3.一个抽象连接的生命周期?
这些问题将在以后进行补充。 今天就这样吧!