用 Java 实现一个简单的多线程 web 服务器
1. 整体思路
- 主线程
- 建立一个ServerSocket
- 调用ServerSocket的accept方法。该方法一直阻塞,等待连接。如果连接建立,就会返回一个Socket对象。
- 生成一个子线程处理Socket。
- 执行步骤2。
- 子线程
- 从Socket获得输入流,读入请求报文,找出请求资源的路径。
- 从Socket获得输出流,响应请求的资源(资源存在) 或 返回错误页面(资源不存在)。
2. 源码
- Tomtiger类(主线程)
package tomtiger;
/**
* @author liuleilei liuleilei2015@gmail.com
* @date 2017年11月20日 下午4:52:55
* @Description:the new Web Server Tomtiger, haha!
*/
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.LinkedList;
public class Tomtiger {
//定义html页面等的存放位置
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "WEB_ROOT";
//定义web服务器占用的端口号
public static final int port = 8080;
//定义一个列表,存放 为每个连接的socket建立的线程。
private List<Thread> connectlist = null;
//main方法,服务器开始启动
public static void main(String[] args) {
//webserver 开始启动
Tomtiger server = new Tomtiger();
server.start();
}
public void start() {
ServerSocket serversocket = null;
try {
//server 监听127.0.0.1:8080
serversocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
//server 已经启动
System.out.println("Tomtiger is running!!");
}catch(IOException e) {
e.printStackTrace();
}
//用来记录求情的数量
int count = 0;
//各线程存放的列表
connectlist = new LinkedList<Thread>();
while(true) {
Socket socket = null;
try {
//为该请求建立连接
socket = serversocket.accept();
System.out.println("连接"+ count +"以建立!!");
//为该socket建立多线程,启动,并加入列表
ConnectionThread connectionthread = new ConnectionThread(socket);
Thread thread = new Thread(connectionthread);
thread.start();
connectlist.add(thread);
System.out.println("连接"+ count++ +"的线程已启动并加入队列!!");
}catch(Exception e) {
e.printStackTrace();
break;
}
}
}
}
- ConnectionThread类(子线程)
package tomtiger;
/**
* @author liuleilei liuleilei2015@gmail.com
* @date 2017年11月20日 下午2:46:53
* @Description: implement the thread for every connection
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.io.IOException;
public class ConnectionThread implements Runnable{
Socket socket = null;
InputStream inputstream = null;
OutputStream outputstream = null;
// 根据socket初始化socket的多线程类
public ConnectionThread(Socket socket) {
this.socket = socket;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
// TODO Auto-generated method stub
try {
// 获得该连接的输入输出流
inputstream = socket.getInputStream();
outputstream = socket.getOutputStream();
//为该连接建立request,request从inputstream中读入http请求报文,转化为字符串,并截取到请求的资源。
Request request = new Request(inputstream);
//从request中获得请求资源的uri
String uri = request.getUri();
// 为请求建立response,response根据传入的outputstream,和uri。建立请求资源文件到outputstream之间的通路,并相应相应的资源。若资源不存在,响应默认页面
Response response = new Response(outputstream);
// response响应请求的资源
response.responseResource(uri);
}catch(IOException e) {
e.printStackTrace();
}finally {
}
}
}
- Request类(处理Socket的输入流)
package tomtiger;
/**
* @author liuleilei liuleilei2015@gmail.com
* @date 2017年11月20日 下午4:00:14
* @Description: implement the request for connection to get the uri
*/
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Request {
private InputStream inputstream = null;
private String uri = null;
// 根据inputstream初始化request
public Request(InputStream inputstream) {
this.inputstream = inputstream;
parseUri();
}
// 读取inputstream并将其转化为字符串
@SuppressWarnings("finally")
private String requestToString() {
String requestString = null;
// 字节流转字符流
BufferedReader bfreader = new BufferedReader(new InputStreamReader(inputstream));
StringBuffer buffer = new StringBuffer();
// 这里我开始用的常用的while进行读取,但是一直卡在这里(不理解)!!!!
char[] temp = new char[2048];
int length = 0;
try {
length = bfreader.read(temp);
buffer.append(temp,0,length);
requestString = buffer.toString();
}catch(IOException e) {
e.printStackTrace();
}finally {
// 输出request
System.out.println("request为:");
System.out.println(requestString);
return requestString;
}
}
// 根据请求报文的特点,请求的文件在第一个和第二个空格之间。所以有了以下方法
public void parseUri() {
String request = requestToString();
if(request != null) {
int space1 = -1;
int space2 = -1;
space1 = request.indexOf(' ');
if(space1 != -1) {
space2 = request.indexOf(' ',space1 + 1);
}
if(space2 > space1) {
// 截取第一个和第二个空格之间的字符串,即请求资源的uri
uri = request.substring(space1 + 1, space2);
}
}
}
// 返回请求资源的uri
public String getUri() {
return uri;
}
}
- Response类(处理Socket的输出流)
package tomtiger;
/**
* @author liuleilei liuleilei2015@gmail.com
* @date 2017年11月20日 下午4:37:49
* @Description: implement the response of connection
*/
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
public class Response {
// 定义读取文件时byte[] 数组的大小
private static final int BUFFER_SIZE = 1024;
// 用来响应的outputstream,该输出流从 为该请求建立的socket中获得,并传入
OutputStream outputstream = null;
// 用outputstream初始化response
public Response(OutputStream outputstream) {
this.outputstream = outputstream;
}
// 该请求资源的文件路径
public void responseResource(String uri) {
// 读取文件时的byte[]
byte[] resourcetemp = new byte[BUFFER_SIZE];
// 输入流
FileInputStream fileinputstream = null;
if(uri == null)
return;
try {
// 建立文件
File resource = new File(Tomtiger.WEB_ROOT,uri);
// 判断文件是否存在
if(resource.exists()) {
System.out.println("请求的资源是:" + resource.getName());
fileinputstream = new FileInputStream(resource);
int length = 0;
String responsehead = "HTTP/1.1 200 OK\r\n" + "\r\n";
outputstream.write(responsehead.getBytes());
while((length = fileinputstream.read(resourcetemp)) > 0) {
outputstream.write(resourcetemp, 0, length);
}
}
else {
String errorPage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>";
outputstream.write(errorPage.getBytes());
}
outputstream.close();
}catch(IOException e){
e.printStackTrace();
}finally {
outputstream = null;
fileinputstream = null;
}
}
}
3. 执行
- 控制台
- 浏览器