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
- 类型相同
- 元素个数相同(不考虑容量,限度,和标记)
- 相同位置上的元素相等
通道
SocketChannel
读取–散布。写入–聚集。关闭。
ServerSocketChannel
不能写/读。只能用于接受入站连接。
Channels类
有与流、阅读器和书写器互相转换的方法。
异步通道
Socket选项
就绪选择
//非阻塞,立即返回
selectNow()
//阻塞,至少有一个准备好了,才返回。
select()
select(long timeOut)
selectKeys()//返回就绪通道
close()//关闭通道
SelectionKey类
注册时会返回该类对象
相当于通道的指针
channel()获取对应通道
attachment()获取附件
cancel()撤销注册