从零开始学Netty(二)netty为什么成为了高人气的服务器框架

网络通信其实就是干这么几件事
1.建立连接
2.客户端发送数据
3.服务器接受到数据,然后根据数据内容进行处理,然后返回数据
4.不停重复2,3
5.关闭连接

我们为什么要用netty来搭建服务器呢?或者说netty为什么成为了高人气的服务器框架呢?
下面我们就来仔细研究一下吧。

在网络编程中,我们至少得有一台服务器,有一台客户端,两者建立连接,然后进行通信。

在早期,我们的服务器都是采用bio的方式进行交流的。

BIO服务器

//服务器端代码
public class Server {

    protected static final String ip = "127.0.0.1";
    protected static final int port = 6010;

    private static void start() {
        try {
            ServerSocket server = new ServerSocket(port);//1

            while(true) {
                Socket client = server.accept();//2
                System.out.println("accept");
                new Thread(new ServerHandler(client)).start();//3

            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        start();
    }


}


//服务器处理逻辑
public class ServerHandler implements Runnable {
    private Socket client;

    public ServerHandler(Socket client) {
        this.client = client;
    }

    public void run() {
        byte[] buffer = new  byte[1024];
        while (true) {
            int read = 0;
            try {
                read = client.getInputStream().read(buffer);
                if (!(read > 0)) break;
                System.out.println(new String(buffer,0, read));
                client.getOutputStream().write(buffer,0,read);
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }

        }


    }
}

//客户端代码
public class Client {


    public static void main(String[] args) {
        start();
    }

    private static void start() {
        try {
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress(Server.ip, Server.port));

            Thread.sleep(1000);//模拟客户端阻塞
            socket.getOutputStream().write("hello world".getBytes());


            byte[] buffer = new  byte[1024];
            while (socket.getInputStream().read(buffer) > 0) {
                System.out.println(new String(buffer));
                buffer = new  byte[1024];
            }
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

从零开始学Netty(二)netty为什么成为了高人气的服务器框架_第1张图片

特点

上面这就是用BIO实现的服务器和客户端通信,可见服务器和客户端之间是能正常通信,处理业务的,人们在实践中发现,大量的连接其实不是一直在传输消息的,而是阻塞状态。但是以BIO的方式来做的话,每一个客户端连接上来都需要创建一个线程来与客户端保持连接。当有大量连接时,服务器需要浪费大量的内存来保持这些连接(分配给线程)。

这个时候出现了NIO技术,即在请求数据的时候,线程不再阻塞了。

如果数据没有准备好,那么当线程请求时会直接收到没有准备好的信号。当然准备好的话,就会收到准备好了的信号。

所以我们就能用一个线程,将全部的连接保存下来,然后挨个去轮询,当找到某个连接数据准备好后就新建线程来处理这个连个连接。

这个时候我们就不需要在新建大量线程去保持连接了,只需要一个或者几个(当连接数太多的时候,可以适当多几个)就可以维持大量的连接,又因为大量的连接其实是未准备好的,所以处理的连接数据的线程数量不会太多。

在此基础上,操作系统提供了select,poll,epoll来处理上面保持连接的问题。当这些连接准备好你注册好的事件的时候,就会返回,你能拿到与之对应的连接,然后通过连接拿到数据进行处理。这就是java的NIO的本质。

NIO服务器

//NIO实现的服务器端代码
public class Server {

    protected static final String ip = "127.0.0.1";
    protected static final int port = 6010;

    public static void main(String[] args) {
        start();
    }

    private static void start() {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();

            ssc.bind(new InetSocketAddress(ip, port));

            ssc.configureBlocking(false);
            Selector selector = Selector.open();

            ssc.register(selector, SelectionKey.OP_ACCEPT);

            while(true) {
                selector.select(); //阻塞方法 若返回则说明有 感兴趣的事件准备好了
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = keys.iterator();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
                while (keyIterator.hasNext()) {//处理客户端连接
                    SelectionKey key = keyIterator.next();


                    if (key.isAcceptable()) {

                        ssc = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = ssc.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("有客户端连接!");

                    }
                    if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        readBuffer.clear();//清除缓冲区
                        int numRead;
                        try{
                            numRead = socketChannel.read(readBuffer);
                            if (numRead == -1) {
                                socketChannel.close();
                                key.cancel();
                                continue;
                            }
                        }catch (IOException e){
                            key.cancel();
                            socketChannel.close();
                            continue;
                        }
                        String msg = new String(readBuffer.array(),0,numRead);
                        System.out.println("接收到消息" + msg);
                        socketChannel.register(selector,SelectionKey.OP_WRITE);


                    }
                    if (key.isWritable()) {
                        String msg = "hello";
                        SocketChannel channel = (SocketChannel) key.channel();
                        System.out.println("发送消息:" + msg);

                        sendBuffer.clear();
                        sendBuffer.put(msg.getBytes());
                        sendBuffer.flip();//由写变为读,反转
                        channel.write(sendBuffer);
                        channel.register(selector,SelectionKey.OP_READ); //注册读操作

                    }
                    keyIterator.remove(); //移除当前的key
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


}


//客户端
public class Client {
    protected static final String ip = "127.0.0.1";
    protected static final int port = 6010;

    public static void main(String[] args) {
        start();
    }

    private static void start() {

        try {
            SocketChannel sc = SocketChannel.open();

            sc.connect(new InetSocketAddress(ip, port));//建立连接

            ByteBuffer readBuffer = ByteBuffer.allocate(1024);//缓冲区
            ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
            sendBuffer.put("hello".getBytes());//将要发送的数据写入buffer

           
            sc.write(sendBuffer);//发送消息
            int length = sc.read(readBuffer);//读取信息
            String s = new String(readBuffer.array(), 0, length);
            System.out.println(s);

            sc.close();

        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

客户端可以用BIO的客户端,也可以NIO的SocketChannel来实现。
运行效果如下
从零开始学Netty(二)netty为什么成为了高人气的服务器框架_第2张图片

用原生NIO来写代码实在是太过繁琐,而且很容易出现一些不知名的bug(因为知识的漏洞)。所以有人将原生NIO进行的封装,netty这个项目就此诞生了。

Netty 服务器

用netty来实现服务器和客户端


//服务器端
public class NettyServer {

    private static final String IP = "127.0.0.1";
    private static final int PORT = 6010;
    private static final int BOSSNUM = Runtime.getRuntime().availableProcessors() * 2;
    private static final int WKNUM = 100;
    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BOSSNUM);
    private static final EventLoopGroup workGroup = new NioEventLoopGroup(WKNUM);
    public static void start() throws InterruptedException {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<Channel>() {
                    protected void initChannel(Channel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4));
                        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                        pipeline.addLast(new TCPChannelHandler());
                    }
                });

        ChannelFuture channelFuture = serverBootstrap.bind(IP, PORT).sync();

        channelFuture.channel().closeFuture().sync();

        System.out.println("start");

    }

    public static void main(String[] args) throws InterruptedException {
        start();
    }
}


//服务器端业务处理代码
public class TCPChannelHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel Active");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("收到" + msg);
        ctx.channel().writeAndFlush("Hello Clent");

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}



//客户端
public class NettyClient implements Runnable{
    public void run() {
        NioEventLoopGroup group = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel ch) throws Exception {

                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new LengthFieldPrepender(4));
                        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                        pipeline.addLast(new TCPClient());
                    }
                });
        try {
        ChannelFuture sync = bootstrap.connect("127.0.0.1", 6010).sync();
        sync.channel().writeAndFlush("hello");



        Thread.sleep(100);
        group.shutdownGracefully();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        Thread thread = new Thread(new NettyClient());
        thread.start();
    }
}



//客户端业务逻辑处理代码
public class TCPClient extends ChannelInboundHandlerAdapter {


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        System.out.println("active");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("receive: " + msg);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}

Netty封装了NIO,降低了NIO实现网络编程的难度,对比代码量就可以看出。而且在代码的可读性上也大大提高了。

小结

不是说NIO一定比BIO好,只是说现在网络环境是连接数量多,传输数据量少(浏览网页)为主,NIO更加适合,假如你的服务器连接人数不多,又是传输数据量比较大的长连接的话,那么BIO肯定比NIO更加适合你。
我说这句话的目的是希望大家不会要盲目推崇NIO,具体问题具体分析。

而Netty是一个基于多路复用io模型的NIO网络编程框架,屏蔽了很多NIO中的陷进,能更加容易的搭建自己的服务器(不一定是http服务器,可以自己实现自己的协议。比如dubbo)。

下一篇正式进入Netty的学习。

你可能感兴趣的:(Netty)