socket通道的转发

概述
通道提供I/O服务的直接连接,用于缓冲区与文件或者Socket之间传输数据。JAVA中只定义了一个接口来完成对通道的抽象,在这个接口中只定义了关闭与是否打开两个方法。在此接口的基础上又分别抽象了可读通道、可写通道、可中断通道、字节通道等,其类结构图如下:
[图片]
ReadableByteChannel:可读取字节的通道
WritableByteChannel:可写入字节的通道
InterruptibleChannel:可被异步关闭和中断的通道
GatheringByteChannel:可从缓冲区序列写入字节的通道
ScatteringByteChannel:可将字节读入缓冲区序列的通道
SelectableChannel:可通过 Selector 实现多路复用的通道

Socket通道(用于监听的通道ServerSocketChannel、连接套接字的通道SocketChannel和面向数据报套接字的通道DatagramChannel)都是在以上通道的基础上扩展的,这些通道类都可以运行非阻塞模式并且是可选择的。

通道是一个连接I/O服务并提供与该服务交互的方法。就某个socket而言,它不会再次实现与之对应的socket通道类中的socket协议API,通道类在实例化时都会创建一个对等socket对象,对等socket可以通过调用socket()方法从一个通道上获取。虽然每个socket通道都有一个关联的socket对象,却并非所有的socket都有一个关联的通道。如果用传统方式(直接实例化)创建了一个Socket对象,不会有关联的SocketChannel并且它的getChannel( )方法将总是返回null。

传统的socket总是阻塞方式进行工作,严重影响应用的扩展性,这些socket通道类有一个公共的父类SelectableChannel,通过调用父类的configureBlocking和isBlocking方法来设置是否阻塞和检测是否阻塞。

ServerSocketChannel
监听套接字通道,与ServerSocket是一个对等体,ServerSocket中的API在这里也适用,这里主要看看它的accept方法,accept方法主要做了几下几件事:
1、确保此通道是打开的且已被绑定,否则会抛出异常;
2、调用本地方法建立一个socket连接,socket有对应的文件描述符和InetSocketAddress
3、进行安全检查
4、设置为阻塞模式,构建一个SocketChannel,然后返回
因此,不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式。
如果此通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null,这样可以提高程序的可伸缩性并降低复杂度,检查是否连接的代码如下:
[java] view plain copy
print?
1. ServerSocketChannel ssc = ServerSocketChannel.open( );
2. ssc.socket().bind (new InetSocketAddress (port));
3. //非阻塞
4. ssc.configureBlocking (false);
5.
6. while (true) {
7. SocketChannel sc = ssc.accept( );
8.
9. // 在非阻塞模式下,当没有连接在等待时会返回null
10. if (sc == null) {
11. Thread.sleep(TIME);
12. else {
13. doSomeThing();
14. sc.close();
15. }
16. }

SocketChannel
面向连接的套接字通道,其对等体是socket。通过在通道上直接调用connect( )方法或在通道关联的Socket对象上调用connect( )来将该socket通道连接:
(1)如果选择使用传统方式进行连接,线程在连接建立好或超时过期之前都将保持阻塞;
(2)如果选择在通道上直接调用connect( )方法来建立连接并且通道处于阻塞模式(默认模式),那么连接过程和(1)是一样的;
(3)如果在非阻塞模式下调用通道的connect()方法进行连接会立即返回,如果返回值是true,说明连接立即建立了,如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。后续可以通过调用finishConnect( )方法来完成连接过程,该方法任何时候都可以安全地进行调用。

假如在一个非阻塞模式的SocketChannel对象上调用finishConnect( )方法,将可能出现下列情形之一:
1、connect( )方法尚未被调用,那么将产生NoConnectionPendingException异常;
2、连接建立过程正在进行尚未完成,那么什么都不会发生,finishConnect( )方法会立即返回false值;
3、在非阻塞模式下调用connect( )方法之后,SocketChannel又被切换回了阻塞模式,调用线程会阻塞直到连接建立完成,finishConnect( )方法接着就会返回true;
4、调用后刚好连接过程已经完成,那么SocketChannel对象的内部状态将被更新到已连接状态,finishConnect( )方法会返回true;
5、连接已经建立,再次调用什么都不会发生,finishConnect( )方法会返回true。

这里提下状态更新,在非阻塞下调用connect方法时,状态为ST_PENDING,在此状态下isConnected()方法返回false,由于在进行读写操作前要确保通道是连接的,所以这个状态下的通道还不能进行读写操作,状态由ST_PENDING变为ST_CONNECTED正是在finishConnect方法中完成的。

Socket通道是线程安全的。并发访问时无需采用特别措施来保护发起访问的多个线程。connect( )和finishConnect( )方法是互相同步的(都会锁住readLock和writeLock),并且只要其中一个操作正在进行,任何读或写的方法调用都会被阻塞,即使是在非阻塞模式下。如果不能忍受一个读或写操作在某个通道上阻塞,最好先调用isConnected( )方法检查连接状态。

DatagramChannel
面向数据报套接字通道,与面向流的的socket不同,DatagramChannel既可以发送单独的数据报给不同的目的地址,也可以接收来自任意地址的数据包。DatagramChannel中有两组读写方法read/write和send/receive,前者只能在建立连接后才能使用。
数据报socket的无状态性质不需要同远程系统进行对话来建立连接状态,由于此原因,DatagramChannel上也就没有单独的finishConnect( )方法。我们可以使用isConnected( )方法来测试一个数据报通道的连接状态。不同于SocketChannel(必须连接了才有用并且只能连接一次),DatagramChannel对象可以任意次数地进行连接或断开连接。每次连接都可以到一个不同的远程地址。调用disconnect( )方法可以配置通道,以便它能再次接收来自安全管理器(如果已安装)所允许的任意远程地址的数据或发送数据到这些地址上。

当一个DatagramChannel处于已连接状态时,发送数据将不用提供目的地址而且接收时的源地址也是已知的。这意味着DatagramChannel已连接时可以使用常规的read( )和write( )方法。

以下是send()方法的部分源码,该方法会验证安全管理器的 checkConnect 方法是否允许使用该数据报的目标地址和端口号,避免此项安全检查开销的方法是先通过connect 方法连接该套接字。
[java] view plain copy
print?
1. synchronized (stateLock) {
2. if (!isConnected()) {
3. if (target == null)
4. throw new NullPointerException();
5. SecurityManager sm = System.getSecurityManager();
6. if (sm != null) {
7. if (ia.isMulticastAddress()) {
8. sm.checkMulticast(isa.getAddress());
9. } else {
10. sm.checkConnect(isa.getAddress().getHostAddress(),
11. isa.getPort());
12. }
13. }
14. } else { // Connected case; Check address then write
15. if (!target.equals(remoteAddress)) {
16. throw new IllegalArgumentException(
17. “Connected address not equal to target address”);
18. }
19. return write(src);
20. }
21. }
还可以看出对于已连接的send方法就是调用的write方法来实现的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值