Java NIO简单实例(入门)

 最近学习了一下Java NIO的开发,刚开始接触selector,觉得有点绕,弄的有点晕,所以在这里写几个简单的例子,记录一下,也与大家分享一下,对刚开始学习NIO的同学,希望有一些帮忙。大神就请多指点了。
 开发稳定NIO对工程师的要求很高,NIO本身也存在很多的BUG,本文的例子只简单的帮助简单NIO的一些概念,对于一些例如TCP粘包/拆包等问题,不予以考虑。
 对于NIO的一些概念什么的,就不在这里阐述了,给大家推荐几个文章,可以参考一下,里面有详细的解释。
 Java NIO基本概念:
     (1) http://ifeve.com/java-nio-all/
     (2)http://blog.csdn.net/jeffleo/article/details/54695959?locationNum=6
 Java NIO开发的注意事项:
     (1)http://blog.csdn.net/martin_liang/article/details/41224503

实例一:不使用selector实现端口通信

import org.apache.log4j.Logger;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SocketTimePrint {
    private static final Logger logger = Logger.getLogger(SocketTimePrint.class);

    public static void main(String[] args) throws Exception{
        Thread server = new Thread(new ServerMsgNoselector());
        Thread client = new Thread(new ClientMsg());

        logger.info("Beginning to start the server for message");
        server.start();
        Thread.sleep(100);
        logger.info("Beginning to start the client for message");
        client.start();
    }
}

class ClientMsg implements Runnable {
    private static final Logger logger = Logger.getLogger(ClientMsg.class);

    @Override
    public void run() {
        try {
            SocketChannel socket = SocketChannel.open();
            socket.connect(new InetSocketAddress("127.0.0.1", 9999));
            socket.configureBlocking(false);
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            while (true) {
                String datestr = format.format(new Date());
                ByteBuffer buff = ByteBuffer.wrap(datestr.getBytes());
                socket.write(buff);
                Thread.sleep(2000);
            }
        } catch (IOException | InterruptedException ie) {
            ie.printStackTrace();
        }
    }
}

class ServerMsgNoselector implements Runnable {
    private static final Logger logger = Logger.getLogger(ServerMsgNoselector.class);

    @Override
    public void run() {
        try {
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            ServerSocket socket = serverChannel.socket();
            socket.bind(new InetSocketAddress(9999));
            serverChannel.configureBlocking(true);

            SocketChannel client = serverChannel.accept();
            client.configureBlocking(true);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while(true) {
                int len = client.read(buffer);
                if (len > 0 && buffer.hasArray()) {
                    String msg = new String(buffer.array(), 0, len);
                    logger.info("The recvicing message is " + msg);
                } else {
                    logger.info("Waiting for message ...");
                }
                buffer.clear();
            }
        }catch(Exception ie) {
            ie.printStackTrace();
        }
    }
}

 例子很简单,分别在两个进程中启动一个client与一个server,通过9999端口进行通信。服务端的channel都设置为了阻塞模式。该例子中,只有一个客户端与一个server,而当在实际应用时,client会有很多连接,我可能也不只监听一个端口,这时就会出现各种问题(可能看一下《Netty权威指南》里面对IO的基本概念及Java IO的演进有一个大致的介绍),而selector就是为了解决这些问题,以达到更高的性能。

实例二:基于selector实现的通信

import org.apache.log4j.Logger;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;

public class SocketSCMode {
    private static final Logger logger = Logger.getLogger(SocketSCMode.class);

    public static void main(String[] args) throws Exception{
        SocketSCMode leo = new SocketSCMode();
        leo.exe();
    }

    public void exe() throws InterruptedException{
        Thread server = new Thread(new ServerMsg());
        Thread client = new Thread(new ClientMsg());

        logger.info("Beginning to start the server for message");
        server.start();
        Thread.sleep(100);
        logger.info("Beginning to start the client for message");
        client.start();
    }

