Netty网络编程一:NIO

写这些东西的原因,因为本人14年毕业,从事工作也有几年了,中间关于书,一直看了很多,有的甚至看过很多遍,但是总是觉得自己没有熟透一门技术,所以在学习使用技术之余,把自己所学的东西记录下来,便于加深印象,提升自我!

目录

1 传统BIO socket通信
2 NIO 编程

一:传统socket通信

在NIO编程没出来之前,java使用的socket编程时
socket 服务端:

public class OldSocketServer {

    private void start(int port) throws IOException {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(port);
            Socket socket = null;
            while (true){
                socket = serverSocket.accept();
                new Thread(new OldSocketServerHandler(socket)).start();
            }
        }finally {
            System.out.println("关闭服务端...");
            serverSocket.close();
        }
    }

    public static void main(String[] args) throws IOException {
        new OldSocketServer().start(8090);
    }
}

对应的handler处理:

public class OldSocketServerHandler implements Runnable {

    private Socket socket;

    public OldSocketServerHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            String body = null;
            while ((body = in.readLine())!=null){
                System.out.println(" 服务端收到消息 : " + body);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(socket != null){
                try {
                    if(in != null){
                        in.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

socket客户端:

public class OldSocketClient {

    private void connect(String ip, int port) throws IOException {
        Socket socket = null;
        PrintWriter out = null;
        try {
            socket = new Socket(ip, port);
            out = new PrintWriter(socket.getOutputStream(), true);
            out.write("this is socket client to server msg.");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(out !=null){
                out.close();
            }
            if(socket != null){
                socket.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new OldSocketClient().connect("localhost",8090);
    }
}

通过上面简单的一个socket通信问题,可以找出几个问题:
问题1. 对于上面的服务端,当每接一个客户端请求,服务端必须new一个线程处理新的请求,对于java这种线程资源非常珍 贵的语言中,这种设计显示是有问题的。
问题2:对于IO数据的读取上,对于InputStream输入流,当对socket的输入流进行读取操作的时候,线程会一直阻塞,直到读取到数据,或数据读取完毕,又或者发生空指针或I/O异常时。 同样输出流输出数据,OutputStream输出流会把所有的数据全部发送出去或者发生异常才会停止阻塞。显然,同步阻塞这种设计是十分浪费系统资源的。

二:nio编程

NIO是JDK1.4引入的,通过快的形式处理数据。NIO常用的几个概念:

1、Buffer(缓冲区)
在传统的面向流的I/O中,数据是直接写入或读取到流对象中的,而在NIO中,所有的数据都是用缓冲区来处理的。缓冲区实质上就是一个数组,通过定义数据的结构,是的缓冲区能被重复利用。
NIO定义的Buffer的继承关系图:
在这里插入图片描述

缓冲区定义四个属性来提供关于其所包含的数据元素的信息:
1).容量 ( Capacity )
缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能 被改变。
2).上界 ( Limit )
缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
3).位置 ( Position )
下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。
4).标记 ( Mark )
一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position = mark。标记在设定前是未定义的 (undefined)。 这四个属性之间总是遵循以下关系:
0 <= mark <= position <= limit <= capacity
新初始化的Buffer图:
在这里插入图片描述

2、通道Channel
Channel是一个通道,是全双工的,就像是自来水管一样,网络数据通过Channel读取和写入。与流IO的不同之处在意,流IO只能进行读InputStream 或者写OutputStream, 而Channel可读可写。
Channel继承关系类图如下,主要有:ServerSocketChannel, SocketChannel, DatagramChannel
在这里插入图片描述

3、多路复用器Selector
多路复用器Selector提供选择已经就绪的任务的能力。selector会不断的轮询注册在其上的Channel, 如果某个Channel发生读或者写事件,则这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey获取就绪Channel的集合,进行后续的I/O操作。
在JDK中selector使用了epoll()来实现select, 故此没有轮询Channel数量限制。

一个NIO的案例:
服务端代码:

public class NioServer {

    private ByteBuffer readBuf = ByteBuffer.allocate(1024);

    private void start(int port) throws IOException {
        ServerSocketChannel serverSocketChannel = null;
        try{
            // 1、打开ServerSocketChannel,用于监听客户端连接,是所有客户端连接的父管道
            serverSocketChannel = ServerSocketChannel.open();
            // 2、绑定监听端口,设置连接为非阻塞模式
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            // 3、创建Reactor线程,创建多路复用器并启动线程
            Selector selector = Selector.open();
            // 4、将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println(" 服务端开始工作.....................");
            run(selector);
        }finally {
            if(serverSocketChannel!=null){
                serverSocketChannel.close();
            }
        }

    }

    private void run(Selector selector) {
        while(true){
            try {
                //1.让多路复用器开始监听
                selector.select();
                //2.返回多路复用器已经选择的结果集
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                while(keys.hasNext()){
                    SelectionKey key = keys.next();
                    keys.remove();
                    if(key.isValid()){
                        if(key.isConnectable()){
                            System.out.println("connectable....");
                        }
                        if(key.isWritable()){
                            System.out.println("writable.........");
                        }
                        if(key.isAcceptable()){
                            System.out.println("acceptable....");
                            accept(key,selector);//这里的key就是服务器端的Channel的key
                        }
                        if(key.isReadable()){
                            System.out.println("readable....");
                            read(key);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void read(SelectionKey key) {
        try {
            // 1. 清空旧的缓冲区
            readBuf.clear();
            //2.获取之前注册的socket通道对象
            SocketChannel sc = (SocketChannel) key.channel();
            //3.读取数据
            int count = sc.read(readBuf);
            //4.如果没有数据
            if(count == -1){
                key.channel().close();
                key.cancel();
                System.out.println("已无可读数据");
                return;
            }
            //5.有数据则进行读取,读取之前需要进行复位方法(把position和limit进行复位)
            readBuf.flip();
            //6.根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
            byte[] bytes = new byte[readBuf.remaining()];
            //7.接收缓冲区数据
            readBuf.get(bytes);
            //8.打印结果
            String body = new String(bytes).trim();
            System.out.println("Server: " + body);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void accept(SelectionKey key, Selector selector) {
        try {
            //1.获取服务端通道
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //2.执行客户端Channel的阻塞方法
            SocketChannel sc = ssc.accept();
            //3.设置阻塞模式
            sc.configureBlocking(false);
            //4.注册到多路复用器上,并设置读取标识
            sc.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        new NioServer().start(8090);
    }
}
客户端代码:
public class NioClient {

    public static void main(String[] args) {
        SocketChannel sc = null;
        ByteBuffer buf = ByteBuffer.allocate(1024);
        try {
            //打开通道
            sc = SocketChannel.open();
            //进行连接
            sc.connect(new InetSocketAddress("127.0.0.1", 8090));
            //把数据放到缓冲区
            buf.put("fangyouyun".getBytes());
            //复位
            buf.flip();
            //写出数据
            sc.write(buf);
            //清空缓冲区数据
            buf.clear();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                sc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

总结:NIO以多路复用的设计方式,以及Buffer缓冲区的设计,都使得性能得到很大的提升,但是API太复杂了,所以选择Netty,因为Netty是基于NIO再次封装,便于开发。

博客中案例代码:https://download.csdn.net/download/qq_22871607/11072379

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值