`

JAVA使用Socket编写HTTP服务器

    博客分类:
  • Java
阅读更多
HTTP服务器核心就是Socket短连接
先简单说概念:
1、socket就是TCP/IP实现的套接字,就是应用层调用下层服务的接口。

2、短连接指的是连接建立后,双方进行数据交互(通常是一个数据包,也可以是多个),交互完毕后立即关闭连接的TCP/IP实现方式就是常说的短连接,最常见的短连接例子就是HTTP协议。

3、长连接则指的是双方交互完毕后,不关闭连接,而让连接一直空闲着等待下一次交互,这样在一次交互前就免去了再重新建立连接的消耗,本机测试一次 socket连接需要耗时23毫秒。

优点就是性能好。缺点有二,一是实现方式比较复杂,需要单独用线程收,发倒是无所谓;二是需要增加链路检测的机制,由于连接在空闲时双方都无法确认对端是否出现异常退出,因为根据TCP/IP,如果连接的一方正常退出,对端都会收到一个信号,而当一方异常退出时,对端是无法收到信号的,这时就会出现 connection reset、connection reset by peer和broken pipe异常。

接下来说说JAVA如何实现短连接。一、先来看发送到方法,这个比较简单。直接获得socket的OutputStream流,然后用write方法,flush方法即可。这里要说明的就是,之前认为使用flush方法在底层就是一个TCP包,其实不然,上层何时按照上面策略封装TCP包上层根本无法知道,经过测试可知,下层封装的TCP包大小与flush无必然联系。这里有个参数可以设置,就是sock.setTcpNoDelay(true),如果设置为true,则对于缓冲区不进行拼接,立即发送。这里涉及nagle算法,用于解决小封包问题,感兴趣的朋友可以自己搜索

热身运动
/**
 * 单个文件的HTTP Server,输入本机地址,返回d:/index.html文件
 */
public class SingleFileHttpServer extends Thread {

  private byte[] content;
  private byte[] header;
  private int port;

  public SingleFileHttpServer(byte[] data, String encoding, String MIMEType) throws UnsupportedEncodingException {
    this.content = data;
    this.port = 80;
    String header = "HTTP/1.0 200 OK\r\n" + "Server: OneFile 1.0 \r\n" 
        + "Content-length: " + this.content.length
        + "\r\n" + "Content-type: " + MIMEType + "\r\n";
    this.header = header.getBytes(encoding);
  }

  public void run() {
    ServerSocket server;
    try {
      server = new ServerSocket(port);
      System.out.println("Accepting connections on port " + server.getLocalPort());
      System.out.println("Data to be sent:");
      System.out.write(this.content);
      while (true) {
        Socket conn = null;
        try {
          conn = server.accept();
          OutputStream out = conn.getOutputStream();
          InputStream in = conn.getInputStream();
          int temp;
          StringBuilder request = new StringBuilder();
          if ((temp = in.read()) != -1)
            request.append((char) temp);
          if (request.toString().indexOf("HTTP/") != -1)
            out.write(header);
          out.write(content);
          out.flush();
        } catch (IOException e) {
          throw new RuntimeException(e);
        } finally {
          conn.close();
        }
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public static void main(String[] args) {
    try {
      String encoding = "ASCII";
      String fileName = "D:/index.html";
      String contentType = "text/plain";
      if (fileName.endsWith("html") || fileName.endsWith("htm"))
        contentType = "text/html";
      InputStream in = new FileInputStream(new File(fileName));
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      int temp;
      while ((temp = in.read()) != -1)
        baos.write(temp);
      byte[] data = baos.toByteArray();
      SingleFileHttpServer server = 
          new SingleFileHttpServer(data, encoding, contentType);
      server.start();
    } catch (FileNotFoundException e) {
    } catch (IOException e) {
    }
  }
}


/**
 * 跳转地址,输入本机地址,自动跳转到sina
 */
public class Redirector implements Runnable {

  private int port;
  private String siteUrl;

  public Redirector(int port, String siteUrl) {
    this.port = port;
    this.siteUrl = siteUrl;
  }

  public void run() {
    try {
      ServerSocket server = new ServerSocket(port);
      while (true) {
        Socket conn = server.accept();
        Thread t = new RedirectThread(conn);
        t.start();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  private class RedirectThread extends Thread {

    private Socket conn;

    public RedirectThread(Socket conn) {
      this.conn = conn;
    }

    public void run() {
      try {
        Writer out = new OutputStreamWriter(conn.getOutputStream());
        Reader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        StringBuilder sb = new StringBuilder();
        int temp;
        while (true) {
          temp = in.read();
          if (temp == '\r' || temp == '\n' || temp == -1)
            break;
          sb.append((char) temp);
        }
        String request = sb.toString();
        int firstSpace = request.indexOf(' ');
        int secondSpace = request.indexOf(' ', firstSpace + 1);
        String theFile = request.substring(firstSpace + 1, secondSpace);
        if (request.indexOf("HTTP") != -1) {
          // 这是一个HTTP响应码,告知客户端要被重定向
          out.write("HTTP/1.0 302 FOUND\r\n");
          // 服务器当前时间
          out.write("Date: " + new Date() + "\r\n");
          // 服务器的名称和版本【可选的】
          out.write("Server: Redirector 1.0\r\n");
          // 告知要重定向的位置,浏览器会自动跳转
          out.write("Location: " + siteUrl + theFile + "\r\n");
          // 指示客户端看到的是HTML,发送一个空行表明结束
          out.write("Content-type: text/html\r\n\r\n");
          out.flush();
        }
        // 有些老浏览器,不支持redirection,我们需要生成HTML说明
        out.write("<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>\r\n");
        out.write("<BODY><H1>Document moved</H1>\r\n");
        out.write("The document " + theFile + " has moved to\r\n<A HREF=\"" 
            + siteUrl + theFile + "\">" + siteUrl + theFile 
            + "</A>.\r\n Please update your bookmarks<P>");
        out.write("</BODY></HTML>\r\n");
        out.flush();
      } catch (IOException e) {
        throw new RuntimeException(e);
      } finally {
        if (conn != null)
          try {
            conn.close();
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
      }
    }
  }

  public static void main(String[] args) {
    int port = 80;
    String siteUrl = "http://www.sina.com.cn";
    Thread server = new Thread(new Redirector(port, siteUrl));
    server.start();
  }
}


核心开始
/**
 * Java版 HTTP服务器
 */
public class JHTTPServer extends Thread {

  private File docRootDir;
  private String indexFileName;
  private ServerSocket server;
  private int numThread = 50;

  public JHTTPServer(File docRootDir) throws IOException {
    this(docRootDir, 80, "index.html");
  }

  public JHTTPServer(File docRootDir, int port, String indexFileName) 
      throws IOException {
    this.docRootDir = docRootDir;
    this.indexFileName = indexFileName;
    server = new ServerSocket(port);
  }

  public void run() {
    for (int i = 0; i < numThread; i++) {
      Thread t = new Thread(new RequestProcessor(docRootDir, indexFileName));
      t.start();
    }
    System.out.println("Accepting connections on port " + server.getLocalPort());
    System.out.println("Document Root: " + docRootDir);
    while(true){
      try {
        Socket conn = server.accept();
        RequestProcessor.processRequest(conn);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }

  public static void main(String[] args) {
    File docRoot = new File("D:/src/HTML_CSS");
    try {
      new JHTTPServer(docRoot).start();
    } catch (IOException e) {
      System.out.println("Server could not start because of an " + e.getClass());
      System.out.println(e);
    }
  }
}

/**
 * 服务线程池
 */
public class RequestProcessor implements Runnable {

  private static List<Socket> pool = new LinkedList<Socket>();
  private File docRootDir;
  private String indexFileName;

  public RequestProcessor(File docRootDir, String indexFileName) {
    if (docRootDir.isFile())
      throw new IllegalArgumentException(
          "documentRootDirectory must be a directory, not a file");
    this.docRootDir = docRootDir;
    try {
      this.docRootDir = docRootDir.getCanonicalFile();
    } catch (IOException ex) {
    }
    this.indexFileName = indexFileName;
  }
  
  public static void processRequest(Socket conn) {
    synchronized (pool) {
      pool.add(pool.size(), conn);
      pool.notifyAll();
    }
  }
  
  public void run() {
    String root = docRootDir.getPath();
    while (true) {
      Socket conn;
      synchronized (pool) {
        while (pool.isEmpty()) {
          try {
            pool.wait();
          } catch (InterruptedException e) {
            throw new RuntimeException(e);
          }
        }
        conn = pool.remove(0);
      }
      try {
        OutputStream raw = new BufferedOutputStream(conn.getOutputStream());
        Writer out = new OutputStreamWriter(raw);
        Reader in = new InputStreamReader(new BufferedInputStream(conn.getInputStream()));
        StringBuilder request = new StringBuilder();
        int c;
        while (true) {
          c = in.read();
          if (c == '\r' || c == '\n')
            break;
          request.append((char) c);
        }
        String get = request.toString();
        // 记录请求日志
        System.out.println(get);
        StringTokenizer st = new StringTokenizer(get);
        String method = st.nextToken();
        String version = "", fileName, contentType;
        if (method.equals("GET")) {
          fileName = st.nextToken();
          if (fileName.endsWith("/"))
            fileName += indexFileName;
          contentType = guessContentTypeFromName(fileName);
          if (st.hasMoreTokens())
            version = st.nextToken();
          File theFile = new File(docRootDir, fileName.substring(1, fileName.length()));
          // 不让请求超出文档根目录
          if (theFile.canRead() && theFile.getCanonicalPath().startsWith(root)) {
            DataInputStream dis = new DataInputStream(
                new BufferedInputStream(new FileInputStream(theFile)));
            byte[] theData = new byte[(int) theFile.length()];
            dis.readFully(theData);
            dis.close();
            if (version.startsWith("HTTP ")) {
              out.write("HTTP/1.0 200 OK\r\n");
              out.write("Date" + new Date() + "\r\n");
              out.write("Server: JHTTP/1.0\r\n");
              out.write("Content-length: " + theData.length + "\r\n");
              out.write("Content-type: " + contentType + "\r\n\r\n");
              out.flush();
            }
            // 发送文件,可能是图片或其它二进制数据,所以使用底层的输出流不是书写器
            raw.write(theData);
            raw.flush();
          } else { // 没有找到文件
            if (version.startsWith("HTTP ")) { // send a MIME header
              out.write("HTTP/1.0 404 File Not Found\r\n");
              Date now = new Date();
              out.write("Date: " + now + "\r\n");
              out.write("Server: JHTTP/1.0\r\n");
              out.write("Content-type: text/html\r\n\r\n");
            }
            out.write("<HTML>\r\n");
            out.write("<HEAD><TITLE>File Not Found</TITLE>\r\n");
            out.write("</HEAD>\r\n");
            out.write("<BODY>");
            out.write("<H1>HTTP Error 404: File Not Found</H1>\r\n");
            out.write("</BODY></HTML>\r\n");
            out.flush();
          }
        } else { // method does not equal "GET"
          if (version.startsWith("HTTP ")) { // send a MIME header
            out.write("HTTP/1.0 501 Not Implemented\r\n");
            Date now = new Date();
            out.write("Date: " + now + "\r\n");
            out.write("Server: JHTTP 1.0\r\n");
            out.write("Content-type: text/html\r\n\r\n");
          }
          out.write("<HTML>\r\n");
          out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
          out.write("</HEAD>\r\n");
          out.write("<BODY>");
          out.write("<H1>HTTP Error 501: Not Implemented</H1>\r\n");
          out.write("</BODY></HTML>\r\n");
          out.flush();
        }
      } catch (IOException e) {
        throw new RuntimeException(e);
      } finally {
        try {
          conn.close();
        } catch (IOException ex) {}
      }
    }
  }

  private String guessContentTypeFromName(String name) {
    if (name.endsWith(".html") || name.endsWith(".htm")) {
      return "text/html";
    } else if (name.endsWith(".txt") || name.endsWith(".java")) {
      return "text/plain";
    } else if (name.endsWith(".gif")) {
      return "image/gif";
    } else if (name.endsWith(".class")) {
      return "application/octet-stream";
    } else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
      return "image/jpeg";
    } else
      return "text/plain";
  }
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics