栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

网络编程--NIO--Channel

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

网络编程--NIO--Channel

参考:https://www.cnblogs.com/ostenant/p/9695183.html

通道(Channel):
  由java.nio.channels包定义的,Channel表示IO源与目标打开的连接,Channel类似于传统的“流”,只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。通道主要用于传输数据,从缓冲区的一侧传到另一侧的实体(如文件、套接字…),反之亦然;通道是访问IO服务的导管,通过通道,我们可以以最小的开销来访问操作系统的I/O服务;顺便说下,缓冲区是通道内部发送数据和接收数据的端点。

在标准的IO当中,都是基于字节流/字符流进行操作的,而在NIO中则是是基于Channel和Buffer进行操作,其中的Channel的虽然模拟了流的概念,实则大不相同。

Channel的分类

广义上来说通道可以被分为两类:文件I/O和网络I/O,也就是文件通道和套接字通道。如果分的更细致一点则是:

  • FileChannel:从文件读写数据;
  • SocketChannel:通过TCP读写网络数据;
  • ServerSocketChannel:可以监听新进来的TCP连接,并对每个链接创建对应的SocketChannel
  • DatagramChannel:通过UDP读写网络中的数据。

通道可以以多种方式创建。Socket通道有可以直接创建新socket通道的工厂方法。但是一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel( )方法来获取。你不能直接创建一个FileChannel对象。

1、 FileChannel

Java NIO中的FileChannel是一个连接到文件的通道,可以通过文件通道读写文件。文件通道总是阻塞式的,因此FileChannel无法设置为非阻塞模式。

文件读写操作
代码实例:

