Java nio 之 Socket通道

来源:互联网 发布:淘宝心级怎么升级快 编辑:程序博客网 时间:2024/04/19 15:03


本文整理自《Java NIO》一书。


全部socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)都是由位于java.nio.channels.spi包中的AbstractSelectableChannel引申而来。这意味着我们可以用一个Selector对象来执行socket通道的有条件的选择(readiness selection)。DatagramChannel和SocketChannel实现定义读和写功能的接口,而ServerSocketChannel不实现。ServerSocketChannel负责监听传入的连接和创建新的SocketChannel对象,它本身从不传输数据


全部socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对等socket对象。这些是我们所熟悉的来自java.net的类(Socket、ServerSocketDatagramSocket),它们已经被更新以识别通道。对等socket可以通过调用socket( )方法从一个通道上获取。此外,这三个java.net类现在都有getChannel( )方法。如果您用传统方式(直接实例化)创建了一个Socket对象,它就不会有关联的SocketChannel并且它的getChannel( )方法将总是返回null。


Socket通道可以在非阻塞模式下运行,要把一个socket通道置于非阻塞模式,我们要依靠所有socket通道类的公有超级类:SelectableChannel#configureBlocking (boolean block),设置或重新设置一个通道的阻塞模式是很简单的,只要调用configureBlocking( )方法即可,传递参数值为true则设为阻塞模式,参数值为false值设为非阻塞模式。真的,就这么简单!您可以通过调用isBlocking( )方法来判断某个socket通道当前处于哪种模式:

SocketChannel sc = SocketChannel.open( ); sc.configureBlocking (false); // nonblocking ... if (!sc.isBlocking()) {     doSomething (sc); }

ServerSocketChannel是一个基于通道的socket监听器。它同我们所熟悉的java.net.ServerSocket执行相同的基本任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。ServerSocketChannel没有bind( )方法,因此有必要取出对等的socket并使用它来绑定到一个端口以开始监听连接。我们也是使用对等ServerSocket的API来根据需要设置其他的socket选项。

ServerSocketChannel ssc = ServerSocketChannel.open( ); ServerSocket serverSocket = ssc.socket( ); // Listen on port 1234serverSocket.bind (new InetSocketAddress (1234));

同它的对等体java.net.ServerSocket一样,ServerSocketChannel也有accept( )方法。一旦您创建了一个ServerSocketChannel并用对等socket绑定了它,然后您就可以在其中一个上调用accept( )。如果您选择在ServerSocket上调用accept( )方法,那么它会同任何其他的ServerSocket表现一样的行为:总是阻塞并返回一个java.net.Socket对象。如果您选择在ServerSocketChannel上调用accept( )方法则会返回SocketChannel类型的对象,返回的对象能够在非阻塞模式下运行。


Socket和SocketChannel类封装点对点、有序的网络连接,类似于我们所熟知并喜爱的TCP/IP网络连接。SocketChannel扮演客户端发起同一个监听服务器的连接。直到连接成功,它才能收到数据并且只会从连接到的地址接收。注意:虽然每个SocketChannel对象都会创建一个对等的Socket对象,反过来却不成立。直接创建的Socket对象不会关联SocketChannel对象,它们的getChannel( )方法只返回null。

SocketChannel socketChannel = SocketChannel.open (new InetSocketAddress ("somehost", somePort));// <==>SocketChannel socketChannel = SocketChannel.open( ); socketChannel.connect (new InetSocketAddress ("somehost", somePort));

在SocketChannel上并没有一种connect( )方法可以让您指定超时(timeout)值,当connect( )方法在非阻塞模式下被调用时SocketChannel提供并发连接:它发起对请求地址的连接并且立即返回值。如果返回值是true,说明连接立即建立了(这可能是本地环回连接);如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。


当通道处于中间的连接等待(connection-pending)状态时,您只可以调用finishConnect( )、isConnectPending( )或isConnected( )方法。一旦连接建立过程成功完成,isConnected( )将返回true值。

InetSocketAddress addr = new InetSocketAddress (host, port); SocketChannel sc = SocketChannel.open( );sc.configureBlocking (false); sc.connect (addr); while (!sc.finishConnect( )) {     doSomethingElse( );}doSomethingWithChannel (sc);sc.close( );


connect( )和finishConnect( )方法是互相同步的,并且只要其中一个操作正在进行,任何读或写的方法调用都会阻塞,即使是在非阻塞模式下。


正如SocketChannel模拟连接导向的流协议(如TCP/IP),DatagramChannel则模拟包导向的无连接协议(如UDP/IP)。DatagramChannel对象既可以充当服务器(监听者)也可以充当客户端(发送者)。如果您希望新创建的通道负责监听,那么通道必须首先被绑定到一个端口或地址/端口组合上。

DatagramChannel channel = DatagramChannel.open( ); DatagramSocket socket = channel.socket( ); socket.bind (new InetSocketAddress (port));


DatagramChannel是无连接的。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据净荷。与面向流的的socket不同,DatagramChannel可以发送单独的数据报给不同的目的地址。同样,DatagramChannel对象也可以接收来自任意地址的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)。


一个未绑定的DatagramChannel仍能接收数据包。当一个底层socket被创建时,一个动态生成的端口号就会分配给它。绑定行为要求通道关联的端口被设置为一个特定的值(此过程可能涉及安全检查或其他验证)。不论通道是否绑定,所有发送的包都含有DatagramChannel的源地址(带端口号)。未绑定的DatagramChannel可以接收发送给它的端口的包,通常是来回应该通道之前发出的一个包。已绑定的通道接收发送给它们所绑定的熟知端口(wellknown port)的包。


DatagramChannel.receive( )方法将下次将传入的数据报的数据净荷复制到预备好的ByteBuffer中并返回一个SocketAddress对象以指出数据来源。如果通道处于阻塞模式,receive( )可能无限期地休眠直到有包到达。如果是非阻塞模式,当没有可接收的包时则会返回null。如果包内的数据超出缓冲区能承受的范围,多出的数据都会被悄悄地丢弃


请注意,数据报协议的不可靠性是固有的,它们不对数据传输做保证。send( )方法返回的非零值并不表示数据报到达了目的地,仅代表数据报被成功加到本地网络层的传输队列。此外,传输过程中的协议可能将数据报分解成碎片。例如,以太网不能传输超过1,500个字节左右的包。如果您的数据报比较大,那么就会存在被分解成碎片的风险,成倍地增加了传输过程中包丢失的几率。被分解的数据报在目的地会被重新组合起来,接收者将看不到碎片。但是,如果有一个碎片不能按时到达,那么整个数据报将被丢弃。


已连接通道会发挥作用的使用场景之一是一个客户端/服务器模式、使用UDP通讯协议的实时游戏。每个客户端都只和同一台服务器进行会话而希望忽视任何其他来源地数据包。将客户端的DatagramChannel实例置于已连接状态可以减少按包计算的总开销(因为不需要对每个包进行安全检查)和剔除来自欺骗玩家的假包。服务器可能也想要这样做,不过需要每个客户端都有一个DatagramChannel对象。


不同于流socket,数据报socket的无状态性质不需要同远程系统进行对话来建立连接状态。没有实际的连接,只有用来指定允许的远程地址的本地状态信息。不同于SocketChannel(必须连接了才有用并且只能连接一次),DatagramChannel对象可以任意次数地进行连接或断开连接。每次连接都可以到一个不同的远程地址。调用disconnect( )方法可以配置通道,以便它能再次接收来自安全管理器(如果已安装)所允许的任意远程地址的数据或发送数据到这些地址上。



0 0
原创粉丝点击