NIO示例程序------回文

1.客户端程序

package cn.fzmili.archetype.nio;

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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * 代码清单 2-4 客户端的主类
 *
 * @author <a href="mailto:norman.maurer@gmail.com">fzmili</a>
 * 程序基本结构: start()---------功能实现的主类。主类中的异常全部由main函数来实现。start()函数只是实现正常逻辑。
 * main()----------实现程序的启动。
 */
public class EchoClient {

    private InetSocketAddress address;
    private ByteBuffer wBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
    private Charset charset = Charset.forName("UTF-8");//设置发送的字符编码

    public EchoClient(String host, int port) {
        this.address = new InetSocketAddress(host, port);
    }

    public EchoClient(InetSocketAddress address) {
        this.address = address;
    }

    public void start() throws Exception {//非阻塞连接版本。
        ///初始化//
        final SocketChannel channel;
        channel = SocketChannel.open();//创建但不连接。
        //channel.configureBlocking(false);//设置为非阻塞模式,阻塞方法将立即返回
        channel.connect(address);//阻塞模式和非阻塞模式的区别在于,非阻塞模式总是返回false,阻塞模式返回值根据实际情况
        //非阻塞模式
        if(channel.isConnectionPending())
            channel.finishConnect();//非阻塞模式下,连接进行时,调用这个完成连接,连接失败时调用,抛出异常。
        if(!channel.isConnected()){
             System.out.println("connecting fail");
             return;
        }
        //获取用户输入
        System.out.println("Connecting Success!");
        Scanner scanner = new Scanner(System.in);//scanner应该是一个单独的线程。
        String text=scanner.nextLine();//到第一个换行符为止。
        text+='\n';//因为scanner.nextLine()不包含换行符,但服务器需要根据换行符来判断返回。
        System.out.println("发送的数据:"+text);
        scanner.close();
        System.in.close();
        //网络交互部分。
        wBuffer.put(charset.encode(text));
        wBuffer.flip();
        channel.write(wBuffer);
        int readBytes=channel.read(rBuffer);//关键问题在于这个阻塞,一直没有数据
        System.out.println("接收的数据:" + getString(rBuffer));
        rBuffer.clear();
        channel.close();
    }

