前言
今天加班回来,终于有时间继续更新NIO的文章了。在前一篇文章我们讲解了缓冲区的知识,并通过代码演示了如何使用缓冲区的API完成一些操作。这里要讲的通道于缓冲区关系密切,简单来说,缓冲区是填充数据的载体,而通道则可以理解为传输数据的载体。回忆在TCP/IP中建立握手的过程,发送端有一个发送缓冲区而接受端有一个接收缓冲区,进程从缓冲区中取数据,之后缓冲区又可以被填满,而传输数据的网络则可以理解为通道。
通道基础
相比JDK1.4之前的BIO,NIO一个重大的改变就是由原来的每次的连接都会创建一个新的线程区执行,变为只为每个请求创建线程,因为如果一个请求需要建立多次连接的话,BIO的效率就非常低下了。NIO处理事件的模型被称为反应器模型。就是在每个请求到达的时候不会立即进行处理,而是会有一个分发线程将处理请求分发给具体的处理线程进行处理。这样设计的好处在于能够提高吞吐量,提高性能。
那么,这里要提到的通道有什么关系呢?在服务端和客户端有一个专门管理通道的对象,这个对象能够监控每个通道的事件(后台的实现逻辑就是不断轮询每个通道所注册的事件,并判断是否满足要求),如果这个对象发现某个通道所注册的事件发生了,那么注册该事件的通道就可以执行一些自己的处理。
在通道API中,顶层接口是Channel
,代码如下:
package java.nio.channels;
public interface Channel
{
public boolean isOpen( );
public void close( ) throws IOException;
}
在该接口中知有打开和关闭通道的方法,那么这两个方法够用吗?当然不够,实际上更具体的方法都在不同的实现类中。而在通道的实现类中又可以分为两类:FileChannel和SocketChannel。这两种通道的打开方式如下:
SocketChannel sc = SocketChannel.open( );
sc.connect (new InetSocketAddress ("somehost", someport));
ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (somelocalport));
DatagramChannel dc = DatagramChannel.open( );
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel( );
通道实战
通过以上的介绍,我们对通道有了一个基本的认识,下面主要演示如何通过代码的方式使用NIO中通道(由于在NIO应用最广的是Socket通道,所以下面的例子都是基于Socket通道)。
通道的简单使用
下面通过从通道拷贝数据带缓冲区为例,对通道的基本使用做一个简单演示:
package com.rhwayfun.patchwork.nio.channel;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
/**
* @author: rhwayfun
* @since: 2016-05-28
*/
public class ChannelCopy {
public static void main(String[] args) throws IOException {
ReadableByteChannel source = Channels.newChannel(System.in);
WritableByteChannel dest = Channels.newChannel(System.out);
channelCopy(source,dest);
source.close();
dest.close();
}
/**
* 通道拷贝
* @param source
* @param dest
* @throws IOException
*/
private static void channelCopy(ReadableByteChannel source, WritableByteChannel dest) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(16 * 1024);
while (source.read(buffer) != -1){
buffer.flip();
while (buffer.hasRemaining()){
dest.write(buffer);
}
buffer.clear();
}
}
/**
* 通道拷贝的另一种方式
* @param source
* @param dest
* @throws IOException
*/
private static void channelCopy2(ReadableByteChannel source, WritableByteChannel dest) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(16 * 1024);
while (source.read(buffer) != -1){
buffer.flip();
dest.write(buffer);
buffer.compact();
}
buffer.flip();
while (buffer.hasRemaining()){
dest.write(buffer);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
TCP服务器
在前面提到有一个对象对服务端和客户端的通道进行管理,这个对象就是Selector,可以理解为选择器,这个选择器就是为通道服务的。服务器和客户端可以注册自己感兴趣的事件,这样Selector就可以不同的多个通道服务器的状态。通常Selector上可以注册的事件类型如下:
事件描述 | 事件定义 |
---|
服务端接收客户端连接事件 | SelectionKey.OP_ACCEPT(16) |
客户端连接服务端事件 | SelectionKey.OP_CONNECT(8) |
读事件 | SelectionKey.OP_READ(1) |
写事件 | SelectionKey.OP_WRITE(4) |
比如服务器在Selector对象上注册了OP_ACCEPT事件,那么当有客户端连接上的时候,该事件就可以被响应。
下面实现了一个简单的TCP服务器和客户端:
客户端
package com.rhwayfun.patchwork.nio.selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
/**
* @author: rhwayfun
* @since: 2016-05-28
*/
public class SelectorClient {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
public SelectorClient(String host,int port){
this.host = host;
this.port = port;
try {
init();
} catch (IOException e) {
e.printStackTrace();
}
}
private void init() throws IOException {
selector = Selector.open();
socketChannel = SocketChannel.open(new InetSocketAddress(host,port));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
new SelectorThread(selector).start();
}
public void writeDataToServer(String message) throws IOException {
ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes("UTF-8"));
socketChannel.write(writeBuffer);
}
public static void main(String[] args) throws IOException {
SelectorClient client = new SelectorClient("localhost",6666);
client.writeDataToServer("我是一个客户端");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
服务器
package com.rhwayfun.patchwork.nio.selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
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;
/**
* @author: rhwayfun
* @since: 2016-05-28
*/
public class SelectorServer {
private static final int PORT = 6666;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
private static final String GREETING = "Welcome to here.";
public static void main(String[] args) {
new SelectorServer().start(args);
}
private void start(String[] args) {
int port = PORT;
if (args.length == 1){
port = Integer.valueOf(args[0]);
}
System.out.println("listening on port " + port);
Iterator<SelectionKey> iterator = null;
try {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverChannel.socket();
SocketAddress address = new InetSocketAddress(port);
Selector selector = Selector.open();
serverSocket.bind(address);
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
int n = selector.select();
if (n == 0){
continue;
}
iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
registerChannel(selector,channel,SelectionKey.OP_READ);
sayHello(channel);
}
if (key.isReadable()){
readDataFromClient(key);
}
iterator.remove();
}
}
} catch (IOException e) {
iterator.remove();
}
}
/**
*
* @param key
*/
private void readDataFromClient(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
buffer.clear();
if (channel.read(buffer) < 0){
channel.close();
}else {
buffer.flip();
String receiveMsg = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
System.out.println("receive client message: " + receiveMsg + " from " + channel.getRemoteAddress());
}
}
/**
* 向客户端发送响应消息
* @param channel
* @throws IOException
*/
private void sayHello(SocketChannel channel) throws IOException {
buffer.clear();
buffer.put(GREETING.getBytes());
buffer.flip();
channel.write(buffer);
}
/**
* 注册客户端连接到选择器上
* @param selector
* @param channel
* @param opRead
* @throws IOException
*/
private void registerChannel(Selector selector, SocketChannel channel, int opRead) throws IOException {
if (channel == null){
return;
}
channel.configureBlocking(false);
channel.register(selector,opRead);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
通道线程
package com.rhwayfun.patchwork.nio.selector;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
/**
* @author: rhwayfun
* @since: 2016-05-28
*/
public class SelectorThread extends Thread{
private Selector selector;
public SelectorThread(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
int n = selector.select();
while (n > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
String receiveMsg = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
System.out.println("receive server message: " + receiveMsg + " from " + channel.getRemoteAddress());
key.interestOps(SelectionKey.OP_READ);
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
以上的代码演示如何使用NIO的相关API创建TCP服务器,把上面的代码理解到位,对NIO的API就掌握的差不多了。其实,在NIO编程中,一个重要的思想是非阻塞模式,选择器就是这种思想的体现,体会Selector在通道监听有助于理解非阻塞模式的应用场景。
小结
这样,我们通过实际的代码演示了如何使用NIO的相关API实现一个简单的TCP服务器,关键在于理解NIO模型的原理,非阻塞模式是NIO的思想内核。