极致简单
通过ServerSock代理一个端口号, 在接收到请求的时候, 建立一个Sock实例(TCP连接), 然后往这个sock实例里面根据所采用的应用程报文格式写入数据. 这里我们使用的应用层协议是HTTP.
WEBROOT是供外界访问文件的所在目录. PORT端口号
原理很简单, 直接看代码就好了.
package com.server.simpleHttpServer;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleHttpServer {
private final int PORT;
private final String WEBROOT;
public static final String RIGHT_HEADER = "HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=UTF-8\r\nConnection: close\r\n\r\n";
public static final String ERROR_HEADER = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html;charset=UTF-8\r\nConnection: Close\r\n\r\n";
public static final String NOTFOUND_CONTENT = "<html><head><title>Blast</title></head> <body> Sorry, 404 Not Found </body> </html>";
private final ServerSocket server;
public SimpleHttpServer(int PORT, String WEBROOT) throws IOException {
this.PORT = PORT;
this.WEBROOT = WEBROOT;
server = new ServerSocket(PORT);
}
public void start() throws IOException {
while (true) {
Socket socket = server.accept();
System.out.println("得到一个来自 " + socket.getRemoteSocketAddress() + " 的连接");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String bf;
StringBuilder sb = new StringBuilder();
while (br.ready() && (bf = br.readLine()) != null) {
sb.append(bf);
}
System.out.println("req " + sb.toString());
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
try {
String filePath = getUri(sb.toString());
if (filePath == null) {
bw.write(ERROR_HEADER);
bw.write(NOTFOUND_CONTENT);
} else {
String content = getFile(filePath);
bw.write(RIGHT_HEADER);
bw.write(content);
}
} catch (IOException | StringIndexOutOfBoundsException e) {
e.printStackTrace();
bw.write(ERROR_HEADER);
bw.write(NOTFOUND_CONTENT);
}
bw.flush();
br.close();
bw.close();
socket.close();
}
}
private String getUri(String firstLine) {
if (firstLine == null || firstLine.length() == 0) {
return null;
}
int i = firstLine.indexOf(" ");
int j = firstLine.indexOf(" ", i + 1);
return firstLine.substring(i + 1, j);
}
private String getFile(String filePath) throws IOException {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(WEBROOT + filePath)))) {
StringBuilder sb = new StringBuilder();
String buf = null;
while ((buf = br.readLine()) != null) {
sb.append(buf);
}
br.close();
return sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new FileNotFoundException();
}
}
}
其实在写这个的时候还是碰到了一个大坑的.
起初, 我用bufferedReader读的时候. 是这样的.
while ((bf = br.readLine()) != null) {
sb.append(bf);
}
然后, 在处理一个进来的连接的时候, 都会阻塞在这里一会. 当时debug了好长时间, 竟然没发现, 哭了….
然后在stackovreflow上找到了解决方案. st地址:https://stackoverflow.com/questions/15521352/bufferedreader-readline-blocks
然后加上了ready()
方法, ready()
方法确保在输入流准备好了之后, 才会返回true
, 所以写在readline()
方法前, 快速失败, 避免阻塞线程.
public class Main {
public static void main(String[] args) throws IOException {
sStart();
}
private static void sStart() throws IOException {
SimpleHttpServer server = new SimpleHttpServer(8080, "C:\\Users\\youngxinler\\Desktop");
server.start();
}
}
运行一下.
访问我放在桌面的这个index.html
<html>
<head>
<title>HTTP-Server</title>
</head>
<body>
welcome to blast!
</body>
</html>
当然, 这个同时处理一个连接, 如果一个连接处理时间过程很长, 后续连接就会被阻塞, 很影响响应度.
下面给出多线程版本.
多线程版本
一个主线程来接受sock连接, 然后再生成新的线程进行处理.
负责处理的线程
package com.server.multiBlockHttpServer;
import java.io.*;
import java.net.Socket;
public class ConnectionHandler extends Thread {
static String webRoot;
private Socket s;
public static final String RIGHT_HEADER = "HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=UTF-8\r\nConnection: close\r\n\r\n";
public static final String ERROR_HEADER = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html;charset=UTF-8\r\nConnection: Close\r\n\r\n";
public static final String NOTFOUND_CONTENT = "<html><head><title>Blast</title></head> <body> Sorry, 404 Not Found </body> </html>";
public ConnectionHandler(Socket s) {
this.s = s;
}
@Override
public void run() {
while (!s.isClosed()) {
try {
BufferedReader br = getBufReader(s);
BufferedWriter bw = getBufWriter(s);
String buf;
StringBuilder sb = new StringBuilder();
while (br.ready() && (buf = br.readLine()) != null) {
sb.append(buf);
}
System.out.println("req " + sb.toString());
try {
String filePath = getUri(sb.toString());
if (filePath == null) {
bw.write(ERROR_HEADER);
bw.write(NOTFOUND_CONTENT);
} else {
String content = getFile(filePath);
bw.write(RIGHT_HEADER);
bw.write(content);
}
} catch (IOException | StringIndexOutOfBoundsException e) {
e.printStackTrace();
bw.write(ERROR_HEADER);
bw.write(NOTFOUND_CONTENT);
}
bw.flush();
br.close();
bw.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String getUri(String firstLine) {
if (firstLine == null || firstLine.length() == 0) {
return null;
}
int i = firstLine.indexOf(" ");
int j = firstLine.indexOf(" ", i + 1);
return firstLine.substring(i + 1, j);
}
private String getFile(String filePath) throws IOException {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(webRoot + filePath)))) {
StringBuilder sb = new StringBuilder();
String buf = null;
while ((buf = br.readLine()) != null) {
sb.append(buf);
}
br.close();
return sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new FileNotFoundException();
}
}
private BufferedReader getBufReader(Socket s) throws IOException {
return new BufferedReader(new InputStreamReader(s.getInputStream()));
}
private BufferedWriter getBufWriter(Socket s) throws IOException {
return new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
}
}
server类
package com.server.multiBlockHttpServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiBlockHttpServer {
private ExecutorService executorService;
private ServerSocket serverSocket;
private final String WEBROOT;
public MultiBlockHttpServer(int port, int nThread, String webRoot) throws IOException {
executorService = Executors.newFixedThreadPool(nThread);
serverSocket = new ServerSocket(port);
this.WEBROOT = webRoot;
}
public void start() throws IOException {
System.out.println("Start blast!");
ConnectionHandler.webRoot = WEBROOT;
while (true) {
Socket s = serverSocket.accept();
System.out.println("来自 " + s.getRemoteSocketAddress() + " 的连接");
executorService.submit(new ConnectionHandler(s));
}
}
}
这样, 主线程负责接收连接, 然后就不用管它了. 剩下的交给新的线程来处理.
但是这里也是有极大性能问题的, 因为我们可以即使开出来很多的线程, 但是线程切换上下文是非常耗时, 而且大量的连接, 可能会生成很多线程, 一个线程大概占用1M内存, 所以, 在高并发下, 可能会直接OutOfMemoryError.
但是像nginx确是单线程性能却高的鸭皮, 这里就涉及到Nio了.
等我有时间再撸一个Nio版本的出来.