SelectableChannel

SelectableChannel

可选通道的使用大致过程如下:
1. 新建通道
2. 将通道已经通道感兴趣的事件注册到选择器Selector上
3. 通过SelectKey获得需要处理的通道,然后对通道进行处理

这里写图片描述

SelectableChannel实现类

下图是SelectableChannel的实现类,可以看出主要分为几大块:

  • 有关UDP协议的:DatagramChannel
  • 有关SCTP协议的:SctpChannel、SctpMultiChannel、SctpServerChannel
  • [有关TCP协议的:ServerSocketChannel、SocketChannel
  • 有关管道的:SinkChannel、SourceChannel这两个抽象类定义在java.nio.channels.Pipe类中

这里写图片描述

所以下面从以上四中类型来讲述:

Socket/ServerSocket、SocketChannel/ServerSocketChannel

     要说SocketChannelServerSocketChannel,就避不开SocketServerSocket这两个类,这两个类位于java.net包下。

  • 服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接。
  • 客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接。
  • 服务器接受到客户端的连接受,再生成一个Socket或者SocketChannel与此客户端通信

     Socket和SocketChannel可以通过 socket.channel() SocketChannel.socket() 方法相互转换,同理ServerSocket 和ServerSocketChannel 也可以相互转换

Socket与ServerSocket
  • Socket: 套接字是网络连接的一个端点。套接字使得一个应用可以从网络中读取和写入数据。放在两个不同计算机上的两个应用可以通过连接发送和接受字节流。也就是在java中通信是在两个Socket之间进行的。
  • ServerSocket:服务器套接字的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请求,它创建一个Socket实例来与客户端进行通信。

所以二者的关系相当于:

这里写图片描述

Socket简介

Socket的API如下:

这里写图片描述

将上面的API函数大致可以分为几部分,上图中的画圈部分并不一定准确,只是大概:

①:是当前Socket的状态

②:是构造Socket,以及与其他Socket进行连接,还有获得当前Socket与连接的Socket的信息

③:有关数据流转的设置,比如超时时间、Nagle算法、窗口大小等

ServerSocket简介

ServerSocket的API如下:

这里写图片描述

①:ServerSocket的状态属性

②:ServerSocket的构造函数、监听地址端口等

③:数据传输的属性,比如支不支持MulticastSockets等

Socket与ServerSocket使用实例

首先开启一个服务端程序:启动后程序会阻塞在serverSocket.accept()这一行。

然后使用下面两种方式进行访问:

  1. 使用socket程序
  2. 使用java程序进行访问

服务端程序:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.CharBuffer;

public class TestServerSocket {

    public static void main(String[] args) {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost(); //主机名为:Q5158E0K1TDIR55
            @SuppressWarnings("resource")
            ServerSocket serverSocket = new ServerSocket(8089, 20, inetAddress);
            while(true) {
                Socket socket = serverSocket.accept();
                socket.setSoTimeout(10);//设置socket超时时间
                 //也可以使用线程池,这么弄,差不多就是一个socket连接一个线程。
                 //当然也可以使用队列,将任务放在队列中,使用线程池来处理
                new Thread(new Runnable() {
                    public void run() {
                        //获得输入流
                        try {
                            //读取数据,当使用命令行telnet时,不输入字符,会一致阻塞在read(b)中
//                          InputStream inputStream = socket.getInputStream();
//                          OutputStream outputStream = socket.getOutputStream();
//                          byte[] b = new byte[100];
//                          while(-1 != inputStream.read(b)) {
//                              System.out.println(0);
//                              //获得输出流,并将其转换为 printStream,并传给客户端“----已收到”
                                outputStream.write("123".getBytes());
//                          }
//                          inputStream.close();
//                          outputStream.close();

                            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
                            CharBuffer chars = CharBuffer.allocate(20);
                            PrintStream printStream = new PrintStream(socket.getOutputStream(), true, "GBK");
                            String str = "";
                            while(-1 != (str = bufferedReader.readLine()).length()) {
                                System.out.println(str);
                                //获得输出流,并将其转换为 printStream,并传给客户端“----已收到”
                                printStream.println("-----已收到" + str + "-----");
                                if("exit".equals(str)) {
                                    break;
                                }
                            }
                            bufferedReader.close();
                            printStream.close();
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }

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

使用telnet访问

这里写图片描述

当我使用telnet进行访问的时候,出现了三个问题

  • 一个是输入中文的问题,使用cmd中发送中文字符变为??,且回传的字符也不对。我认为代码是没有问题,假如传输的是gbk字符,那么接受用gbk接受。我认为是当前telnet不支持中文。我下载了一个PuTTY使用telnet进行发送字符,设置telnet连接传输为utf-8,然后java文件中使用utf-8进行接受解析,最后返回显示在PuTTY中不乱码。
  • 一个是命令行中输入的字符有时不显示的问题:在输入telnet Q5158E0K1TDIR55 8089后,回车,然后点击ctrl+],然后回车,此时就会回显了。
  • 如何从命令行关闭连接:点击ctrl+],然后输入quit,就会退出

使用socket程序进行访问

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class TestSocket {

    public static void main(String[] args) {
        try {
            Socket socket = new Socket("Q5158E0K1TDIR55", 8089);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
            PrintStream printStream = new PrintStream(socket.getOutputStream(), true, "GBK");
            printStream.println("我们都是好孩子");
            System.out.println(bufferedReader.readLine());
            socket.shutdownOutput();
            socket.shutdownInput();
            socket.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
SocketChannel/ServerSocketChannel

可选择的通道,通过SelectKey选择准备就绪的通道

  1. 创建一个选择器Selector,这个选择器包含(标识符selectkey、准备就绪的通道)的集合
  2. 创建通道(使用open创建,绑定地址),将(通道、通道感兴趣的事件)注册到Selector上,等待事件的到来
  3. 遍历触发的事件的集合,获得事件对应的通道,最后处理事件

这两个类都继承自抽象类AbstractSelectableChannel,该类的API函数如下:

这里写图片描述

SocketChannel与ServerSocketChannel的API函数如下:

这里写图片描述

代码示例

     SocketChannel、ServerSocketChannel对于SelectableChannel来说是同一级别的,都是注册在Selector上。

服务端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
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;

/**
 * 测试可选通道,
 * 1.将通道(包括ServerSocketChannel、SocketChannel)注册到Selector上,
 * 2.由Selector获得当前通道状态
 * 3.将这些用户感兴趣的通道状态的通道拿出来处理
 * @author zt
 */
public class TestServerSocketChannel {

    public static void main(String[] args) {
        TestServerSocketChannel testServerSocketChannel = new TestServerSocketChannel();
        testServerSocketChannel.test();
    }
    public void test() {
        try {
            //创建一个选择器,在这个选择器中创建了一个Pipe
            Selector selector = Selector.open();
            //打开一个ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //设置通道为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //在通道上绑定一个 ServerSocket 地址
            serverSocketChannel.socket().bind(new InetSocketAddress(8090));
            //将通道注册到选择器上,并且设置选择器感兴趣的事件是 OP_ACCEPT
            //注意:一个通道可以注册到多个选择器上,但是一个同一个通道在同一个选择器中只能注册一次
            //另外,不同的通道在选择器中注册的感兴趣事件不一样,也就是第二个参数是有限制的,由Channel类型决定
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //此时相当于服务端已经弄好了,就等连接到来
            System.out.println("-------服务端开始等待客户端连接------------");
            while(true) { //等待连接到来
                //选择器阻塞,等待事件到来,事件不到来,阻塞,事件到来,返回事件到来的通道个数
                int i = selector.select();
                if(i == 0) {
                    continue;
                }
                //遍历感兴趣的事件
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while(it.hasNext()) {
                    SelectionKey selectionKey = it.next();
                    //如果是OP_ACCEPT,表示这个事件是ServerSocketChannel的事件,因为SocketChannel没有这个事件
                    if(selectionKey.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                        //ServerSocketChannel准备好接受连接,产生一个SocketChannel,用于获得当前准备接受的连接
                        //建立连接
                        SocketChannel channel = server.accept();
                        //将建立的连接设置为准备读,并注册到选择器上
                        registerChannel(selector, channel, SelectionKey.OP_READ);
                        //向建立的通道中写入 "Hi there!\r\n"
                        sayHello(channel);
                    }
                    //如果是可读事件,没有在ServerSocketChannel上注册可读,只在SocketChannel上注册了可读
                    if(selectionKey.isReadable()) {
                        //从通道中读取数据,
                        //只有通道中有数据了,才会进入这个if块中
                        readFromSocket(selectionKey);
                    }
                    //此时只是将当前触发的事件list中移除,
                    //在下一次的select时,还会查看该通道是否有感兴趣的事件发生
                    it.remove();
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 向通道中写入"Hi there!\r\n"
     * @param channel
     * @throws IOException
     */
    private void sayHello(SocketChannel channel) throws IOException {
        byteBuffer.clear();
        byteBuffer.put("Hi there!\r\n".getBytes());
        byteBuffer.flip();
        channel.write(byteBuffer);
    }
    /**
     * 将channel注册到选择器上
     * @param selector 选择器
     * @param channel 通道
     * @param opt 感兴趣的事件
     * @throws IOException
     */
    public void registerChannel(Selector selector, SocketChannel channel, int opt) throws IOException {
        if(null == channel) {
            return;
        }
        channel.configureBlocking(false);
        channel.register(selector, opt);
    }
    private ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);

    /**
     * 从selectionKey的通道中读取数据
     * @param selectionKey
     * @throws IOException
     */
    public void readFromSocket(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        byteBuffer.clear();
        int count = 0;
        //当有数据可读时,第一次读到的数据不应该是0,它会一直读取,直到读取的数据为0,或为-1,就将
        while((count = socketChannel.read(byteBuffer)) > 0) {
            byteBuffer.flip();
            //将数据回写到通道中
            while(byteBuffer.hasRemaining()) {
                socketChannel.write(byteBuffer);
            }
            byteBuffer.clear();
        }
        if(count < 0) {
            socketChannel.close();
        }
    }
}

客户端代码:

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

public class TestSocketChannel {

    public static void main(String[] args) {

        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(true);
            System.out.println(socketChannel.connect(new InetSocketAddress("Q5158E0K1TDIR55", 8090)));
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            while(true) {
                int count = 0;
                while((count = socketChannel.read(byteBuffer)) > 0) {
                    byteBuffer.flip();
                    while (byteBuffer.hasRemaining()) {
                        System.out.print((char) byteBuffer.get());
                    }
                    //这句无法读出正确的值
                    //System.out.println("收到char:" + byteBuffer.asCharBuffer());
                    byteBuffer.clear();
                }
                socketChannel.write(byteBuffer.put("return".getBytes()));

            }

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

DatagramSocket与DatagramChannel

     DatagramSocket是对UDP的封装,DatagramSocket本身不维护连接的状态,因为UDP协议面向非连接,所以也不会产生IO流,只是用来发送与接收数据报。在java中数据报使用DatagramPacket来表示,所以最有用的方法是send与receive,表示发送与接收报文。可以使用DatagramSocket来收发数据报,也可以使用DatagramChannel来收发数据。

     UDP的这种方式,定义服务端与客户端都是DatagramSocket,该类作为两个端点,只是用来接收报文与发送报文。两个DatagramSocket之间的交互使用DatagramPacker来交换信息。由于UDP面向无连接,所以两个端点端不需要持有另一端的地址以及port信息。而两个端点进行交互时使用的DatagramPacker含有该报文发送方的信息。

     DatagramChannel,可以充当服务器(监听者)也可以充当客户端(发送者)。如果您希望新创建的通道负责监听,那么通道必须首先被绑定到一个端口或地址/端口组合上。

他们的API函数如下所示:

下面是DatagramSocket的函数API:

①:当前DatagramSocket的一些状态

②:初始化DatagramSocket的信息,这里传入的地址与端口,都是自己的。

③:数据的传输以及一些配置

④:获得Channel

这里写图片描述

下图是DatagramSocketChannel的函数API:

这里写图片描述

DatagramSocket示例:

服务端,也就是绑定一个自己的端口接收数据

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * 开启一个udp端口,等待数据到来。并将传来的数据传回去
 * @author zt
 *
 */
public class TestDatagramSocketServer {

    private static byte[] buf = new byte[1024];
    @SuppressWarnings("resource")
    public static void main(String[] args) {
        try {
            //在8090端口等待数据到来
            DatagramSocket datagramSocket = new DatagramSocket(8090);
            //定义接收数据包
            DatagramPacket receiveP = new DatagramPacket(buf , buf.length);
            //在此处阻塞,直到有数据到来
            datagramSocket.receive(receiveP);

            System.out.println("接受到的数据:" + new String(buf, 0, receiveP.getLength()));
            //定义发送的数据包
            DatagramPacket sendP = new DatagramPacket(buf, buf.length, receiveP.getAddress(), receiveP.getPort());
            datagramSocket.send(sendP);

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

客户端:发送报文数据

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

/**
 * 客户端使用SyStem.in作为输入,将数据发送到另一个DatagramSocket中
 * @author zt
 *
 */
public class TestDatagramSocketClient {

    public static void main(String[] args) {
        try {
            DatagramSocket datagramSocket = new DatagramSocket();
            byte[] buf = new byte[1024];
            DatagramPacket sendP = new DatagramPacket(new byte[0] , 0, InetAddress.getByName("127.0.0.1"), 8090);
            DatagramPacket receiveP = new DatagramPacket(buf, buf.length);
            //使用控制台输入作为输入
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNextLine()) {
                byte[] bytes = scanner.nextLine().getBytes();
                sendP.setData(bytes);
                datagramSocket.send(sendP);
                datagramSocket.receive(receiveP);
                System.out.println(new String(buf, 0, receiveP.getLength()));
            }

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

DatagramSocketChannel示例(下面例子来自java NIO,使用 DatagramChannel 的时间服务客户端):

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.DatagramChannel;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
/**
 * Request time service, per RFC 868. RFC 868
 * (http://www.ietf.org/rfc/rfc0868.txt) is a very simple time protocol
 * whereby one system can request the current time from another system.
 * Most Linux, BSD and Solaris systems provide RFC 868 time service
 * on port 37. This simple program will inter-operate with those.
 * The National Institute of Standards and Technology (NIST) operates
 * a public time server at time.nist.gov.
 *
 * The RFC 868 protocol specifies a 32 bit unsigned value be sent,
 * representing the number of seconds since Jan 1, 1900. The Java
 * epoch begins on Jan 1, 1970 (same as unix) so an adjustment is
 * made by adding or subtracting 2,208,988,800 as appropriate. To
 * avoid shifting and masking, a four-byte slice of an
 * eight-byte buffer is used to send/recieve. But getLong( )
 * is done on the full eight bytes to get a long value.
 *
 * When run, this program will issue time requests to each hostname
 * given on the command line, then enter a loop to receive packets.
 * Note that some requests or replies may be lost, which means
 * this code could block forever.
 *
 * @author Ron Hitchens (ron@ronsoft.com)
 */
public class TestDatagramChannelClient {
    private static final int DEFAULT_TIME_PORT = 37;
    private static final long DIFF_1900 = 2208988800L;
    protected int port = DEFAULT_TIME_PORT;
    protected List remoteHosts;
    protected DatagramChannel channel;
    public TestDatagramChannelClient (String [] argv) throws Exception {
        if (argv.length == 0) {
            throw new Exception ("Usage: [ -p port ] host ...");
        }
        parseArgs (argv);
        this.channel = DatagramChannel.open( );
    }
    protected InetSocketAddress receivePacket (DatagramChannel channel, ByteBuffer buffer) throws Exception {
        buffer.clear( );
        // Receive an unsigned 32-bit, big-endian value
        return ((InetSocketAddress) channel.receive (buffer));
    }
    // Send time requests to all the supplied hosts
    protected void sendRequests( ) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate (1);
        Iterator it = remoteHosts.iterator( );
        while (it.hasNext( )) {
            InetSocketAddress sa = (InetSocketAddress) it.next( );
            System.out.println ("Requesting time from "
                    + sa.getHostName() + ":" + sa.getPort( ));
            // Make it empty (see RFC868)
            buffer.clear().flip( );
            // Fire and forget
            channel.send (buffer, sa);
        }
    }
    // Receive any replies that arrive
    public void getReplies( ) throws Exception {
        // Allocate a buffer to hold a long value
        ByteBuffer longBuffer = ByteBuffer.allocate (8);
        // Assure big-endian (network) byte order
        longBuffer.order (ByteOrder.BIG_ENDIAN);
        // Zero the whole buffer to be sure
        longBuffer.putLong (0, 0);
        // Position to first byte of the low-order 32 bits
        longBuffer.position (4);
        // Slice the buffer; gives view of the low-order 32 bits
        ByteBuffer buffer = longBuffer.slice( );
        int expect = remoteHosts.size( );
        int replies = 0;
        System.out.println ("");
        System.out.println ("Waiting for replies...");
        while (true) {
            InetSocketAddress sa;
            sa = receivePacket (channel, buffer);
            buffer.flip( );
            replies++;
            printTime (longBuffer.getLong (0), sa);
            if (replies == expect) {
                System.out.println ("All packets answered");
                break;
            }
            // Some replies haven't shown up yet
            System.out.println ("Received " + replies
                    + " of " + expect + " replies");
        }
    }
    // Print info about a received time reply
    protected void printTime (long remote1900, InetSocketAddress sa) {
        // local time as seconds since Jan 1, 1970

        long local = System.currentTimeMillis( ) / 1000;
        // remote time as seconds since Jan 1, 1970
        long remote = remote1900 - DIFF_1900;
        Date remoteDate = new Date (remote * 1000);
        Date localDate = new Date (local * 1000);
        long skew = remote - local;
        System.out.println ("Reply from "
                + sa.getHostName() + ":" + sa.getPort( ));
        System.out.println (" there: " + remoteDate);
        System.out.println (" here: " + localDate);
        System.out.print (" skew: ");
        if (skew == 0) {
            System.out.println ("none");
        } else if (skew > 0) {
            System.out.println (skew + " seconds ahead");
        } else {
            System.out.println ((-skew) + " seconds behind");
        }
    }
    protected void parseArgs (String [] argv) {
        remoteHosts = new LinkedList( );
        for (int i = 0; i < argv.length; i++) {
            String arg = argv [i];
            // Send client requests to the given port
            if (arg.equals ("-p")) {
                i++;
                this.port = Integer.parseInt (argv [i]);
                continue;
            }
            // Create an address object for the hostname
            InetSocketAddress sa = new InetSocketAddress (arg, port);
            // Validate that it has an address
            if (sa.getAddress( ) == null) {
                System.out.println ("Cannot resolve address: "
                        + arg);
                continue;
            }
            remoteHosts.add (sa);
        }
    }
    // --------------------------------------------------------------
    public static void main (String [] argv) throws Exception {
        TestDatagramChannelClient client = new TestDatagramChannelClient (argv);
        client.sendRequests( );
        client.getReplies( );
    }
}

下面代码是RFC868时间服务,作为时间服务器:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.DatagramChannel;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
/**
 * Provide RFC 868 time service (http://www.ietf.org/rfc/rfc0868.txt).
 * This code implements an RFC 868 listener to provide time
 * service. The defined port for time service is 37. On most
 * unix systems, root privilege is required to bind to ports
 * below 1024. You can either run this code as root or
 * provide another port number on the command line. Use
 * "-p port#" with TimeClient if you choose an alternate port.
 *
 * Note: The familiar rdate command on unix will probably not work
 * with this server. Most versions of rdate use TCP rather than UDP
 * to request the time.
 * * @author Ron Hitchens (ron@ronsoft.com)
 */
public class TestDatagramChannelServer {
    private static final int DEFAULT_TIME_PORT = 37;
    private static final long DIFF_1900 = 2208988800L;
    protected DatagramChannel channel;
    public TestDatagramChannelServer (int port) throws Exception {
        this.channel = DatagramChannel.open( );
        this.channel.socket( ).bind (new InetSocketAddress (port));
        System.out.println ("Listening on port " + port
                + " for time requests");
    }
    public void listen( ) throws Exception {
        // Allocate a buffer to hold a long value
        ByteBuffer longBuffer = ByteBuffer.allocate (8);
        // Assure big-endian (network) byte order
        longBuffer.order (ByteOrder.BIG_ENDIAN);
        // Zero the whole buffer to be sure
        longBuffer.putLong (0, 0);
        // Position to first byte of the low-order 32 bits
        longBuffer.position (4);
        // Slice the buffer; gives view of the low-order 32 bits
        ByteBuffer buffer = longBuffer.slice( );
        while (true) {
            buffer.clear( );
            SocketAddress sa = this.channel.receive (buffer);
            if (sa == null) {
                continue; // defensive programming
            }
            // Ignore content of received datagram per RFC 868
            System.out.println ("Time request from " + sa);
            buffer.clear( ); // sets pos/limit correctly
            // Set 64-bit value; slice buffer sees low 32 bits
            longBuffer.putLong (0,
                    (System.currentTimeMillis( ) / 1000) + DIFF_1900);
            this.channel.send (buffer, sa);
        }
    }
    // --------------------------------------------------------------
    public static void main (String [] argv) throws Exception {
        int port = DEFAULT_TIME_PORT;
        if (argv.length > 0) {
            port = Integer.parseInt (argv [0]);
        }
        try {
            TestDatagramChannelServer server = new TestDatagramChannelServer (port);
            server.listen( );
        } catch (SocketException e) {
            System.out.println ("Can't bind to port " + port
                    + ", try a different one");
        }
    }
}

Pipe

Pipe的API如下:

这里写图片描述

     Pipe 类创建一对提供环回机制的 Channel 对象。这两个通道的远端是连接起来的,以便任何写在 SinkChannel 对象上的数据都能出现在 SourceChannel 对象上。

     Pipe 实例是通过调用不带参数的 Pipe.open( )工厂方法来创建的。Pipe 类定义了两个嵌套的通道类来实现管路。这两个类是 Pipe.SourceChannel(管道负责读的一端)和 Pipe.SinkChannel(管道负责写的一端)。这两个通道实例是在 Pipe 对象创建的同时被创建的,可以通过在 Pipe 对象上分别调用 source( )和 sink( )方法来取回。

代码示例(来之Java nio书):

import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.Pipe;
import java.nio.channels.Channels;
import java.util.Random;
/**
 * Test Pipe objects using a worker thread.
 *
 * Created April, 2002
 * @author Ron Hitchens (ron@ronsoft.com)
 */
public class TestPipe {
    public static void main (String [] argv) throws Exception {
        // Wrap a channel around stdout
        WritableByteChannel out = Channels.newChannel (System.out);
        // Start worker and get read end of channel
        ReadableByteChannel workerChannel = startWorker (10);
        ByteBuffer buffer = ByteBuffer.allocate (100);
        while (workerChannel.read (buffer) >= 0) {
            buffer.flip( );
            out.write (buffer);
            buffer.clear( );
        }
    }
    // This method could return a SocketChannel or
    // FileChannel instance just as easily
    private static ReadableByteChannel startWorker (int reps) throws Exception {
        Pipe pipe = Pipe.open( );
        Worker worker = new Worker (pipe.sink( ), reps);
        worker.start( );
        return (pipe.source( ));
    }
    //-----------------------------------------------------------------
    /**
     * A worker thread object which writes data down a channel.
     * Note: this object knows nothing about Pipe, uses only a
     * generic WritableByteChannel.
     */
    private static class Worker extends Thread {
        WritableByteChannel channel;
        private int reps;
        Worker (WritableByteChannel channel, int reps) {
            this.channel = channel;
            this.reps = reps;
        }
        //Thread execution begins here
        public void run( ) {
            ByteBuffer buffer = ByteBuffer.allocate (100);
            try {
                for (int i = 0; i < this.reps; i++) {
                    doSomeWork (buffer);
                    //channel may not take it all at once
                    while (channel.write (buffer) > 0) {
                        //empty
                    }
                }
                this.channel.close( );
            } catch (Exception e) {
                //easy way out; this is demo code
                e.printStackTrace( );
            }
        }
        private String [] products = {
                "No good deed goes unpunished",
                "To be, or what?",
                "No matter where you go, there you are",
                "Just say \"Yo\"",
                "My karma ran over my dogma"
        };
        private Random rand = new Random( );
        private void doSomeWork (ByteBuffer buffer) {
            int product = rand.nextInt (products.length);
            buffer.clear( );
            buffer.put (products [product].getBytes( ));
            buffer.put ("\r\n".getBytes( ));
            buffer.flip( );
        }
    }
}

SctpChannel、SctpMultiChannel、SctpServerChannel

     这三个类是在jdk1.7中添加的。下面的内容来自https://blog.csdn.net/thirtyfive16/article/details/53124087?locationNum=5&fps=1

     SCTP是一个可靠的,面向消息的传输协议。在TCP/IP协议中和UDP/TCP处于同一层。SCTP是基于sesstion的,要先建立端点之间的连接,然后传输数据。

     SCTP支持多址的,也就是说一个端点可以由多个地址表示,每个地址可以用来传输数据,从而提供了网络的冗余。端点之间可以在建立连接的时候,交换多个地址。其中一个地址是主地址,也是向对端传输数据的默认地址。一个端口代表一个特定的session。

     SCTP是基于消息的。每一个association支持多个独立的逻辑流。每个流代表了一系列顺序消息。每个流都是相互独立的,也就是流的唯一标识和序列号在数据包中存在,从而使得每个流中的消息都是顺序传递的。

     SCTP API是基于NIO设计的,因此可以利用非阻塞的复用I/O. 引入了一个新的包com.sun.nio.sctp。包名字不是java.nio.channels.sctp,这意味着这些API是完全公开的。当这些API成熟后,再引入到标准API中。

     这个包中的主要类有三个新channel类型。可以分为两个逻辑组。

  1. 第一个组和TCP类似,包括了sctpChannel 和 SctpServerChannel。一个SctpChannel只能控制一个association,也就是说只能和一个端点发送接受数据。 SctpSeverChannel监听和接受在socket地址上的接入。
  2. 第二组包括了SctpMultiChannel。这个类可以控制多个association,因此可以和多个端点传送数据。

     SCTP是事件驱动的。程序能接收到特定的SCTP事件。这些事件尤其实在SctpMultiChannel有用。SctpMultiChannel可以控制多个association,所以需要跟踪每个association的状态。例如,当收到AssociationChangNotification的时候,表示有个新的连接的association或者断开。如果association支持动态地址配置,PeerAddressChangeNotification表示IP地址在对端刚刚增加或者删除。

     例子展示了多个流。服务器端视线了一个时间的协议,它把当前日期时间在一个流中以英语形式传递,一个流中以法语形式传递。为了简便,本程序没有考虑异常处理。

服务端代码如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import com.sun.nio.sctp.MessageInfo;
import com.sun.nio.sctp.SctpChannel;
import com.sun.nio.sctp.SctpServerChannel;

public class DaytimeServer {
    static int SERVER_PORT = 3456;
    static int US_STREAM = 0;
    static int FR_STREAM = 1;

    static SimpleDateFormat USformatter = new SimpleDateFormat(
                                "h:mm:ss a EEE d MMM yy, zzzz", Locale.US);
    static SimpleDateFormat FRformatter = new SimpleDateFormat(
                                "h:mm:ss a EEE d MMM yy, zzzz", Locale.FRENCH);

    public static void main(String[] args) throws IOException {
        SctpServerChannel ssc = SctpServerChannel.open();
        InetSocketAddress serverAddr = new InetSocketAddress(SERVER_PORT);
        ssc.bind(serverAddr);

        ByteBuffer buf = ByteBuffer.allocateDirect(60);
        CharBuffer cbuf = CharBuffer.allocate(60);
        Charset charset = Charset.forName("ISO-8859-1");
        CharsetEncoder encoder = charset.newEncoder();

        while (true) {
            SctpChannel sc = ssc.accept();

            /* get the current date */
            Date today = new Date();
            cbuf.put(USformatter.format(today)).flip();
            encoder.encode(cbuf, buf, true);
            buf.flip();

            /* send the message on the US stream */
            MessageInfo messageInfo = MessageInfo.createOutgoing(null,
                                                                 US_STREAM);
            sc.send(buf, messageInfo);

            /* update the buffer with French format */
            cbuf.clear();
            cbuf.put(FRformatter.format(today)).flip();
            buf.clear();
            encoder.encode(cbuf, buf, true);
            buf.flip();

            /* send the message on the French stream */
            messageInfo.streamNumber(FR_STREAM);
            sc.send(buf, messageInfo);

            cbuf.clear();
            buf.clear();

            sc.close();
        }
    }
}

客户端代码:

import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import com.sun.nio.sctp.AbstractNotificationHandler;
import com.sun.nio.sctp.AssociationChangeNotification;
import com.sun.nio.sctp.AssociationChangeNotification.AssocChangeEvent;
import com.sun.nio.sctp.HandlerResult;
import com.sun.nio.sctp.MessageInfo;
import com.sun.nio.sctp.SctpChannel;
import com.sun.nio.sctp.ShutdownNotification;

public class DaytimeClient {
    static int SERVER_PORT = 3456;
    static int US_STREAM = 0;
    static int FR_STREAM = 1;

    public static void main(String[] args) throws IOException {
        InetSocketAddress serverAddr = new InetSocketAddress("localhost", SERVER_PORT);
        ByteBuffer buf = ByteBuffer.allocateDirect(60);
        Charset charset = Charset.forName("ISO-8859-1");
        CharsetDecoder decoder = charset.newDecoder();

        SctpChannel sc = SctpChannel.open(serverAddr, 0, 0);

        /* handler to keep track of association setup and termination */
        AssociationHandler assocHandler = new AssociationHandler();

        /* expect two messages and two notifications */
        MessageInfo messageInfo = null;
        do {
            messageInfo = sc.receive(buf, System.out, assocHandler);
            buf.flip();

            if (buf.remaining() > 0 &&
                    messageInfo.streamNumber() == US_STREAM) {

                System.out.println("(US) " + decoder.decode(buf).toString());
            } else if (buf.remaining() > 0 && 
                    messageInfo.streamNumber() == FR_STREAM) {

                System.out.println("(FR) " +  decoder.decode(buf).toString());
            }
            buf.clear();
        } while (messageInfo != null);

        sc.close();
    }

    static class AssociationHandler extends AbstractNotificationHandler<PrintStream>
    {
        public HandlerResult handleNotification(AssociationChangeNotification not,
                PrintStream stream) {
            if (not.event().equals(AssocChangeEvent.COMM_UP)) {
                int outbound = not.association().maxOutboundStreams();
                int inbound = not.association().maxInboundStreams();
                stream.printf("New association setup with %d outbound streams" +
                        ", and %d inbound streams.\n", outbound, inbound);
            }

            return HandlerResult.CONTINUE;
        }

        public HandlerResult handleNotification(ShutdownNotification not, PrintStream stream) {
            stream.printf("The association has been shutdown.\n");
            return HandlerResult.RETURN;
        }
    }
}
### 回答1: Java 中的 Channel 对象本身不支持序列化,但是可以将 Channel 对象封装在可序列化的对象中,并在需要的时候使用 Channel 对象。 比如,你可以定义一个可序列化的类,其中包含一个 Channel 对象的成员变量: ``` import java.io.Serializable; import java.nio.channels.Channel; public class SerializableChannel implements Serializable { private final Channel channel; public SerializableChannel(Channel channel) { this.channel = channel; } public Channel getChannel() { return channel; } } ``` 然后你就可以将 SerializableChannel 对象序列化并传输到另一个 JVM 进程中,并在需要的时候获取 Channel 对象: ``` SerializableChannel serializableChannel = ...; Channel channel = serializableChannel.getChannel(); ``` 注意,在传输 SerializableChannel 对象之后,Channel 对象可能不再与原来的通道连接,因此你需要根据你的需求确定是否需要重新打开通道。 ### 回答2: 在Java中,Channel对象是用于访问IO操作的实体,例如文件、网络连接等。由于Channel的设计目的是用于IO操作,它并不直接支持序列化接口。但是我们可以通过一些间接的方式来实现对Channel对象的序列化。 一种常见的方法是使用包装类。我们可以创建一个自定义的类,将Channel对象作为该类的成员变量,并实现Serializable接口。通过这种方式,在序列化时,我们可以将该类对象作为一个整体进行传输,间接实现对Channel对象的传输。 另一种方法是使用外部化类。我们可以创建一个实现Externalizable接口的类,在类中定义与Channel对象相关的属性和方法,并在序列化和反序列化时,手动存储和读取Channel对象的状态。这样我们可以控制序列化和反序列化的过程,将Channel中的关键信息存储和恢复。 除了以上两种简单的方法,还可以利用第三方库来实现对Channel对象的序列化。例如,可以使用Google的Protobuf库或Apache的Avro库来定义和序列化Channel对象的数据结构,然后将序列化后的字节流进行传输或持久化。 总之,由于Channel对象的设计初衷是用于IO操作,它本身并不直接支持序列化接口。但我们可以通过以上提到的方式,间接地实现对Channel对象的序列化,方便在分布式系统中进行传输和持久化操作。 ### 回答3: 在Java中,channel对象是通过实现SelectableChannel接口来创建的,而SelectableChannel接口实际上是继承了Channel接口和Closeable接口。可序列化的接口是Serializable接口,因此要使Channel对象可序列化,需要满足以下几个步骤: 1. 创建自定义的Channel类,该类需要实现SelectableChannel接口,并且需要实现Serializable接口。 ```java public class MyChannel implements SelectableChannel, Serializable { // 实现SelectableChannel接口和Serializable接口的方法 // ... } ``` 2. 序列化操作时,首先需要确保该类的所有成员变量都是可序列化的。如果有非序列化的成员变量,可以使用transient关键字进行修饰,使其不参与序列化。 ```java public class MyChannel implements SelectableChannel, Serializable { private transient int id; // 不参与序列化 // 需要序列化的其他成员变量 // ... } ``` 3. 如果父类也实现了Serializable接口,需要调用父类的序列化方法、字段以及构造函数。 ```java public class MyChannel extends SelectableChannel implements Serializable { public MyChannel() { super(); // 调用父类的构造函数 } private void writeObject(java.io.ObjectOutputStream out) throws IOException { // 调用父类的序列化方法 out.defaultWriteObject(); // 其他序列化操作 // ... } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { // 调用父类的反序列化方法 in.defaultReadObject(); // 其他反序列化操作 // ... } } ``` 通过以上步骤,就能够在Java中实现将Channel对象序列化。但需要注意的是,序列化并不会序列化channel的状态,仅用于传输对象。在对象反序列化后,需要重新进行channel的状态配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值