Java网络编程 Ch11非阻塞IO

package ch11;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

public class ChargenClient {

    public static int DEFAUL_PORT = 19;

    public static void main(String[] args) {
        if(args.length==0) {
            System.out.println("Usage: java ChargenClient host [port]");
            return;
        }

        int port;
        try {
            port = Integer.parseInt(args[1]);
        }catch(RuntimeException ex) {
            port = DEFAUL_PORT;
        }

        try {
            SocketAddress address = new InetSocketAddress(args[0], port);
            //阻塞打开方式。这条语句没有执行成功,就不能执行下面语句。
            SocketChannel client = SocketChannel.open(address);

            //可以直接写入通道本身。已经清楚74个字符。allocate固定下大小。
            ByteBuffer buffer = ByteBuffer.allocate(74);
            //将System.out封装在通道里面,就可以完全利用通道。
            WritableByteChannel out = Channels.newChannel(System.out);

            //read读取通道中数据,填充缓冲区。并返回成功读取并存储在缓冲区的字数,
            client.configureBlocking(true);
            while(client.read(buffer)!=-1) {
                //回绕缓冲区。因为写入完成时,position指向数据最后。
                buffer.flip();
                //写出到输出
                out.write(buffer);
                //清空缓冲区,才能下次写入
                buffer.clear();
            }

          /*
            //非阻塞
            client.configureBlocking(false);
            //读取代码需要重写
            while (true) {
                    int n = client.read(buffer);
                    if(n>0) {
                        buffer.flip();
                        out.write(buffer);
                        buffer.clear();
                    }//非阻塞时,服务器发生故障才会出现-1
                    else if(n==-1) {
                        break;
                    }
            }
*/          
        }catch(IOException ex) {
            ex.printStackTrace();
        }
    }
}

一个示例服务器

package ch11;

import java.io.IOException;
//IP地址加端口号。InetAddress是IP地址
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.util.*;

public class ChargenServer {
    public static int DEFAULT_PORT = 19;

    public static void main(String[] args) {
        int port;
        try {
            port = Integer.parseInt(args[0]);
        }catch (RuntimeException e) {
            port = DEFAULT_PORT;
        }
        System.out.println("Listening for connection on port "+port);
        /**用于输入**********************************************/
        byte[] rotation = new byte[95*2];
        for(byte i=' '; i<='~'; i++) {
            rotation[i-' '] = i;
            rotation[i+95-' '] = i;
        }
        /****创建服务器通道,创建Selector*************************/
        ServerSocketChannel serverChannel;
        Selector selector;
        try {
            //静态工厂方法,创建新的ServerSocketChannel对象
            serverChannel = ServerSocketChannel.open();
            // socket方法获取Channel的对等端对象。
            ServerSocket ss= serverChannel.socket();
            //将通道绑定到端口。
            InetSocketAddress address = new InetSocketAddress(port);
            // 也可以不获取ServerSocket。serverChannel.bind(new InetSocketAddress(19));
            ss.bind(address);
            //在accept之前调用。设置非阻塞模式。此时accept会立即返回null,要进行检查。
            serverChannel.configureBlocking(false);
            //Selector静态工厂方法
            selector = Selector.open();
            //通道向该selector注册,并添加关注的操作。
            serverChannel.register(selector,  SelectionKey.OP_ACCEPT);
        }catch (IOException e) {
                e.printStackTrace();
                return;
        }

      /******************* 业务处理  ,如果有线程就绪**********************/
        while(true) {
            try {
                //检查是否有可以操作的数据。
                selector.select();
            }catch (IOException  e) {
                    e.printStackTrace();
                    break;
            }

            //返回就绪通道的SelectionKey对象。
            Set<SelectionKey> readKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readKeys.iterator();
            while(iterator.hasNext()) {
                //处理后删掉
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    //判断就绪的是什么通道

                  //如果是服务器,准备监听,创建新Socket通道添加到Selector
                    if(key.isAcceptable()) {
                        //获取就绪通道
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        //监听
                        SocketChannel client = server.accept();
                    System.out.println("Accepted connection from "+client);
                    client.configureBlocking(false);
                      //将Socket添加到Selector
                    SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE);
                    ByteBuffer buffer = ByteBuffer.allocate(74);
                    buffer.put(rotation,0,72);
                    buffer.put((byte) '\r');
                    buffer.put((byte) '\n');
                    buffer.flip();
                      //Attaches the given object to this key。。添加附件。attachment()获取附件
                    key2.attach(buffer);
                    }else if(key.isWritable()) {
                        //获取就绪通道
                        SocketChannel client = (SocketChannel) key.channel();
                        //获取附件,转为ByteBuffer
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        //hasRemaining检查是否还有没向外输出的数据。并不是判缓冲区空!!而是position和limit的关系
                        //如果被读取完了,position=limit;但此时,缓冲区有数据。
                        if(!buffer.hasRemaining()) {
                            //position=0;limit不变。
                            buffer.rewind();
                            //得到position当前数据,并将position+1
                            int first = buffer.get();
                            //  准备重写
                            buffer.rewind();
                            int position = first-' '+1;
                            buffer.put(rotation, position,72);
                            buffer.put((byte)'\r');
                            buffer.put((byte)'\n');
                            //准备向外输出
                            buffer.flip();
                        }
                        client.write(buffer);
                    }
                }catch (IOException e) {
                    //客户中断,抛出异常。取消这个键
                        key.cancel();
                        try {
                            //关闭对应键的通道
                            key.channel().close();
                        }catch (IOException ex) {
                        }
                }
            }
        }
    }

}

