服务器端:
-
import java.io.IOException;
-
import java.net.InetSocketAddress;
-
import java.nio.ByteBuffer;
-
import java.nio.channels.SelectableChannel;
-
import java.nio.channels.SelectionKey;
-
import java.nio.channels.Selector;
-
import java.nio.channels.ServerSocketChannel;
-
import java.nio.channels.SocketChannel;
-
import java.nio.charset.Charset;
-
-
public class ServerNio {
-
public static void main(String[] args) throws IOException{
-
//打开一个多路复用器对象,作用是监控多个SelectableChannel的IO状况。
-
Selector selector = Selector.open();
-
//使用open()方法打开一个未绑定的服务器插口通道,支持非阻塞操作
-
ServerSocketChannel serverChannel = ServerSocketChannel.open();
-
//将服务器插口绑定到指定ip和端口号
-
serverChannel.socket().bind( new InetSocketAddress("127.0.0.1",55555));
-
//调整服务器插口通道的阻塞模式为非阻塞模式
-
serverChannel.configureBlocking(false);
-
//将服务器插口通道注册到多路复用器(selector选择容器中)
-
//SelectionKey表示注册通道时产生的选择键
-
serverChannel.register(selector , SelectionKey.OP_ACCEPT);
-
-
//创建一个1字节的缓冲区
-
ByteBuffer buffer = ByteBuffer.allocate(1024);
-
//指定编码集
-
Charset charset = Charset.forName("UTF-8");
-
-
//服务器不断地调用select()方法,若大于0表示有多少个Channel通道具有可用的I/O操作
-
while(selector.select() > 0){
-
//遍历已经注册到selector容器的通道
-
for(SelectionKey sk: selector.selectedKeys()){
-
//把正在处理的sk从集合中移除
-
selector.selectedKeys().remove(sk);
-
-
//如果sk对应的通道包含客户端的连接请求
-
if(sk.isAcceptable()){
-
//返回创建此键的通道
-
SelectableChannel sc1 = sk.channel();
-
//如果这个通道是服务器插口通道类型的
-
if(sc1 instanceof ServerSocketChannel){
-
//将其强制转换成服务器插口通道类型的对象
-
ServerSocketChannel ssc = (ServerSocketChannel)sc1;
-
//调用accept()方法接受连接并返回普通插口通道类型的I/O接口
-
SocketChannel socketChannel1 = ssc.accept();
-
//设置采用非阻塞模式
-
socketChannel1.configureBlocking(false);
-
//将插口通道也注册到selector
-
socketChannel1.register(selector,SelectionKey.OP_READ);
-
//将sk对应的Channel设置成准备接受其他请求
-
sk.interestOps(SelectionKey.OP_ACCEPT);
-
}
-
}
-
-
//如果sk对应的通道有数据需要读取
-
if(sk.isReadable()){
-
//获取该SelectionKey对应的Channel,该Channel中有可读得数据
-
SelectableChannel sc2 = sk.channel();
-
if(sc2 instanceof SocketChannel){
-
SocketChannel socketChannel2 = (SocketChannel)sc2;
-
String msg = "";
-
-
//开始读取数据
-
while(socketChannel2.read(buffer)>0){
-
buffer.flip(); //重绕(锁定)缓冲区(pos=0,limit=pos)
-
msg+=charset.decode(buffer);
-
System.out.println(msg);
-
buffer.clear(); //重置缓冲区(pos=0,limit=capacity)
-
}
-
//将sk对应的Channel设置成准备下一次读取
-
sk.interestOps(SelectionKey.OP_READ);
-
-
//发送给客户端
-
//如果msg的长度大于0,即聊天信息不为空
-
if(msg.length()>0){
-
//遍历该selector里注册的所有SelectKey
-
for(SelectionKey key:selector.keys()){
-
//获取该key对应的Channel
-
SelectableChannel targetChannel = key.channel();
-
if(targetChannel instanceof SocketChannel){
-
//将读到的内容写入该Channel中
-
SocketChannel dest =(SocketChannel)targetChannel;
-
dest.write(charset.encode(msg));
-
}
-
}
-
}
-
}
-
}
-
}
-
}
-
}
-
}
复制代码
客户端:
-
import java.io.IOException;
-
import java.net.InetSocketAddress;
-
import java.nio.channels.SelectionKey;
-
import java.nio.channels.Selector;
-
import java.nio.channels.SocketChannel;
-
import java.nio.charset.Charset;
-
import java.util.Scanner;
-
-
-
public class ClientNio {
-
public static void main(String[] args) throws IOException{
-
//定义检测SocketChannel的Selector对象
-
Selector selector = Selector.open();
-
InetSocketAddress server = new InetSocketAddress("127.0.0.1",55555);
-
//调用open静态方法创建连接到指定主机的SocketChannel
-
SocketChannel clientChannel = SocketChannel.open(server);
-
//设置该客户端插口通道为非阻塞模式
-
clientChannel.configureBlocking(false);
-
//注册插口到selector容器
-
clientChannel.register(selector, SelectionKey.OP_READ);
-
-
//定义编码和解码的字符集
-
Charset charset = Charset.forName("UTF-8");
-
-
//启动一个线程用于读取服务器发送来的数据
-
new ClientNioThread(selector,charset);
-
-
//创建键盘输入流
-
Scanner scanner = new Scanner(System.in);
-
-
//向服务器发送数据
-
while( scanner.hasNextLine()){
-
//读取键盘输入
-
String message = scanner.nextLine();
-
//将键盘输入的内容输出到SocketChannel
-
clientChannel.write(charset.encode(message));
-
}
-
}
-
}
复制代码
读取服务器端数据的线程:
-
import java.io.IOException;
-
import java.nio.ByteBuffer;
-
import java.nio.channels.SelectableChannel;
-
import java.nio.channels.SelectionKey;
-
import java.nio.channels.Selector;
-
import java.nio.channels.SocketChannel;
-
import java.nio.charset.Charset;
-
-
public class ClientNioThread extends Thread{
-
private Selector selector;
-
private Charset charset;
-
-
public ClientNioThread(Selector selector,Charset charset){
-
this.selector = selector;
-
this.charset = charset;
-
this.start(); //启动线程
-
}
-
-
@Override
-
public void run() {
-
try{
-
ByteBuffer buffer = ByteBuffer.allocate(1024);
-
while( selector.select() > 0 ){
-
//遍历每个有可用I/O操作Channel对应的SelectionKey
-
for( SelectionKey sk : selector.selectedKeys() ){
-
//删除正在处理的SelectionKey
-
selector.selectedKeys().remove(sk);
-
-
SelectableChannel sc = sk.channel();
-
if( sc instanceof SocketChannel ){
-
SocketChannel socketChannel = (SocketChannel) sc;
-
StringBuilder sb = new StringBuilder();
-
while( socketChannel.read( buffer ) > 0){
-
buffer.flip();
-
sb.append( charset.decode( buffer ));
-
buffer.clear();
-
}
-
//打印输出读取的内容
-
System.out.println( sb.toString());
-
}
-
//为下一次读取做准备
-
sk.interestOps( SelectionKey.OP_READ);
-
}
-
}
-
}catch(IOException e){
-
e.printStackTrace();
-
}
-
-
}
-
-
}
复制代码
总结:
服务器端:
1-获得Selector对象
2 获得ServerSocketChannel 对象serverChannel
3 把serverChannel跟指定的ip和端口(serverChannel对应的ServerSocket)
4 设置非阻塞模式
5 把serverChannel 注册到selector,指定模式(SelectionKey.OP_ACCEPT)
6 反复执行
1> select()选择可以选择的通道
2> 遍历所有的selectedKeys
isAcceptable:打开监听 SocketChannel channel = serverChannel.accept();
把通过监听获得到的channel设置为无阻塞模式
注册channel到selector上,同时指定模式(OP_READ)
为sk的interest集合插入一个值(为下次操作做准备)
sk.interestOps(SelectionKey)
sk.isReadable:根据sk获得它对应的通道:SelectableChannel sc = sk.channel();
类型检查if(sc.instanceof SocketChannel)
如果满足条件做类型转换:SocketChannel socketChannel = (SocketChannel)sc
读取数据(读取ByteBuffer)socketChannel.read(buffer);
向客户端发送:
if(msg.length()>0){
//遍历keys
for(SelectionKey key:selector keys()){
SelectableChannel selectChannel = key.channel();
if(selectChannel instance of SocketChannel){
SocketChannel sssccc = (SocketChannel)selectChannel
sssccc.write(charset.encode(msg));
}
}
}
客户端:
1 获得一个Selector对象selector;
2 获得SocketChannel同时指定要连接的服务器的InetSocketAddress对象
3 设置为非阻塞模式
4 注册SocketChannel到selector同时指定模式为SelectionKey.OP_READ
5 使用主线程向服务器发送数据
6 使用单独的一个线程读取服务器发送来的数据