非阻塞通讯(1)

本文探讨了非阻塞通讯的基础概念,提到了ServerSocketChannel和SocketChannel在多线程通讯中的角色,并且指出它们与SelectableChannel的关系。文章内容到此为止,作者预告下篇将继续深入讲解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对之前学过socket进行总结,但是这次会比较深入的体会一下,主要以孙卫琴老师的书为主,加上自己的总结:
一般的socket和serversocket就不总结了,主要进入非阻塞通信进行总结:
首先考虑一下阻塞的通讯在哪些地方会产生阻塞:
1,从线程方面考虑:
线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠N毫秒,然后恢复运行;
线程执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才恢复运行;
线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒;
线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如,当线程执行System.in.read()方法时,如果用户没有向控制台输入数据,则该线程会一直等读到用户的输入数据才从read()方法返回;
2,进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态
请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回;
线程从Socket的输入流读入数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现异常,才从输入流的read()方法返回或异常中断,输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型;(1)int read()只要输入流中有一个字节,就算足够;(2)int read(byte[] buff)只要输入流中的字节数目与参数buff数组的长度相同,就算足够(3)String readLine()只要输入流中有一行字符串,就算足够,值得注意的是,InputStream类并没有readLine()方法,在过滤流BufferedReader类中才有此方法
线程向Socket的输入流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输入流的write方法返回或异常中断;
调用Socket的setSolinger方法设置了关闭Socket的延迟时时间,那么当线程执行socket的close()方法时,会进入阻塞状态,直到底层Socket发送完所有剩下 数据,或者超过了setSolinger方法设置的延迟时间,才从close()方法返回;
3,在服务器程序中,线程在以下情况下可能会进入阻塞状态
线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才能从accept()方法返回;
线程从Socket的输入流读入数据,如果输入流没有足够的数据,就会进入阻塞状态;
线程向Socket的输出流写入一批数据,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write方法返回或异常中断。
由此,如果从性能方面来说,多线程的设计是十分重要,主要能提高并发性,另外一方面,在通过Socket的输入流和输出流来写数据时,都可能进入阻塞状态,称为阻塞I/O。

下面来介绍java.nio中主要的几个类:
ServerSocketChannel:ServerSocket的代替类,支持阻塞通信和非阻塞通信;
SocketChannel:Socket的代替类,支持阻塞通信与非阻塞通信;
Selector:为ServerSocketChannel监控接收连接就绪事件,为SocketChannel监控连接就绪,读就绪和写就绪事件;
SelectorKey:代表ServerSocketChannel及SocketChannel向Selector注册事件的句柄,当一个SelectorKey对象位于Selector对象的selected-keys集合中时,就表示与这个SelectionKey对象相关的事件发生了。

ServerSocketChannel及SocketChannel都是SelectableChannel的子类,SelectableChannel类及其子类都能委托Selector来监控它们可能发生的一些事情,这种委托过程也成为注册事件的过程。





实际上ServerSocketChannel和SocketChannel跟SelectableChannel的继承关系中间多了一个AbstarctSelectableChannel:

java.lang.Object
  继承者 java.nio.channels.spi.AbstractInterruptibleChannel
      继承者 java.nio.channels.SelectableChannel
          继承者 java.nio.channels.spi.AbstractSelectableChannel
              继承者 java.nio.channels.ServerSocketChannel

SelectionKey key = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);实际上这个方法调用的是SelectableChannel的register的方法;
ServerSocketChannel只可能发生一种事件,SelectionKey.OP_ACCEPT:接收连接就绪事件,表示至少有一个客户链接,服务器可以接收这个连接。
SocketChannel可能发生以下三种事件,SelectionKey.OP_ACCEPT:连接就绪事件,表示客户与服务器的连接已经建立成功;
Selection.OP_READ:读就绪事件,表示输入流中已经有了可读数据,以执行读操作了
Selection.OP_WRITE:写就绪事件,表示已经向输出流写数据了。
现在来看看register的源码:
AbstractSelectableChannel.java中:
 public final SelectionKey register(Selector sel, int ops,
                       Object att)
    throws ClosedChannelException
    {
    if (!isOpen())// 判断此通道是否处于打开状态。
        throw new ClosedChannelException();
    if ((ops & ~validOps()) != 0)//如果ops不在操作集中则抛出异常
        throw new IllegalArgumentException();
    synchronized (regLock) {//获得锁,防止多程程造成不安全
        if (blocking)//如果是设置为阻塞,则抛出异常
        throw new IllegalBlockingModeException();
        SelectionKey k = findKey(sel);//获得SelectionKey句柄
            if (k != null) {
                k.interestOps(ops);//获取此键的 interest 集合
        k.attach(att);//把Object关联上去
            }
        if (k == null) {//如果k为空,那么重新注册
        // New registration
        k = ((AbstractSelector)sel).register(this, ops, att);//向此选择器注册给定的通道。返回表示给定通道向此选择器注册的新键
        addKey(k);//把selectionKey注册到Selctor上
        }
            return k;
        }
    }

 private SelectionKey findKey(Selector sel) {
    synchronized (keyLock) {
            if (keys == null)//如果SelectionKey[]为空,则返回null
                return null;
        for (int i = 0; i < keys.length; i++)
                if ((keys[i] != null) && (keys[i].selector() == sel))//keys[i].selector(),返回为此选择器创建的键。即使已取消该键后,此方法仍将继续返回选择器。如果SelectionKey不为空并且SelectionKey返回的selector如果等于sel,那么Selector上的SelectionKey句柄返回
                    return keys[i];
        return null;
    }
    }
    
    
     private void addKey(SelectionKey k) {
    synchronized (keyLock) {
        int i = 0;
        if ((keys != null) && (keyCount < keys.length)) {
        // Find empty element of key array
        for (i = 0; i < keys.length; i++)
            if (keys[i] == null)
            break;
        } else if (keys == null) {
                keys =  new SelectionKey[3];
            } else {
        // Grow key array
        int n = keys.length * 2;
        SelectionKey[] ks =  new SelectionKey[n];
        for (i = 0; i < keys.length; i++)
            ks[i] = keys[i];
        keys = ks;
        i = keyCount;
        }
        keys[i] = k;
        keyCount++;
    }
    }

暂时先到这里,明天继续......


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值