《Java编程思想第四版》笔记---18章(6)NIO 通道

1.通道的解释

      通道式(Channel)是java.nio的第二个主要创新。通道既不是一个扩展也不是一项增强,而是全新的、极好的Java I/O示例,提供与I/O服务的直接连接。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据

通常情况下,通道与操作系统的文件描述符(FileDescriptor)和文件 句柄(FileHandler)有着一对一的关系。虽然通道比文件描述符更广义,但开发者经常使用到的多数通道都是连接到开放的文件描述符的。 Channel类提供维持平台独立性所需的抽象过程,不然仍然会模拟现代操作系统本身的I/O性能。

通道是一种途径,借助该途径,可以用最小的总开销来访问操作系统本身的I/O服务。缓冲区则是通道内部用来发送和接收数据的端点,如下图:


通道是访问I/O服务的导管,I/O可以分为广义的两大类:File I/O和Stream I/O。那么相应的,通道也有两种类型,它们是文件(File)通道和套接字(Socket)通道。文件通道指的是FileChannel,套接字通道则 有三个,分别是SocketChannel、ServerSocketChannel和DatagramChannel。


2.观察源代码

   (1)getChannel() 实现

  我们通过jdk 源代码来观察一下通道的实现     


 FileInputStream.java    
   public FileChannel getChannel() {    
           synchronized (this) {    
                   if (channel == null) {    
                       channel = FileChannelImpl.open(fd, true, false, this);    
                       fd.incrementAndGetUseCount();    
                   }    
                   return channel;    
            }    
   }    

   FileChannelImpl.java
   // Required to prevent finalization of creating stream (immutable)    
   private Object parent;    
   这个字段的注释有意思:阻止流的建立,我们知道流的概念是存在java.io中的。    

   private FileChannelImpl(FileDescriptor fd, boolean readable,boolean writable, Object parent, boolean append) {    
           this.fd = fd;    
           this.readable = readable;    
           this.writable = writable;    
           this.parent = parent;    
           this.appending = append;    
   }    

   //  Invoked by getChannel() methods    
   //  of java.io.File{Input,Output}Stream and RandomAccessFile    
   public static FileChannel open(FileDescriptor fd,boolean readable, boolean writable,Object parent){    
            return new FileChannelImpl(fd, readable, writable, parent, false);    

   }    

   public static FileChannel open(FileDescriptor fd,boolean readable, boolean writable,Object parent, boolean append){    
            return new FileChannelImpl(fd, readable, writable, parent, append);    
   }    

       观察得知,通道的建立从源代码的角度看起来好像真没有什么特别的,为了方便我们的理解,我们可以认为就是一个简单抽象,没什么特别的。   


 (2)Channel 接口

public interface Channel extends Closeable {

    /**
     * Tells whether or not this channel is open.  </p>
     *
     * @return <tt>true</tt> if, and only if, this channel is open */ public boolean isOpen(); /** * Closes this channel. * * <p> After a channel is closed, any further attempt to invoke I/O * operations upon it will cause a {@link ClosedChannelException} to be * thrown. * * <p> If this channel is already closed then invoking this method has no * effect. * * <p> This method may be invoked at any time. If some other thread has * already invoked it, however, then another invocation will block until * the first invocation is complete, after which it will return without * effect. </p> * * @throws IOException If an I/O error occurs */ public void close() throws IOException; }

         和缓冲区不同,通道API主要由接口指定。不同的操作系统上通道实现会有根本性的差异,所以通道API仅仅描述了可以做什么,因此很自然地,通道实现经常使用操作系统的本地代码,通道接口允许开发者以一种受控且可移植的方式来访问底层的I/O服务。

可以从底层的Channel接口看到,对所有通道来说只有两种共同的操作:检查一个通道是否打开isOpen()和关闭一个打开的通道close(),其余所有的东西都是那些实现Channel接口以及它的子接口的类。


从Channel接口引申出的其他接口都是面向字节的子接口:


包括WritableByteChannel和 ReadableByteChannel。这也正好支持了我们之前的所学:通道只能在字节缓冲区上操作。层次接口表明其他数据类型的通道也可以从 Channel接口引申而来。这是一种很好的镭射机,不过非字节实现是不可能的,因为操作系统都是以字节的形式实现底层I/O接口的。

