简单的web server性能测试

    最近一直在读《java并发编程实践》,书是绝对的好书,翻译不能说差,也谈不上好,特别是第一部分的前面几章,有的地方翻译的南辕北辙了,还是要对照着英文版来看。我关注并发编程是从学习Erlang开始的,在多核来临的时代,有人说并发将是下一个10年的关键技术。java5之前的多线程编程很复杂,况且我也没有从事此类应用的开发,了解不多,而从jdk5引入了让人流口水的concurrent包之后,java的并发编程开始变的有趣起来。
   书中第6章以编写一个web server为例子,引出了几种不同版本的写法:单线程、多线程以及采用jdk5提供的线程池实现。我就用apache自带的ab工具测试了下各个版本的性能,在redhat9 p4 2g内存的机器上进行了测试。

ab -n 50000 -c 1000 http://localhost/index.html >benchmark

单线程模式,顺序性地处理每一个请求,50000并发很快就没有响应了,不参与比较了。再来看看我们自己写的多线程方式处理每个请求:

package net.rubyeye.concurrency.chapter6;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ThreadPerTaskWebServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(80);
        while (true) {
            final Socket connection = server.accept();
            Runnable task = new Runnable() {
                public void run() {
                    try {
                        handleRequest(connection);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            };
            new Thread(task).start();
        }
    }

    public static void handleRequest(Socket socket) throws IOException {
        try {
            InetAddress client = socket.getInetAddress();
            // and print it to gui
            s(client.getHostName() + " connected to server.\n");
            // Read the http request from the client from the socket interface
            // into a buffer.
            BufferedReader input = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            // Prepare a outputstream from us to the client,
            // this will be used sending back our response
            // (header + requested file) to the client.
            DataOutputStream output = new DataOutputStream(socket
                    .getOutputStream());

            // as the name suggest this method handles the http request, see
            // further down.
            // abstraction rules
            http_handler(input, output);
            socket.close();
        } catch (Exception e) { // catch any errors, and print them
            s("\nError:" + e.getMessage());
        }

    } // go back in loop, wait for next request

    // our implementation of the hypertext transfer protocol
    // its very basic and stripped down
    private static void http_handler(BufferedReader input,
            DataOutputStream output) {
        int method = 0; // 1 get, 2 head, 0 not supported
        String http = new String(); // a bunch of strings to hold
        String path = new String(); // the various things, what http v, what
        // path,
        String file = new String(); // what file
        String user_agent = new String(); // what user_agent
        try {
            // This is the two types of request we can handle
            // GET /index.html HTTP/1.0
            // HEAD /index.html HTTP/1.0
            String tmp = input.readLine(); // read from the stream
            String tmp2 = new String(tmp);
            tmp.toUpperCase(); // convert it to uppercase
            if (tmp.startsWith("GET")) { // compare it is it GET
                method = 1;
            } // if we set it to method 1
            if (tmp.startsWith("HEAD")) { // same here is it HEAD
                method = 2;
            } // set method to 2

            if (method == 0) { // not supported
                try {
                    output.writeBytes(construct_http_header(501, 0));
                    output.close();
                    return;
                } catch (Exception e3) { // if some error happened catch it
                    s("error:" + e3.getMessage());
                } // and display error
            }
            // }

            // tmp contains "GET /index.html HTTP/1.0 ."
            // find first space
            // find next space
            // copy whats between minus slash, then you get "index.html"
            // it's a bit of dirty code, but bear with me
            int start = 0;
            int end = 0;
            for (int a = 0; a < tmp2.length(); a++) {
                if (tmp2.charAt(a) == ' ' && start != 0) {
                    end = a;
                    break;
                }
                if (tmp2.charAt(a) == ' ' && start == 0) {
                    start = a;
                }
            }
            path = tmp2.substring(start + 2, end); // fill in the path
        } catch (Exception e) {
            s("errorr" + e.getMessage());
        } // catch any exception

        // path do now have the filename to what to the file it wants to open
        s("\nClient requested:" + new File(path).getAbsolutePath() + "\n");
        FileInputStream requestedfile = null;

        try {
            // NOTE that there are several security consideration when passing
            // the untrusted string "path" to FileInputStream.
            // You can access all files the current user has read access to!!!
            // current user is the user running the javaprogram.
            // you can do this by passing "../" in the url or specify absoulute
            // path
            // or change drive (win)

            // try to open the file,
            requestedfile = new FileInputStream(path);
        } catch (Exception e) {
            try {
                // if you could not open the file send a 404
                output.writeBytes(construct_http_header(404, 0));
                // close the stream
                output.close();
            } catch (Exception e2) {
            }
            ;
            s("error" + e.getMessage());
        } // print error to gui

        // happy day scenario
        try {
            int type_is = 0;
            // find out what the filename ends with,
            // so you can construct a the right content type
            if (path.endsWith(".zip") || path.endsWith(".exe")
                    || path.endsWith(".tar")) {
                type_is = 3;
            }
            if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
                type_is = 1;
            }
            if (path.endsWith(".gif")) {
                type_is = 2;
                // write out the header, 200 ->everything is ok we are all
                // happy.
            }
            output.writeBytes(construct_http_header(200, 5));

            // if it was a HEAD request, we don't print any BODY
            if (method == 1) { // 1 is GET 2 is head and skips the body
                while (true) {
                    // read the file from filestream, and print out through the
                    // client-outputstream on a byte per byte base.
                    int b = requestedfile.read();
                    if (b == -1) {
                        break; // end of file
                    }
                    output.write(b);
                }

            }
            // clean up the files, close open handles
            output.close();
            requestedfile.close();
        }

        catch (Exception e) {
        }

    }

    private static void s(String s) {
    //    System.out.println(s);
    }

    // this method makes the HTTP header for the response
    // the headers job is to tell the browser the result of the request
    // among if it was successful or not.
    private static String construct_http_header(int return_code, int file_type) {
        String s = "HTTP/1.0 ";
        // you probably have seen these if you have been surfing the web a while
        switch (return_code) {
        case 200:
            s = s + "200 OK";
            break;
        case 400:
            s = s + "400 Bad Request";
            break;
        case 403:
            s = s + "403 Forbidden";
            break;
        case 404:
            s = s + "404 Not Found";
            break;
        case 500:
            s = s + "500 Internal Server Error";
            break;
        case 501:
            s = s + "501 Not Implemented";
            break;
        }

        s = s + "\r\n"; // other header fields,
        s = s + "Connection: close\r\n"; // we can't handle persistent
        // connections
        s = s + "Server: SimpleHTTPtutorial v0\r\n"; // server name

        // Construct the right Content-Type for the header.
        // This is so the browser knows what to do with the
        // file, you may know the browser dosen't look on the file
        // extension, it is the servers job to let the browser know
        // what kind of file is being transmitted. You may have experienced
        // if the server is miss configured it may result in
        // pictures displayed as text!
        switch (file_type) {
        // plenty of types for you to fill in
        case 0:
            break;
        case 1:
            s = s + "Content-Type: image/jpeg\r\n";
            break;
        case 2:
            s = s + "Content-Type: image/gif\r\n";
        case 3:
            s = s + "Content-Type: application/x-zip-compressed\r\n";
        default:
            s = s + "Content-Type: text/html\r\n";
            break;
        }

        // //so on and so on
        s = s + "\r\n"; // this marks the end of the httpheader
        // and the start of the body
        // ok return our newly created header!
        return s;
    }
}