    class ClientMsg implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel socket = SocketChannel.open();
                socket.connect(new InetSocketAddress("127.0.0.1", 9999));
                socket.configureBlocking(false);
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                while (true) {
                    String datestr = format.format(new Date());
                    ByteBuffer buff = ByteBuffer.wrap(datestr.getBytes());
                    //logger.info("Sending the message " + datestr);
                    socket.write(buff);
                    Thread.sleep(2000);
                }
            } catch (IOException | InterruptedException ie) {
                ie.printStackTrace();
            }
        }
    }

    class ServerMsg implements Runnable {
        @Override
        public void run() {
            logger.info("Enter the server thread");
            try {
                ServerSocketChannel serverChannel = ServerSocketChannel.open();
                ServerSocket socket = serverChannel.socket();
                socket.bind(new InetSocketAddress(9999));
                serverChannel.configureBlocking(false);
                Selector selector = Selector.open();
                SelectionKey serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
                long flag = 1L;

                while (true) {
                    int count = selector.select();
                    if (count > 0) {
                        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                        while (iter.hasNext()) {
                            SelectionKey key = iter.next();
                            logger.info("Flag-->" + flag + "   The interest is " + key.interestOps());
                            if (key.isAcceptable()) {
                                logger.info("Flag-->" + flag + "   The server accept one requesting");
                                iter.remove();
                                ServerSocketChannel server = (ServerSocketChannel) key.channel();
                                SocketChannel client = server.accept();
                                client.configureBlocking(false);
                                client.register(selector, SelectionKey.OP_READ);
                            } else if (key.isReadable()) {
                                iter.remove();
                                logger.info("Flag-->" + flag + "   Reading the mesage");
                                SocketChannel client = (SocketChannel) key.channel();
                                ByteBuffer buff = ByteBuffer.allocate(1024);
                                int len = client.read(buff);
                                if (len > 0 && buff.hasArray()) {
                                    byte[] arr = buff.array();
                                    String msg = new String(arr, 0, len);
                                    logger.info("Flag-->" + flag + "   The recevied message is " + msg);
                                }
                            }
                        }
                    }
                    flag++;
                }
            } catch (IOException ie) {
                ie.printStackTrace();
            }
        }
    }
}

 逻辑上与第一个例子完全一样,要注意的是iter.remove()这个操作。
 在该例子中,把if (key.isAcceptable())下的remove操作去掉,会报一个空指针异常。过程如下:
 1、把ServerSocketChannel注册到selector上,启动client
 2、第一次select,发现ServerSocketChannel是acceptable的,即有已选择的键(不然进程会阻塞在select方法上),因些,返回遍历SelectionKey的iter(已选择键的集合)
 3、进入if (key.isAcceptable())下的代码块,通过accept返回一个SocketChannel与client进行通信,并把这个SocketChannel注册到selector上。遍历结束
 4、此时server端接收了client的连接,client开发发送数据。server再执行select方法,发现SocketChannel是Readable,再次返回已选择键的集合
 5、由于第一次遍历,ServerSocketChannel没有从已选择键的集合去掉,并且,selector不会更新已选择键的集合中键的read集合,因此,遍历iter时,会再次进入if (key.isAcceptable()),而此时,没有client进行连接请求,因些,accept方法返回的是一下null,也就出现了空指针异常。

 如果我们去掉程序中的第二个remove会发生什么?进程会一直阻塞在select方法中,因为select方法是增量的,已在已选择键的集合中的channel,不会去再处理。也就是说,select方法,只有当已选择键的集合有新元素添加时才会返回(这么理解有点不准确。。。)

实例三:多人聊天室

 这个例子是参照别人的代码写的,当然,没人家写的好,修修补补的,总算能按大概的意思运行了,提供给大家做反面教材吧(-_-!!!)

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.*;

public class MultiplayerChat {

    static {
        Logger.getLogger(ServerMsgRoom.class).setLevel(Level.ERROR);
        Logger.getLogger(ClientMsgRoom.class).setLevel(Level.INFO);
    }

    public static void main(String[] args) throws Exception{
        Thread client1 = new Thread(new ClientMsgRoom(true), "leo");
        Thread client2 = new Thread(new ClientMsgRoom(true), "Makukin");
        Thread client3 = new Thread(new ClientMsgRoom(true), "Forrestleo");

        Thread server = new Thread(new ServerMsgRoom(), "ServerRoom");

        System.out.println("Start server ...");
        server.start();
        Thread.sleep(100);
        System.out.println("Start client");
        client1.start();
        client2.start();
        client3.start();
        server.join();
    }

