前言
网络编程是Java的一大难点,JDK自带的api可以实现网络编程。
我们将从一个应用程序开始我们对传输的学习,这个应用程序只简单地接受连接,然后向客户端写“Hi!”,然后关闭连接。
1. OIO实现
应用程序的阻塞(OIO)版代码如下:
package chx.demo;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
public class PlainOioServer {
public void serve(int port) throws IOException {
//1. 将服务器绑定到指定端口
final ServerSocket socket = new ServerSocket(port);
try {
for(;;) {
// 2. for循环不断地接受连接
final Socket clientSocket = socket.accept();
System.out.println(
"Accepted connection from " + clientSocket);
// 3. 创建一个新的线程来处理该连接
new Thread(new Runnable() {
@Override
public void run() {
OutputStream out;
try {
// 4. 将消息写给已连接的客户端
out = clientSocket.getOutputStream();
out.write("Hi!\r\n".getBytes(
Charset.forName("UTF-8")));
out.flush();
// 5. 关闭连接
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException ex) {
// ignore on close
}
}
// 7.启动线程
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码完全可以处理中等数量的并发客户端。但是随着应用程序变得流行起来,你会发现它并不能很好地伸缩到支撑成千上万的并发连入连接。
你决定改用异步网络编程,但是很快就发现异步API 是完全不同的,以至于现在你不得不重写你的应用程序。
2. NIO实现
其非阻塞版代码如下:
package nia.chapter4;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.util.Iterator;
import java.util.Set;
public class PlainNioServer {
public void serve(int port) throws IOException {
// 实例化ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
// 1. 将服务器绑定到选定的端口
ss.bind(address);
// 2. 打开Selector来处理 Channel
Selector selector = Selector.open();
// 3. 将ServerSocketChannel注册到Selector以接受连接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;){
try {
// 4. 等待需要处理的新事件;阻塞将一直持续到下一个传入事件
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
//handle exception
break;
}
// 5. 获取所有接收事件的SelectionKey实例
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
// 6.检查事件是否是一个新的已经就绪可以被接受的连接
if (key.isAcceptable()) {
ServerSocketChannel server =
(ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 7.接受客户端,并将它注册到选择器
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println(
"Accepted connection from " + client);
}
// 8.检查套接字是否已经准备好写数据
if (key.isWritable()) {
SocketChannel client =
(SocketChannel) key.channel();
ByteBuffer buffer =
(ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
// 9. 将数据写到已连接的客户端
if (client.write(buffer) == 0) {
break;
}
}
// 10.关闭连接
client.close();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// ignore on close
}
}
}
}
}
}
如同你所看到的,虽然这段代码所做的事情与之前的版本完全相同,但是代码却截然不同。
如果为了用于非阻塞I/O 而重新实现这个简单的应用程序,都需要一次完全的重写的话,那么不难想象,移植真正复杂的应用程序需要付出什么样的努力。
所以Java JDK原生实现NIO是比较复杂的,学习门槛比较大。这个时候就轮到Netty闪亮登场了。