学了两天NIO,很多地方还不是很透彻,所以以下代码仅做参考…
selector选择器单线程管理所有通道,用新线程接收客户端数据,再用新线程响应客户端数据
可直接运行,通过浏览器直接就可以访问,打印原生socket,更好学习http协议,并发访问没测,有兴趣的可以自己写客户端测试
package com.ideal.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class NioServerTest{
public static Selector selector = null;
public static void main(String[] args) throws IOException {
selector = Selector.open(); //创建selector
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //创建serversocketchannel监听8080端口
serverSocketChannel.configureBlocking(false); //设置成非阻塞!
serverSocketChannel.bind(new InetSocketAddress(8080)); //监听8080端口
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //注册事件为可接受事件
System.out.println("服务器成功启动,并且监听8080端口,等待请求...");
//循环selector关注的事件
while(true){
int selectNum = selector.selectNow(); //selectNow是非阻塞的,所以socket.write()后不需要唤起selector,区别于select()是阻塞的
if(selectNum>0){
Set<SelectionKey> selectionKeys = selector.selectedKeys(); //已选择键集
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove(); //从当前已选择键集中移除
if(key.isAcceptable()){ //可接受事件(客户端访问服务器的时候触发)
handleAccept(key);
}else if(key.isReadable()){ //读事件(服务器开始接收客户端的套接字通道数据)
handleRequest(key);
}else if(key.isWritable()){ //写事件(服务器响应客户端)
handleResponse(key);
}else{
System.out.println("未关注事件......");
}
}
}
}
}
private static void handleAccept(SelectionKey key){ //这里就不单独拿个线程出来了,也可以做,只要锁住同一个socketchannel就好了
try{
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false); //把获取到的socketchannel设置成非阻塞的
sc.register(selector,SelectionKey.OP_READ); //把socketchannel通道注册到selector中,并且是读事件
}catch (Exception e){
e.printStackTrace();
}
}
private static void handleRequest(SelectionKey key) {
key.cancel(); //这一步很关键,这行表示取消channel和selector之间的关联,直接从选择键中移除这个通道,之前的迭代器remove表示从有效键中移除,注意区别
ExecutorService executor = getThread();
executor.submit(()->{
//Selector selector = key.selector();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
channel.read(buffer);
System.out.println("客户端访问信息:"+System.getProperty("line.separator")+new String(buffer.array()));
/*
* 这里进行业务操作.......
*/
ByteBuffer writeBuffer = ByteBuffer.allocate(2048*10);
writeBuffer.put(("HTTP/1.1 200 OK"+System.getProperty("line.separator")).getBytes());
writeBuffer.put(("Server:NioServerTest/1.0"+System.getProperty("line.separator")).getBytes());
writeBuffer.put(("Content-Type:text/html;charset=UTF-8"+System.getProperty("line.separator")).getBytes());
writeBuffer.put(("Date:"+ new Date().toLocaleString()+System.getProperty("line.separator")+System.getProperty("line.separator")).getBytes());
writeBuffer.put(("我是HTML响应内容").getBytes());
channel.register(selector,SelectionKey.OP_WRITE,writeBuffer); //由于前面已经把当前通道和selector解除关联,所以这里重新进行关联,并且关注写事件
} catch (IOException e) {
e.printStackTrace();
}
});
}
private static void handleResponse(SelectionKey key) {
key.cancel(); //一定要取消当前的key,否则主线程会不断进入该子线程,直到最后socketchannel.close()
ExecutorService executor = getThread();
executor.submit(()->{
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.flip();
try {
while(buffer.hasRemaining()){
channel.write(buffer);
}
System.out.println("成功响应客户端.......");
channel.close(); //关闭通道,成功完成一次请求响应
} catch (IOException e) {
e.printStackTrace();
}
});
}
private static ExecutorService getThread(){
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(200); //线程池暂定200
return executor;
}
}