`

Java网络服务器编程(NIO版)

阅读更多

 

 

Java网络服务器编程 一文演示了如何使用JavaSocket API编写一个简单的TCP Echo Server。其阻塞式IO的处理方式虽然简单,但每个客户端都需要一个单独的Thread来处理,当服务器需要同时处理大量客户端时,这种做法不再可行。使用NIO API可以让一个或有限的几个Thread同时处理连接到服务器上的所有客户端。(关于NIO API的一些介绍,可以在Java NIO API详解一文中找到。)

 

NIO API允许一个线程通过Selector对象同时监控多个SelectableChannel来处理多路IONIO应用程序一般按下图所示工作:

 

Figure 1

 

Figure 1 所示,Client一直在循环地进行select操作,每次select()返回以后,通过selectedKeys()可以得到需要处理的SelectableChannel并对其一一处理。

  

这样做虽然简单但也有个问题,当有不同类型的SelectableChannel需要做不同的IO处理时,在图中Client的代码就需要判断channel的类型然后再作相应的操作,这往往意味着一连串的if else。更糟糕的是,每增加一种新的channel,不但需要增加相应的处理代码,还需要对这一串if else进行维护。(在本文的这个例子中,我们有ServerSocketChannelSocketChannel这两种channel需要分别被处理。)

 

如果考虑将channel及其需要的IO处理进行封装,抽象出一个统一的接口,就可以解决这一问题。在Listing 1中的NioSession就是这个接口。

  

NioSessionchannel()方法返回其封装的SelectableChannel对象,interestOps()返回用于这个channel注册的interestOpsregistered()是当SelectableChannel被注册后调用的回调函数,通过这个回调函数,NioSession可以得到channel注册后的SelectionKeyprocess()函数则是NioSession接口的核心,这个方法抽象了封装的SelectableChannel所需的IO处理逻辑。

 

Listing 1:

 

public interface NioSession {

 

    public SelectableChannel channel();

 

     public int interestOps();

  

    public void registered(SelectionKey key);

 

    public void process();  

 

}

 

 NioSession一起工作的是NioWorker这个类(Listing 2),它是NioSession的调用者,封装了一个Selector对象和Figure 1中循环select操作的逻辑。理解这个类可以帮助我们了解该如何使用NioSession这个接口。

  

NioWorker实现了Runnable接口,循环select操作的逻辑就在run()方法中。在NioWorker – NioSession这个框架中,NioSessionchannel注册的时候会被作为attachment送入register函数,这样,在每次select()操作的循环中,对于selectedKeys()中的每一个SelectionKey,我们都可以通过attachment拿到其相对应的NioSession然后调用其process()方法。

  

每次select()循环还有一个任务,就是将通过add()方法加入到这个NioWorkerNioSession注册到Selector上。在Listing 2的代码中可以看出,NioSession中的channel()被取出并注册在Selector上,注册所需的interestOpsNioSession中取出,NioSession本身则作为attachment送入register()函数。注册成功后,NioSessionregistered()回调函数会被调用。

 

NioWorkeradd()方法的作用是将一个NioSession加入到该NioWorker中,并wakeup当前的select操作,这样在下一次的select()调用之前,这个NioSession会被注册。stop()方法则是让一个正在run()NioWorker停止。closeAllChannels()会关闭当前注册的所有channel,这个方法可在NioWorker不再使用时用来释放IO资源。

  

Listing 2:

  

public class NioWorker implements Runnable {

     public NioWorker(Selector sel) {

        _sel = sel;

        _added = new HashSet();

     }

     public void run() {

        try {

            try {

  

              while (_run) {

                  _sel.select();

                    Set selected = _sel.selectedKeys();

                   for (Iterator itr = selected.iterator(); itr.hasNext();) {

                      SelectionKey key = (SelectionKey) itr.next();

                      NioSession s = (NioSession) key.attachment();

                      s.process();

                      itr.remove();

                   }

                   synchronized (_added) {

                      for (Iterator itr = _added.iterator(); itr.hasNext();) {

                          NioSession s = (NioSession) itr.next();

                          SelectionKey key = s.channel().register(_sel, s.interestOps(), s);

                          s.registered(key);

                          itr.remove();

                      }

                   }

               }

           } finally {

               _sel.close();

            }

        } catch (IOException ex) {

            throw new Error(ex);

        }

     }

 

    public void add(NioSession s) {

 

       synchronized (_added) {

           _added.add(s);

 

       }

      _sel.wakeup();

 

   }

  

    public synchronized void stop() {

       _run = false;

       _sel.wakeup();

 

    }

 

    public void closeAllChannels() {

 

       for (Iterator itr = _sel.keys().iterator(); itr.hasNext();) {

          SelectionKey key = (SelectionKey) itr.next();

          try {        

              key.channel().close();

           } catch (IOException ex) {}

       }

    }

    protected Selector _sel = null;

    protected Collection _added = null;

    protected volatile boolean _run = true;

 }

 

 

 

Echo Server这个例子中,我们需要一个ServerSocketChannel来接受新的TCP连接,对于每个TCP连接,我们还需要一个SocketChannel来处理这个TCP连接上的IO操作。把这两种channel和上面的NioWorker – NioSession结构整合在一起,可以得到NioServerSessionNioEchoSession这两个类,它们分别封装了ServerSocketChannelSocketChannel及其对应的IO操作。下面这个UML类图描述了这4个类的关系: 

 

Figure 2

 

可以看到NioWorkerNioSession对新加入的两个类没有任何依赖性,NioServerSessionNioEchoSession通过实现NioSession这个接口为系统加入了新的功能。这样的一个体系架构符合了Open-Close原则,新的功能可以通过实现NioSession被加入而无需对原有的模块进行修改,这体现了面向对象设计的强大威力。

 

NioServerSession的实现(Listing 3)相对比较简单,其封装了一个ServerSocketChannel以及从这个channel上接受新的TCP连接的逻辑。NioServerSession还需要一个NioWorker的引用,这样每接受一个新的TCP连接,NioServerSession就为其创建一个NioEchoSession的对象,并将这个对象加入到NioWorker中。

 

Listing 3:

 

public class NioServerSession implements NioSession {

 

 

    public NioServerSession(ServerSocketChannel channel, NioWorker worker) {

       _channel = channel;

       _worker = worker;

    }

 

    public void registered(SelectionKey key) {}

 

    public void process() {

       try {

           SocketChannel c = _channel.accept();

           if (c != null) {

              c.configureBlocking(false);

              NioEchoSession s = new NioEchoSession(c);

              _worker.add(s);

           }

       } catch (IOException ex) {

           throw new Error(ex);

       }

    }

 

    public SelectableChannel channel() {

 

 

       return _channel;

 

 

    }

  

    public int interestOps(){

       return SelectionKey.OP_ACCEPT;

     }

 

    protected ServerSocketChannel _channel;

 

    protected NioWorker _worker; 

}

 

 

NioEchoSession的行为要复杂一些,NioEchoSession会先从TCP连接中读取数据,再将这些数据用同一个连接写回去,并重复这个步骤直到客户端把连接关闭为止。我们可以把“Reading”和“Writing”看作NioEchoSession的两个状态,这样可以用一个有限状态机来描述它的行为,如下图所示:

 

 

Figure 3

 

接下来的工作就是如何实现这个有限状态机了。在这个例子中,我们使用State模式来实现它。下面这张UML类图描述了NioEchoSession的设计细节。

 

Figure 4

 

NioEchoSession所处的状态由EchoState这个抽象类来表现,其两个子类分别对应了“Reading”和“Writing”这两个状态。NioEchoSession会将process()interestOps()这两个方法delegateEchoState来处理,这样,当NioEchoSession处于不同的状态时,就会有不同的行为。

 

Listing 4EchoState的实现。EchoState定义了process()interestOps()这两个抽象的方法来让子类实现。NioEchoSession中的process()方法会被delegate到其当前EchoStateprocess()方法,NioEchoSession本身也会作为一个描述context的参数被送入EchoStateprocess()方法中。EchoState定义的interestOps()方法则会在NioEchoSession注册和转变State的时候被用到。

 

EchoState还定义了两个静态的方法来返回预先创建好的ReadStateWriteState,这样做的好处是可以避免在NioEchoSession转换state的时候创建一些不必要的对象从而影响性能。然而,这样做要求state类必须是无状态的,状态需要保存在context类,也就是NioEchoSession中。

 

Listing 4:

 

public abstract class EchoState {

 

    public abstract void process(NioEchoSession s) throws IOException;

     public abstract int interestOps();

 

    public static EchoState readState() {

       return _read;

    }

 

     public static EchoState writeState() {

        return _write;

     }

 

    protected static EchoState _read = new ReadState();

    protected static EchoState _write = new WriteState();

}

 

Listing 5NioEchoSession的实现。NioEchoSession包含有一个SocketChannel,这个channel注册后得到的SelectionKey,一个用于存放数据的ByteBuffer和一个记录当前stateEchoState对象。在初始化时,EchoState被初始化为一个ReadStateNioEchoSessionprocess()方法和interestOps()方法都delegate到当前的EchoState中。其setState()方法用于切换当前state,在切换state后,NioEchoSession会通过SelectionKey更新注册的interestOpsclose()方法用于关闭这个NioEchoSession对象。

 

Listing 5:

 

public class NioEchoSession implements NioSession {

    public NioEchoSession(SocketChannel c) {

  

       _channel = c;

       _buf = ByteBuffer.allocate(128);

       _state = EchoState.readState();

    }

 

    public void registered(SelectionKey key) {

       _key = key;

    }

 

    public void process() {

       try {

           _state.process(this);

       } catch (IOException ex) {

           close();

           throw new Error(ex);

       }

    }

 

    public SelectableChannel channel() {

       return _channel;

    }

 

    public int interestOps() {

       return _state.interestOps();

    }

 

    public void setState(EchoState state) {

       _state = state;

       _key.interestOps(interestOps());

    }

 

    public void close() {

       try {

           _channel.close();

       } catch (IOException ex) {

           throw new Error(ex);

       }

    }

 

    protected SocketChannel _channel = null;

    protected SelectionKey _key;

    protected ByteBuffer _buf = null;

    protected EchoState _state = null;

}

 

Listing 6Listing 7分别是ReadStateWriteState的实现。ReadStateprocess()中会先从NioEchoSessionchannel中读取数据,如果未能读到数据,NioEchoSession会继续留在ReadState;如果读取出错,NioEchoSession会被关闭;如果读取成功,NioEchoSession会被切换到WriteStateWriteState则负责将NioEchoSession中已经读取的数据写回到channel中,全部写完后,NioEchoSession会被切换回ReadState

 

Listing 6:

 

public class ReadState extends EchoState {

    public void process(NioEchoSession s)

       throws IOException

    {

       SocketChannel channel = s._channel;

       ByteBuffer buf = s._buf;

       int count = channel.read(buf);

       if (count == 0) {

           return;

       }

 

       if (count == -1) {

           s.close();

           return;

       }

 

       buf.flip();

       s.setState(EchoState.writeState());

    }

 

    public int interestOps() {

       return SelectionKey.OP_READ;

    }

}

 

 

 

 

Listing 7:

  

public class WriteState extends EchoState {

 

    public void process(NioEchoSession s)

       throws IOException

    {

       SocketChannel channel = s._channel;

       ByteBuffer buf = s._buf;

       channel.write(buf);

       if (buf.remaining() == 0) {

           buf.clear();

           s.setState(EchoState.readState());

       }

    }

 

    public int interestOps() {

       return SelectionKey.OP_WRITE;

    }

}

  

NioEchoServer(Listing 8)被用来启动和关闭一个TCP Echo Server,这个类实现了Runnable接口,调用其run()方法就启动了Echo Server。其shutdown()方法被用来关闭这个Echo Server,注意shutdown()run()finally block中的同步代码确保了只有当Echo Server被关闭后,shutdown()方法才会返回。

 

Listing 8:

 

public class NioEchoServer implements Runnable {

 

    public void run() {

       try {

           ServerSocketChannel serv = ServerSocketChannel.open();

           try {

              serv.socket().bind(new InetSocketAddress(7));

              serv.configureBlocking(false);

              _worker = new NioWorker(Selector.open());

              NioServerSession s = new NioServerSession(serv, _worker);

              _worker.add(s);

              _worker.run();

           } finally {

              _worker.closeAllChannels();

              synchronized (this) {

                  notify();

              }

           }

       } catch (IOException ex) {

           throw new Error(ex);

       }

    }

  

    public synchronized void shutdown() {

       _worker.stop();

       try {

           wait();

       } catch (InterruptedException ex) {

           throw new Error(ex);

       }

    }

 

    protected NioWorker _worker = null;

 }

 

最后,通过一个简单的main()函数(Listing 9),我们就可以运行这个Echo Server了。

 

 

Listing 9:

 

    public static void main(String [] args) {

       new NioEchoServer().run();

    }

 

 

我们可以通过telnet程序来检验这个程序的运行状况:

 

1. 打开一个命令行,输入 telnet localhost 7 来运行一个telnet程序并连接到Echo Server上。

 

2. telnet程序中输入字符,可以看到输入的字符被显示在屏幕上。(这是因为Echo Server将收到的字符写回到客户端)

 

3. 多打开几个telnet程序进行测试,可以看到Echo Server能通过NIO API用一个Thread服务多个客户端。

Java 1.4开始提供的NIO API常用于开发高性能网络服务器,本文演示了如何用这个API开发一个TCP Echo Server

 

  • 大小: 6.7 KB
  • 大小: 12.7 KB
  • 大小: 7.7 KB
  • 大小: 4.7 KB
分享到:
评论

相关推荐

    Java网络编程(第4版)PDF

    《Java网络编程(第4版)》是一本深入探讨Java平台上的网络编程技术的专业书籍,适合想要提升Java通讯技术的学者阅读。此书全面覆盖了Java网络编程的基础和高级概念,帮助开发者理解如何利用Java语言构建高效、可靠的...

    java网络编程第四版pdf

    除了以上章节,书中还涵盖了套接字编程、服务器Socket、网络套接字API、URL和URLConnection类,以及高级主题如NIO(非阻塞I/O)和异步I/O。这些内容详细阐述了如何利用Java进行网络通信,包括建立连接、发送和接收...

    Java网络编程第三版.pdf

    《Java网络编程第三版》是Java开发者深入理解网络编程的重要参考资料。这本书主要涵盖了Java平台上的网络应用程序开发,从基础概念到高级技术,为读者提供了一套全面的学习路径。以下是本书中涉及的一些关键知识点:...

    一站式学习Java网络编程 全面理解BIO:NIO:AIO1

    全面理解 Java 网络编程 - BIO、NIO、AIO 本课程旨在帮助学生全面理解 Java 网络编程中的 BIO、NIO、AIO 三剑客,掌握 RPC 编程的基础知识,并结合实战项目巩固所学。 一、网络编程三剑客 - BIO、NIO、AIO BIO...

    Java网络编程(第3版) 中文版

    Java网络编程(第3版) 《Java网络编程》第三版会为你介绍Java网络API的最新特性。本书讨论了JDK 1.4和1.5(现在已命名为J2SE 5)中所做的所有修改和增补。本书内容全面,涵盖了从网络基础知识到远程方法调用(RMI)...

    JAVA网络编程第4版

    你将学习如何使用Java的网络类库既快速又轻松地完成常见的网络编程任务,如编写多线程服务器、加密通信、广播到本地网络,以及向服务器端程序提交数据。 作者哈诺德提供了真正可实用的程序来讲解他介绍的方法和类。...

    Java网络编程/Java网络编程实例

    Java网络编程是Java开发中的重要领域,它涵盖了网络应用程序的设计、实现和调试。在这个主题下,我们可以探讨多个关键知识点: 1. **Java Socket编程**:Java的Socket类提供了基于TCP/IP协议的网络通信能力。通过...

    java网络编程

    在本资料中,《Java网络编程》第三版提供了深入浅出的讲解,旨在帮助开发者提升对这一领域的理解。 1. **基础概念**: - **网络模型**:Java网络编程基于OSI七层模型和TCP/IP四层模型。理解这些模型有助于理解网络...

    Java网络编程 NIO Netty

    Java网络编程领域中,NIO(Non-blocking Input/Output,非阻塞I/O)和Netty框架是两个关键概念。NIO是一种I/O模型,它与传统的BIO(Blocking I/O)模型不同,BIO在处理连接时一旦进行读写操作就会阻塞,直到数据传输...

    JAVA网络编程大全,pdf版

    PDF版的书籍为学习者提供了便捷的电子阅读体验,随时随地都能深化对Java网络编程的理解。 1. Java网络编程基础 Java网络编程基于Java SE平台,它提供了丰富的API来处理网络通信。核心类如`Socket`和`ServerSocket`...

    Java网络编程期末考试复习题库+答案

    在Java中,网络编程主要依赖于Java的Socket编程、ServerSocket、URL类以及NIO(非阻塞I/O)等核心API。这份"Java网络编程期末考试复习题库+答案"为学生提供了全面的复习资源,涵盖了Java网络编程的主要知识点。 1. ...

    java网络高级编程PDF电子书

    此外,Java网络高级编程还包括了NIO(非阻塞I/O)和NIO.2(New I/O API)。NIO允许程序以非阻塞方式处理多个输入/输出流,极大地提高了服务器端的并发性能。NIO.2在Java 7中引入,进一步完善了异步I/O的功能,如文件...

    java网络高级编程

    WebSocket是一种在浏览器和服务器之间建立持久连接的协议,Java EE 7及更高版本提供了`javax.websocket`包来支持WebSocket编程,允许双向实时通信。 此外,网络编程中还会涉及多线程和异步处理,因为网络I/O通常是...

    精通Java网络编程第二版

    《精通Java网络编程第二版》是一本面向Java程序员和网络编程爱好者的专业书籍,由汪晓平、贾敬习、李功三位作者合力撰写,并由清华大学出版社出版。这本书旨在帮助读者深入理解Java语言在网络编程领域的应用,提升在...

    Java网络编程-第三版(中文版).pdf(Elliotte Rusty Harold)

    《Java网络编程》第三版是由Elliotte Rusty Harold编著的一本专业书籍,中文版为国内Java开发者提供了深入理解网络编程的宝贵资源。这本书详细介绍了如何使用Java语言进行网络应用开发,涵盖了从基础概念到高级技术...

    java nio 通信服务器、客户端完整例子

    用java编写的nio通信的例子,nio是io编程的新版本,比io较流行。同时本例子是适用socket通信的。可以在此基础上,添加您的个人应用。本例子适用于:java通信的学习者,android平台通信的学习者。

    Java网络高级编程源码人邮金勇华曲俊生

    《Java网络高级编程源码》是由人民邮电出版社出版,由金勇华和曲俊生两位专家编著的一本深入探讨Java网络编程的专著。这本书涵盖了Java在互联网开发中的高级应用,旨在帮助读者理解并掌握Java网络编程的核心技术。 ...

    Java网络编程(第4版)

    《Java网络编程(第4版)》是一本深入探讨Java平台上的网络编程技术的专业书籍,适合Java开发者和学习者进一步提升网络编程技能。本书详细介绍了如何利用Java API进行网络通信,包括TCP/IP协议、套接字(Socket)编程、...

Global site tag (gtag.js) - Google Analytics