Java--网络编程之NIO

一、简介
1、基本概念区分
(1)同步:Java自己处理IO读写。
(2)异步:Java将IO读写委托给OS处理,将数据缓冲区地址和大小传给OS,完成后OS通知Java处理。
(3)阻塞:调用后会一直阻塞到读写完成后再返回。
(4)非阻塞:如果不能立马读写,调用后就会马上返回,当IO事件分发器通知可再次读写时再进行读写,不断循环直到读写完成。
2、关于BIO、NIO、AIO模型
(1)BIO:同步并阻塞
服务器的实现模式为:一个连接,一个线程。
缺点:客户端连接数与服务端连接数成正比,容易造成不必要的线程开销,甚至导致内存溢出。
(2)NIO:同步非阻塞
服务器的实现模式为:多个请求,一个线程。请求会注册在IO复用器selector上,多路复用器轮询,直到连接有IO请求时才启动一个线程进行处理。
(3)AIO:异步非阻塞
服务器的实现模式为:多个有效请求,一个线程。客户端的IO请求会由OS先完成再通知服务器应用去启动线程处理。
二、NIO编程
1、NIO与BIO的不同
NIO解决的是BIO的并发问题,在使用BIO时由于每创建一个线程,就要为这个线程分配一定的内存空间,而操作系统本身对线程的总数也会有一定的限制,如果客户端的请求过多,服务端程序可能因为不堪重负而拒绝客户端请求 ,甚至瘫痪。而 NIO中当一个连接创建后,不需要新创新一个线程,这个连接会被注册到复用器上,所有连接只需要一个线程即可 ,这个线程中的复用器会进行轮询,发现连接上有请求时才开启一个线程进行处理。
2、NIO中的三大组件
(1)通道Channel
①类型
FileChannel:从文件中读取数据。
DatagramChannel:通过UDP读取网络中的数据。
SocketChannel:通过TCP读写网络中数据,用来建立TCP连接。
ServerSocketChannel:一般在服务端使用,用于监听新的TCP连接,对每个新进来的连接创建SocketChannel。
②特点
既可以从通道中读取数据,又可以写数据到通道(不同于流,流的读写通常是单向的);通道可以异步地读写。
(2)缓冲区Buffer
缓冲区主要是跟通道Channel打交道,数据总是从Buffer写入到Channel中,或者从Channel读取数据到Buffer。
①类型:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。
②状态变量
capacity:最大容量。
position:当前读写的位置。
limit:缓冲区现存元素的计数。
mark:标记位置,调用mark()来设定mark=postion,调用reset()设定position=mark(标记在未设定前是undefined)。
这四个属性的关系:0<=mark<=position<=limit<=capacity。
③缓冲区相等的必要条件:缓冲区的对象类型相同;对象都剩余同样数量的元素;缓冲区中被get()的剩余数据元素序列必须一致。
④buffer相关方法
allocate(capacity):在堆上创建指定大小的对象空间。
allocateDirect(capacity):在堆外空间创建指定大小的空间。有时buffer作用时间超长,在堆内创建会影响gc效率,在堆外创建的话,不受gc的影响。
wrap(byte()):通过存在的数组创建对象。
wrap(byte[] ,offerset,length):通过存在的数组创建对象。
put():往buffer中写入数据,position指针移动。
flip():读写模式切换,limit指向pos,pos指向mark。//重要
get():pos指针移动。
clear():清空buffer缓存 mark=undefined。 pos=0,limit=capacity。
(3)复用器Selector
能检测一到多个NIO通道,并知道通道是否做好准备。Selector的存在让一个单独的线程可以管理多个Channel,从而管理多个连接。
①监听事件
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
②方法
Selector相关方法:
select():选择器进行监听,有事件则返回,会一直阻塞直到至少有一个事件到达。
select(long timeout):和select()一样,只是设定了最长阻塞时长timeout,单位是毫秒。
SelectionKey相关方法:
channel():将注册到selector中的通道在SelectionKey中也维护一个。
selector():将当前选择器实例在SelectionKey中维护一个。
isValid():当前的selectionKey是否有效。
cancel() : 取消对当前key的关注。
isWritable():是否为可写事件。
isReadable():是否为可读事件。
isConnectable():是否为可连接事件。
isAcceptable():是否为可接受事件。
3、服务端
(1)具体流程
①创建ServerSocketChannel实例;
②绑定端口并将通道设置为非阻塞(如果通道在某个事件上被阻塞,那么服务器就必须等这个事件处理完才能去处理其它事件,选择器失去意义);
③实例化Selector复用器,Selector.open();
④将ServerSocketChannel实例注册到selector复用器上,关注accept事件;
⑤复用器进行监听,有事件则返回,selector.select()会一直阻塞直到至少有一个事件到达;
⑥获得感兴趣事件集合并遍历,判断是否有可接收事件,若有则调用accept(),实例化socketChannel,并设置为非阻塞后注册给复用器;
⑦通过Buffer从Channel中读取数据,最后关闭Selector、SocketChannel、ServerSocketChannel等资源。
(2)代码实现

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