测试结果如下:
Concurrency Level:      1000
Time taken for tests:   111.869356 seconds
Complete requests:      50000
Failed requests:        0
Write errors:           0
Total transferred:      4950000 bytes
HTML transferred:       250000 bytes
Requests per second:    446.95 [#/sec] (mean)
Time per request:       2237.387 [ms] (mean)
Time per request:       2.237 [ms] (mean, across all concurrent requests)
Transfer rate:          43.20 [Kbytes/sec] received

修改下上面的程序,采用jdk5提供的线程池:

    private static final int NTHREADS = 5;

    private static Executor exec;

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(80);
        if (args.length == 0)
            exec = Executors.newFixedThreadPool(NTHREADS);
        else
            exec = Executors.newFixedThreadPool(Integer.parseInt(args[0]));
        while (true) {
            final Socket connection = server.accept();
            Runnable task = new Runnable() {
                public void run() {
                    try {
                        handleRequest(connection);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            };
            exec.execute(task);
        }
    }

默认线程池大小取5,后经过反复测试,线程池大小在5左右,测试结果达到最佳。测试采用线程池的结果如下:

Concurrency Level:      1000
Time taken for tests:   51.648142 seconds
Complete requests:      50000
Failed requests:        0
Write errors:           0
Total transferred:      4978908 bytes
HTML transferred:       251460 bytes
Requests per second:    968.09 [#/sec] (mean)
Time per request:       1032.963 [ms] (mean)
Time per request:       1.033 [ms] (mean, across all concurrent requests)
Transfer rate:          94.14 [Kbytes/sec] received

与上面结果一比较,牛人写的线程池终究是大大不一样。当连接数增加到10W以上,两个版本之间的性能差异就更明显了。这里采用的是固定线程池,如果采用缓冲线程池会怎么样呢?newFixedThreadPool改为newCachedThreadPool方法,测试可以发现结果与固定线程池的最佳结果相似。CachedThreadPool更适合此处短连接、高并发的场景。后来,我想Erlang写一个简单的web server,性能上会不会超过采用线程池的这个版本呢?试试:

%% httpd.erl - MicroHttpd 
-module(httpd).
-export([start/0,start/1,start/2,process/2]).
-import(regexp,[split/2]). 
-define(defPort,80). 
-define(docRoot,"."). 
start() -> start(?defPort,?docRoot).
start(Port) -> start(Port,?docRoot). 
start(Port,DocRoot) -> 
      case gen_tcp:listen(Port, [binary,{packet, 0},{active, false}]) of 
          {ok, LSock}     -> 
               server_loop(LSock,DocRoot);   
          {error, Reason}     -> 
              exit({Port,Reason}) 
      end.
      %% main server loop - wait for next connection, spawn child to process it
      server_loop(LSock,DocRoot) ->   
          case gen_tcp:accept(LSock) of   
                    {ok, Sock}     ->  
                          spawn(?MODULE,process,[Sock,DocRoot]),  
                          server_loop(LSock,DocRoot);    
                  {error, Reason}     ->    
          exit({accept,Reason})  
  end.
  %% process current connection
process(Sock,DocRoot) ->  
      Req = do_recv(Sock),  
      {ok,[Cmd|[Name|[Vers|_]]]} = split(Req,"[ \r\n]"),  
      FileName = DocRoot ++ Name, 
      LogReq = Cmd ++ " " ++ Name ++ " " ++ Vers, 
      Resp = case file:read_file(FileName) of  
                {ok, Data}     ->    
                     io:format("~p ~p ok~n",[LogReq,FileName]), 
                    Data;   
                {error, Reason}     ->   
                     io:format("~p ~p failed ~p~n",[LogReq,FileName,Reason]),   
                   error_response(LogReq,file:format_error(Reason))  
         end, 
        do_send(Sock,Resp),
        gen_tcp:close(Sock). 
        %% construct HTML for failure message 
error_response(LogReq,Reason) ->  
  "<html><head><title>Request Failed</title></head><body>\n" ++
      "<h1>Request Failed</h1>\n" ++ 
      "Your request to " ++ LogReq ++ 
    " failed due to: " ++ Reason ++  "\n</body></html>\n"
.
      %% send a line of text to the 
do_send(Sock,Msg) ->  
      case gen_tcp:send(Sock, Msg) of  
      ok        ->
          ok;  
      {error, Reason}     -> 
          exit(Reason)  
  end. 
          %% receive data from the socket
do_recv(Sock) ->  
      case gen_tcp:recv(Sock, 0) of    
           {ok, Bin}     -> 
                  binary_to_list(Bin);   
           {error, closed}     -> 
                  exit(closed);    
           {error, Reason}     -> 
                  exit(Reason)  
  end.

执行:

 erl -noshell +P 5000 -s httpd start

+P参数是将系统允许创建的process数目增加到50000,默认是3万多。测试结果:

Concurrency Level:      1000
Time taken for tests:   106.35735 seconds
Complete requests:      50000
Failed requests:        0
Write errors:           0
Total transferred:      250000 bytes
HTML transferred:       0 bytes
Requests per second:    471.54 [#/sec] (mean)
Time per request:       2120.715 [ms] (mean)
Time per request:       2.121 [ms] (mean, across all concurrent requests)
Transfer rate:          2.30 [Kbytes/sec] received
    结果让人大失所望,这个结果与我们自己写的多线程java版本差不多,与采用线程池的版本就差多了,减少并发的话,倒是比java版本的快点。侧面验证了这个讨论的结论:erlang的优势就是高并发而非高性能。当然,这三者都比不上C语言写的多线程web server。测试了unix/linux编程实践中的例子,速度是远远超过前三者,不过支持的并发有限,因为系统创建的线程在超过5000时就崩溃了。如果采用jdk5进行开发,应当充分利用新的并发包,可惜我们公司还停留在1.4。
文章转自庄周梦蝶  ,原文发布时间 2007-08-29

时间: 2024-11-03 22:02:20

简单的web server性能测试的相关文章

Web服务性能测试:Node完胜Java

简介 我最近做了一些简单的关于内存的Web Service性能测试.我使用Java(REST + SOAP)和Node.js(REST)将一些接口功能缓存起来.跟期望的一样,Node应用的性能远远超出Java.(响应时间至少快1倍以上). 译者注*  NodeJS跟许多其他单线程语言一样,对内存并不贪婪,因为没有关于线程的内存开销,内存占用不会随着连接数的增长而增长,尤其在剔除掉读写文件/数 据库等异步操作后,完全基于内存的NodeJS将有更显著的性能提升,从某种意义上来说基于内存的nodejs

在Java ME平台中使用Subversion、NetBeans IDE和Sun Java System Web Server

问题 源代码管理(SCM)产品用于管理多个版本的项目文件,允许您保存新版本.恢复较旧版本以及在不同版本之间进行比较.当您在修改项目的过程中引入了bug,这将是一个非常有价值的功能.大多数较新源代码管理(SCM)产品除了可管理各文件的版本之外,还提供了在 Bell 实验室中开发的源代码控制系统(SCCS). 即使对于小型项目,每一位开发人员也应该使用源代码管理(SCM).小型项目转变成大型项目是必然的事情,虽然从工作上说仅仅需要修复错误和添加功能.跟踪和管理软件的能力是获得成功的关键因素. 应用程

Delphi开发Web Server程序返回图像的方法

Internet/Intranet在九十年代可能是最流行的计算机术语了,不管是计算机行业内的人士还是计算机外的人士,都会使用Internet/Intranet,有的查资料,有的是宣传自己和公司,甚至有许多以前从没有想到用Internet的东西现在也在用Internet来解决, 比如有的程控数字计算机维护用Internet来解决.在这一切应用之中,基于Web Server的应用程序的开发是基本点,但如何开发Web Server的程序呢? Delphi 3是Borland公司1997年推出的可视化.

安全配置和维护Apache WEB Server

apache|server|web|安全 前言:在目前的Internet时代,主页已成为树立公司形象和展示自我天地的一个重要手段,配置一台强大且安全的Web Server就显得尤其重要.在众多的Web Server产品中,Apache是应用最为广泛的一个产品, 同时也是一个设计上非常安全的程序.但是,同其它应用程序一样,Apache也存在安全缺陷.本文将详细介绍如何正确配置和维护Apache WEB Server的安全性问题等. 一.Apache服务器的介绍 Apache服务器它是Interne

用C#制作一个最简单的Web Service

web 下面,我们看看如何建立和部署一个最简单的Web服务 建立Web服务 1.在wwwroot目录下建立一个叫做Webservice的目录. 2.建立下面这样一个文件: <%@ WebService Language="c#" Class="AddNumbers"%> using System;using System.Web.Services;public class AddNumbers : WebService{[WebMethod]public

如何用node.js实现一个简单的web服务器

node.js实现web服务器还是比较简单的,我了解node.js是从<node入门>开始的,如果你不了解node.js也可以看看! 我根据那书一步一步的练习完了,也的确大概了解了node.js,不过里面写的路由的地方总感觉不方便,十一放假最后一天,试着写了个简单的web服务器,现在分享记录于此! http模块已提供了基本功能,所以我主要解决两个问题,1是静态资源的处理,2是动态资源的路由. 静态资源在node.js里的意思是不变的,如图片.前端js.css.html页面等. 动态资源我们一般

使用NetBeans IDE创建并运行一个简单的web应用程序

在本教程中,您将使用 NetBeans IDE 创建并运行一个简单的 web 应用程序,Hello Web.本示例应用程序要求您输入一个名字,之后使用那个名字显示一条信息.首先,您需要使用一个输入框来实现这个页面.之后您使用一个用户可以选择名字的下拉列表来替换那个输入框.下拉列表中输入的名字来自数据库表. 本教程需要以下技术以及资源的支持 JavaServer Faces 组件/Java EE 平台 1.2 with Java EE 5*1.1 with J2EE 1.4 Travel 数据库

02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python

02-Twisted 构建 Web Server 的 Socket 长链接问题 郑昀 201005 隶属于<07.杂项> 背景 利用几句简单代码可以构建一个 Web Server: from twisted.internet import reactor from twisted.web.server import Site from my_webhandler import * reactor.listenTCP(8080, Site(MyWebResource.setup())) 更复杂的

在Zeus Web Server中安装PHP语言支持_php基础

前言Zeus是一个运行于Unix下的非常优秀的Web Server,而PHP则是Unix下一个非常优秀的后台脚本语言. 这两个产品都是为非常喜欢的产品.为什么要写这样的一个Howto呢?是因为有大量的网站脚本是使用PHP开发的, 而这些程序运行在Zeus下也是一个非常好的选择.写这份文档的目的在于能让大家的PHP系统良好的运行于Zeus服务器上. 很早的时候我写过一份整合Zeus和PHP的文章,它主要是讲如何将PHP以FastCGI的本地调用方式来运行于Zeus中的, 本份Howto主要是来讲如