服务端:
package nio;
import org.springframework.util.StringUtils;
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
public class ServerSocketChannels implements Runnable {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private volatile boolean stop;
public ServerSocketChannels(int port){
try {
//创建多路复用器selector,工厂方法
selector = Selector.open();
//创建ServerSocketChannel,工厂方法
serverSocketChannel = ServerSocketChannel.open();
//绑定ip和端口号,默认的IP=127.0.0.1,对连接的请求最大队列长度设置为backlog=1024,如果队列满时收到连接请求,则拒绝连接
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
//设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//注册serverSocketChannel到selector多路服用器上面,监听accrpt请求
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("the time is start port = " + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
this.stop = true;
}
@Override
public void run() {
//如果server没有停止
while(!stop){
try {
//selector.select()会一直阻塞到有一个通道在你注册的事件上就绪了
//selector.select(1000)会阻塞到1s后然后接着执行,相当于1s轮询检查
selector.select(1000);
//找到所有准备接续的key
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key = null;
while(it.hasNext()){
key = it.next();
it.remove();
try {
//处理准备就绪的key
handle(key);
}catch (Exception e){
if(key != null){
//请求取消此键的通道到其选择器的注册
key.cancel();
//关闭这个通道
if(key.channel() != null){
key.channel().close();
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
if(selector != null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void handle(SelectionKey key) throws IOException {
//如果key是有效的
if(key.isValid()){
//监听到有新客户端的接入请求
//完成TCP的三次握手,建立物理链路层
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = (SocketChannel) ssc.accept();
//设置客户端链路为非阻塞模式
sc.configureBlocking(false);
//将新接入的客户端注册到多路复用器Selector上
sc.register(selector, SelectionKey.OP_READ);
}
//监听到客户端的读请求
if(key.isReadable()){
//获得通道对象
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//从channel读数据到缓冲区
int readBytes = sc.read(readBuffer);
if (readBytes > 0){
//Flips this buffer. The limit is set to the current position and then
// the position is set to zero,就是表示要从起始位置开始读取数据
readBuffer.flip();
//eturns the number of elements between the current position and the limit.
// 要读取的字节长度
byte[] bytes = new byte[readBuffer.remaining()];
//将缓冲区的数据读到bytes数组
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("the time server receive order: " + body);
String currenttime = "query time order".equals(body) ? new Date(System.currentTimeMillis()).toString(): "bad order";
doWrite(sc, currenttime);
}else if(readBytes < 0){
key.channel();
sc.close();
}
}
}
}
public static void doWrite(SocketChannel channel, String response) throws IOException {
if(!StringUtils.isEmpty(response)){
byte [] bytes = response.getBytes();
//分配一个bytes的length长度的ByteBuffer
ByteBuffer write = ByteBuffer.allocate(bytes.length);
//将返回数据写入缓冲区
write.put(bytes);
write.flip();
//将缓冲数据写入渠道,返回给客户端
channel.write(write);
}
}
}
服务端启动程序:
package nio;
/**
* 服务端启动程序
*/
public class ServerMain {
public static void main(String[] args) {
int port = 8010;
ServerSocketChannels server = new ServerSocketChannels(port);
new Thread(server,"timeserver-001").start();
}
}
客户端程序:
package nio;
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;
import java.util.Set;
public class TimeClientHandler implements Runnable {
//服务器端的ip
private String host;
//服务器端的端口号
private int port;
//多路服用选择器
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
public TimeClientHandler(String host, int port){
this.host = host == null ? "127.0.0.1": host;
this.port = port;
try {
//初始化一个Selector,工厂方法
selector = Selector.open();
//初始化一个SocketChannel,工厂方法
socketChannel = SocketChannel.open();
//设置非阻塞模式
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
/**
* 首先尝试连接服务端
* @throws IOException
*/
public void doConnect() throws IOException {
//如果连接成功,像多路复用器selector监听读请求
if(socketChannel.connect(new InetSocketAddress(this.host, this.port))){
socketChannel.register(selector, SelectionKey.OP_READ);
//执行写操作,像服务器端发送数据
doWrite(socketChannel);
}else {
//监听连接请求
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
public static void doWrite(SocketChannel sc) throws IOException {
//构造请求消息体
byte [] bytes = "query time order".getBytes();
//构造ByteBuffer
ByteBuffer write = ByteBuffer.allocate(bytes.length);
//将消息体写入发送缓冲区
write.put(bytes);
write.flip();
//调用channel的发送方法异步发送
sc.write(write);
//通过hasRemaining方法对发送结果进行判断,如果消息全部发送成功,则返回true
if(!write.hasRemaining()){
System.out.println("send order 2 server successd");
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop){
try {
selector.select(1000);
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> its =keys.iterator();
SelectionKey key = null;
while (its.hasNext()){
key = its.next();
its.remove();
try {
handle(key);
}catch (Exception e){
if(key != null){
key.cancel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
public void handle(SelectionKey key) throws IOException {
if(key.isValid()){
SocketChannel sc = (SocketChannel) key.channel();
if(key.isConnectable()){
//如果连接成功,监听读请求
if(sc.finishConnect()){
sc.register(this.selector, SelectionKey.OP_READ);
//像服务端发送数据
doWrite(sc);
}else{
System.exit(1);
}
}
//监听到读请求,从服务器端接受数据
if(key.isReadable()){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(byteBuffer);
if(readBytes > 0){
byteBuffer.flip();
byte [] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("now body is "+ body);
stop = true;
}else if(readBytes < 0){
key.cancel();
sc.close();
}
}
}
}
//释放所有与该多路复用器selector关联的资源
if(selector != null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端启动程序:
package nio;
/**
* 客户端启动程序
*/
public class ClientMain {
public static void main(String[] args) {
int port = 8010;
TimeClientHandler client = new TimeClientHandler("",port);
new Thread(client,"client-001").start();
}
}
现在说一下nio的执行过程:
第一步:启动server服务器,初始化多路复用器selector、ServerSocketChannel通道、设置通道的模式为非阻塞、注册channel到selector上,并监听accept请求;
第二步:启动server服务器,循环selectionKeys,当有channel准备好时就处理,否则一直循环;
第三步:启动client端,初始化多路复用器selector、SocketChannel通道,设置通道的模式为非阻塞;
第四步:client首先尝试连接server,此时socketChannel.connect(new InetSocketAddress(this.host, this.port)返回false,表示server还没有返回信息,server收到连接请求后,监听到client的接入请求,会初始化一个新的client、并将新接入的client注册到多路复用器Selector上,并应答client;再回到client端,由于client没有及时收到server端的应答,所以client胡监听一个connect请求,socketChannel.register(selector, SelectionKey.OP_CONNECT),当server返回应答信息时,client会收到一个connect请求,key.isConnectable(),如果此时sc.finishConnect()连接完成,client会监听一个read请求,并像server发送数据doWrite(sc),然后server会收到一个read请求,key.isReadable()处理完后返回给client,client也会收到一个读请求,收到server的返回数据,此时,整个交互过程结束;
截取一下书上的步骤:来自netty权威指南:
nio的优点:
1、客户端发起的连接操作connect是异步的,可以通过在多路复用器selector上监听connect事件等待后续结果,不需要像之前的客户端那样被同步阻塞;
2、SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样IO线程就可以处理其它的链路,不需要同步等待这个链路可用;
3、线程模型的优化,由于jdk的selector在linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制(只受限与操作系统的最大句柄数或者单个进程的句柄限制),这意味这一个selector可以连接成千上万个客户端连接,而性能不会随着客户端连接数的增长呈线性下降,因此,它适合做高性能、高负载的网络服务器;