package com.example.dtest.nio.channel.fileChannel;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelTest01 {

    public static void main(String[] args) {

//        测试写:
        System.out.println("Start to write");
//        通过FileChannel写入数据
        testWriteOnFileChannel();

//        测试读:
        System.out.println("Start to read");
//        通过FileChannel读取数据
        testReadOnFileChannel();
    }

//    文件写操作
    public static void testWriteOnFileChannel(){

        

        try {
            RandomAccessFile randomAccessFile = new RandomAccessFile("D:\test.txt", "rw");
            FileChannel fileChannel = randomAccessFile.getChannel();

            byte[] bytes = new String("Java Non-blocking IO").getBytes();
            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

//            将缓冲区中的字节写入文件通道中

            fileChannel.write(byteBuffer);
//            强制将通道中未写入磁盘的数据立刻写入到磁盘
            fileChannel.force(true);
//            清空缓冲区,释放内存
            byteBuffer.clear();
            fileChannel.close();


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

//    文件读操作:
    public static void testReadOnFileChannel(){

        try {
            FileInputStream fileInputStream =  new FileInputStream(new File("D:\test.txt"));
            FileChannel fileChannel = fileInputStream.getChannel();

            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
//            不断地写入缓冲区,写一次读一次
            while (fileChannel.read(byteBuffer) != -1){
//                缓冲区从写模式切换为读模式
                byteBuffer.flip();
//                开始读取
                while (byteBuffer.hasRemaining()){
//                    从一个字节一个字节地读取,并向后移动position地位置
                    System.out.println((char) byteBuffer.get());
                }
//                缓冲区不会被自动覆盖,需要主动调用该方法(实际上还是覆盖)
                byteBuffer.clear();
//
            }


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }


    }



}

测试结果:

2、transferFrom和transferTo

(一). transferFrom()的使用
(二). transferTo()的使用

FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中。
transferTo()方法将数据从FileChannel传输到目标channel中。下面是一个简单的例子:

package com.example.dtest.nio.channel.transferfromOrTo;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

public class TransferFromOrTo {

    public static void main(String[] args) {

        testTransferFrom();
        testTransferTo();

    }

//    FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中
    public static void testTransferFrom(){

        try {
            RandomAccessFile fromFile = new RandomAccessFile("D://file1.txt", "rw");
            FileChannel fromChannel = fromFile.getChannel();
            RandomAccessFile toFile = new RandomAccessFile("D://file2.txt", "rw");
            FileChannel toChannel = toFile.getChannel();

            long position = 0;
            long count = fromChannel.size();
            toChannel.transferFrom(fromChannel, position, count);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

//    transferTo()方法将数据从FileChannel传输到目标channel中
    public static void testTransferTo() {
        try {
            RandomAccessFile fromFile = new RandomAccessFile("D://file1.txt", "rw");
            FileChannel fromChannel = fromFile.getChannel();
            RandomAccessFile toFile = new RandomAccessFile("D://file3.txt", "rw");
            FileChannel toChannel = toFile.getChannel();

            long position = 0;
            long count = fromChannel.size();
            fromChannel.transferTo(position, count, toChannel);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }




}

测试结果:
file1里面的内容都会写入到file2和file3里面去

3、ServerSocketChannel

Java NIO中的ServerSocketChannel是一个可以监听新进来的TCP连接的通道。它类似ServerSocket,要注意的是和DatagramChannel和SocketChannel不同,ServerSocketChannel本身不具备传输数据的能力,而只是负责监听传入的连接和创建新的SocketChannel。

(一). 创建ServerSocketChannel

通过ServerSocketChannel.open()方法来创建一个新的ServerSocketChannel对象,该对象关联了一个未绑定ServerSocket的通道。通过调用该对象上的socket()方法可以获取与之关联的ServerSocket。

ServerSocketChannel socketChannel = ServerSocketChannel.open();

(二). 为ServerSocketChannel绑定监听端口号

在JDK 1.7之前,ServerSocketChannel没有bind()方法,因此需要通过他关联的的socket对象的socket()来绑定。
// JDK1.7之前

serverSocketChannel.socket().bind(new InetSocketAddress(25000));

从JDK1.7及以后,可以直接通过ServerSocketChannel的bind()方法来绑定端口号。
// JDK1.7之后

serverSocketChannel.bind(new InetSocketAddress(25000));

设置ServerSocketChannel的工作模式

ServerSocketChannel底层默认采用阻塞的工作模式,它提供了一个configureBlocking()方法,允许配置ServerSocketChannel以非阻塞方式运行。

代码演示:
阻塞模式:

package com.example.dtest.nio.channel.serversocketChannel.block;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerSocketChannelBlock {

    public static void main(String[] args) {

        try {
            blockingTest();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

//    阻塞模式
    public static void blockingTest() throws IOException{

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(25000));

        System.out.println("ServerSocketChannel listening on 25000...");

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        while (true){

            SocketChannel socketChannel = serverSocketChannel.accept();
            // 新连接没到达之前,后面的程序无法继续执行
            InetSocketAddress remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress();
            System.out.println("Remote address: " + remoteAddress.getHostString());

            StringBuilder stringBuilder = new StringBuilder();
            while (socketChannel.read(byteBuffer) != -1){
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()){
                    stringBuilder.append((char)byteBuffer.get());
//                    System.out.println((char)byteBuffer.get());
                }
                System.out.println(stringBuilder.toString());
                byteBuffer.clear();
            }



        }

    }




}

非阻塞模式:

package com.example.dtest.nio.channel.serversocketChannel.noblock;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerSocketChannelNONBlock {

    public static void main(String[] args) {

        try {
            nonBlockingTest();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //    非阻塞模式
    public static void nonBlockingTest() throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(25001));
        System.out.println("ServerSocketChannel listening on 25001...");

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        StringBuilder stringBuilder = new StringBuilder();
        while (true){

            SocketChannel socketChannel = serverSocketChannel.accept();
            // 新连接没到达之前,后面程序一直循环,直到检测到socketChannel不为null时进入真正的执行逻辑
            System.out.println("SocketChannel: " + socketChannel);
            if(socketChannel != null){

                InetSocketAddress remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress();
                System.out.println("Remote address: " + remoteAddress.getHostString());

                while (socketChannel.read(byteBuffer) != -1){
                    byteBuffer.flip();
                    while (byteBuffer.hasRemaining()){
                        stringBuilder.append((char) byteBuffer.get());
//                        System.out.println((char) byteBuffer.get());
                    }
                    byteBuffer.clear();
                    System.out.println(stringBuilder.toString());
                }


            }

        }


    }

}


4、 SocketChannel

socket是用来连接服务器端的,所以要服务器端开放才行,他自己不是服务,这里可以配合serversocketChannel(只做服务端)联调测试

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道,它是Socket类的对等类。

通常SocketChannel在客户端向服务器发起连接请求,每个SocketChannel对象创建时都关联一个对等的Socket对象。同样SocketChannel也可以运行在非阻塞模式下。

SocketChannel的用法
SocketChannel创建的方式有两种:

客户端主动创建:客户端打开一个SocketChannel并连接到某台服务器上;
服务端被动创建:一个新连接到达ServerSocketChannel时,服务端会创建一个SocketChannel。

(一). 创建SocketChannel

通过SocketChannel的静态方法open()创建SocketChannel对象。此时通道虽然打开,但并未建立连接。此时如果进行I/O操作会抛出NotYetConnectedException异常。

SocketChannel socketChannel = SocketChannel.open();

(二). 连接指定服务器

通过SocketChannel对象的connect()连接指定地址。该通道一旦连接,将保持连接状态直到被关闭。可通过isConnected()来确定某个SocketChannel当前是否已连接。

阻塞模式:
如果在客户端的SocketChannel阻塞模式下,即服务器端的ServerSocketChannel也为阻塞模式:

socketChannel.connect(new InetSocketAddress("127.0.0.1", 25000));
// connect()方法调用以后,socketChannel底层的连接创建完成后,才会执行后面的打印语句
System.out.println("连接创建完成...");

非阻塞模式:
两点需要注意:其一,SocketChannel需要通过configureBlocking()设置为非阻塞模式;其二,非阻塞模式下,connect()方法调用后会异步返回,为了确定连接是否建立,需要调用finishConnect()的方法。

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 25001));
// connect()方法调用以后,异步返回,需要手动调用finishConnect确保连接创建

while(!socketChannel.finishConnect()){
    // 检测到还未创建成功则睡眠10ms
    TimeUnit.MILLISECONDS.sleep(10);
}
System.out.println("连接创建完成...");

(三). 从SocketChannel读数据

利用SocketChannel对象的read()方法将数据从SocketChannel读取到Buffer。

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

// 非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了,所以需要关注它的int返回值。
while (socketChannel.read(byteBuffer) != -1) {
    byteBuffer.flip();
    while (byteBuffer.hasRemaining()) {
        System.out.println((char) byteBuffer.get());
    }
    byteBuffer.clear();
}

(四). 向SocketChannel写数据

利用SocketChannel对象的write()将Buffer的数据写入SocketChannel。

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("Client Blocking SocketChannel".getBytes());
// byteBuffer.put("Client Non-Blocking SocketChannel".getBytes());
byteBuffer.flip();

// 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()
while (byteBuffer.hasRemaining()) {
    socketChannel.write(byteBuffer);
}

// 保持睡眠,观察控制台输出
TimeUnit.SECONDS.sleep(20000);
socketChannel.close();

代码实例:
阻塞:

package com.example.dtest.nio.channel.socketchannel.block;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

public class SocketChannelBlockTest {

    public static void main(String[] args) {

        try {
            blockingWrite();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    public static  void blockingWrite() throws Exception{

        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("127.0.0.1",25000));

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("Client Blocking SocketChannel".getBytes());
            byteBuffer.flip();

            while (byteBuffer.hasRemaining()){
                socketChannel.write(byteBuffer);
            }

            TimeUnit.SECONDS.sleep(2000);
            socketChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

非阻塞:

package com.example.dtest.nio.channel.socketchannel.nonblock;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

public class SocketChannelNonBlockTest {

    public static void main(String[] args) {

        try {
            nonBlockingWrite();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void nonBlockingWrite() throws Exception{

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("127.0.0.1",25001));

        while (!socketChannel.finishConnect()){
            TimeUnit.MICROSECONDS.sleep(10);
        }

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("Client Non-Blocking SocketChannel".getBytes());
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()){
            socketChannel.write(byteBuffer);
        }

        TimeUnit.SECONDS.sleep(20000);
        socketChannel.close();

    }

}

启动服务端1(25000):ServerSocketChannelBlock:

启动服务端2(25001):ServerSocketChannelNONBlock:

启动客户端1(25000):SocketChannelBlockTest

查看服务端1:

启动客户端2(25001):

查看服务端2:

测试成功!

若是没启动服务端就启动客户端会报错:

java.net.ConnectException: Connection refused: connect

5、DatagramChannel

Java NIO中的DatagramChannel是一个能收发UDP包的通道,其底层实现为DatagramSocket + Selector。DatagramChannel可以调用socket()方法获取对等DatagramSocket对象。
DatagramChannel对象既可以充当服务端(监听者),也可以充当客户端(发送者)。如果需要新创建的通道负责监听,那么该通道必须绑定一个端口(或端口组):

DatagramChannel的完整示例:
数据报发送方:

package com.example.dtest.nio.channel.datagramchannel.send;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramchannelSendTest {

    public static void main(String[] args) {

        try {
            DatagramChannel datagramChannel = DatagramChannel.open();
            ByteBuffer byteBuffer = ByteBuffer.wrap("DatagramChannel Sender".getBytes());
            int byteSent = datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1",50020));
            System.out.println("Byte sent is: " + byteSent);


        } catch (IOException e) {
            e.printStackTrace();
        }


    }

}

数据报接收方:

package com.example.dtest.nio.channel.datagramchannel.get;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramchannelGetTest {

    public static void main(String[] args) {
        DatagramChannel datagramChannel;

        {
            try {
                datagramChannel = DatagramChannel.open();
                datagramChannel.socket().bind(new InetSocketAddress(50020));

                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                datagramChannel.receive(byteBuffer);
                byteBuffer.flip();

                StringBuilder stringBuilder = new StringBuilder();

                while (byteBuffer.hasRemaining()) {
//                    System.out.println((char) byteBuffer.get());
                    stringBuilder.append((char) byteBuffer.get());
                }

                System.out.println(stringBuilder.toString());

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }





}

首先启动数据接收方:DatagramchannelGetTest:


然后启动数据发送方:

查看数据接收方:

数据接收成功!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/582111.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号