    public static String getString(ByteBuffer buffer) {
        String string = "";
        try {
            for (int i = 0; i < buffer.position(); i++) {
                string += (char) buffer.get(i);//使用绝对定位,不会移动指针。
            }
            return string;
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    public static void main(String[] args) throws NumberFormatException {
        InetSocketAddress address;
        switch (args.length) {
            case 0://默认IP,默认端口,用于测试
                address = new InetSocketAddress("127.0.0.1", 9999);
                break;
            case 2://正常情况,如果有问题,则产生异常
            {
                String ip = args[0];
                int port = Integer.parseInt(args[1]);
                address = new InetSocketAddress(ip, port);
            }
            default://其他情况
                System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <port>");
                return;
        }
        try {
            new EchoClient(address).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("---主进程结束----");
    }
}

2.服务器程序

package cn.fzmili.archetype.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class EchoServer {

    private InetSocketAddress address;
    private final int bufferSize = 2048;

    public EchoServer(String host, int port) {
        this.address = new InetSocketAddress(host, port);
    }

    public EchoServer(InetSocketAddress address) {
        this.address = address;
    }

    public void start() {
        ///init()初始化
        Selector selector = null;
        try {
            //创建Channel和Selector
            ServerSocketChannel channel = ServerSocketChannel.open();
            selector = Selector.open();
            channel.configureBlocking(false);//将通道设置为非阻塞
            channel.bind(this.address);
            /*
            *将Channel注册到selector,并指定感兴趣的事件
            *有4种事件:electionKey.OP_CONNECT  SelectionKey.OP_ACCEPT  SelectionKey.OP_READ    SelectionKey.OP_WRITE 
            *OP_CONNECT----用于客户端,
            *OP_ACCEPT-----用于服务器
            *OP_READ & OP_WRITE--------服务器/客户端通用
             */
            channel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("Server started .... port:" + address.getPort());
        } catch (Exception e) {
            System.out.println("Error-" + e.getMessage());
            return;
        }
        //处理连接///
        Map<Integer, ByteBuffer> buffers = new HashMap<>();//用于存储为每个Channel分配的ByteBuffer,方便回文。
        try {
            int id = 0;
            while (true) {//对于服务器需要不断的检查是否有数据。
                Thread.sleep(1 * 1000);
                /*
                *阻塞,直到有就绪事件为止。
                *有多个客户端连接都阻塞在这个地方。每个客户端都是一个Channel。
                *一个selector可以处理多个channell。这也就是提高并发数的关键。
                *select()不阻塞的时候,代表有需要读写
                 */
                selector.select();
                ///处理init()中选择的建/
                Set<SelectionKey> readySelectionKey = selector.selectedKeys();
                Iterator<SelectionKey> it = readySelectionKey.iterator();
                //处理每一个连接
                while (it.hasNext()) {
                    SelectionKey channelKey = it.next();
                    //Accept---------客户端请求TCP连接
                    if (channelKey.isAcceptable()) {
                        /*
                        * 获取通道 接受连接,
                        * 设置非阻塞模式(必须),同时需要注册 读写数据的事件,这样有消息触发时才能捕获
                        *每个客户端的Channel是由Selector.channel()获取的。SelectionKey就是通道的指针。
                         */
                        ServerSocketChannel channel = (ServerSocketChannel) channelKey.channel();
                        channel.accept()
                                .configureBlocking(false)
                                .register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)//向selector注册channel,返回与这个Chanenl关联的SelectionKey,该Key就是Channel的指针,代表该Channel。
                                .attach(++id);//向SelectionKey添加一个标识,由于是Key就代表Channel,所以也是Channel标识。
                        /*
                            上面的程序分解以便理解:
                        channel.accept();
                        channel.configureBlocking(false);
                        SelectionKey key=channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//向selector注册channel,返回与这个Chanenl关联的SelectionKey,该Key就是Channel的指针,代表该Channel。
                        key.attach(++id);//向SelectionKey添加一个标识,由于是Key就代表Channel,所以也是Channel标识。
                        
                         */
                        buffers.put(id, ByteBuffer.allocate(bufferSize));//为每个连接准备一个缓冲区,并和Channel关联起来。
                        System.out.println("ChannelID:" + id+"\tConnected");
                    }
                    if (channelKey.isReadable()) {// 读数据,所有的Channel都是在这读的,不要想象只有一个Channel。
                        SocketChannel clientChannel = (SocketChannel) channelKey.channel();
                        ByteBuffer buf = buffers.get((Integer) channelKey.attachment());
                        try {
                            int readBytes = clientChannel.read(buf);
                            if(readBytes==-1) continue;// read=-1代表没有东西读取,这时候没有必要输出。
                            System.out.print("ChannelID:" + channelKey.attachment()+ "\tInput");
                            System.out.print("\tReadBytes:" + readBytes);
                            System.out.println("\tContent:" + getString(buf));//getString采用绝对读写,不影响读写指针。
                        } catch (Exception e) {
                            clientChannel.close();
                            channelKey.cancel();
                            System.out.println("channelID:"+channelKey.attachment()+"\tClosed");
                            continue;
                        }

                    }
                    if (channelKey.isWritable()) {// 写数据
                        // System.out.println(channelKey.attachment()+ " - 写数据事件");
                        SocketChannel clientChannel = (SocketChannel) channelKey.channel();
                        ByteBuffer buf = buffers.get((Integer) channelKey.attachment());
                        /*以下这段代码是实现回文的主要代码
                        *buf.position----------指向下一个要写入的位置
                        *buf.get(index)------------获取当前最后一个字符。
                         */
                        if (buf.position() > 0) {//该判断保证不会超出ByteBuffer的下界限,上界限没时间搞。
                            if (buf.get(buf.position() - 1) == '\n') {
                                buf.flip();
                                clientChannel.write(buf);
                                buf.clear();
                            }
                        }

                    }
                    // 必须removed 否则会继续存在,下一次循环还会进来,
                    // 注意removed 的位置,针对一个.next() remove一次
                    it.remove();
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
            System.out.println("Error - " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (selector != null) {
                try {
                    selector.close();
                } catch (IOException e) {
                    System.out.println("Error-" + e.getMessage());
                    e.printStackTrace();
                }
            }
        }
    }

    //一个实用的类,用于转换字符串。
    public static String getString(ByteBuffer buffer) {
        String string = "";
        try {
            for (int i = 0; i < buffer.position(); i++) {
                string += (char) buffer.get(i);//使用绝对定位,不会移动指针。
            }
            return string;
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    public static void main(String[] args) throws Exception {
        InetSocketAddress address;
        switch (args.length) {
            case 0://默认IP,默认端口,用于测试
                address = new InetSocketAddress("127.0.0.1", 9999);
                break;
            case 1://指定绑定端口(IP绑定到所有端口)
            {
                //设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
                int port = Integer.parseInt(args[0]);
                address = new InetSocketAddress(9999);
                break;
            }
            case 2://正常情况,如果有问题,则产生异常
            {
                String ip = args[0];
                int port = Integer.parseInt(args[0]);
                address = new InetSocketAddress(ip, port);
            }
            default://其他情况
                System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
                return;
        }

        new EchoServer(address).start();
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值