看一下基本的接口:

public interface ReadableByteChannel extends Channel {
    public int read(ByteBuffer dst) throws IOException;
}
public interface WritableByteChannel
    extends Channel{
    public int write(ByteBuffer src) throws IOException;
}

public interface ByteChannel
    extends ReadableByteChannel, WritableByteChannel{} 

         通道可以是单向的也可以是双向的。一个Channel类可能实现定义read()方法的ReadableByteChannel接口,而另一个Channel类也许实现WritableByteChannel接口以提供write()方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据,就像上面的ByteChannel。

通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行,非阻塞模式的通道永远不会让调用的线程休眠,请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式

比方说非阻塞的通道SocketChannel:

public abstract class SocketChannel
    extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel{ ... }
public abstract class AbstractSelectableChannel
    extends SelectableChannel{
   ...
} 

可以看出,socket通道类从SelectableChannel类引申而来,从SelectableChannel引申而来的类可以和支持有条件的选择的选择器(Selectors)一起使用。将非阻塞I/O和选择器组合起来可以使开发者的程序利用多路复用I/O,选择器和多路复用将在之后的文章予以说明。


3.文件通道和套接字通道的实例化不同点
文件通道:可从 FileInputStream、FileOutputStream 或 RandomAccessFile  对象上调用getChannel()方法获得文件通道,不能直接创建。 

套接字通道:此类的实例必须首先通过 register 方法进行注册。此方法返回一个表示该通道已向选择器注册的新 SelectionKey 对象。  

**********************************************************************************************

文件通道

   public abstract class FileChannel    
   extends AbstractInterruptibleChannel    
   implements ByteChannel, GatheringByteChannel, ScatteringByteChannel    
   文件通道,用于读取、写入、映射和操作文件的通道。    


文件通道总是阻塞式的,因此不能被置于非阻塞模式下

前面提到过了,FileChannel对象不能直接创建,一个 FileChannel实例只能通过在一个打开的File对象(RandomAccessFile、FileInputStream或 FileOutputStream)上调用getChannel()方法获取,调用getChannel()方法会返回一个连接到相同文件的 FileChannel对象且该FileChannel对象具有与file对象相同的访问权限,然后就可以使用通道对象来利用强大的 FileChannel API了。

FileChannel对象是线程安全的, 多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的。影响通道位置或者影响文件的操作都是单线程的,如果有一个 线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或文件系统的影响。


例子如下:

 
 
  1. import java.nio.*;  
  2. import java.nio.channels.*;  
  3. import java.io.*;  
  4.  
  5. public class FileChannel{  
  6.    //分配字节缓冲区时指定其大小  
  7.    private static final int BSIZE = 1024;  
  8.    public static void main(String[] args) throw Exception{  
  9.        //获取FileOutputStram的文件通道  
  10.        FileChannel fc = new FileOutputStream(“data.txt”).getChannel();  
  11.        //向字节缓冲区中写入字节数组  
  12.        fc.write(ByteBuffer.wrap(“Some text”.getBytes()));  
  13.        fc.close();  
  14.        //以读写方式获取随机访问文件的文件通道  
  15.        fc = new RandomAccessFile(“data.txt”, rw”).getChannel();  
  16.        //定位到字节缓冲区当前内容之后  
  17.        fc.position(fc.size());  
  18.        //向字节缓冲区中追加内容  
  19.        fc.write(ByteBuffer.wrap(“Some more”.getBytes()));  
  20.        fc.close();  
  21.        //获取FileInputStream的文件通道  
  22.        fc = new FileInputStream(“data.txt”).getChannel();  
  23.        //分配字节缓冲区  
  24.        ByteBuffer buff = ByteBuffer.allocate(BSIZE);  
  25.        //将字节数组从文件通道读入到字节缓冲区中  
  26.        fc.read(buff);  
  27.        //放置缓冲区,为缓冲区写出或相对获取做准备  
  28.        buff.flip();  
  29.        //判断缓冲区是是否还有元素  
  30.        while(buff.hasRemaining()){  
  31.            //获取字节缓冲区字节的相对方法  
  32.            System.out.println((char)buff.get());  
  33.        }  
  34. }  
  35. } 

