JAVA BIO和NIO及简单实现

最近简单研究了一下JAVA的NIO,话说出来这么久了,感觉这一块一直是空白,用的也比较少。但是感觉socket io这部分在以后工作中的高并发项目可能会用到,所以最近抽空研究了解了一下。


1、阻塞IO

阻塞IO比较简单,就是用普通的socket去写,因为没有什么太复杂的处理。建立一个socket,然后,获取它的inputstream和outputstream,然后进行读写操作。
Server端主要代码,handleSocket就是从socket里面读取数据,然后向client写数据:

ServerSocket serverSocket = new ServerSocket(port);
	while (true) {
		socket = serverSocket.accept();
		handleSocket(socket);
	}

Client只是简单的发送一句消息:

        socket = new Socket(host, port);
	out = socket.getOutputStream();
	out.write(data);
	out.flush();
	in = socket.getInputStream();
	byte[] buffer = new byte[128];
	int receivedBytes;
	if((receivedBytes = in.read(buffer))!=-1){
		System.out.println("Client: received msg from server: " + new String(buffer, 0, receivedBytes));
	}

        socket = new Socket(host, port);
	out = socket.getOutputStream();
	out.write(data);
	out.flush();
	in = socket.getInputStream();
	byte[] buffer = new byte[128];
	int receivedBytes;
	if((receivedBytes = in.read(buffer))!=-1){
		System.out.println("Client: received msg from server: " + new String(buffer, 0, receivedBytes));
	}

同时client5000个,使用了大概5s。然后还可以使用多线程Server,就是在每一个client到的时候分配一个线程来处理这个IO,理论上可以增加效率,但是似乎是因为每次处理时间太短,效果不明显。主要代码:


        boolean flag = false;
	int count = 0;
	ServerSocket serverSocket = new ServerSocket(port);
	Date start = null;
	while (true) {
		socket = serverSocket.accept();
		if (!flag) {
			start = new Date();
			flag = true;
		}
		pool.execute(new RequestHandler(socket));
		if(++count== threadCount){
			flag = false;
			Date end = new Date();
			System.out.println(threadCount+" client requests spends: " + (end.getTime() -start.getTime()));
		}
	}
其中的pool是ExecutorService,提供线程池,然后RequestHandler是一个Runnable类,用来处理一个socket连接。简单讲来,就是把前面server处理socket的代码放到了这个handler里面。


2.非阻塞方式的Socket编程

传统阻塞方式的Socket编程,在读取或者写入数据时,TCP程序会阻塞直到客户端和服务端成功连接,UDP程序会阻塞直到读取到数据或写入数据。阻塞方式会影响程序性能,JDK5之后的NIO引入了非阻塞方式的Socket编程,非阻塞方式的Socket编程主要是使用Socket通道和Selector通道选择器,将Socket通道注册到通道选择器上,通过通道选择器选择通道已经准备好的事件的进行相应操作。

NIO socket简单例子如下:

import java.net.*;  
import java.nio.channels.*;  
import java.util.*;  
import java.io.*;  
  