    public static boolean canReadable(SocketChannel channel) {
        return (channel.validOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ;
    }

    public static boolean canWriteable(SocketChannel channel) {
        return (channel.validOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
    }
}

class ClientMsgRoom implements Runnable{
    private static final Logger logger = Logger.getLogger(ClientMsgRoom.class);
    private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

    private String name;
    private String HOST;
    private int PORT;
    private ByteBuffer buff;
    private boolean pringFlag;

    public ClientMsgRoom(boolean flag) {
        HOST = "127.0.0.1";
        PORT = 9999;
        buff = ByteBuffer.allocate(1024);
        pringFlag = flag;
    }

    @Override
    public void run() {
        name = Thread.currentThread().getName();
        try {
            SocketChannel client = SocketChannel.open();
            client.connect(new InetSocketAddress(HOST, PORT));
            client.configureBlocking(false);

            String headMsg = "Register : " + name + "\n";
            logger.info(headMsg);
            buff.put(headMsg.getBytes());
            buff.flip();
            while(buff.hasRemaining()) {
                client.write(buff);
            }
            logger.info("Register the client ... ");
            while(true) {
                boolean flag = false;
                if(MultiplayerChat.canReadable(client)){
                    int len = 0;
                    buff.clear();
                    while((len = client.read(buff)) > 0) {
                        String msg = new String(buff.array(), 0, len);
                        if(pringFlag)
                            System.out.println(name + "-->\n" + msg);
                        flag = true;
                    }
                }

                if(flag && MultiplayerChat.canWriteable(client)) {
                    String reMsg = name + "'s time is " + format.format(new Date()) + "\n";
                    //logger.info("Sending message  " + reMsg);
                    buff.clear();
                    buff.put(reMsg.getBytes());
                    buff.flip();
                    while(buff.hasRemaining()) {
                        client.write(buff);
                    }
                }
                Thread.sleep(5000);
            }
        }catch(IOException | InterruptedException ie) {
            ie.printStackTrace();
        }
    }
}

class ServerMsgRoom implements Runnable {
    private static final Logger logger = Logger.getLogger(ServerMsgRoom.class);

    private int PORT;
    private ByteBuffer buff;
    private Vector<SelectionKey> chaters;

    public ServerMsgRoom() {
        PORT = 9999;
        buff = ByteBuffer.allocate(1024);
        chaters = new Vector<>();
    }

    @Override
    public void run() {
        try {
            ServerSocketChannel server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.socket().bind(new InetSocketAddress(PORT));
            Selector selector = Selector.open();
            server.register(selector, SelectionKey.OP_ACCEPT);

            while(true) {
                int count = selector.select();
                if(count <= 0)
                    continue;

                logger.info("Begin to ack the keys and iter's size is " + count);
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()) {
                    SelectionKey key = iter.next();
                    if(key.isAcceptable()) {
                        ServerSocketChannel serverChannel = (ServerSocketChannel)key.channel();
                        SocketChannel socket = serverChannel.accept();
                        logger.info("Add one register");
                        socket.configureBlocking(false);
                        SelectionKey rkey = socket.register(selector, SelectionKey.OP_READ);
                        chaters.add(rkey);
                    }else if(key.isValid() && key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel)key.channel();
                        int len = 0;
                        StringBuilder builder = new StringBuilder();
                        buff.clear();
                        while((len = clientChannel.read(buff)) > 0) {
                            String msg = new String(buff.array(), 0 , len);
                            builder.append(msg);
                        }
                        logger.info("PrintMessage " + builder.toString());
                        Iterator<SelectionKey> iterWriter = chaters.iterator();
                        while(iterWriter.hasNext()) {
                            SelectionKey keyWriter = iterWriter.next();
                            List<String> list = (List<String>) keyWriter.attachment();
                            if (list == null) {
                                list = new ArrayList<>();
                                keyWriter.attach(list);
                            }
                            list.add(builder.toString());
                        }
                        key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
                    }else if(key.isValid() && key.isWritable()) {
                        List<String> list = (List<String>) key.attachment();
                        SocketChannel writer = (SocketChannel) key.channel();
                        if(list != null) {
                            logger.info("The attaching list is " + list.toString());
                            Iterator<String> iterReader = list.iterator();
                            while(iterReader.hasNext()) {
                                String message = iterReader.next();
                                buff.clear();
                                buff.put(message.getBytes());
                                buff.flip();
                                while(buff.hasRemaining()) {
                                    writer.write(buff);
                                }
                                logger.info("Writing message is " + message);
                                iterReader.remove();
                            }
                        }else{
                            logger.warn("The list is null");
                        }
                        key.interestOps(SelectionKey.OP_READ);
                    }
                    iter.remove();
                }
            }
        }catch(IOException ie) {
            ie.printStackTrace();
        }

    }
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值