public class NIOServerDemo {
    public static void main(String[] args) {
        try {
            //创建ServerSocketChannel通道实例
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.bind(new InetSocketAddress(8889));
            System.out.println("服务端启动");
            //将channel设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //实例化IO复用器selector
            Selector selector = Selector.open();
            //将serverSocketChannel注册到复用器上,并关注accept事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //监听复用器中是否有事件完成,select()会阻塞,直至有事件完成才返回
            while (selector.select() > 0) {
                //获取完成事件的结果集
                //包含已完成事件的集合
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isAcceptable()) {
                        //有可接受事件完成
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
                        //接收用户的连接
                        SocketChannel socketChannel = serverSocketChannel1.accept();
                        System.out.println("客户端" + socketChannel.getRemoteAddress()+"连接上");
                        //将socketChannel设置为非阻塞
                        socketChannel.configureBlocking(false);
                        //将socketChannel通道注册到复用器上,并监听read事件
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    if (selectionKey.isReadable()) {
                        //有可读事件发生
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //进行读操作,对于Buffer是写操作
                        int read = channel.read(buffer);
                        if (read != -1) {
                            //进行读写模式的切换
                            buffer.flip();
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            String msg = new String(bytes);
                            if (!"".equals(msg)) {
                                //关闭socketChanel实例
                                channel.close();
                            }
                            System.out.println("收到客户端发送的:" + new String(bytes));
                        }
                    }
                }
            }
            //关闭资源
            selector.close();
            serverSocketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4、客户端
(1)具体流程
①实例化SocketChannel,并设置为非阻塞。
②实例化Selector;
③连接服务端,将socketchannel注册到selector,并关注可连接事件;
④监听复用器中已完成的事件,判断集合中的已完成事件是否为可连接事件,并通过finishConnect将可连接事件完成连接;
⑤发送、接收消息,最后关闭资源。
(2)代码实现

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;

public class NIOClientDemo {
    public static void main(String[] args) {
        try {
            //创建SocketChannel实例
            SocketChannel socketChannel = SocketChannel.open();
            //将SocketChannel实例设置为非阻塞
            socketChannel.configureBlocking(false);
            //创建selector实例
            Selector selector = Selector.open();
            //连接服务端,因为前面设置的是非阻塞,所以调用这个方法会立即返回.
            //因此如果返回true就不需要放到select中
            if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 8889))) {
                //将SocketChannel实例注册到选择器中,并关注connect事件
                socketChannel.register(selector, SelectionKey.OP_CONNECT);
                //感兴趣操作是否完成
                selector.select();
//                System.out.println("有可接收事件发生");
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isValid() && key.isConnectable()) {
                        SocketChannel Channel = (SocketChannel) key.channel();
                        //如果连接成功则完成连接
                        if (Channel.isConnectionPending())
                            Channel.finishConnect();
                        System.out.println("客户端连接成功");
                        Channel.configureBlocking(false);
                        //监听SocketChannel读事件。
                        Channel.register(selector, SelectionKey.OP_READ);
                    }
                }
            }
            //设置buffer缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //创建控制台实例
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.print("请输入:");
                String msg = scanner.nextLine();
                //客户端结束条件
                if ("exit".equals(msg)){
                    break;
                }
                //向缓存写入数据
                buffer.put(msg.getBytes());
                buffer.flip();
                //发送数据
                socketChannel.write(buffer);
                //清空缓存
                buffer.clear();
                //监听读事件,没有则阻塞
                selector.select();
                socketChannel.read(buffer);
                //读写模式切换
                buffer.flip();
                //通过缓冲区有效元素大小确定接收数组大小
                byte[] bytes = new byte[buffer.remaining()];
                //从缓存读取数据
                buffer.get(bytes);
                System.out.println("recv:" + msg);
                buffer.clear();
            }
            socketChannel.close();
            selector.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值