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();//select第一次是阻塞的,后续是非阻塞的,所以后续select返回值都为0 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(); } }
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)。
代码说明
1、java NIO使用了IO复用模型,根据《unix网络编程》一书的说法,IO multiplexing,仍然是同步IO,因为在第二阶段,从内核copy数据到进程内时,进程是阻塞的。所以,如果想更深入的理解NIO,有必要研究此模型。
2、对照IO复用模型,NIO中有selector类,而且channel必须是非block的才能与selector一起使用,select未收到就绪channel之前是阻塞的,之后是非阻塞的,这点要注意。
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高。