缓冲区

流和通道的区别在于流是基于字节的,而通道是基于块的。另外一个区别是支持同一对象读/写。

缓冲区可以看作数组。但底层不一定是数组。

除boolean,Java所有基本类型都有特定的Buffer子类,ByteBuffer,CharBuffer。

缓冲区的属性

position

将读和写的下一个位置

//获取
public final int position()
  //设置
public final Buffer position(int new Position)
capacity

可以保存的数量

public final int capacity()读取

limit

​ 缓冲区可访问数据的末尾位置。

mark
//将当前位置设为标记值
public final Buffer mark()
  //将标记值设置为当前位置值.设置为低于现有标记,丢弃这次设置。
  public final Buffer reset()

方法 没有修改、删除数据的方法,只能改变上面的属性。

clear() 为写入准备

position = 0; limit = capacity;

rewind() 为重写准备

position = 0;

flip()为读取准备

limit= position;position = 0;

remaining() hasRemaining()

创建缓冲区

分配–用于输入

工厂方法调用allocate()

直接分配

allocateDirect(100); 不会创建底层数组。不建议

包装–用于输出

wrap 直接包装已有的数组。不要新建,一个一个输入。

包装后的缓冲区和底层数组会互相影响。

填充和排空

353

绝对方法
//绝对方法不更新位置
public abstract byte get(int index)
public abstract ByteBuffer put(int index, byte b)

批量方法

public ByteBuffer get(byte[] dst, int offset, int length)
public ByteBuffer get(byte[] dst)
public ByteBuffer put(byte[] array , int offset, int length)
public ByteBuffer put(byte[] array)

数据转换

视图缓冲区

//视图和底层缓冲区的位置和限度是独立的,必须分别考虑。
ByteBuffer buffer = ByteBuffer.allocate(4);
IntBuffer view = buffer.asIntBuffer();

只有原ByteBuffer才能对通道进行读写。而SocketChannel类也只有读写ByteBuffer的方法,无法读取其他类型的缓冲区。

压缩缓冲区

compact()

将还未写出的数据提到缓冲区前端。position更新为数据最后,

复制缓冲区

duplicate()

初始和复制的缓冲区共享相同的数据,但有独立的标记,限度和位置。

分片缓冲区

slice() from position to limit

Object方法

equals

  1. 类型相同
  2. 元素个数相同(不考虑容量,限度,和标记)
  3. 相同位置上的元素相等

通道

SocketChannel

读取–散布。写入–聚集。关闭。

ServerSocketChannel

不能写/读。只能用于接受入站连接。

Channels类

有与流、阅读器和书写器互相转换的方法。

异步通道

Socket选项

就绪选择

//非阻塞,立即返回
selectNow()
//阻塞,至少有一个准备好了,才返回。
select()
select(long timeOut)

  selectKeys()//返回就绪通道
  close()//关闭通道

SelectionKey类

注册时会返回该类对象

相当于通道的指针

channel()获取对应通道

attachment()获取附件

cancel()撤销注册

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值