传统的Socket是线程阻塞的,导致阻塞的原因有,sleep睡眠,wait等待,IO延迟等待、代码被同步等
1.非阻塞
非阻塞指的是执行某些操作时,如果还没就绪,那么不会等待,立即返回,而等待事件的发生仍然是阻塞的
JDK中java.nio包提供了对非阻塞通信的支持,常见的替代类如下:
ServerSocketChannel 采用通道连接,替代ServerSocket
SocketChannel 替代Socket
Selector 选择器,用于监听以上产生的事件
SelectorKey 通道向选择器注册事件的句柄,以此判断发生了什么事件
2.注册事件
SelectorKey key = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
表示事件的静态常量:
SelectionKey.OP_ACCEPT:接收连接就绪事件
SelectionKey.OP_CONNECT:连接就绪事件
SelectionKey.OP_READ:读就绪事件
SelectionKey.OP_WRITE:写就绪事件
3.缓冲区
缓冲区的好处,减少物理读写次数,缓冲区内存一直被重用,减少了内存的分配和回收
java.nio包提供了新的IO类
BufferedInputStream,BufferedOutputStream,
BufferedReader,BufferedWriter
缓冲区的属性
容量capacity:可以保存大小
极限limit:缓冲区当前终点,不能对超过极限的区域进行读写
位置position:用于读取数据
缓冲区提供的方法
clear:极限设为容量大小,位置设为0
flip:极限设为位置大小,位置设为0
rewind:不改变极限,位置设为0
remaining:返回缓冲区剩余容量,即位置—极限
compact:删除缓冲区0—位置的内容,位置—极限的内容复制到0—位置,极限变为容量大小
Buffer类是一个抽象类
最基本的缓冲区是ByteBuffer,获取示例的静态方法
allocate(int Byte);分配空间,返回ByteBuffer对象
directAllocate(int Byte);返回直接缓冲区,可以提高IO速度,但开销很大,缓冲区较大而且会长期存在
4.字符编码
java.nio.Charset提供的编码和解码方法
ByteBuffer encode(String str);字符串转字节序列存放在ByteBuffer对象中
ByteBuffer encode(CharBuffer cb);字符缓冲区中的字符进行编码,然后将字节序列存放到ByteBuffer对象中
CharBuffer decode(ByteBuffer bb);ByteBuffer字节序列解码,存放到CharBuffer对象中
设置编码:Charset.forName("UTF_8");
返回默认编码:defaultCharset();
5.通道
通道是用来连接数据源和数据汇的,中间通过缓冲区
Channel接口的方法
close()关闭通道
isOpen()判断通道是否打开
通道创建时被打开,一旦关闭通道,就不能重新打开了
Channel两个子接口
ReadableByteChannel
WritableByteChannel
6.SelectableChannel
支持阻塞IO和非阻塞IO的通道
主要方法
configureBlocking(boolean);true表示设为阻塞模式
两个继承类
ServerSocketChannel
SocketChannel
7.摘取的源码
public class EchoServer {
ServerSocketChannel serverSocketChannel;
ExecutorService executorService;
public EchoServer() throws Exception{
// 根据CPU数创建线程池
executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()*2);
// 创建ServerSocketChannel对象
// 每个ServerSocketChannel对象都与一个ServerSocket对象关联
// 此时没有与任何端口绑定并处于阻塞模式
serverSocketChannel= ServerSocketChannel.open();
// socket方法返回与之关联的ServerSocket对象,此处设置socket可以复用
serverSocketChannel.socket().setReuseAddress(true);
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
System.out.println("服务器已启动");
}
public void service(){
while(true){
SocketChannel socketChannel = null;
try {
// 阻塞等待客户端
socketChannel = serverSocketChannel.accept();
// 提交给线程池执行,runnable一个任务
executorService.execute(new Handler(socketChannel));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
new EchoServer().service();
}
}
// 执行的业务逻辑,传入每个socket客户端
class Handler implements Runnable{
private SocketChannel socketChannel;
public Handler(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
@Override
public void run() {
// 取得通道对应的Socket
Socket socket = socketChannel.socket();
System.out.println("客户端连接:"+socket.getInetAddress()+":"+socket.getPort());
try {
//BufferedReader br = getReader(socket);
//PrintWriter pw = getWriter(socket);
readLine(socketChannel);
// String msg = null;
// while((msg=br.readLine())!=null){
// System.out.println(msg);
// pw.println(echo(msg));
// if(msg.equals("88")){
// break;
// }
// }
} catch (Exception e) {
e.printStackTrace();
} finally{
if(socketChannel!=null){
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private String echo(String msg) {
return "echo:"+msg;
}
private BufferedReader getReader(Socket socket) throws Exception{
// 取得Socket的输入流
InputStream in = socket.getInputStream();
return new BufferedReader(new InputStreamReader(in));
}
private PrintWriter getWriter(Socket socket) throws Exception{
OutputStream out = socket.getOutputStream();
return new PrintWriter(out,true);
}
// 采用缓冲区来读取数据,极限、位置等看打印结果就能清楚了
public String readLine(SocketChannel socketChannel) throws IOException {
// 创建1024大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 一个字节的缓冲区
ByteBuffer temp = ByteBuffer.allocate(1);
boolean isLine = false;
boolean isEnd = false;
String data = null;
// 当一行没有结束,并且没有到达结尾
while(!isLine&&!isEnd){
// 清除缓冲区,极限设为容量
temp.clear();
// 阻塞模式,只要读取1个字节或末尾才返回
// 非阻塞模式,可能返回0
int n = socketChannel.read(temp);
if(n==-1){
isEnd = true;
break;
}
if(n==0){
continue;
}
System.out.println("开始位置="+buffer.position());
System.out.println("开始极限="+buffer.limit());
System.out.println("开始容量="+buffer.capacity());
// 极限设为位置,位置设为0
// 只有极限以内可以读写
temp.flip();
// 复制内容到buffer中
buffer.put(temp);
System.out.println("写入buffer位置="+buffer.position());
System.out.println("写入buffer极限="+buffer.limit());
System.out.println("写入buffer容量="+buffer.capacity());
// 把极限设为写入数据的位置
buffer.flip();
System.out.println("flip后位置="+buffer.position());
System.out.println("flip后极限="+buffer.limit());
System.out.println("flip后容量="+buffer.capacity());
Charset charset = Charset.forName("GBK");
CharBuffer charBuffer = charset.decode(buffer);
data = charBuffer.toString();
if(data.indexOf("\r\n")!=-1){
// 如果有回车换行,一行结束
isLine = true;
// 切除回车换行
data = data.substring(0, data.indexOf("\r\n"));
break;
}
System.out.println("位置="+buffer.position());
System.out.println("极限="+buffer.limit());
System.out.println("容量="+buffer.capacity());
// 位置设为极限,为下次读取准备
buffer.position(buffer.limit());
// 把极限设为容量
buffer.limit(buffer.capacity());
}
return data;
}
}