服务器端:
- 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 使用单独的一个线程读取服务器发送来的数据