NIO VS BIO
NIO: new inputstream/outputstream
BIO: blocked inputstream/outputstream
BIO是传统的IO,是阻塞的,在serversocket编程中,每个线程处理一个连接。
NIO是新IO,在非阻塞模式下,采用IO多路复用模型,节约系统线程、减少上下文切换,提高系统效率。
IO多路复用模型,一个线程管理多个连接,节省系统资源
传统IO模型,在read,accepet时会阻塞,一个连接占用一个线程。
看懂了上述两张图,也就弄懂了传统socket编程与新socket编程的区别及优缺点。
server端代码
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class MyServerSocket {
public static void main(String[] args) throws IOException {
Charset charset = Charset.defaultCharset();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8888));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);//serversocketchannel只能接受Accept类型的channel
while(true){
int selectResult = selector.select();//selec是阻塞的
System.out.println("socket ready的数量:"+selectResult);
if(selectResult>0){//选中的socket
Set<SelectionKey> set = selector.selectedKeys();
Iterator<SelectionKey> iterator = set.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){//serversocketchannel
System.out.println("accept");
ServerSocketChannel tempSSC = (ServerSocketChannel) selectionKey.channel();
SocketChannel tempSC = tempSSC.accept();
tempSC.configureBlocking(false);
tempSC.register(selector, SelectionKey.OP_READ);
}
if(selectionKey.isConnectable()){
System.out.println("connect");
}
if(selectionKey.isReadable()){
System.out.println("read");
SocketChannel tempSC = (SocketChannel) selectionKey.channel();
ByteBuffer dst = ByteBuffer.allocate(15);//单位字节
int readResult = tempSC.read(dst);
while(readResult != -1 && readResult != 0){//流终止会返回0或-1
dst.flip();
System.out.println("数据是:"+charset.decode(dst));
dst.clear();
readResult = tempSC.read(dst);
}
}
if(selectionKey.isWritable()){
System.out.println("write");
}
iterator.remove();//就绪channel处理后,从迭代器中移除,下次select后,会接收到新的channel
}
}
}
// ssc.close();
}
}
client端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class MySocket {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress(8888));
int capacity = 10;//单位,字节
ByteBuffer buffer = ByteBuffer.allocate(capacity);
Scanner scanner = new Scanner(System.in);
String content = null;
while((content =scanner.nextLine()) != null){
byte[] bytes = content.getBytes();
buffer.put(bytes);
buffer.flip();
while(buffer.hasRemaining()){
sc.write(buffer);
}
buffer.clear();
}
sc.close();
}
}
运行
先启动server端,然后启动client端,client输入数据,回车,server端就会收到数据。同理再起一个client,输入数据,server端收到数据。
实现了一个select线程,负责多个连接(channel)。
server端:
client端:
代码说明
1、java NIO使用了IO复用模型,根据《unix网络编程》一书的说法,IO multiplexing,仍然是同步IO,因为在第二阶段,从内核copy数据到进程内时,进程是阻塞的。所以,如果想更深入的理解NIO,有必要研究此模型。
2、对照IO复用模型,NIO中有selector类,而且channel必须是非block的才能与selector一起使用,这点要注意。
3、server端的缓冲一定要比client端的缓冲大,避免出现乱码现象。否则,比如client端缓冲是4,server端缓冲是2,编码是utf8,汉字占用3个字节,客户端发送"a你",服务端以2个字节为单元解码,a不乱码,“你”,被分成两部分解码,所以出现俩?号
想到的问题
1、如果,对select到的channel处理速度过慢,就会影响到后续的通信。可以使用线程池处理后续数据的操作,提高性能。
2、传统BIO(blocked IO)的“一连接一线程”,处理速度的确快,但是面对高访问,比如1000个连接,处理起来就棘手了。通过NIO单select负责多channel(连接)节省了程和资源,从而解决此问题。当然,在访问连接不多的情况下,NIO性能不一定比BIO高。