Java网络编程 非阻塞I/O

对于CPU速度高于网络的情况,传统的Java解决方案是缓冲和多线程。多个线程可以同时为几个不同的连接生成数据,并将数据存储在缓冲其中,知道网络准备好发送。

一些基础概念
[b]缓冲区Buffer[/b]
位置 position 缓冲区将被读取或写入的下一个位置(循环中的数组下标)
容量 capacity 缓冲区可以保存的元素最大数目(数组长度)
限度 limit 缓冲区中保存数据的最后一个位置,只要不改变限度,就无法读写超过这个位置的数据,即使缓冲区有更大的容量也没有用
标记 mark 缓冲区客户端专有的索引,make()的到当前position reset()返回

与读取InputSream不同,读取缓冲区实际上不会改变其中的数据,只会改变向前或向后改变位置,达到从缓冲区某个位置开始读取,
类似的,程序可以调整限度,从而控制可读取的数据的末尾。只有容量是不能变的。

Buffer超类中提供一下方法
*[b]clean 清空[/b] postion设为0,limit设置capacity
*[b]filp 回绕[/b] limit设为position
*[b]rewind 回到[/b] position设为0
*[b]remaining [/b]limit - position
*[b]hasRemaining方法[/b] limit - position > 0?

[b]排空 get
填充 put
挤压 compact[/b]
将缓冲区中所有剩余的数据一道缓冲区开头,为元素释放更多空间,缓冲区的位置设置为数据结尾。
用于读取一个通道在写入另一个通道,用一个缓冲区就能完成随即交替
[b]复制 duplicate[/b]
返回值不是一个副本,复制的缓冲区和原来的缓冲区共享相同的数据,所以主要应当在只准备读取缓冲区时,才使用此方法。
尽管他们共享相同的数据,但是初始和复制的缓冲区有独立的编辑、限度和位置。当希望多个通道并行地传输相同数据时,复制就非常有用。
[b]分片 slicing[/b]
复制的一个变形,分片也会创建和原缓冲区共享相同数据的新缓冲区,但是分片的初始位置是原缓冲区的当前位置。即分片是原缓冲区
的子序列,只包含原缓冲区当前位置到限度的所有元素

[b]Channels工具类[/b],可以将传统的基于I/O的流,包装在通道中,也可以从通道转换基于I/O的流。
例如,所有当前的XML API都是用流、文件或其他传统I/O API来读取XML文档。如果编写用于处理SOAP请求的HTTP服务器,先使用通道读取HTTP请求主体,在将通道转换为流
SocketChannel channel = server.accept();
processHTTPHeader(channel);
XMLReader praser = XMLReaderFactory.createXMLReader();
parser.prase(Channels.newInputStream(channel));

[b]就绪选择[/b]
能够选择读写时不阻塞的socket。将不同的通道注册到一个Selector对象。每个通道分配一个SelectionKey,然后程序可以询问Selector对象,那些通道已经准备就绪,可以无阻塞地完成目标操作
1 工厂方法Selector.open()创建新的选择器
2 实现SelectableChannel的类(FileChannel没有实现) register()方法。通过将选择器传递给通道的一个注册方法,向选择器注册通道
选择就绪的通道
selectNow()非阻塞 select() select(long timeout)阻塞


import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
import java.io.IOException;
/**
* 客户端连接时,服务器将发送连续的字符序列,直到客户端断开连接,
* 客户端任何输入都被忽略
*/
public class ChargenServer {

public static int DEFAULT_PORT = 19;

public static void main(String[] args) {
int port = DEFAULT_PORT;
System.out.println("Listening for connections on port " + port);
byte[] rotation = new byte[95 * 2];
for (byte i = ' '; i <= '~'; i++) {
rotation[i - ' '] = i;
rotation[i + 95 - ' '] = i;
}
ServerSocketChannel serverChannel;
Selector selector;
try {
serverChannel = ServerSocketChannel.open();
// 获取ServerSocketChannel的对等端(prre)对象
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address);
//非阻塞状态下,没有连接时,serverChannel.accept()会立即返回null
serverChannel.configureBlocking(false);
// 创建一个Selector,使程序能够对所有准备好的连接进行循环处理
selector = Selector.open();
// 在监视通道的选择器中进行注册
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException ex) {
ex.printStackTrace();
return;
}
while (true) {
try {
// 调用选择器的select()方法,检查是否有就绪的通道,
// 如果没有就绪通道,选择器就会等待(阻塞)
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
break;
}
// 找到了就绪的通道,selectedKeys()方法返回就绪通道的SelectionKey
// socket空闲时,即为可写,有数据来时,可读
// Set集合好像链接到服务器的客户端集合
Set readyKeys = selector.selectedKeys();
Iterator iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
try {
// 就绪通道是ServerSocketChannel,程序会接受一个新的SocketChannel
// 并将其添加到选择器
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 注册了SelectionKey.OP_ACCEPT,当有accept发生时,Selector会监控到
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
// 每个SelectionKey都有一个Object类型的“附件”,
// 在这里,可以将通道要写入网络的缓冲区存储到这个对象中
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();
key2.attach(buffer);
// 就绪通道是SocketChannel,程序会向通道写数据
} else if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
// hasRemaining:如果有没写完的数据,就写入到通道
// 否则,同rotation数据中的下一行数据重新填充缓冲区,再写入通道
if (!buffer.hasRemaining()) {
buffer.rewind();
// 得到上一行的首字符
int first = buffer.get();
// 准备好改变缓冲区中的数据
buffer.rewind();
// 寻找rotation中新的首字符位置
int position = first - ' ' + 1;
// 将数据从rotation复制到缓冲区
buffer.put(rotation, position, 72);
buffer.put((byte) '\r');
buffer.put((byte) '\n');
// 准备缓冲区写入
buffer.flip();
}
client.write(buffer);
}
} catch (IOException ex) {
key.cancel();
try {
// 取消键后,仍可以得到键的通道
key.channel().close();
} catch (IOException cex) {
}
}
}
}
}
}