输出结果:
Some text Some more
注意:Java new I/O的目标是提高I/O速度,快速移动大批量的数据,因此,字节缓冲区的大小非常重要,例子中的1K大小不一定是合适的,应用程序需要在生产环境中测试确定合适的缓冲区大小。


文件通道传输:
文件通道的transferFrom(ReadableByteChannel src, long position, long count)方法将字节从给定的可读字节通道传输到此通道的文件中,transferTo(long position, long count, WritableByteChannel target)方法将此通道的文件传输到给定的可写字节通道。例子如下:

 
 
  1. import java.nio.channels.*;  
  2. import java.io.*  
  3.  
  4. public class ChannelTransfer{  
  5.    public static void main(String[] args) throws Exception{  
  6.    FileChannel in = new FileInputStream(“ChannelTransfer.java”).getChannel();  
  7.    FileChannel out = new FileOutputStream(“out.txt”).getChannel();  
  8.    //将输入文件通道传输到输出文件通道  
  9.    in.transferTo(0, in.size(); out);  
  10.    //从输入文件通道传输到输出文件通道  
  11.    out.transferFrom(in, 0, in.size());  
  12. }  
  13. }


******************************************************************************************

套接字(Socket)通道

套接字通道:

 public abstract class SocketChannel    
   extends AbstractSelectableChannel    
   implements ByteChannel, ScatteringByteChannel, GatheringByteChannel    

 服务器套接字通道:      
 public abstract class ServerSocketChannel    
   extends AbstractSelectableChannel    
 

http://www.cnblogs.com/szlbm/p/5513157.html   

Socket通道与文件通道有着不一样的特征,分三点说:

1、NIO的Socket通道类可以运行于非阻塞模式并且是可选择的,这两个性能可以激活大程序(如网络服务器和中间件组件)巨大的可伸缩性和灵活性,因此,再也没有为每个Socket连接使用一个线程的必要了。这一特性避免了管理大量线程所需的上下文交换总开销,借助NIO类,一个或几个线程就可以管理成百上千的活动Socket连接了并且只有很少甚至没有性能损失

