探索java多线程(连载)1 守护线程

Posted on

探索java多线程(连载)1 守护线程 - ikon - BlogJava

ikon

posts - 1, comments - 0, trackbacks - 0, articles - 1 BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合 :: 管理

日历

<2011年3月>日一二三四五六272812345678910111213141516171819202122232425262728293031123456789

常用链接

留言簿

随笔档案

搜索

最新评论 ## 探索java多线程(连载)1 守护线程

Posted on 2011-03-22 19:25 ikon 阅读(1692) 评论(0) 编辑 收藏

  在java中有一类线程,专门在后台提供服务,此类线程无需显式关闭,当程序结束了,它也就结束了,这就是守护线程 daemon thread。如果还有非守护线程的线程在执行,它就不会结束。      守护线程有何用处呢?让我们来看个实践中的例子。

  在我们的系统中经常应用各种配置文件(黑名单,禁用词汇),当修改配置文件后,一般要重启服务,系统才能够加载;当重启服务的代价比较高的情况下,这种加载方式不能满足我们的要求,这个时候守护线程该发挥它的作用了,它可以实时加载你的配置文件,无需重启。(当然,相当重要的配置文件,不推荐实时加载)

package com.ikon.thread.daemon; import java.io.File; import java.util./; /// ///// / 文件监测 / @author ikon99999 / // public abstract class FileWatchdog extends Thread { static final public long DEFAULT_DELAY = 20/*1000;
protected HashMap fileList; protected long delay = DEFAULT_DELAY;
boolean warnedAlready = false;
boolean interrupted = false; public static class Entity { File file; long lastModify; Entity(File file,long lastModify) { this.file = file; this.lastModify = lastModify; } }
protected FileWatchdog() { fileList = new HashMap (); setDaemon(true); } public void setDelay(long delay) { this.delay = delay; } public void addFile(File file) { fileList.put(file.getAbsolutePath(),new Entity(file,file.lastModified()));
}
public boolean contains(File file) { if( fileList.get(file.getAbsolutePath()) != null) return true; else return false; }
abstract protected void doOnChange(File file); protected void checkAndConfigure() { HashMap map = (HashMap)fileList.clone(); Iterator it = map.values().iterator();
while( it.hasNext()) {
Entity entity = (Entity)it.next();
boolean fileExists; try { fileExists = entity.file.exists(); } catch(SecurityException e) { System.err.println ("Was not allowed to read check file existance, file:["+ entity.file .getAbsolutePath() +"]."); interrupted = true; return; } if(fileExists) {
long l = entity.file.lastModified(); // this can also throw a SecurityException if(l > entity.lastModify) { // however, if we reached this point this entity.lastModify = l; // is very unlikely. newThread(entity.file); } } else { System.err.println ("["+entity.file .getAbsolutePath()+"] does not exist."); } } }
private void newThread(File file) { class MyThread extends Thread { File f; public MyThread(File f) { this.f = f; }
public void run() { doOnChange(f); } }
MyThread mt = new MyThread(file); mt.start(); } public void run() {
while(!interrupted) { try { Thread.currentThread().sleep(delay); } catch(InterruptedException e) { // no interruption expected } checkAndConfigure(); } } }

FileWatchdog是个抽象类,本身是线程的子类;在构造函数中设置为守护线程;
此类用hashmap维护着一个文件和最新修改时间值对,checkAndConfigure()方法用来检测哪些文件的修改时间更新了,如果发现文件更新了则调用doOnChange方法来完成监测逻辑;doOnChange方法是我们需要实现的;看下面关于一个黑名单服务的监测服务:

1package com.ikon.thread.daemon; 2 3import java.io.File; 4 5/// ///// 6 / 黑名单服务 7 / @author ikon99999 8 / 2011-3-21 9 // 10public class BlacklistService { 11 private File configFile = new File("c:/blacklist.txt"); 12
13 public void init() throws Exception{ 14 loadConfig(); 15 ConfigWatchDog dog = new ConfigWatchDog(); 16 dog.setName("daemon_demo_config_watchdog");//a 17 dog.addFile(configFile);//b 18 dog.start();//c 19 } 20
21 public void loadConfig(){ 22 try{ 23 Thread.sleep(1/
1000);//d 24
25 System.out.println("加载黑名单"); 26 }catch(InterruptedException ex){ 27 System.out.println("加载配置文件失败!"); 28 } 29 } 30
31 public File getConfigFile() { 32 return configFile; 33 } 34 35 public void setConfigFile(File configFile) { 36 this.configFile = configFile; 37 } 38 39 40 private class ConfigWatchDog extends FileWatchdog{ 41
42 @Override 43 protected void doOnChange(File file) { 44 System.out.println("文件"+file.getName()+"发生改变,重新加载"); 45 loadConfig(); 46 } 47
48 } 49
50 public static void main(String[] args) throws Exception { 51 BlacklistService service = new BlacklistService(); 52 service.init(); 53
54 Thread.sleep(60/60/1000);//e 55 } 56} 57 ConfigWatchDog内部类实现了doOnChange(File file)方法,当文件被修改后,watchdog调用doOnChange方法完成重新加载操作; 在blackservice的init方法中初始化watchdog线程; d:模拟文件加载耗时 e:主要是防止主线程退出; 其实上面的FileWatchdog就是取自log4j;

新用户注册 刷新评论列表

找优秀程序员,就在博客园 网易有道诚聘CRM研发工程师 锦江国际诚聘Java高级软件工程师 福州几维网络诚聘Java服务端程序员 IT新闻: · 开放,开放,开放 —— 垄断 · GNOME讨论放弃支持非Linux操作系统 · Chrome 13将隐藏地址栏 · 联想:USB 3.0将在2012年成为主流 · 意法半导体 CEO :诺基亚 Windows Phone 将采用 U8500 双核芯片 博客园 博问 IT新闻 Java程序员招聘 标题 请输入标题 姓名 请输入你的姓名 主页 请输入验证码 验证码 /* 内容(请不要发表任何与政治相关的内容) 请输入评论内容 Remember Me? 登录 [使用Ctrl+Enter键可以直接提交] 推荐职位: · 北京.NET 研发工程师 (北京捷报数据) · (北京).NET软件开发工程师(北京龙达) · 厦门高级.NET软件工程师(服务于美国Amazon) · 高级Web页面前端开发工程师(新蛋中国) · 厦门Java服务端程序员(福州几维网络) · .NET 高级软件开发工程师 (新蛋中国) · 北京ASP.NET 工程师(月薪12k)(北京盛安德) · 厦门C/#游戏客户端程序员 (福州几维网络)

博客园首页随笔: · 字符编码浅谈(二) · Windows Phone 7编程实践—必应地图导航 · 绕死你不偿命的UNICODE、_UNICODE、TEXT、T、_T、_TEXT、TEXT宏 · 学习笔记:JAVA RMI远程方法调用简单实例 · 关于CellSet转DataTable的改进方案 知识库: · 程序员的本质 · Scrum之成败——从自身案例说起 · 清除代码异味 · 详解.NET程序集的加载规则 · 如何通过ildasm/ilasm修改assembly的IL代码 最简洁阅读版式: 探索java多线程(连载)1 守护线程 网站导航:

博客园 IT新闻 知识库 博客生活 IT博客网 C++博客 博问 管理 Powered by: BlogJava Copyright © ikon

Tomcat 预编译JSP 脚本

Posted on

Tomcat 预编译JSP 脚本

参考:

The Apache Jakarta Tomcat 5.5 Servlet/JSP Container Jasper 2 JSP Engine How To

http://jakarta.apache.org/tomcat/tomcat-5.5-doc/jasper-howto.html

jspc

http://ant.apache.org/manual/OptionalTasks/jspc.html

用Tomcat进行预编译的ant脚本如下:

build.properties的内容为:

tomcat.home=D:/Tomcat 5.5 webapp.name=blh webapp.path=D:/Tomcat 5.5/webapps/blh

build.xml的内容为:

  1. <?xml version="1.0" encoding="GBK"?>

只需要设置好Ant的path环境变量,然后修改build.properties。执行ant all命令即可。 生成好的jar文件是{$webappname}JSP.jar。 在做为产品发布的时候,只需要你的类jar包和JSP预编译的包放到WEB-INF/lib/目录下即可,如${webappname}.jar和JSP预编译的包${webappname}JSP.jar; 然后删除掉你的所有的预编过的JSP源文件; 并且${webapp.path}/WEB-INF/webJSP.xml里的servlet映射,添加到${webapp.path}/WEB-INF/web.xml中。

来源: [http://blog.csdn.net/terry_f/article/details/3725382](http://blog.csdn.net/terry_f/article/details/3725382)

java nio网络编程的一点心得

Posted on

java nio网络编程的一点心得

前几日用java nio写了一个tcp端口转发小工具,还颇费周折,其中一个原因在于网上资料很混乱,不少还是错误的。这篇文章中我会以一个EchoServer作为例子。先看《Java网络编程》中的写法,这也是在网上颇为常见的一个写法。 Java代码 收藏代码

  1. public class EchoServer {
  2. public static int DEFAULT_PORT = 7777;
  3. public static void main(String[] args) throws IOException {
  4. System.out.println("Listening for connection on port " + DEFAULT_PORT);
  5. Selector selector = Selector.open();
  6. initServer(selector);
  7. while (true) {
  8. selector.select();
  9. for (Iterator itor = selector.selectedKeys().iterator(); itor.hasNext();) {
  10. SelectionKey key = (SelectionKey) itor.next();
  11. itor.remove();
  12. try {
  13. if (key.isAcceptable()) {
  14. ServerSocketChannel server = (ServerSocketChannel) key.channel();
  15. SocketChannel client = server.accept();
  16. System.out.println("Accepted connection from " + client);
  17. client.configureBlocking(false);
  18. SelectionKey clientKey = client.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
  19. ByteBuffer buffer = ByteBuffer.allocate(100);
  20. clientKey.attach(buffer);
  21. }
  22. if (key.isReadable()) {
  23. SocketChannel client = (SocketChannel) key.channel();
  24. ByteBuffer buffer = (ByteBuffer) key.attachment();
  25. client.read(buffer);
  26. }
  27. if (key.isWritable()) {
  28. // System.out.println("is writable...");
  29. SocketChannel client = (SocketChannel) key.channel();
  30. ByteBuffer buffer = (ByteBuffer) key.attachment();
  31. buffer.flip();
  32. client.write(buffer);
  33. buffer.compact();
  34. }
  35. } catch (IOException e) {
  36. key.cancel();
  37. try { key.channel().close(); } catch (IOException ioe) { }
  38. }
  39. }
  40. }
  41. }
  42. private static void initServer(Selector selector) throws IOException,
  43. ClosedChannelException {
  44. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  45. ServerSocket ss = serverChannel.socket();
  46. ss.bind(new InetSocketAddress(DEFAULT_PORT));
  47. serverChannel.configureBlocking(false);
  48. serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  49. }
  50. }

public class EchoServer {

public static int DEFAULT_PORT = 7777;


public static void main(String[] args) throws IOException {
    System.out.println("Listening for connection on port " + DEFAULT_PORT);


    Selector selector = Selector.open();

    initServer(selector);


    while (true) {
        selector.select();


        for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor.hasNext();) {

            SelectionKey key = (SelectionKey) itor.next();
            itor.remove();

            try {
                if (key.isAcceptable()) {

                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();

                    System.out.println("Accepted connection from " + client);
                    client.configureBlocking(false);

                    SelectionKey clientKey = client.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
                    ByteBuffer buffer = ByteBuffer.allocate(100);

                    clientKey.attach(buffer);
                }

                if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();

                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    client.read(buffer);

                }
                if (key.isWritable()) {

                    // System.out.println("is writable...");
                    SocketChannel client = (SocketChannel) key.channel();

                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    buffer.flip();

                    client.write(buffer);
                    buffer.compact();

                }
            } catch (IOException e) {

                key.cancel();
                try { key.channel().close(); } catch (IOException ioe) { }

            }
        }

    }
}


private static void initServer(Selector selector) throws IOException,

        ClosedChannelException {
    ServerSocketChannel serverChannel = ServerSocketChannel.open();

    ServerSocket ss = serverChannel.socket();
    ss.bind(new InetSocketAddress(DEFAULT_PORT));

    serverChannel.configureBlocking(false);
    serverChannel.register(selector, SelectionKey.OP_ACCEPT);

}

} 上面的代码很典型,运行结果似乎也是正确的。 Java代码 收藏代码

  1. marlon$ java EchoServer&
  2. --> Listening for connection on port 7777
  3. marlon$ telnet localhost 7777
  4. --> Accepted connection from java.nio.channels.SocketChannel[connected local=/127.0.0.1:7777 remote=/127.0.0.1:65030]
  5. hello
  6. --> hello
  7. world
  8. -->world

marlon$ java EchoServer&

--> Listening for connection on port 7777 marlon$ telnet localhost 7777

--> Accepted connection from java.nio.channels.SocketChannel[connected local=/127.0.0.1:7777 remote=/127.0.0.1:65030] hello

--> hello world

-->world 但是如果你这时top用看一下发现服务器进程CPU占用到95%以上,如果取消掉32行的注释,服务器会不断地输出"is writable...",这是为什么呢?让我们来分析当第一个客户端连接上时发生什么情况。

  1. 在连接之前,服务器第11行:selector.select()处阻塞。当阻塞时,内核会将这个进程调度至休眠状态,此时基本不耗CPU。
  2. 当客户端发起一个连接时,服务器检测到客户端连接,selector.select()返回。selector.selectedKeys()返回已就绪的SelectionKey的集合,在这种情况下,它只包含一个key,也就是53行注册的acceptable key。服务器开始运行17-25行的代码,server.accept()返回代码客户端连接的socket,第22行在socket上注册OP_READ和OP_WRITE,表示当socket可读或者可写时就会通知selector。
  3. 接着服务器又回到第11行,尽管这时客户端还没有任何输入,但这时selector.select()不会阻塞,因为22行在socket注册了写操作,而socket只要send buffer不满就可以写,刚开始send buffer为空,socket总是可以写,于是server.select()立即返回,包含在22行注册的key。由于这个key可写,所以服务器会运行31-38行的代码,但是这时buffer为空,client.write(buffer)没有向socket写任何东西,立即返回0。
  4. 接着服务器又回到第11行,由于客户端连接socket可以写,这时selector.select()会立即返回,然后运行31-38行的代码,像步骤3一样,由于buffer为空,服务器没有干任何事又返回到第11行,这样不断循环,服务器却实际没有干事情,却耗大量的CPU。 从上面的分析可以看出问题在于我们在没有数据可写时就在socket上注册了OP_WRITE,导致服务器浪费大量CPU资源,解决办法是只有数据可以写时才注册OP_WRITE操作。上面的版本还不只浪费CPU那么简单,它还可能导致潜在的死锁。虽然死锁在我的机器上没有发生,对于这个简单的例子似乎也不大可能发生在别的机器上,但是在对于复杂的情况,比如我写的端口转发工具中就发生了,这还依赖于jdk的实现。对于上面的EchoServer,出现死锁的场景是这样的:

  5. 假设服务器已经启动,并且已经有一个客户端与它相连,此时正如上面的分析,服务器在不断地循环做无用功。这时用户在客户端输入"hello"。

  6. 当服务器运行到第11行:selector.select()时,这时selector.selectedKeys()会返回一个代表客户端连接的key,显然这时客户端socket是既可读又可写,但jdk却并不保证能够检测到两种状态。如果它检测到key既可读又可写,那么服务器会执行26-38行的代码。如果只检测到可读,那么服务器会执行26-30行的代码。如果只检测到可写,那么会执行31-38行的代码。对于前两种情况,不会造成死锁,因为当执行完29行,buffer会读到用户输入的内容,下次再运行到36行就可以将用户输入内容echo回。但是对最后一种情况,服务器完全忽略了客户端发过来的内容,如果每次selector.select()都只能检测到socket可写,那么服务器永远不能将echo回客户端输入的内容。 避免死锁的一个简单方法就是不要在同一个socket同时注册多个操作。对于上面的EchoServer来说就是不要同时注册OP_READ和OP_WRITE,要么只注册OP_READ,要么只注册OP_WRITE。下面的EchoServer修正了以上的错误: Java代码 收藏代码

  7. public static void main(String[] args) throws IOException {

  8. System.out.println("Listening for connection on port " + DEFAULT_PORT);
  9. Selector selector = Selector.open();
  10. initServer(selector);
  11. while (true) {
  12. selector.select();
  13. for (Iterator itor = selector.selectedKeys().iterator(); itor.hasNext();) {
  14. SelectionKey key = (SelectionKey) itor.next();
  15. itor.remove();
  16. try {
  17. if (key.isAcceptable()) {
  18. ServerSocketChannel server = (ServerSocketChannel) key.channel();
  19. SocketChannel client = server.accept();
  20. System.out.println("Accepted connection from " + client);
  21. client.configureBlocking(false);
  22. SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);
  23. ByteBuffer buffer = ByteBuffer.allocate(100);
  24. clientKey.attach(buffer);
  25. } else if (key.isReadable()) {
  26. SocketChannel client = (SocketChannel) key.channel();
  27. ByteBuffer buffer = (ByteBuffer) key.attachment();
  28. int n = client.read(buffer);
  29. if (n > 0) {
  30. buffer.flip();
  31. key.interestOps(SelectionKey.OP_WRITE); // switch to OP_WRITE
  32. }
  33. } else if (key.isWritable()) {
  34. System.out.println("is writable...");
  35. SocketChannel client = (SocketChannel) key.channel();
  36. ByteBuffer buffer = (ByteBuffer) key.attachment();
  37. client.write(buffer);
  38. if (buffer.remaining() == 0) { // write finished, switch to OP_READ
  39. buffer.clear();
  40. key.interestOps(SelectionKey.OP_READ);
  41. }
  42. }
  43. } catch (IOException e) {
  44. key.cancel();
  45. try { key.channel().close(); } catch (IOException ioe) { }
  46. }
  47. }
  48. }
  49. }

    public static void main(String[] args) throws IOException {

     System.out.println("Listening for connection on port " + DEFAULT_PORT);
    
    Selector selector = Selector.open();
    initServer(selector);


    while (true) {

        selector.select();


        for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor.hasNext();) {
            SelectionKey key = (SelectionKey) itor.next();

            itor.remove();
            try {

                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();

                    SocketChannel client = server.accept();
                    System.out.println("Accepted connection from " + client);

                    client.configureBlocking(false);
                    SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);

                    ByteBuffer buffer = ByteBuffer.allocate(100);
                    clientKey.attach(buffer);

                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();

                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    int n = client.read(buffer);

                    if (n > 0) {
                        buffer.flip();

                        key.interestOps(SelectionKey.OP_WRITE);        // switch to OP_WRITE
                    }

                } else if (key.isWritable()) {
                    System.out.println("is writable...");

                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();

                    client.write(buffer);
                    if (buffer.remaining() == 0) {    // write finished, switch to OP_READ

                        buffer.clear();
                        key.interestOps(SelectionKey.OP_READ);

                    }
                }

            } catch (IOException e) {
                key.cancel();

                try { key.channel().close(); } catch (IOException ioe) { }
            }

        }
    }

}

主要变化,在第19行接受客户端连接时只注册OP_READ操作,第28行当读到数据时才切换到OP_WRITE操作,第35-38行,当写操作完成时再切换到OP_READ操作。由于一个key同时只能执行一个操作,我将原来三个并行if换成了if...else。 上面的代码不够优雅,它将处理服务器Socket和客户连接Socket的代码搅在一起,对于简单的EchoServer这样做没什么问题,当服务器变得复杂,使用命令模式将它们分开变显得非常必要。首先创建一个接口来抽象对SelectionKey的处理。 Java代码 收藏代码

  1. interface Handler {
  2. void execute(Selector selector, SelectionKey key);
  3. }

    interface Handler {

     void execute(Selector selector, SelectionKey key);
    

    } 再来看main函数: Java代码 收藏代码

  4. public static void main(String[] args) throws IOException {

  5. System.out.println("Listening for connection on port " + DEFAULT_PORT);
  6. Selector selector = Selector.open();
  7. initServer(selector);
  8. while (true) {
  9. selector.select();
  10. for (Iterator itor = selector.selectedKeys().iterator(); itor.hasNext();) {
  11. SelectionKey key = (SelectionKey) itor.next();
  12. itor.remove();
  13. Handler handler = (Handler) key.attachment();
  14. handler.execute(selector, key);
  15. }
  16. }
  17. }
  18. private static void initServer(Selector selector) throws IOException,
  19. ClosedChannelException {
  20. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  21. ServerSocket ss = serverChannel.socket();
  22. ss.bind(new InetSocketAddress(DEFAULT_PORT));
  23. serverChannel.configureBlocking(false);
  24. SelectionKey serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  25. serverKey.attach(new ServerHandler());
  26. }

    public static void main(String[] args) throws IOException {

     System.out.println("Listening for connection on port " + DEFAULT_PORT);
    
    Selector selector = Selector.open();
    initServer(selector);


    while (true) {

        selector.select();


        for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor.hasNext();) {
            SelectionKey key = (SelectionKey) itor.next();

            itor.remove();
            Handler handler = (Handler) key.attachment();

            handler.execute(selector, key);
        }

    }
}


private static void initServer(Selector selector) throws IOException,

        ClosedChannelException {
    ServerSocketChannel serverChannel = ServerSocketChannel.open();

    ServerSocket ss = serverChannel.socket();
    ss.bind(new InetSocketAddress(DEFAULT_PORT));

    serverChannel.configureBlocking(false);
    SelectionKey serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);

    serverKey.attach(new ServerHandler());
}

main函数非常简单,迭代SelectionKey,对每个key的attachment为Handler,调用它的execute的方法,不用管它是服务器Socket还是客户Socket。注意initServer方法将serverKey附加了一个ServerHandler。下面是ServerHandler的代码: Java代码 收藏代码

  1. class ServerHandler implements Handler {
  2. public void execute(Selector selector, SelectionKey key) {
  3. ServerSocketChannel server = (ServerSocketChannel) key.channel();
  4. SocketChannel client = null;
  5. try {
  6. client = server.accept();
  7. System.out.println("Accepted connection from " + client);
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. return;
  11. }
  12. SelectionKey clientKey = null;
  13. try {
  14. client.configureBlocking(false);
  15. clientKey = client.register(selector, SelectionKey.OP_READ);
  16. clientKey.attach(new ClientHandler());
  17. } catch (IOException e) {
  18. if (clientKey != null)
  19. clientKey.cancel();
  20. try { client.close(); } catch (IOException ioe) { }
  21. }
  22. }
  23. }

    class ServerHandler implements Handler {

     public void execute(Selector selector, SelectionKey key) {
         ServerSocketChannel server = (ServerSocketChannel) key.channel();
    
         SocketChannel client = null;
         try {
    
             client = server.accept();
             System.out.println("Accepted connection from " + client);
    
         } catch (IOException e) {
             e.printStackTrace();
    
             return;
         }
    
        SelectionKey clientKey = null;

        try {
            client.configureBlocking(false);

            clientKey = client.register(selector, SelectionKey.OP_READ);
            clientKey.attach(new ClientHandler());

        } catch (IOException e) {
            if (clientKey != null)

                clientKey.cancel();
            try { client.close(); } catch (IOException ioe) { }

        }
    }

}

ServerHandler接收连接,为每个客户Socket注册OP_READ操作,返回的clientKey附加上ClientHandler。 Java代码 收藏代码

  1. class ClientHandler implements Handler {
  2. private ByteBuffer buffer;
  3. public ClientHandler() {
  4. buffer = ByteBuffer.allocate(100);
  5. }
  6. public void execute(Selector selector, SelectionKey key) {
  7. try {
  8. if (key.isReadable()) {
  9. readKey(selector, key);
  10. } else if (key.isWritable()) {
  11. writeKey(selector, key);
  12. }
  13. } catch (IOException e) {
  14. key.cancel();
  15. try { key.channel().close(); } catch (IOException ioe) { }
  16. }
  17. }
  18. private void readKey(Selector selector, SelectionKey key) throws IOException {
  19. SocketChannel client = (SocketChannel) key.channel();
  20. int n = client.read(buffer);
  21. if (n > 0) {
  22. buffer.flip();
  23. key.interestOps(SelectionKey.OP_WRITE); // switch to OP_WRITE
  24. }
  25. }
  26. private void writeKey(Selector selector, SelectionKey key) throws IOException {
  27. // System.out.println("is writable...");
  28. SocketChannel client = (SocketChannel) key.channel();
  29. client.write(buffer);
  30. if (buffer.remaining() == 0) { // write finished, switch to OP_READ
  31. buffer.clear();
  32. key.interestOps(SelectionKey.OP_READ);
  33. }
  34. }
  35. }

    class ClientHandler implements Handler {

     private ByteBuffer buffer;
    
    public ClientHandler() {
        buffer = ByteBuffer.allocate(100);

    }


    public void execute(Selector selector, SelectionKey key) {
        try {

            if (key.isReadable()) {
                readKey(selector, key);

            } else if (key.isWritable()) {
                writeKey(selector, key);

            }
        } catch (IOException e) {

            key.cancel();
            try { key.channel().close(); } catch (IOException ioe) { }

        }
    }


    private void readKey(Selector selector, SelectionKey key) throws IOException {

        SocketChannel client = (SocketChannel) key.channel();
        int n = client.read(buffer);

        if (n > 0) {
            buffer.flip();

            key.interestOps(SelectionKey.OP_WRITE);        // switch to OP_WRITE
        }

    }


    private void writeKey(Selector selector, SelectionKey key) throws IOException {
        // System.out.println("is writable...");

        SocketChannel client = (SocketChannel) key.channel();
        client.write(buffer);

        if (buffer.remaining() == 0) {    // write finished, switch to OP_READ
            buffer.clear();

            key.interestOps(SelectionKey.OP_READ);
        }

    }
}

这个代码没有什么新内容,只是将根据key是可读还可写拆分为两个方法,代码结构显得更清晰。对于EchoServer,这么做确实有些过度工程,对于稍微复杂一点的服务器这么做是很值得的。 代码:EchoServer.java, EchoServer2.java, EchoServer3.java 参考:

  1. The Rox Java NIO Tutorial
  2. Architecture of a Highly Scalable NIO-Based Server

JSTL标签 参考手册

Posted on

JSTL标签 参考手册

JSTL标签 参考手册**

博客分类:

前言

=========================================================================

JSLT标签库,是日常开发经常使用的,也是众多标签中性能最好的。把常用的内容,放在这里备份一份,随用随查。尽量做到不用查,就可以随手就可以写出来。这算是Java程序员的基本功吧,一定要扎实。

JSTL全名为JavaServer Pages Standard Tag Library,目前最新的版本为1.1版。JSTL是由JCP(Java Community Process)所制定的标准规范,它主要提供给Java Web开发人员一个标准通用的标签函数库。 Web程序员能够利用JSTL和EL来开发Web程序,取代传统直接在页面上嵌入Java程序(Scripting)的做法,以提高程序的阅读性、维护性和方便性。 JSTL 1.1必须在支持Servlet 2.4且JSP 2.0以上版本的Container才可使用

<%@ taglib %>引入标签库

=========================================================================

1、以classPath中,加入jar包: standard-1.1.2.jar , jstl-1.1.2.jar

2、在相目\WEB-INF\tld\文件夹中放入常用的tld文件:c.tld,fmt.tld

3、在jsp文件的顶部加入以下内容: Java代码

  1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  2. <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
  3. <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

**核心标签库 **

==========================================================================

JSTL 核心标签库(C标签)标签共有13个,功能上分为4类: 1.表达式控制标签:out、set、remove、catch 2.流程控制标签:if、choose、when、otherwise 3.循环标签:forEach、forTokens 4.URL操作标签:import、url、redirect

**标签**


为循环控制,它可以将集合(Collection)中的成员循序浏览一遍。

标签的语法 说明

语法1:迭代一集合对象之所有成员 Html代码

  1. 本体内容

语法2:迭代指定的次数

Html代码

  1. 本体内容

标签的 属性说明

标签的 属性 : varStatus属性: 它的提供另外四个属性:index,count,fist和last,它们个自的意义如下: Java代码

  1. 属性 类型 意义
  2. index number 现在指到成员的索引
  3. count number 总共指到成员的总和
  4. first boolean 现在指到成员是否为第一个
  5. last boolean 现在指到成员是否为最后一个

遍历 List列表:

对于一个基本类型的数组,当前元素将作为相应包装类(Integer、Float等等)的一个实例提供。 Html代码

  1. ${item["domain"]==null?" ":item["domain"]}
  2.  

遍历Map:

对于一个java.util.Map,当前元素则作为一个java.util.Map.Entry提供。 Html代码

  1. ${item.value.id}
  2. ${item.value.urlOnClass}
  3. ${item.value.urlOnMethod}

**标签**


用来浏览一字符串中所有的成员,其成员是由定义符号(delimiters)所分隔的。

标签的语法 说明 Html代码

  1. <c:forTokens items="stringOfTokens" delims="delimiters" [var="varName"]
  2. [varStatus="varStatusName"] [begin="begin"] [end="end"] [step="step"]>
  3. 本体内容

标签的 属性说明

**标签**


主要用来显示数据的内容

标签的语法 说明

语法1:没有本体(body)内容 Html代码

语法2:有本体内容

Html代码

  1. default value

标签的 属性说明

一般来说,默认会将<、>、’、” 和 & 转换为 <、>、&/#039;、&/#034; 和&。假若不想转换时,只需要设定的escapeXml属性为fasle就可以了。

**标签**


主要用来将变量储存至JSP范围中或是JavaBean的属性中。

标签的语法 说明

语法1:将value的值储存至范围为scope的 varName 变量之中 Html代码

语法2:将本体内容的数据储存至范围为scope的 varName 变量之中

Html代码

  1. … 本体内容

语法3:将 value的值储存至 target 对象的属性中 Html代码

语法4:将本体内容的数据储存至target 对象的属性中

Html代码

  1. … 本体内容

标签的 属性说明

**标签**


主要用来移除变量。

标签的语法 说明 Html代码

**标签**


主要用来处理产生错误的异常状况,并且将错误信息储存起来。

标签的语法 说明 Html代码

  1. … 欲抓取错误的部分

**标签**


的用途就和我们一般在程序中用的if一样。

标签的语法 说明

语法1:没有本体内容(body) Html代码

语法2:有本体内容 Html代码

  1. 本体内容

示例:

Html代码

  1. 内容
  2. 内容
  3. 内容

c:choose> 标签


标签的语法 说明 Html代码

  1. 85
  2. 你的成绩为优秀!
  3. 您的成绩为良好!
  4. 您的成绩为及格
  5. 对不起,您没有通过考试!

格式 化标签库

==========================================================================

一:JSTL格式化标签又称为I18N标签库,主要用来编写国际化的WEB应用,使用此功能可以对一个特定的语言请求做出合适的处理。

例如:中国内地用户将显示简体中文,台湾地区则显示繁体中文,使用I18N格式化标签库还可以格式化数字和日期,例如同一数字或日趋,在不同国家可能有不同的格式,使用I18N格式标签库可以将数字和日期格式为当地的格式。 在JSP页面中要使用到格式化标签,需要引入下面的语句: <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"% > 二:概览


格式化标签
<fmt:fromatNumber>
<fmt:formatDate>
<fmt:parseDate>
<fmt:parseNumber>
<fmt:setTimeZone>
<fmt:timeZone>
国际化标签
<fmt:setLocale>
<fmt:requestEncoding>
<fmt:bundle>
<fmt:message>
<fmt:param>
<fmt:setBundle>

三:


此标签会根据区域定制的方式将数字格式化成数字,货币,百分比。
此标签的属性:
value:要格式化的数字
type:按照什么类型格式化
pattern:自定义格式化样式
currencyCode:ISO-4721货币代码,只适用于按照货币格式化的数字
currencySymbol: 货币符号,如¥,只适用于按照货币格式化的数字
groupingUsed: 是否包含分隔符
maxIntegerDigits: 整数部分最多显示多少位
mixIntegerDigits: 整数部分最少显示多少位
maxFractionDigits: 小数部分最多显示多位位
minFractionDigits: 小数部分最少显示多位位
var:存储格式化后的结果
scope: 存储的范围
示例1:    

Java代码 <%@ page language="java" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">



chapter4.jsp




















<%@ page language="java" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> chapter4.jsp




注意:如果要实现国际化,那么编码格式要设置为utf-8. 从程序运行效果可以看出,设定的区域不同,格式化数字的显示也会不同. 四:type属性:可以是数字(number),货币(currency),百分比(percent) 示例2:
Java代码 <%@ page language="java" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">


chapter4.jsp















<%@ page language="java" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> chapter4.jsp




currencyCode为货币代码,例如美元为USD,人民币为CNY等 currencySymbol为货币符号例如,人民币为¥,美元为$。 如果不指定区域,则会根据语言区域自动选择currencySymbol 示例3:
Java代码 <%@ page language="java" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">


chapter4.jsp














<%@ page language="java" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> chapter4.jsp




currencySymbol属性还可以自定义要显示的头标识,但是一定得type="currency"才会生效,例如:
Java代码 <%@ page language="java" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">


chapter4.jsp














<%@ page language="java" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> chapter4.jsp




自定义数字样式
会显示: 12.3100 1.234E3 会四舍五入 var:定义一个变量,存储格式化后的结果,scope指定变量存储的范围.用法和前面讲的标签一致. 五: ------------------------------------------------------------------- 此标签用来将字符串类型的数字,货币或百分比转换成数字类型,和标签的作用正好相反. value: 要转换的字符串 type: 指定要转换的字符串为什么类型,可取值:number,percent,currency pattern: 自定义格式化样式 parseLocale: 指定区域来转换字符串 IntegerOnly: 转换后的数字是否只显示整数部分 var: 存储转换后的结果 scope: 存储的范围 示例1: 显示: 500800200 示例2: 显示: 0.52 (52%在这里是一个字符串, type指定这个字符串是什么类型的值) 示例3: 显示123, ¥123在这里是一个字符串, type指定这个字符串是什么类型的值 示例4:

显示: 123.333 123 integerOnly确定是否只显示整数部分. 示例5:

parseLocale="en_US"主要是配合当type="currency"时用的, 如果要转换货币的字符串类型为value="¥123.333",不设置语言环境的话,会取当前浏览器的默认设置,否则就要加上parseLocale="zh_CN",指定环境为中文环境 如果要转换货币的字符串类型为value="$123.333",不设置语言环境的话,会取当前浏览器的默认设置,如果默认为zh_cn的话,程序会报错的,否则就要加上parseLocale="en_US",指定环境为英文美国环境 六: ------------------------------------------------------------------- 此标签可以将日期格式化. 属性介绍: value 用来格式化的时间或日期 type 指定格式化的是日期还是时间,或者两者都是取值范围:date,time,both pattern 自定义格式化样式 dateStyle 日期的格式化样式 timeStyle 时间的格式化样式 timeZone 指定使用的时区 var 存储格式化后的结果 scope 指定存储的范围 自定义格式:
-------------------------- Java代码
















注意这里小时 hh表示12小时制, HH代表24小时制 示例1:
-------------------------- Java代码







大家可以看到大陆和台湾显示日期的格式是有区别的. 显示结果: 2009-12-7 2009/12/7 示例2:
-------------------------- Java代码







显示结果: 14:59:28 下午 02:59:28 type可取值及意义: date 格式化日期 time格式化时间 both格式化日期时间 示例3:
-------------------------- Java代码







输出结果: 2009-12-7 21:24:26 2009/12/7 下午 09:24:26 dateStyle用来设定日期显示的样式,其值可以是default, short, medium, long, full,请看示例:
-------------------------- Java代码





















显示结果如下: 2009-12-7 21:30:49 09-12-7 21:30:49 2009-12-7 21:30:49 2009年12月7日 21:30:49 2009年12月7日 星期一 21:30:49 可以看到dateStyle属性只对日期部分起作用,时间部分没有作用. timeStyle用来显示时间部分的样式,取值范围同上
-------------------------- Java代码





















输出: 2009-12-7 21:35:52 2009-12-7 下午9:35 2009-12-7 21:35:52 2009-12-7 下午09时35分52秒 2009-12-7 下午09时35分52秒 CST timeZone用来设定时区,时区的意思类似于酒店里大堂放的几个时钟,比如现在时间会有北京时间,东京时间,纽约时间,伦墩时间, 取值范围为:EST, CST, MST, PST
-------------------------- Java代码




















输出结果: 下午09时41分37秒 CST 上午08时41分37秒 EST 上午07时41分37秒 CST 上午06时41分37秒 MST 上午05时41分37秒 PST 七: ------------------------------------------------------------------- 将字符串类型的时间转换为日期类型. value 用来格式化的时间或日期的字符串 type 指定格式化的是日期还是时间,或者两者都是取值范围:date,time,both pattern 自定义格式化样式 dateStyle 日期的格式化样式 timeStyle 时间的格式化样式 timeZone 指定使用的时区 var 存储格式化后的结果 scope 指定存储的范围 示例: 输出: Sat Apr 05 00:00:00 CST 2008, 这里已经将字符串” 2008-4-5”转换为了日期对象了.转换一定得注意,类似于2008-4-5这样的字符串,type必须为date,类似于12:34:56的字符串,type必须为time类似于2008-4-5 12:34:56这样的字符串,type必须为both还要注意浏览器的语言环境的设置,如果为zh_tw,那么字符串就必须得符合当地的标准,如为2009/12/7 下午 09:24:26就正确转换为日期对象,否则就会报错. 八: ------------------------------------------------------------------- value 设定时区 var 存储设定的时区 scope 存储的范围 value用来设定时区,可以是EST,CST,MST,PST等,如果有var属性,则将结果存储在所设定的范围之内.在属性范围内的页面都会使用该时区为默认时区.
Java代码











输出: 上午09时25分12秒 EST 上午09时25分12秒 EST 上午09时25分12秒 EST 此时区在该页面内都有效 九: ------------------------------------------------------------------- 用来暂时设置时区.
Java代码












此标签的时区只是部分,在标签开始至标签结束内有效,其它地方无效,其它地方还是会使用默认时区 Function标签 **库 ** ========================================================================== JSTL Functions 标签库中提供了一组常用的 EL 函数,主要用于处理字符串,在 JSP 中可以直接使用这些函数。 在 JSP 文件中使用 Functions 标签库,要先通过 taglib 指令引入该标签库: <%@taglib uri=”http://java.sun.com/jsp/jstl/functions” prefix=”fn” %. ## 18.1fn:contains 函数 fn:contains 函数用于判断在源字符串中是否包含目标字符串,其语法为: fn:contains(String source,String target) -------boolean; 以上 source 参数指定源字符串, target 参数指定目标字符串,返回类型为 boolean 。 例如对于以下 EL 表达式: ${fn:contains(“Tomcat”,”cat”)} ${fn:contains(“Tomcat”,”CAT”)} 第一个 EL 表达式的值为 true ,第二个 EL 表达式的值为 false 。 ## 18.2fn:containsIgnoreCase 函数 fn:containsIgnoreCase 函数用于判断在源字符串中是否包含目标字符串,并且在判断时忽略大小写,其语法为: fn: containsIgnoreCase (String source,String target) -------boolean; 以上 source 参数指定源字符串, target 参数指定目标字符串,返回类型为 boolean 。 例如对于以下 EL 表达式: ${fn: containsIgnoreCase (“Tomcat”,”CAT”)} ${fn: containsIgnoreCase (“Tomcat”,”Mike”)} 第一个 EL 表达式的值为 true ,第二个 EL 表达式的值为 false 。 ## 18.3 fn:startsWith 函数 fn:startsWith 函数用于判断源字符串是否以指定的目标字符串开头,其语法为: fn:startsWith(String source,String target) ----boolean 以上 source 参数指定源字符串, target 参数指定目标字符串,返回类型为 boolean 。 例如对于以下 EL 表达式: ${fn: startsWith (“Tomcat”,”Tom”)} ${fn: startsWith (“Tomcat”,”cat”)} 第一个 EL 表达式的值为 true ,第二个 EL 表达式的值为 false 。 ## 18.4 fn:endsWith 函数 fn: endsWith 函数用于判断源字符串是否以指定的目标字符串结尾,其语法为: fn: endsWith (String source,String target) ----boolean 以上 source 参数指定源字符串, target 参数指定目标字符串,返回类型为 boolean 。 例如对于以下 EL 表达式: ${fn: endsWith (“Tomcat”,”cat”)} ${fn: endsWith (“Tomcat”,”Tom”)} 第一个 EL 表达式的值为 true ,第二个 EL 表达式的值为 false 。 ## 18.5 fn:indexOf 函数 fn:indexOf 函数用于在源字符串中查找目标字符串,并返回源字符串中最先与目标字符串匹配的第一个字符的索引,如果在源字符串中不包含目标字符串,就返回 -1 ,源字符串中的第一个字符的索引为 0 。 fn:indexOf 函数的语法为: fn: indexOf (String source,String target) ----int 以上 source 参数指定源字符串, target 参数指定目标字符串,返回类型为 int 。 例如对于以下 EL 表达式: 1 ${fn: indexOf (“Tomcat”,”cat”)}
2 ${fn: indexOf (“2211221”,”21”)}
3 ${fn: indexOf (“Tomcat”,”Mike”)}
其输出结果为: 1 3 2 1 3 -1 ## 18.6 fn:replace 函数 fn:replace 函数用于把源字符串中的一部分替换为另外的字符串,并返回替换后的字符串。 fn:replace 函数的语法为: fn: replace (String source,String before,String after) ----String 以上 source 参数指定源字符串, before 参数指定源字符串中被替换的子字符串, after 参数指定用于替换的子字符串,返回类型为 String 。 例如对于以下 EL 表达式: 1 ${ fn: replace(“TomcAt”,”cAt”,”cat”)}
2 ${ fn: replace(“2008/1/9”,”/”,”-”)}
其输出结果为: 1 Tomcat 2 2008-1-9 ## 18.7 fn:substring 函数 fn:substring 函数用于获取源字符串中的特定子字符串,它的语法为: fn:substring(String source,int beginIndex,int endIndex) ------String 以上 source 参数指定源字符串, beginIndex 参数表示子字符串中的第一个字符在源字符串中的索引,endIndex 参数表示子字符串的最后一个字符在源字符串中的索引加 1 ,返回类型为 String ,源字符串中的第一个字符的索引为 0 。 例如对于以下 EL 表达式: 1 ${ fn: substring (“Tomcat”,0,3)}
2 ${ fn: substring (“Tomcat”,3,”6”)}
其输出结果为: 1 Tom 2 cat ## 18.8 fn:substringBefore 函数 fn:substringBefore 函数用于获取源字符串中指定子字符串之前的子字符串,其语法为: fn:substringBefore(String source,String target) ----String 以上 source 参数指定源字符串, target 参数指定子字符串,返回类型为 String 。如果在源字符串中不包含特定子字符串,就返回空字符串。 例如对于以下 EL 表达式: 1 ${ fn: substringBefore (“Tomcat”,”cat”)}
2 ${ fn: substringBefore (“mydata.txt”,”.txt”)}
其输出结果为: 1 Tom 2 mydata ## 18.9 fn:substringAfter 函数 fn: substringAfter 函数用于获取源字符串中指定子字符串之后的子字符串,其语法为: fn: substringAfter (String source,String target) ----String 以上 source 参数指定源字符串, target 参数指定子字符串,返回类型为 String 。如果在源字符串中不包含特定子字符串,就返回空字符串。 例如对于以下 EL 表达式: 1 ${ fn: substringAfter (“Tomcat”,”Tom”)}
2 ${ fn: substringAfter (“mydata.txt”,” mydata.”)}
其输出结果为: 1 cat 2 txt ## 18.10 fn:split 函数 fn:split 函数用于将源字符串拆分为一个字符串数组,其语法为: fn: split (String source,String delimiter) ----String[] 以上 source 参数指定源字符串, delimiter 参数指定用于拆分源字符串的分隔符,返回类型为 String[] 。如果在源字符串中不包含 delimiter 参数指定的分隔符,或者 delimiter 参数为 null ,那么在返回的字符串数组中只有一个元素,为源字符串。 例如对于以下 EL 表达式: ${token}
其输出结果为: www mywebsite org 再例如对于以下代码: ${strs[0]} 其输出结果为: www.mywebsite.org ## 18.11 fn:join 函数 fn:join 函数用于将源字符串数组中的所有字符串连接为一个字符串,其语法为: fn:join(String source[],String separator) ----String 以上 source 参数指定源字符串数组, separator 参数指定用于连接源字符串数组中的各个字符串的分隔符,返回类型为 String 。 例如对于以下代码: <% String strs[] = {“www”,”mywebsite”,”org”}; %> ” var=”strs”/> ${fn:join(strs,”.”)} 其输出结果为: www. mywebsite. org ## 18.12 fn:toLowerCase 函数 fn:toLowerCase 函数用于将源字符串中的所有字符改为小写,其语法为: fn:toLowerCase(String source) -----String 以上 source 参数指定源字符串,返回类型为 String 。 例如对于以下 EL 表达式: fn:toLowerCase(“TomCat”) 其输出结果为: tomcat ## 18.13 fn:toUpperCase 函数 fn: toUpperCase 函数用于将源字符串中的所有字符改为大写,其语法为: fn: toUpperCase (String source) -----String 以上 source 参数指定源字符串,返回类型为 String 。 例如对于以下 EL 表达式: fn: toUpperCase (“TomCat”) 其输出结果为: TOMCAT ## 18.14 fn:trim 函数 fn:trim 函数用于将源字符串中的开头和末尾的空格删除,其语法为: fn:trim(String source) ----String 以上 source 参数指定源字符串,返回类型为 String 。 例如对于以下 EL 表达式: fn:trim(“ Tomcat ”) 以上 EL 表达式的值为“ Tomcat ”。 ## 18.15 fn:escapeXml 函数 fn:escapeXml 函数用于将源字符串中的字符“ < ”、“ > ”、“ ” ”和“ & ”等转换为转义字符,本书第 1 章的 1.2 节( HTML 简介)介绍了转义字符的概念。 fn:escapeXml 函数的行为与 标签的 escapeXml 属性为 true 时的转换行为相同, fn:escapeXml 函数的语法为: fn:escapeXml(String source) ----String 以上 source 参数指定源字符串,返回类型为 String 。 例程 18-1 的 out.jsp 演示了 fn:escapeXml 函数的用法。 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> out 1.${fn:escapeXml(" 表示粗体字 ") }
2.
3.${" 表示粗体字 "}
   对于 out.jsp 中的以下代码:

          1.${fn:escapeXml("<b> 表示粗体字 </b>") }<br/>

2.

3.${" 表示粗体字 "}

   其输出结果为:

          1.&lt;b&gt; 表示粗体字 &lt;/b&gt;<br/>

2.<b> 表示粗体字 </b>

3. 表示粗体字

18.16 fn:length 函数

   fn:length 函数用于返回字符串中的字符的个数,或者集合和数组的元素的个数,其语法为:

          fn:length(source) ---- int

   以上 source 参数可以为字符串、集合或者数组,返回类型为 int 。

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>

<%@page import="java.util.ArrayList"%>

length <% int[] array = {1,2,3,4}; ArrayList list = new ArrayList(); list.add("one"); list.add("two"); list.add("three"); %> 数组长度: ${fn:length(array)}
集合长度: ${fn:length(list)}
字符串长度: ${fn:length("Tomcat")}

Functions 标签库概览

l fn:contains 函数 : 用于判断在源字符串中是否包含目标字符串。

l fn:containsIgnoreCase 函数 : 用于判断在源字符串中是否包含目标字符串 , 并且在判断时忽略大小写。

l fn:startsWith 函数 : 用于判断源字符串是否以指定的目标字符串开头。

l fn: endsWith 函数:用于判断源字符串是否以指定的目标字符串结尾。

l fn:indexOf 函数:用于在源字符串中查找目标字符串,并返回源字符串中最先与目标字符串匹配的第一个字符的索引。

l fn:replace 函数:用于把源字符串中的一部分替换为另外的字符串,并返回替换后的字符串。

l fn:substring 函数:用于获取源字符串中的特定子字符串。

l fn:substringBefore 函数:用于获取源字符串中指定子字符串之前的子字符串。

l fn: substringAfter 函数:用于获取源字符串中指定子字符串之后的子字符串

l fn:split 函数:用于将源字符串拆分为一个字符串数组。

l fn:join 函数:用于将源字符串数组中的所有字符串连接为一个字符串。

l fn:toLowerCase 函数:用于将源字符串中的所有字符改为小写。

l fn: toUpperCase 函数:用于将源字符串中的所有字符改为大写。

l fn:trim 函数:用于将源字符串中的开头和末尾的空格删除。

l fn:escapeXml 函数:用于将源字符串中的字符“ < ”、“ > ”、“ ” ”和“ & ”等转换为转义字符。

l fn:length 函数:用于返回字符串中的字符的个数,或者集合和数组的元素的个数 来源: [http://elf8848.iteye.com/blog/245559](http://elf8848.iteye.com/blog/245559)

Java aio(异步网络IO)初探

Posted on

Java aio(异步网络IO)初探

< > 猎头职位: 上海: Junior Product Manager

相关文章:

  • JDK7 AIO 初体验
  • JavaSE7新特性 异步非阻塞I/O 网络通信 AIO
  • JAVA NIO 简介 推荐群组: D语言 更多相关推荐 企业应用

    按照《Unix网络编程》的划分,IO模型可以分为:阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO。如何区分呢?首先一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO服用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。 Java nio 2.0的主要改进就是引入了异步IO(包括文件和网络),这里主要介绍下异步网络IO API的使用以及框架的设计,以TCP服务端为例。首先看下为了支持AIO引入的新的类和接口: java.nio.channels.AsynchronousChannel

     标记一个channel支持异步IO操作。
    

    java.nio.channels.AsynchronousServerSocketChannel

     ServerSocket的aio版本,创建TCP服务端,绑定地址,监听端口等。
    

    java.nio.channels.AsynchronousSocketChannel

     面向流的异步socket channel,表示一个连接。
    

    java.nio.channels.AsynchronousChannelGroup

     异步channel的分组管理,目的是为了资源共享。一个AsynchronousChannelGroup绑定一个线程池,这个线程池执行两个任务:处理IO事件和派发CompletionHandler。AsynchronousServerSocketChannel创建的时候可以传入一个 AsynchronousChannelGroup,那么通过AsynchronousServerSocketChannel创建的 AsynchronousSocketChannel将同属于一个组,共享资源。
    

    java.nio.channels.CompletionHandler

     异步IO操作结果的回调接口,用于定义在IO操作完成后所作的回调工作。AIO的API允许两种方式来处理异步操作的结果:返回的Future模式或者注册CompletionHandler,我更推荐用CompletionHandler的方式,这些handler的调用是由 AsynchronousChannelGroup的线程池派发的。显然,线程池的大小是性能的关键因素。AsynchronousChannelGroup允许绑定不同的线程池,通过三个静态方法来创建:
    

Java代码 收藏代码

  1. public static AsynchronousChannelGroup withFixedThreadPool(int nThreads,
  2. ThreadFactory threadFactory)
  3. throws IOException
  4. public static AsynchronousChannelGroup withCachedThreadPool(ExecutorService executor,
  5. int initialSize)
  6. public static AsynchronousChannelGroup withThreadPool(ExecutorService executor)
  7. throws IOException

public static AsynchronousChannelGroup withFixedThreadPool(int nThreads,

                                                           ThreadFactory threadFactory)
    throws IOException

public static AsynchronousChannelGroup withCachedThreadPool(ExecutorService executor,

                                                            int initialSize)

public static AsynchronousChannelGroup withThreadPool(ExecutorService executor) throws IOException

 需要根据具体应用相应调整,从框架角度出发,需要暴露这样的配置选项给用户。
 在介绍完了aio引入的TCP的主要接口和类之后,我们来设想下一个aio框架应该怎么设计。参考非阻塞nio框架的设计,一般都是采用**Reactor**模式,Reacot负责事件的注册、select、事件的派发;相应地,异步IO有个**Proactor**模式,Proactor负责 CompletionHandler的派发,查看一个典型的IO写操作的流程来看两者的区别:
 Reactor:  send(msg) -> 消息队列是否为空,如果为空  -> 向Reactor注册OP_WRITE,然后返回 -> Reactor select -> 触发Writable,通知用户线程去处理 ->先注销Writable(很多人遇到的cpu 100%的问题就在于没有注销),处理Writeable,如果没有完全写入,继续注册OP_WRITE。注意到,写入的工作还是用户线程在处理。
 Proactor: send(msg) -> 消息队列是否为空,如果为空,发起read异步调用,并注册CompletionHandler,然后返回。 -> 操作系统负责将你的消息写入,并返回结果(写入的字节数)给Proactor -> Proactor派发CompletionHandler。可见,写入的工作是操作系统在处理,无需用户线程参与。事实上在aio的API 中,**AsynchronousChannelGroup就扮演了Proactor的角色**。
CompletionHandler有三个方法,分别对应于处理成功、失败、被取消(通过返回的Future)情况下的回调处理:

Java代码 收藏代码

  1. public interface CompletionHandler {
  2. void completed(V result, A attachment);
  3. void failed(Throwable exc, A attachment);
  4. void cancelled(A attachment);
  5. }

public interface CompletionHandler {

 void completed(V result, A attachment);


void failed(Throwable exc, A attachment);




void cancelled(A attachment);

}

其中的泛型参数V表示IO调用的结果,而A是发起调用时传入的attchment。
在初步介绍完aio引入的类和接口后,我们看看一个典型的tcp服务端是怎么启动的,怎么接受连接并处理读和写,这里引用的代码都是yanf4j 的aio分支中的代码,可以从svn checkout,svn地址: [http://yanf4j.googlecode.com/svn/branches/yanf4j-aio](http://yanf4j.googlecode.com/svn/branches/yanf4j-aio)
第一步,创建一个AsynchronousServerSocketChannel,创建之前先创建一个 AsynchronousChannelGroup,上文提到AsynchronousServerSocketChannel可以绑定一个 AsynchronousChannelGroup,那么通过这个AsynchronousServerSocketChannel建立的连接都将同属于一个AsynchronousChannelGroup并共享资源:

Java代码 收藏代码

  1. this.asynchronousChannelGroup = AsynchronousChannelGroup
  2. .withCachedThreadPool(Executors.newCachedThreadPool(),
  3. this.threadPoolSize);

this.asynchronousChannelGroup = AsynchronousChannelGroup

                .withCachedThreadPool(Executors.newCachedThreadPool(),
                        this.threadPoolSize);

 然后初始化一个AsynchronousServerSocketChannel,通过open方法:

Java代码 收藏代码

  1. this.serverSocketChannel = AsynchronousServerSocketChannel
  2. .open(this.asynchronousChannelGroup);

this.serverSocketChannel = AsynchronousServerSocketChannel

            .open(this.asynchronousChannelGroup);


通过nio 2.0引入的SocketOption类设置一些TCP选项:

Java代码 收藏代码

  1. this.serverSocketChannel
  2. .setOption(
  3. StandardSocketOption.SO_REUSEADDR,true);
  4. this.serverSocketChannel
  5. .setOption(
  6. StandardSocketOption.SO_RCVBUF,16/*1024);

this.serverSocketChannel

                .setOption(
                        StandardSocketOption.SO_REUSEADDR,true);

this.serverSocketChannel .setOption(

                        StandardSocketOption.SO_RCVBUF,16/*1024);


绑定本地地址:

Java代码 收藏代码

  1. this.serverSocketChannel
  2. .bind(new InetSocketAddress("localhost",8080), 100);

this.serverSocketChannel

                .bind(new InetSocketAddress("localhost",8080), 100);



其中的100用于指定等待连接的队列大小(backlog)。完了吗?还没有,最重要的**监听**工作还没开始,监听端口是为了等待连接上来以便accept产生一个AsynchronousSocketChannel来表示一个新建立的连接,因此需要发起一个accept调用,调用是异步的,操作系统将在连接建立后,将最后的结果——**AsynchronousSocketChannel**返回给你:

Java代码 收藏代码

  1. public void pendingAccept() {
  2. if (this.started && this.serverSocketChannel.isOpen()) {
  3. this.acceptFuture = this.serverSocketChannel.accept(null,
  4. new AcceptCompletionHandler());
  5. } else {
  6. throw new IllegalStateException("Controller has been closed");
  7. }
  8. }

public void pendingAccept() {

    if (this.started && this.serverSocketChannel.isOpen()) {
        this.acceptFuture = this.serverSocketChannel.accept(null,

                new AcceptCompletionHandler());


    } else {
        throw new IllegalStateException("Controller has been closed");

    }
}

注意,重复的accept调用将会抛出PendingAcceptException,后文提到的read和write也是如此。accept方法的第一个参数是你想传给CompletionHandler的attchment,第二个参数就是注册的用于回调的CompletionHandler,最后返回结果Future。你可以对future做处理,这里采用更推荐的方式就是注册一个CompletionHandler。那么accept的CompletionHandler中做些什么工作呢?显然一个赤裸裸的 AsynchronousSocketChannel是不够的,我们需要将它封装成session,一个session表示一个连接(mina里就叫 IoSession了),里面带了一个缓冲的消息队列以及一些其他资源等。在连接建立后,除非你的服务器只准备接受一个连接,不然你需要在后面继续调用pendingAccept来发起另一个accept请求

Java代码 收藏代码

  1. private final class AcceptCompletionHandler implements
  2. CompletionHandler {
  3. @Override
  4. public void cancelled(Object attachment) {
  5. logger.warn("Accept operation was canceled");
  6. }
  7. @Override
  8. public void completed(AsynchronousSocketChannel socketChannel,
  9. Object attachment) {
  10. try {
  11. logger.debug("Accept connection from "
    • socketChannel.getRemoteAddress());
  12. configureChannel(socketChannel);
  13. AioSessionConfig sessionConfig = buildSessionConfig(socketChannel);
  14. Session session = new AioTCPSession(sessionConfig,
  15. AioTCPController.this.configuration
  16. .getSessionReadBufferSize(),
  17. AioTCPController.this.sessionTimeout);
  18. session.start();
  19. registerSession(session);
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. logger.error("Accept error", e);
  23. notifyException(e);
  24. } finally {
  25. pendingAccept();
  26. }
  27. }
  28. @Override
  29. public void failed(Throwable exc, Object attachment) {
  30. logger.error("Accept error", exc);
  31. try {
  32. notifyException(exc);
  33. } finally {
  34. pendingAccept();
  35. }
  36. }
  37. }

private final class AcceptCompletionHandler implements

        CompletionHandler<AsynchronousSocketChannel, Object> {


    @Override
    public void cancelled(Object attachment) {

        logger.warn("Accept operation was canceled");
    }


    @Override

    public void completed(AsynchronousSocketChannel socketChannel,
            Object attachment) {

        try {
            logger.debug("Accept connection from "

                    + socketChannel.getRemoteAddress());
            configureChannel(socketChannel);

            AioSessionConfig sessionConfig = buildSessionConfig(socketChannel);
            Session session = new AioTCPSession(sessionConfig,

                    AioTCPController.this.configuration
                            .getSessionReadBufferSize(),

                    AioTCPController.this.sessionTimeout);
            session.start();

            registerSession(session);
        } catch (Exception e) {

            e.printStackTrace();
            logger.error("Accept error", e);

            notifyException(e);
        } finally {

pendingAccept

(); }

    }


    @Override
    public void failed(Throwable exc, Object attachment) {

        logger.error("Accept error", exc);
        try {

            notifyException(exc);
        } finally {

pendingAccept

(); }

    }
}



注意到了吧,我们在failed和completed方法中在最后都调用了pendingAccept来继续发起accept调用,等待新的连接上来。有的同学可能要说了,这样搞是不是递归调用,会不会堆栈溢出?实际上不会,因为发起accept调用的线程与CompletionHandler回调的线程并非同一个,不是一个上下文中,两者之间没有耦合关系。要注意到,CompletionHandler的回调共用的是 AsynchronousChannelGroup绑定的线程池,因此**千万别在CompletionHandler回调方法中调用阻塞或者长时间的操作**,例如sleep,回调方法最好能支持超时,防止线程池耗尽。
连接建立后,怎么读和写呢?回忆下在nonblocking nio框架中,连接建立后的第一件事是干什么?注册OP_READ事件等待socket可读。异步IO也同样如此,连接建立后马上发起一个异步read调用,等待socket可读,这个是Session.start方法中所做的事情:

Java代码 收藏代码

  1. public class AioTCPSession {
  2. protected void start0() {
  3. pendingRead();
  4. }
  5. protected final void pendingRead() {
  6. if (!isClosed() && this.asynchronousSocketChannel.isOpen()) {
  7. if (!this.readBuffer.hasRemaining()) {
  8. this.readBuffer = ByteBufferUtils
  9. .increaseBufferCapatity(this.readBuffer);
  10. }
  11. this.readFuture = this.asynchronousSocketChannel.read(
  12. this.readBuffer, this, this.readCompletionHandler);
  13. } else {
  14. throw new IllegalStateException(
  15. "Session Or Channel has been closed");
  16. }
  17. }
  18. }

public class AioTCPSession {

protected void start0() {
    pendingRead();

}


protected final void pendingRead() {
    if (!isClosed() && this.asynchronousSocketChannel.isOpen()) {

        if (!this.readBuffer.hasRemaining()) {
            this.readBuffer = ByteBufferUtils

                    .increaseBufferCapatity(this.readBuffer);
        }

        this.readFuture = this.asynchronousSocketChannel.read(
                this.readBuffer, this, this.readCompletionHandler);

    } else {
        throw new IllegalStateException(

                "Session Or Channel has been closed");
    }

}

}

 AsynchronousSocketChannel的read调用与AsynchronousServerSocketChannel的accept调用类似,同样是非阻塞的,返回结果也是一个Future,但是写的结果是整数,表示写入了多少字节,因此read调用返回的是 **Future<Integer>**,方法的第一个参数是读的缓冲区,操作系统将IO读到数据拷贝到这个缓冲区,第二个参数是传递给 CompletionHandler的attchment,第三个参数就是注册的用于回调的CompletionHandler。这里保存了read的结果Future,这是为了在关闭连接的时候能够主动取消调用,accept也是如此。现在可以看看read的CompletionHandler的实现:

Java代码 收藏代码

  1. public final class ReadCompletionHandler implements
  2. CompletionHandler {
  3. private static final Logger log = LoggerFactory
  4. .getLogger(ReadCompletionHandler.class);
  5. protected final AioTCPController controller;
  6. public ReadCompletionHandler(AioTCPController controller) {
  7. this.controller = controller;
  8. }
  9. @Override
  10. public void cancelled(AbstractAioSession session) {
  11. log.warn("Session(" + session.getRemoteSocketAddress()
    • ") read operation was canceled");
  12. }
  13. @Override
  14. public void completed(Integer result, AbstractAioSession session) {
  15. if (log.isDebugEnabled())
  16. log.debug("Session(" + session.getRemoteSocketAddress()
    • ") read +" + result + " bytes");
  17. if (result < 0) {
  18. session.close();
  19. return;
  20. }
  21. try {
  22. if (result > 0) {
  23. session.updateTimeStamp();
  24. session.getReadBuffer().flip();
  25. session.decode();
  26. session.getReadBuffer().compact();
  27. }
  28. } finally {
  29. try {
  30. session.pendingRead();
  31. } catch (IOException e) {
  32. session.onException(e);
  33. session.close();
  34. }
  35. }
  36. controller.checkSessionTimeout();
  37. }
  38. @Override
  39. public void failed(Throwable exc, AbstractAioSession session) {
  40. log.error("Session read error", exc);
  41. session.onException(exc);
  42. session.close();
  43. }
  44. }

public final class ReadCompletionHandler implements

    CompletionHandler<Integer, AbstractAioSession> {


private static final Logger log = LoggerFactory
        .getLogger(ReadCompletionHandler.class);

protected final AioTCPController controller;


public ReadCompletionHandler(AioTCPController controller) {
    this.controller = controller;

}


@Override
public void cancelled(AbstractAioSession session) {

    log.warn("Session(" + session.getRemoteSocketAddress()
            + ") read operation was canceled");

}


@Override
public void completed(Integer result, AbstractAioSession session) {

    if (log.isDebugEnabled())
        log.debug("Session(" + session.getRemoteSocketAddress()

                + ") read +" + result + " bytes");
    if (result < 0) {

        session.close();
        return;

    }
    try {

        if (result > 0) {
            session.updateTimeStamp();

            session.getReadBuffer().flip();
            session.decode();

            session.getReadBuffer().compact();
        }

    } finally {
        try {

            session.pendingRead();
        } catch (IOException e) {

            session.onException(e);
            session.close();

        }
    }

    controller.checkSessionTimeout();
}


@Override

public void failed(Throwable exc, AbstractAioSession session) {
    log.error("Session read error", exc);

    session.onException(exc);
    session.close();

}

}

如果IO读失败,会返回失败产生的异常,这种情况下我们就主动关闭连接,通过session.close()方法,这个方法干了两件事情:关闭channel和取消read调用:

Java代码 收藏代码

  1. if (null != this.readFuture) {
  2. this.readFuture.cancel(true);
  3. }
  4. this.asynchronousSocketChannel.close();

if (null != this.readFuture) {

        this.readFuture.cancel(true);
    }

this.asynchronousSocketChannel.close();

在读成功的情况下,我们还需要判断结果result是否小于0,如果小于0就表示对端关闭了,这种情况下我们也主动关闭连接并返回。如果读到一定字节,也就是result大于0的情况下,我们就尝试从读缓冲区中decode出消息,并派发给业务处理器的回调方法,最终通过pendingRead继续发起read调用等待socket的下一次可读。可见,我们并不需要自己去调用channel来进行IO读,而是操作系统帮你直接读到了缓冲区,然后给你一个结果表示读入了多少字节,你处理这个结果即可。而nonblocking IO框架中,是reactor通知用户线程socket可读了,然后用户线程自己去调用read进行实际读操作。这里还有个需要注意的地方,就是decode出来的消息的派发给业务处理器工作最好交给一个线程池来处理,避免阻塞group绑定的线程池。

IO写的操作与此类似,不过通常写的话我们会在session中关联一个缓冲队列来处理,没有完全写入或者等待写入的消息都存放在队列中,队列为空的情况下发起write调用:

Java代码 收藏代码

  1. protected void write0(WriteMessage message) {
  2. boolean needWrite = false;
  3. synchronized (this.writeQueue) {
  4. needWrite = this.writeQueue.isEmpty();
  5. this.writeQueue.offer(message);
  6. }
  7. if (needWrite) {
  8. pendingWrite(message);
  9. }
  10. }
  11. protected final void pendingWrite(WriteMessage message) {
  12. message = preprocessWriteMessage(message);
  13. if (!isClosed() && this.asynchronousSocketChannel.isOpen()) {
  14. this.asynchronousSocketChannel.write(message.getWriteBuffer(),
  15. this, this.writeCompletionHandler);
  16. } else {
  17. throw new IllegalStateException(
  18. "Session Or Channel has been closed");
  19. }
  20. }

    protected void write0(WriteMessage message) {

    boolean needWrite = false; synchronized (this.writeQueue) {

       needWrite = this.writeQueue.isEmpty();
       this.writeQueue.offer(message);
    

    } if (needWrite) {

       pendingWrite(message);
    

    }

    }

protected final void pendingWrite(WriteMessage message) {
    message = preprocessWriteMessage(message);

    if (!isClosed() && this.asynchronousSocketChannel.isOpen()) {
        this.asynchronousSocketChannel.write(message.getWriteBuffer(),

                this, this.writeCompletionHandler);
    } else {

        throw new IllegalStateException(
                "Session Or Channel has been closed");

    }
}




write调用返回的结果与read一样是一个Future<Integer>,而write的CompletionHandler处理的核心逻辑大概是这样:

Java代码 收藏代码

  1. @Override
  2. public void completed(Integer result, AbstractAioSession session) {
  3. if (log.isDebugEnabled())
  4. log.debug("Session(" + session.getRemoteSocketAddress()
    • ") writen " + result + " bytes");
  5. WriteMessage writeMessage;
  6. Queue writeQueue = session.getWriteQueue();
  7. synchronized (writeQueue) {
  8. writeMessage = writeQueue.peek();
  9. if (writeMessage.getWriteBuffer() == null
  10. || !writeMessage.getWriteBuffer().hasRemaining()) {
  11. writeQueue.remove();
  12. if (writeMessage.getWriteFuture() != null) {
  13. writeMessage.getWriteFuture().setResult(Boolean.TRUE);
  14. }
  15. try {
  16. session.getHandler().onMessageSent(session,
  17. writeMessage.getMessage());
  18. } catch (Exception e) {
  19. session.onException(e);
  20. }
  21. writeMessage = writeQueue.peek();
  22. }
  23. }
  24. if (writeMessage != null) {
  25. try {
  26. session.pendingWrite(writeMessage);
  27. } catch (IOException e) {
  28. session.onException(e);
  29. session.close();
  30. }
  31. }
  32. }

@Override

public void completed(Integer result, AbstractAioSession session) {
    if (log.isDebugEnabled())

        log.debug("Session(" + session.getRemoteSocketAddress()
                + ") writen " + result + " bytes");


    WriteMessage writeMessage;

    Queue<WriteMessage> writeQueue = session.getWriteQueue();
    synchronized (writeQueue) {

        writeMessage = writeQueue.peek();
        if (writeMessage.getWriteBuffer() == null

                || !writeMessage.getWriteBuffer().hasRemaining()) {
            writeQueue.remove();

            if (writeMessage.getWriteFuture() != null) {
                writeMessage.getWriteFuture().setResult(Boolean.TRUE);

            }
            try {

                session.getHandler().onMessageSent(session,
                        writeMessage.getMessage());

            } catch (Exception e) {
                session.onException(e);

            }
            writeMessage = writeQueue.peek();

        }
    }

    if (writeMessage != null) {
        try {

            session.pendingWrite(writeMessage);
        } catch (IOException e) {

            session.onException(e);
            session.close();

        }
    }

}

compete方法中的result就是实际写入的字节数,然后我们判断消息的缓冲区是否还有剩余,如果没有就将消息从队列中移除,如果队列中还有消息,那么继续发起write调用。 重复一下,这里引用的代码都是yanf4j aio分支中的源码,感兴趣的朋友可以直接check out出来看看: http://yanf4j.googlecode.com/svn/branches/yanf4j-aio。 在引入了aio之后,java对于网络层的支持已经非常完善,该有的都有了,java也已经成为服务器开发的首选语言之一。java的弱项在于对内存的管理上,由于这一切都交给了GC,因此在高性能的网络服务器上还是Cpp的天下。java这种单一堆模型比之erlang的进程内堆模型还是有差距,很难做到高效的垃圾回收和细粒度的内存管理。 这里仅仅是介绍了aio开发的核心流程,对于一个网络框架来说,还需要考虑超时的处理、缓冲buffer的处理、业务层和网络层的切分、可扩展性、性能的可调性以及一定的通用性要求。

刚看了一点,第一行有个错别字,不是IO服用,是复用,嘿嘿 老大终于开始介绍java NIO了。。 还有一点,看过一些源代码,对事件驱动还是理解不深,也请老大介绍下把。 rain2005 写道

刚看了一点,第一行有个错别字,不是IO服用,是复用,嘿嘿 老大终于开始介绍java NIO了。。 还有一点,看过一些源代码,对事件驱动还是理解不深,也请老大介绍下把。 多谢指正,关于事件机制,我会画个UML图可能比较清晰

上面提到几个类 怎么在jdk6中没发现那 是不是要下载第三方框架 正好想研究一下 收藏啦 xly_971223 写道

上面提到几个类 怎么在jdk6中没发现那 是不是要下载第三方框架 正好想研究一下 收藏啦 aio是在jdk7引入的,请下载JDK7 preview1版本,或者open jdk

dennis_zane 写道

xly_971223 写道

上面提到几个类 怎么在jdk6中没发现那 是不是要下载第三方框架 正好想研究一下 收藏啦 aio是在jdk7引入的,请下载JDK7 preview1版本,或者open jdk 是不是这个意思。 jdk7以前的nio是非阻塞IO,操作系统底层比方说linux,是用IO复用select实现的 jdk7用的是真正的异步IO,操作系统底层是用epoll实现的 是这样的吗? rain2005 写道

dennis_zane 写道

xly_971223 写道

上面提到几个类 怎么在jdk6中没发现那 是不是要下载第三方框架 正好想研究一下 收藏啦 aio是在jdk7引入的,请下载JDK7 preview1版本,或者open jdk 是不是这个意思。 jdk7以前的nio是非阻塞IO,操作系统底层比方说linux,是用IO复用select实现的 jdk7用的是真正的异步IO,操作系统底层是用epoll实现的 是这样的吗? epoll也不是异步IO啊。异步IO在linux上目前仅限于文件系统,并且还没有得到广泛应用,很多平台都没有这玩意。 java aio在windows上是利用iocp实现的,这是真正的异步IO。而在linux上,是通过epoll模拟的。

楼主,你好,我写的server是p2p的应用,恩,想请教一下,因为我对数据库这一块的操作并不是特别的多,基本是客户自己也有很多是服务器,不知道是否可以在事件响应的当前线程来对数据库操作呢?前提是把线程池子的数目设的大些?或者用那个JDK提供的可自己增加线程的池子? 因为如果在弄个池子来处理数据库的话,担心线程太多了, 你如果时间充分心情好的话,真希望你能讲解一下和别人公用一台服务器(主机)是怎么用的呢。。。 总之要谢谢你对这段代码的讲解,kang sang mi da wujingsong 写道

楼主,你好,我写的server是p2p的应用,恩,想请教一下,因为我对数据库这一块的操作并不是特别的多,基本是客户自己也有很多是服务器,不知道是否可以在事件响应的当前线程来对数据库操作呢?前提是把线程池子的数目设的大些?或者用那个JDK提供的可自己增加线程的池子? 因为如果在弄个池子来处理数据库的话,担心线程太多了, 你如果时间充分心情好的话,真希望你能讲解一下和别人公用一台服务器(主机)是怎么用的呢。。。 总之要谢谢你对这段代码的讲解,kang sang mi da 按我的经验来说,类似数据库操作这样的IO操作,最好还是起个线程池来处理,防止阻塞框架内部的处理线程。如果这样的操作不是特别多,那么直接在响应线程处理也未尝不可,还是建议你自己搞两个版本性能对比一下。 wujingsong 写道

和别人公用一台是怎么用的呢? 我还真不明白什么意思,现在我们的应用基本都跑在虚拟机上了,几个应用跑在一个物理机上。虚拟化我不懂,就不乱弹了。

kang sa mi da,谢谢楼主的回复,确实是个很好的建议. 闲聊啊,今天无意看到Google 上一个音乐的图片链接,点进去后,看到了一个不大容易理解的词,说什么 "在南中国常年保持高收听率的极有个性的节目", 费解.....广东那边是这么叫的吗?不大可能吧..