import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.io.IOException;

public class ChargenClient {

public static int DEFAULT_PORT = 19;

public static void main(String[] args) {
int port = DEFAULT_PORT;
try {
SocketAddress address = new InetSocketAddress("localhost", port);
SocketChannel client = SocketChannel.open(address);
ByteBuffer buffer = ByteBuffer.allocate(74);
WritableByteChannel out = Channels.newChannel(System.out);
// 通道会从Socket读取数据,填充到缓冲区,返回成功读取并存储在缓冲区的字节数
// 设置为非阻塞时,当没有字节返回时会立即返回0,
// 此时程序应写成:
// client.configureBlocking(false);
// while (true) {
// int n = client.read(buffer);
// if (n > 0) {
// buffer.flip();
// out.write(buffer);
// buffer.clear();
// } else if (n == -1)
// break; // 服务器故障了
// }
while (client.read(buffer) != -1) {
buffer.flip(); // 回绕 limit = position, position = 0
out.write(buffer);
buffer.clear();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}



import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
import java.io.IOException;

/**
* 服务器立即开始发送四字节的big-endian整数,从0开始,每次增加1,服务器最后总会绕回负数
* 服务器无限运行。客户端在得到足够信息后关闭连接
*/
public class IntgenServer {

public static int DEFAULT_PORT = 1919;

public static void main(String[] args) {
int port = DEFAULT_PORT;
System.out.println("Listening for connections on port " + port);
ServerSocketChannel serverChannel;
Selector selector;
try {
serverChannel = ServerSocketChannel.open();
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address);
serverChannel.configureBlocking(false);
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException ex) {
ex.printStackTrace();
return;
}
while (true) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
break;
}
Set readyKeys = selector.selectedKeys();
Iterator iterator = readyKeys.iterator();
while (iterator.hasNext()) {
iterator.next();iterator.next();
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE);
ByteBuffer output = ByteBuffer.allocate(4);
output.putInt(0);
output.flip();
key2.attach(output);
} else if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
if (!output.hasRemaining()) {
output.rewind();
int value = output.getInt();
output.clear();
output.putInt(value + 1);
output.flip();
}
client.write(output);
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
}
}
}
}
}
}


import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.io.IOException;

public class IntgenClient {

public static int DEFAULT_PORT = 1919;

public static void main(String[] args) {
int port = DEFAULT_PORT;
try {
SocketAddress address = new InetSocketAddress("localhost", port);
SocketChannel client = SocketChannel.open(address);
ByteBuffer buffer = ByteBuffer.allocate(4);
IntBuffer view = buffer.asIntBuffer();
for (int expected = 0;; expected++) {
client.read(buffer);
int actual = view.get();
buffer.clear();
view.rewind();
if (actual != expected) {
System.err.println("Expected " + expected + "; was " + actual);
break;
}
System.out.println(actual);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}



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

/**
* 缓冲区的大小非常重要,大的缓冲区会隐藏bug,如果缓冲区足够大,可以保证
* 所有测试用例不需要进行回绕或排空
*/
public class EchoServer {

public static int DEFAULT_PORT = 7;

public static void main(String[] args) {
int port = DEFAULT_PORT;
System.out.println("Listening for connections on port " + port);
ServerSocketChannel serverChannel;
Selector selector;
try {
serverChannel = ServerSocketChannel.open();
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address);
serverChannel.configureBlocking(false);
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException ex) {
ex.printStackTrace();
return;
}
while (true) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
break;
}
Set readyKeys = selector.selectedKeys();
Iterator iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
SelectionKey clientKey = client
.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
ByteBuffer buffer = ByteBuffer.allocate(100);
clientKey.attach(buffer);
}
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
client.read(output);
}
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
// 将缓冲区中所有剩余的数据(没有写出的)移到缓冲区开头,缓冲器的位置设为数据结尾
// 这样只利用一个缓冲区就能完成比较随机的交替读写。
output.compact();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
}
}
}
}
}
}
import java.util.Set;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;

public class EchoClient {

public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
Selector selector = null;
SocketChannel sc = null;
try {
selector = Selector.open();
sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(new InetSocketAddress(InetAddress.getByName("localhost"), 7));
print("客户端启动,准备连接...");
if (sc.isConnectionPending()) {
sc.finishConnect();
}
print("完成连接");
sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
boolean writed = false;
boolean down = false;
while (!down && selector.select() > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey key : selectionKeys) {
// int ops = key.readyOps();
// if ((ops & SelectionKey.OP_WRITE) ==
// SelectionKey.OP_WRITE && !writed) {
if (key.isWritable() && !writed) {
System.out.print("Input(bye to end): ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
if (s != null && !s.trim().equals("")) {
buffer.clear();
buffer.put(s.getBytes());
buffer.flip();
sc.write(buffer);
writed = true;
if (s.equals("bye")) {
down = true;
break;
}
}
}
// if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ
// && writed) {
if (key.isReadable() && writed) {
buffer.clear();
sc.read(buffer);
buffer.flip();
byte[] b = new byte[buffer.limit()];
buffer.get(b);
print(new String(b));
writed = false;
}
}
selectionKeys.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}

private static void print(String s) {
System.out.println(s);
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值