public class NIOSocket{  
    private static final int CLINET_PORT = 10200;  
    private static final int SEVER_PORT = 10201;  
    //面向流的连接套接字的可选择通道  
    private SocketChannel ch;  
    //通道选择器  
    private Selector sel;  
    public static void main(String[] args) throws IOException{  
        //打开套接字通道  
ch = SocketChannel.open();  
//打开一个选择器  
sel = Selector.open();    
try{  
    //获取与套接字通道关联的套接字,并将该套接字绑定到本机指定端口  
        ch.socket().bind(new InetSocketAddress(CLINET_PORT));  
        //调整此通道为非阻塞模式  
        ch.configureBlocking(false);  
        //为通道选择器注册通道,并指定操作的选择键集  
        ch.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE |  
 SelectionKey.OP_CONNECT);  
            //选择通道上注册的事件,其相应通道已为I/O操作准备就绪  
            sel.select();  
            //返回选择器的已选择键集  
            Iterator it = sel.selectedKeys().iterator();  
            while(it.hasNext()){  
                //获取通道的选择器的键  
    SelectionKey key = (SelectionKey)it.next();  
    it.remove();  
    //如果该通道已经准备好套接字连接  
    if(key.isConnectable()){  
    InetAddress addr = InetAddress.getLocalHost();  
    System.out.println(“Connect will not block”);  
//调用此方法发起一个非阻塞的连接操作,如果立即建立连接,则此方法//返回true.否则返回false,且必须在以后使用finishConnect()完成连接操作  
//此处建立和服务端的Socket连接  
    if(!ch.connect(new InetSocketAddress(addr, SEVER_PORT))){  
        //完成非立即连接操作  
    ch.finishConnect();  
}  
}  
//此通道已准备好进行读取  
if(key.isReadable()){  
    System.out.println(“Read will not block”);  
}  
//此通道已准备好进行写入  
if(key.isWritable()){  
    System.out.println(“Write will not block”);  
}  
}  
} finally{  
ch.close();  
sel.close();      
}  
}  
} 
NIO Socket编程中有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的套接字通道socketchannel注册到Selector,程序不用阻塞等待,可以并行做别的事情,当有事件发生时,Selector会通知程序,传回一组SelectionKey,程序读取这些Key,就会获得注册过的socketchannel,然后,从这个Channel中读取和处理数据。
Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。
2.使用NIO非阻塞方式Socket实现服务端和客户端程序:
通过下面一个简单的客户端/服务端程序说明一下NIO socket的基本API和步骤。
(1).服务端程序:
import java.net.*;  
import java.util.*;  
import java.io.*;  
import java.nio.*;  
import java.nio.channels.*;  
import java.nio.charset.*;  
  
public class NIOSocketServer{  
    public static final int PORT = 8080;  
    public static void main(String[] args)throws IOException{  
        //NIO的通道channel中内容读取到字节缓冲区ByteBuffer时是字节方式存储的,  
        //对于以字符方式读取和处理的数据必须要进行字符集编码和解码  
    String encoding = System.getProperty(“file.encoding”);  
    //加载字节编码集  
    Charset cs = Charset.forName(encoding);  
    //分配两个字节大小的字节缓冲区  
    ByteBuffer buffer = ByteBuffer.allocate(16);  
    SocketChannel ch = null;  
    //打开服务端的套接字通道  
    ServerSocketChannel ssc = ServerSocketChannel.open();  
    //打开通道选择器  
    Selector sel = Selector.open();  
    try{  
        //将服务端套接字通道连接方式调整为非阻塞模式  
    ssc.configureBlocking(false);  
    //将服务端套接字通道绑定到本机服务端端口  
    ssc.socket().bind(new InetSocketAddress(PORT));  
    //将服务端套接字通道OP_ACCEP事件注册到通道选择器上  
    SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT);  
    System.out.println(“Server on port:” + PORT);  
    while(true){  
        //通道选择器开始轮询通道事件  
    sel.select();  
    Iterator it = sel.selectedKeys().iterator();  
    While(it.hasNext()){  
        //获取通道选择器事件键  
    SelectionKey skey = (SelectionKey)it.next();  
    it.remove();  
//服务端套接字通道发送客户端连接事件,客户端套接字通道尚未连接  
    if(skey.isAcceptable()){  
        //获取服务端套接字通道上连接的客户端套接字通道  
    ch = ssc.accept();  
    System.out.println(“Accepted connection from:” + ch.socket());  
    //将客户端套接字通过连接模式调整为非阻塞模式  
    ch.configureBlocking(false);  
    //将客户端套接字通道OP_READ事件注册到通道选择器上  
    ch.register(sel, SelectionKey.OP_READ);  
}  
//客户端套接字通道已经连接  
else{  
    //获取创建此通道选择器事件键的套接字通道  
    ch = (SocketChannel)skey.channel();  
    //将客户端套接字通道数据读取到字节缓冲区中  
    ch.read(buffer);  
    //使用字符集解码字节缓冲区数据  
    CharBuffer cb = cs.decode((ByteBuffer)buffer.flip());  
    String response = cb.toString();  
    System.out.println(“Echoing:” + response) ;  
    //重绕字节缓冲区,继续读取客户端套接字通道数据  
    ch.write((ByteBuffer)buffer.rewind());  
    if(response.indexOf(“END”) != -1) ch.close();  
    buffer.clear();  
}  
}  
}  
}finally{  
    if(ch != null) ch.close();  
    ssc.close();  
    sel.close();  
}  
}  
}  

(2).客户端程序:

import java.net.*;  
import java.util.*;  
import java.io.*;  
import java.nio.*;  
import java.nio.channels.*;  
import java.nio.charset.*;  
  
public class NIOSocketClient{  
    private static final int CLIENT_PORT = 10200;  
    public static void main(String[] args) throws IOException{  
        SocketChannel sc = SocketChannel.open();  
        Selector sel = Selector.open();  
        try{  
    sc.configureBlocking(false);  
    sc.socket.bind(new InetSocketAddress(CLIENT_PORT));  
    sc.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE  
| SelectionKey.OP_CONNECT);  
            int i = 0;  
            boolean written = false;  
            boolean done = false;  
            String encoding = System.getProperty(“file.encoding”);  
            Charset cs = Charset.forName(encoding);  
            ByteBuffer buf = ByteBuffer.allocate(16);  
            while(!done){  
    sel.select();  
    Iterator it = sel.selectedKeys().iterator();  
    while(it.hasNext()){  
    SelectionKey key = (SelectionKey)it.next();  
    It.remove();  
    //获取创建通道选择器事件键的套接字通道  
    sc = (SocketChannel)key.channel();  
    //当前通道选择器产生连接已经准备就绪事件,并且客户端套接字  
//通道尚未连接到服务端套接字通道  
    if(key.isConnectable() && !sc.isConnected()){  
    InetAddress addr = InetAddress.getByName(null);  
    //客户端套接字通道向服务端套接字通道发起非阻塞连接  
    boolean success = sc.connect(new InetSocketAddress(  
addr, NIOSocketServer.PORT));  
                    //如果客户端没有立即连接到服务端,则客户端完成非立即连接操作  
                        if(!success) sc.finishConnect();  
}  
//如果通道选择器产生读取操作已准备好事件,且已经向通道写入数据  
if(key.isReadable() && written){  
    if(sc.read((ByteBuffer)buf.clear()) > 0){  
    written = false;  
    //从套接字通道中读取数据  
    String response = cs.decode((ByteBuffer)buf.flip()).toString();  
    System.out.println(response);  
    if(response.indexOf(“END”) != -1) done = true;  
}  
}  
//如果通道选择器产生写入操作已准备好事件,并且尚未想通道写入数据  
if(key.isWritable() && !written){  
    //向套接字通道中写入数据  
    if(i < 10) sc.write(ByteBuffer.wrap(new String(“howdy ” + i +  
 ‘\n’).getBytes()));  
else if(i == 10)sc.write(ByteBuffer.wrap(newString(“END”).  
getBytes()));  
                        written = true;  
                        i++;  
}  
}  
}  
}finally{  
    sc.close();  
    sel.close();  
}  
}  
}  




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BIONIO、AIO 都是 Java 提供的三种 I/O 模型,它们分别代表阻塞 I/O、非阻塞 I/O 和异步 I/O。 1. BIO(Blocking I/O) BIO 是一种阻塞 I/O 模型,也是最早的 I/O 模型。在这种模型下,当线程执行输入/输出操作时会被阻塞,直到有数据可读或可写。因此,这种模型适用于连接数目比较小且固定的架构,这样可以用比较少的线程来管理所有的连接。但是,当连接数目增加时,系统的性能会急剧下降。 2. NIO(Non-blocking I/O) NIO 是一种非阻塞 I/O 模型,相对于 BIONIO 可以支持多路复用,也就是说一个线程可以同时处理多个连接的 I/O 操作。NIO 通过 Selector 维护一个线程所关注的多个 Channel,当其的某个 Channel 准备好 I/O 操作时,Selector 将通知该 Channel 执行 I/O 操作。这样就避免了每个连接都需要一个独立的线程进行 I/O 操作的情况,提高了系统的性能。 3. AIO(Asynchronous I/O) AIO 是一种异步 I/O 模型,相对于 NIO,AIO 的主要特点是能够在异步操作完成时通知线程进行后续处理,而不需要线程阻塞等待。AIO 的实现需要系统支持,因此目前只能在部分操作系统上使用。AIO 适用于高并发、高吞吐量的场景,但是它的实现NIO 更加复杂。 总的来说,BIO 适用于连接数目比较小的情况,NIO 适用于连接数目多且连接时间比较短的情况,AIO 适用于连接数目多且连接时间比较长的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值