2、全部Socket通道类(DatagramChannel、 SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对应的Socket对象,就是我们所熟悉的来自 java.net的类(Socket、ServerSocket和DatagramSocket),这些Socket可以通过调用socket()方法从 通道类获取,此外,这三个java.net类现在都有getChannel()方法

3、每个Socket通道(在java.nio.channels包中)都有一个关联的java.net.socket对象,反之却不是如此,如果使用传统方式(直接实例化)创建了一个Socket对象,它就不会有关联的SocketChannel并且它的getChannel()方法将总是返回null

概括地讲,这就是Socket通道所要掌握的知识点知识点,不难,记住并通过自己写代码/查看JDK源码来加深理解。



多路复用的概念
   这里我们又新增了两个新的概念    
   public abstract class Selector   SelectableChannel 对象的多路复用器。    
   我们来看一下维基百科的解释-多路复用    
   多路复用(Multiplexing,又称“多工”)是一个通信和计算机网络领域的专业术语,在没有歧义的情况下,“多路复用”也可被称为“复用”。多路复用通常表示在一个信道上传输多路信号或数据流的过程和技术。因为多路复用能够将多个低速信道整合到一个高速信道进行传输,从而有效地利用了高速信道。通过使用多路复用,通信运营商可以避免维护多条线路,从而有效地节约运营成本。    
   public abstract class SelectionKey 表示 SelectableChannel 在 Selector 中的注册的标记。    
   SelectionKey有以下4个标记    
   OP_ACCEPT   用于套接字接受操作的操作集位。    
   OP_CONNECT 用于套接字连接操作的操作集位。    
   OP_READ       用于读取操作的操作集位。    
   OP_WRITE     用于写入操作的操作集位。    
   我们可以这样理解,多路复用器是个硬件设备装入了通道这个虚拟设备,复用器对每个进入其中的通道都标记(这个标记就是我们的SelectionKey )了其当前的状态(自己的理解,不一定准确),比如某个通道处于读就绪的状态(是不是有点线程的味道),就表明可以开始读了,如何查看通道是否就绪就看我们的多路复用器了。    
   多路复用器的方法:select()选择一组键,其相应的通道已为 I/O 操作准备就绪。    

非阻塞模式

前面第一点说了,NIO的Socket通道可以运行于非阻塞模式,这个陈述虽然简单却有着深远的含义。传统Java Socket的阻塞性质曾经是Java程序可伸缩性的最重要制约之一,非阻塞I/O是许多复杂的、高性能的程序构建的基础。

要把一个Socket通道置于非阻塞模式,要依赖的是Socket通道类的弗雷SelectableChannel,下面看一下这个类的简单定义:

public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel{
    ...
    public abstract void configureBlocking(boolean block) throws IOException;
    public abstract boolean isBlocking(); public abstract Object blockngLock(); ... }

因为这篇文章是讲述Socket通道的,因此省略了和选择器相关的方法,这些省略的内容将在下一篇文章中说明。

从SelectableChannel的API中可以看出,设置或重新设置一个通 道的阻塞模式是很简单的,只要调用configureBlocking()方法即可,传递参数值为true则设为阻塞模式,参数值为false则设为非阻 塞模式,就这么简单。同时,我们可以通过调用isBlocking()方法来判断某个Socket通道当前处于哪种模式中。

偶尔,我们也会需要放置Socket通道的阻塞模式被更改,所以API中有一个 blockingLock()方法,该方法会返回一个非透明对象引用,返回的对象是通道实现修改阻塞模式时内部使用的,只有拥有此对象的锁的线程才能更改 通道的阻塞模式,对于确保在执行代码的关键部分时Socket通道的阻塞模式不会改变以及在不影响其他线程的前提下暂时改变阻塞模式来说,这个方法是非常 方便的。


Socket通道服务端程序

OK,接下来先看下Socket通道服务端程序应该如何编写:

  
  
    
    
  1. public class NonBlockingSocketServer{
  2. public static void main(String[] args) throws Exception{
  3. int port = 1234;
  4. if (args != null && args.length > 0){
  5. port = Integer.parseInt(args[0]);
  6. }
  7. ServerSocketChannel ssc = ServerSocketChannel.open();
  8. ssc.configureBlocking(false);
  9. ServerSocket ss = ssc.socket();
  10. ss.bind(new InetSocketAddress(port));
  11. System.out.println("开始等待客户端的数据!时间为" + System.currentTimeMillis());
  12. while (true){
  13. SocketChannel sc = ssc.accept();
  14. if (sc == null){
  15. // 如果当前没有数据,等待1秒钟再次轮询是否有数据,在学习了Selector之后此处可以使用Selector
  16. Thread.sleep(1000);
  17. }else{
  18. System.out.println("客户端已有数据到来,客户端ip为:" + sc.socket().getRemoteSocketAddress()
  19. + ", 时间为" + System.currentTimeMillis()) ;
  20. ByteBuffer bb = ByteBuffer.allocate(100);
  21. sc.read(bb);
  22. bb.flip();
  23. while (bb.hasRemaining()){
  24. System.out.print((char)bb.get());
  25. }
  26. sc.close();
  27. System.exit(0);
  28. }
  29. }
  30. }
  31. }

整个代码流程大致上就是这样,没什么特别值得讲的,注意一下第18行~第22行,由于这里还没有讲到Selector,因此当客户端Socket没有到来的时候选择的处理办法是每隔1秒钟轮询一次。

 

Socket通道客户端程序

服务器端经常会使用非阻塞Socket通达,因为它们使同时管理很多Socket 通道变得更容易,客户端却并不强求,因为客户端发起的Socket操作往往比较少,且都是一个接着一个发起的。但是,在客户端使用一个或几个非阻塞模式的 Socket通道也是有益处的,例如借助非阻塞Socket通道,GUI程序可以专注于用户请求并且同时维护与一个或多个服务器的会话。在很多程序上,非 阻塞模式都是有用的,所以,我们看一下客户端应该如何使用Socket通道:

  
  
    
    
  1. public class NonBlockingSocketClient{
  2. private static final String STR = "Hello World!";
  3. private static final String REMOTE_IP= "127.0.0.1";
  4. public static void main(String[] args) throws Exception{
  5. int port = 1234;
  6. if (args != null && args.length > 0){
  7. port = Integer.parseInt(args[0]);
  8. }
  9. SocketChannel sc = SocketChannel.open();
  10. sc.configureBlocking(false);
  11. sc.connect(new InetSocketAddress(REMOTE_IP, port));
  12. while (!sc.finishConnect()){
  13. System.out.println("同" + REMOTE_IP+ "的连接正在建立,请稍等!");
  14. Thread.sleep(10);
  15. }
  16. System.out.println("连接已建立,待写入内容至指定ip+端口!时间为" + System.currentTimeMillis());
  17. ByteBuffer bb = ByteBuffer.allocate(STR.length());
  18. bb.put(STR.getBytes());
  19. bb.flip(); // 写缓冲区的数据之前一定要先反转(flip)
  20. sc.write(bb);
  21. bb.clear();
  22. sc.close();
  23. }
  24. }

总得来说和普通的Socket操作差不多,通过通道读写数据,非常方便。不过再次提醒,通道只能操作字节缓冲区也就是ByteBuffer的数据

 

运行结果展示

上面的代码,为了展示结果的需要,在关键点上都加上了时间打印,这样会更清楚地看到运行结果。

首先运行服务端程序(注意不可以先运行客户端程序,如果先运行客户端程序,客户端程序会因为服务端未开启监听而抛出ConnectionException),看一下:


看到红色方块,此时程序是运行的,接着运行客户端程序:


看到客户端已经将"Hello World!"写入了Socket并通过通道传到了服务器端,方框变灰,说明程序运行结束了。此时看一下服务器端有什么变化:


看到服务器端打印出了字符串"Hello World!",并且方框变灰,程序运行结束,这和代码是一致的。

注意一点,客户端看到的时间是XXX10307,服务器端看到的时间是XXX10544,这是很正常的,因为前面说过了,服务器端程序是每隔一秒钟轮询一次是否有Socket到来的。

当然,由于服务端程序的作用是监听1234端口,因此完全可以写客户端的代码,可以直接访问http://127.0.0.1:1234/a/b/c/d/?e=5&f=6&g=7就可以了,看一下效果:


有了这个基础,我们就可以自己解析HTTP请求,甚至可以自己写一个Web服务器。

 

客户端Socket通道复用性的研究

这个是我今天上班的时候想到的一个问题,补充到最后。

服务器端程序不变,客户端现在是单个线程发送了一次数据到服务端的,假如现在我的 客户端有多条线程同时通过Socket通道发送数据到服务端又会是怎么样的现象?首先将服务端端的代码稍作改变,让服务端SocketChannel在拿 到客户端的数据之后程序不会停止运行而是会持续监听来自客户端的Socket,由于服务器端的代码比较多,这里只列一下改动的地方,:

...
bb.flip();
while (bb.hasRemaining())
{
    System.out.print((char)bb.get());
}
System.out.println();
//sc.close(); //System.exit(0); ...

接着看一下对客户端代码的启动,把写数据的操作放到线程的run方法中去:

  
  
    
    
  1. public class NonBlockingSocketClient{
  2. private static final String STR = "Hello World!";
  3. private static final String REMOTE_IP = "127.0.0.1";
  4. private static final int THREAD_COUNT = 5;
  5. private static class NonBlockingSocketThread extends Thread{
  6. private SocketChannel sc;
  7. public NonBlockingSocketThread(SocketChannel sc){
  8. this.sc = sc;
  9. }
  10. public void run(){
  11. try{
  12. System.out.println("连接已建立,待写入内容至指定ip+端口!时间为" + System.currentTimeMillis());
  13. String writeStr = STR + this.getName();
  14. ByteBuffer bb = ByteBuffer.allocate(writeStr.length());
  15. bb.put(writeStr.getBytes());
  16. bb.flip(); // 写缓冲区的数据之前一定要先反转(flip)
  17. sc.write(bb);
  18. bb.clear();
  19. } catch (IOException e){
  20. e.printStackTrace();
  21. }
  22. }
  23. }
  24. public static void main(String[] args) throws Exception{
  25. int port = 1234;
  26. if (args != null && args.length > 0){
  27. port = Integer.parseInt(args[0]);
  28. }
  29. SocketChannel sc = SocketChannel.open();
  30. sc.configureBlocking(false);
  31. sc.connect(new InetSocketAddress(REMOTE_IP, port));
  32. while (!sc.finishConnect()){
  33. System.out.println("同" + REMOTE_IP + "的连接正在建立,请稍等!");
  34. Thread.sleep(10);
  35. }
  36. NonBlockingSocketThread[] nbsts = new NonBlockingSocketThread[THREAD_COUNT];
  37. for (int i = 0; i < THREAD_COUNT; i++)
  38. nbsts[i] = new NonBlockingSocketThread(sc);
  39. for (int i = 0; i < THREAD_COUNT; i++)
  40. nbsts[i].start();
  41. // 一定要join保证线程代码先于sc.close()运行,否则会有AsynchronousCloseException
  42. for (int i = 0; i < THREAD_COUNT; i++)
  43. nbsts[i].join();
  44. sc.close();
  45. }
  46. }

启动了5个线程,我们可能期待服务端能有5次的数据到来,实际上是:


原因就是客户端的五个线程共用了同一个SocketChannel,这样相当于五 个线程把数据轮番写到缓冲区,写完之后再把数据通过通道传输到服务器端。ByteBuffer的write方法放心,是加锁的,反编译一下 sun.nio.ch.SocketChannelImpl就知道了,因此不会出现"Hello World!Thread-X"这些字符交叉的情况。

所以有了这个经验,我们让每个线程都new一个自己的SocketChannel,于是客户端程序变成了:

    
    
  1. public class NonBlockingSocketClient{
  2. private static final String STR = "Hello World!";
  3. private static final String REMOTE_IP = "127.0.0.1";
  4. private static final int THREAD_COUNT = 5;
  5. private static class NonBlockingSocketThread extends Thread{
  6. public void run(){
  7. try{
  8. int port = 1234;
  9. SocketChannel sc = SocketChannel.open();
  10. sc.configureBlocking(false);
  11. sc.connect(new InetSocketAddress(REMOTE_IP, port));
  12. while (!sc.finishConnect()){
  13. System.out.println("同" + REMOTE_IP + "的连接正在建立,请稍等!");
  14. Thread.sleep(10);
  15. }
  16. System.out.println("连接已建立,待写入内容至指定ip+端口!时间为" + System.currentTimeMillis());
  17. String writeStr = STR + this.getName();
  18. ByteBuffer bb = ByteBuffer.allocate(writeStr.length());
  19. bb.put(writeStr.getBytes());
  20. bb.flip(); // 写缓冲区的数据之前一定要先反转(flip)
  21. sc.write(bb);
  22. bb.clear();
  23. sc.close();
  24. } catch (IOException e){
  25. e.printStackTrace();
  26. } catch (InterruptedException e){
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. public static void main(String[] args) throws Exception{
  32. NonBlockingSocketThread[] nbsts = new NonBlockingSocketThread[THREAD_COUNT];
  33. for (int i = 0; i < THREAD_COUNT; i++)
  34. nbsts[i] = new NonBlockingSocketThread();
  35. for (int i = 0; i < THREAD_COUNT; i++)
  36. nbsts[i].start();
  37. // 一定要join保证线程代码先于sc.close()运行,否则会有AsynchronousCloseException
  38. for (int i = 0; i < THREAD_COUNT; i++)
  39. nbsts[i].join();
  40. }
  41. }

此时再运行,观察结果:


看到没有问题,服务器端分五次接收来自客户端的请求了。

当然,这也是有一定问题的:

1、如果服务器端开放多线程使用ServerSocket通道去处理来自客户端的数据的话,面对成千上万的高并发很容易地就会耗尽服务器端宝贵的线程资源

2、如果服务器端只有一条ServerSocket通道线程处理来自客户端的数据的话,一个客户端的数据处理得慢将直接影响后面线程的数据处理

这么一说似乎又回到了非阻塞I/O的老问题了。不过,Socket通道讲解到此,大体的概念我们已经清楚了,接着就轮到NIO的最后也是最难、最核心的部分----选择器,将在下一篇文章进行详细的讲解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星点点-

请我喝杯咖啡呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值