1.非阻塞方式的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(); } } }