正在复习Java基础知识,依葫芦画瓢写了个NIO服务的demo,又改进了下,做成了双向读写。分享给需要的朋友
废话少说,代码如下:
B_Server
package nio_demo_01;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 10:31 AM 2019/10/16
*
* NIO Demo
*/
public class B_Server implements Runnable {
private Selector selector;
private ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
public B_Server(int port) {
try {
// 1. 打开多路复用
this.selector = Selector.open();
// 2. 打开服务器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 3. 设置服务器通道为阻塞模式
ssc.configureBlocking(false);
// 4. 绑定端口
ssc.bind(new InetSocketAddress(port));
// 5. 把服务器channel注册到选择器中,监听阻塞时间
ssc.register(this.selector, SelectionKey.OP_ACCEPT);
System.out.println("注册服务端channel");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true) {
try {
System.out.println("进入while了");
// 1. 让选择器开始监听
this.selector.select();
System.out.println("选择器已经打开");
// 2. 返回选择器结果集
Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
System.out.println("这里做了什么");
// 3. 遍历
while(keys.hasNext()) {
// 4. 获取其中一个元素
SelectionKey key = keys.next();
// 5. 可以移除掉
/*
channel是注册在selector中的,在后面的轮询中,是先将已准备好的channel挑选出来,即selector.select(),再通
过selectedKeys()生成的一个SelectionKey迭代器进行轮询的,一次轮询会将这个迭代器中的每个SelectionKey都遍历
一遍,每次访问后都remove()相应的SelectionKey,但是移除了selectedKeys中的SelectionKey不代表移除了selector
中的channel信息(这点很重要),注册过的channel信息会以SelectionKey的形式存储在selector.keys()中,也就是说
每次select()后的selectedKeys迭代器中是不能还有成员的,但keys()中的成员是不会被删除的(以此来记录channel信息)。
*/
keys.remove();
// 6. 判断有效性
if(key.isValid()) {
// 7. 状态:阻塞
if(key.isAcceptable()) {
System.out.println("阻塞key: " + key);
this.accept(key);
}
// 8. 状态:可读
if(key.isReadable()) {
System.out.println("可读key: " + key);
this.read(key);
}
// 9. 状态:可写
if(key.isWritable()) {
System.out.println("可写key: " + key);
this.write(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void accept(SelectionKey key) {
try {
// 1. 获取服务器通道
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
// 2. 执行阻塞方法
SocketChannel sc = ssc.accept();
// 3. 设置阻塞模式:非阻塞
sc.configureBlocking(false);
// 4. 注册到选择器上,并设置可读
sc.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(SelectionKey key) {
try {
// 1. 清空缓冲期数据
this.byteBuffer.clear();
// 2. 获取之前注册到选择器的socket通道
SocketChannel sc = (SocketChannel)key.channel();
// 3. 读取数据
int count = sc.read(this.byteBuffer);
// 4. 如果没有数据
if(count == -1) {
key.channel().close();
key.cancel();
return;
}
// 5. 有数据就读取,读取之前需要进行flip(把position 和limit进行固定)
this.byteBuffer.flip();
// 6. 根据缓冲区的数据长度创建相应大小的byte数组,接受缓冲区数据
byte[] bytes = new byte[byteBuffer.remaining()];
// 7. 接收缓冲区数据
this.byteBuffer.get(bytes);
// 8. 打印结果
String body = new String(bytes).trim();
System.out.println("Server: " + body);
// 9. 可以写回给客户端数据
sc.configureBlocking(false);
sc.register(this.selector, SelectionKey.OP_WRITE);
} catch (IOException e) {
e.printStackTrace();
}
}
private void write(SelectionKey key) {
SocketChannel sc = (SocketChannel)key.channel();
try {
// sc.register(this.selector, SelectionKey.OP_WRITE);
System.out.println("来了没有????????????????");
byte[] bytes = "呵呵".getBytes();
writeBuffer.put(bytes);
writeBuffer.flip();
sc.write(writeBuffer);
writeBuffer.clear();
key.channel().close();
key.cancel();
// sc.register(this.selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
} catch (IOException e) {
}
}
public static void main(String[] args) {
new Thread(new B_Server(8910)).start();
}
}
B_Client
package nio_demo_01;
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.util.Iterator;
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 11:39 AM 2019/10/16
*
* 双向通信的客户端【方法分离】
*/
public class B_Client implements Runnable {
private SocketChannel sc = null;
private Selector selector = null;
public static void main(String[] args) {
B_Client b_client = new B_Client();
new Thread(b_client).start();
b_client.write();
}
public B_Client() {
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8910);
try {
sc = SocketChannel.open();
sc.connect(address);
sc.configureBlocking(false); // 一定要注意:必须connect之后,才能设置非阻塞,否则会在发消息时抛出NotYetConnectedException
this.selector = Selector.open();
sc.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private void write() {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
while (true) {
byte[] bytes = new byte[1024];
System.in.read(bytes);
byteBuffer.put(bytes);
byteBuffer.flip();
sc.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
}
}
private void read(SelectionKey key) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
// 2. 获取之前注册到选择器的socket通道
SocketChannel sc = (SocketChannel)key.channel();
// 3. 读取数据
int count = sc.read(byteBuffer);
// 4. 如果没有数据
if(count == -1) {
key.channel().close();
key.cancel();
return;
}
// 5. 有数据就读取,读取之前需要进行flip(把position 和limit进行固定)
byteBuffer.flip();
// 6. 根据缓冲区的数据长度创建相应大小的byte数组,接受缓冲区数据
byte[] bytes = new byte[byteBuffer.remaining()];
// 7. 接收缓冲区数据
byteBuffer.get(bytes);
// 8. 打印结果
String body = new String(bytes).trim();
System.out.println("Server: " + body);
// 记住,一定要1. 关闭通道,2. 取消key。不然while(true)时,会不断取出该key
key.channel().close();
key.cancel();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
@Override
public void run() {
while(true){
try {
this.selector.select();
Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
if(key.isValid()){
if(key.isReadable()){
this.read(key);
}
}
keys.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
好,结束