微信公众号:大黄奔跑
关注我,可了解更多有趣的面试和编程相关问题。
写在之前
Hello,大家好,我是只会写 HelloWorld
的程序员大黄。
书接上回,本篇继续更新Netty系列文章——何为 channel。
Channel为何物
说起channel,直观翻译为管道,何为管道?类似于如下东西

想想生活中的管道可以做啥?最主要的作用是传输东西。
NIO 中国年的Channel也类似,主要有三大特点
既可以从通道中读取数据,又可以写数据到通道。
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
通道可以异步地读写。
Channel与buffer关系如下:

Channel的徒子徒孙
Channel中最主要的类型如下:

FileChannel
作用:从文件中读写数据,允许多线程读写,本身是线程安全的。
DatagramChannel
作用:能通过UDP读写网络中的数据。
SocketChannel
作用:能通过TCP读写网络中的数据。
ServerSocketChannel
作用:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel;支持异步的读写。
FileChannel如何配合buffer使用
如果我们准备将字符串写入到某个文件中,首先需要一个缓冲空间用来写字符串,然后再利用通道将缓冲区中的信息写入到文件中;Channel可以从buffer中进行读写。
具体流程如下:

小demo演示
/**
* 将message写入到本地文件中
*
* @param message 要写入的文本
* @param descFilePath 本地目标文件
*/
private static void writeToFile (String message, String descFilePath) {
try {
// 1. 创建一个用于写入文件的输出流
FileOutputStream fileOutputStream = new FileOutputStream(descFilePath);
// 2. 通过fileOutputStream获取对应的fileChannel,而底层实际上是FileChannelImpl
FileChannel channel = fileOutputStream.getChannel();
// 3. 创建一个Buffer【解决内容存储的Buffer位置】
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 4. 将原message写入到buffer中
byteBuffer.put(message.getBytes());
// 5. 对ByteBuffer进行反转,开始读取, 将前几步写入的数据读取出来
byteBuffer.flip();
// 6. 将ByteBuffer数据写入到FileChannel,这一步会不断的移动position直到limit
channel.write(byteBuffer);
// 7. 关闭
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
SocketChannel如何配合buffer使用
SocketChannel 是一个客户端用来进行 TCP 连接的 Channel。
创建一个 SocketChannel 的方法有两种,一种是站在客户端角度;另一种站在服务器角度。
打开一个 SocketChannel, 然后将其连接到某个服务器中
当一个 ServerSocketChannel 接受到连接请求时, 会返回一个 SocketChannel 对象.
打开SocketChannel
主要有两种方式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://127.0.0.1", 80));
读取数据
读取数据方式与FileChannel类似,先分配缓冲区,将数据写入到缓冲区,再从缓冲区读取数据到Channel中
ByteBuffer buf = ByteBuffer.allocate(48);
byteBuffer.put("Hello world");
int bytesRead = socketChannel.read(buf);
写入数据
由上面的代码继续分析,先将buffer由写模式切换到读模式,然后从buffer中读取数据到Channel中。
byteBuffer.flip();
channel.write(buf);
关闭通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.close();
异步模式
最初介绍SocketChannel 和 ServerSocketChannel 时提到了两者可以设置为异步模式,支持非阻塞的读、写、连接。
比如连接模式下,有可能连接还没有建立, connect 方法就返回了, 因此我们需要检查当前是否是连接到了主机, 因此通过一个 while 循环来判断.
// 先设置非阻塞
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 80));
// 只有连接完成了,才开始执行连接之后的任务
while(! socketChannel.finishConnect() ){
}
异步读写模式
在异步模式下, 读写的方式是一样的。
在读取时, 因为是异步的, 因此我们必须检查 read 的返回值, 来判断当前是否读取到了数据。
// 只能允许读了,才开始真正的读操作
if (xxxKey.isReadable()) {
// 1. 根据key反向获取到对应的Channel
SocketChannel channel = (SocketChannel) key.channel();
// 2. 获取该Channel关联的buffer
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
channel.read(byteBuffer);
}
ServerSocketChannel如何配合buffer使用
与SocketChannel使用方法类型,不过ServerSocketChannel是服务于服务器端的设置,用户监听客户端连接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(6667);
// 服务器绑定端口并且启动
serverSocketChannel.socket().bind(inetSocketAddress);
// 等待客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
如何监听是否连接
我们可以使用ServerSocketChannel.accept()方法来监听客户端的 TCP 连接请求, accept()方法会阻塞, 直到有连接到来,当有连接时,这个方法会返回一个 SocketChannel 对象
SocketChannel socketChannel = serverSocketChannel.accept();
切换为异步方式
异步情况下,accept()是非阻塞的, 因此如果此时没有连接到来, 那么 accept()方法会返回null;
但是有一个弊端在于系统需要一直循环,比较消耗资源。关于这一点正是下一篇文章想要介绍的内容 Selector,解决这种空轮询问题
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel =serverSocketChannel.accept();
if(socketChannel != null){
//do something with socketChannel...
}
}
UDP好友-DatagramChannel
打开连接
与可靠传输的SocketChannel相比,DatagramChannel只需要绑定端口,因为在某种意义上是不需要进行可靠的连接,然后再进行传输数据。
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(8080));
读取数据
直接将数据从buffer中读取到channel
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
发送数据
String mes = "Hello"
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(mes.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("127.0.0.1", 8080));
总结
本文主要介绍了 Channel 作用及其用法,秉着说人话的初衷写本篇文章,深知写让人通俗易懂的文章之难,查阅了诸多资料,如有一些思考不正确,还往指正及互相交流。
争取写最通俗易懂的netty教程。




