最近在研究非阻塞通信socket编程,有个问题不明白~
如果某个SocketChannel对象sc,像某个Selector对象selector注册了READ和WRITE事件,然后调用selector.select()监听事件,书上说当写就绪(writeable)或者读就绪(readable)的时候,select()方法才返回,然后进行判断,再进行发送或者读取,可是系统是怎么知道已经进入写就绪状态或者读就绪状态呢?实际中也没有像单击按钮这样的动作来触发事件啊~~一直想不明白~~~~请赐教~~~~另外,对于高并发量的应用,是用非阻塞,还是用线程池,那个更有效率?

解决方案 »

  1.   

    事件来源,并不局限于人机交互,像你这个例子,事件的来源就是网络上的动作!
    非阻塞很多情况下能够支持非常庞大的客户接入,不过编码上比较复杂,你可以尝试下mina!
    高并发的请求,如果每个请求生命周期都很短,就用传统的方式,如果生命周期很长,比如作聊天程序
    每个客户端都占一个长连接的,就可以考虑用nio
      

  2.   

    读事件只能通道中有未读数据就会产生。写事件的话只要通道没事干就允许写,服务端或者客户端如果不管三七二十一在接收或者连接成功后就注册读和写事件的话,那会产生性能问题。这是因为写事件只要通道没事干时就允许写,如果一起注册的话,就会导致写事件会一直产生,但是实际上我们没有那么多东西要写。我们没有东西要写,所以也没必要预先注册监听个写事件。一般来说,在接收或者连接成功后,只注册读事件。如果读取完成需要处理,处理完后需要写数据时再注册写事件,全部写完时注销掉写事件,只留读事件在那里。当然了,如果是主动发送数据,也只在需要发送数据时才注册写事件。写的话,使用buffer.flip();
    while(buffer.hasRemaining()) {
        socketChannel.write(buffer);
    }一次性全部写完,写完后取消写事件。
      

  3.   

    是啊,虽然说使用非阻塞式的 Channel 可以达到很高的效率,但是在代码编写上比传统的 Socket 和非阻塞式的 Channel 复杂很多。最为复杂的就是需要操纵 byte 缓冲区的 ByteBuffer。客户端向服务端发送数据,如果服务端是非阻塞式的,在服务端读事件发生时,并不会一次性地把数据读到,可能会分好几批读完,因此就需要很好地操纵这个作为附件的 ByteBuffer另外,进行通信时,由于服务端是非阻塞式的,因此服务端是不知道读到什么时候这批数据才算读完。这一般需要在开始部分加上总共需要发送的字节数量,或者特定的终止符,服务端才能知道这批数据是读完了。这需要设计一下具体的通信报文协议,比如简单一点可以这样:1~4 字节  int  魔数值
    5~8 字节  int  功能
    9~12 字节 int  请求内容长度
    13~  字节 byte  请求内容前 12 个字节类似于请求头,从第 13 个字节开如是请求正文,长度在请求头中可以获得。这样的话,我们只要从 13 个字节开始读满请求头中的长度字节这个报文就算全部读完了。
      

  4.   

    对于楼上说的我已经明白了,客户端和服务器端的报文协议肯定是会自定义的,然后通过报文里的某些内容判断不同的客户端……但在并发的时候会不会出现这么个情况:
    服务器S用ByteBuffer保存报文,假设足够大,两个客户端A、B都会发送长度为10的报文给服务器,按照Buffer会分几批读完的规律,会不会出现在这几次读取中每次读取的位置不一样,比如第一次读取A的2个报文,第二次读取B的2个报文,最后读完的buffer里内容是A、B的混合报文~~
    还是说每次通过SelectionKey的channel()方法返回的SocketChannel只针对一个客户端~
      

  5.   

    SelectionKey的channel()方法返回的SocketChannel这个当然只是针对一个客户端了,否则要乱套了,你可以使用 SocketChannel#socket()#getLocalAddress() 和 getLocalPort() 两个方法就可以知道是不是一个客户端了。服务器S用ByteBuffer保存报文,假设足够大我一般不会设成足够大,会将其封装一下,当 ByteBuffer 剩余的空间不足指定数量或者百分比时,就扩充 50%。
      

  6.   

    但是一个客户端在写的时候就需要注意一下了,特别是那种长连接,而且可能会并发写的时候。可以将需要写的内容放到一个队列当中,另起一个线程专门负责写,当队列不为空时,就注册写线程,一个一个地将队列的内容发送完毕。这样做不会导致并发线程 A 发送 123,线程 B 发送 abc,在服务端收到这样的消息 12a3bc。由于非阻塞通信的特点,写入的数据不处理的话,并不能保证原本是一条的数据全部写完。但是为了保证服务端接收时不串位,那可以使用 2 楼的那段代码,这样可以保证一条数据能一次性全部写完。
      

  7.   

    一次性全部写入,这样能保证服务端序列化地接收,不会错位。就算在网络中这条数据中的某些数据错位了,TCP 也能保证接收方收到的是与发送方同样顺序的数据。