使用Socket与ServerSocket来构建客户端与服务器通信的桥梁,那个时候的架构是这样的:
Server端负责既负责与客户端连接又负责处理请求并返回处理结果;
下面是一个简单的Demo,麻雀虽小,五脏俱全,简单实现了Tomcat的主要功能:
"GET /index.html HTTP/1.1 Host: localhost:8080 Connection:Close"意为请求服务器ContextPath下相对路径为 "/index.html" 的这个资源:
/**
* @CreatedBy:成江灿
* @Date:2019/12/22/15:24
* @Description:Socket通信客户端
*/
public class Client {
public static void main(String[] args) {
Socket socket = new Socket("127.0.0.1", 8080){
OutputStream os = socket.getOutputStream();
PrintWriter out = new PrintWriter(os, true);
/*向服务器发送一个HTTP1.1的请求*/
out.println("GET /index.html HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection Close");
out.println();
/*读取服务器端响应的信息,这里用了带缓冲的Reader*/
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuffer stringBuffer = new StringBuffer(8096);
//一直等待服务器端的响应(NIO通过事件机制和Selector解决了这个问题)
boolean loop = true;
while(loop){
if(in.ready()){
int i = 0;
//输入流读完后返回的值为-1
while(i != -1){
i = in.read();
stringBuffer.append((char)i);
}
loop = false;
}
Thread.currentThread().sleep(50L);
}
}
}
}
复制代码
服务器端的主要作用是根据请求流中的信息生成相应的Request对象,然后根据Request对象拿到URL信息并通过Response将对应URL的资源写回给客户端; ps:因为传统的JavaIO会卡在accept()方法下,为防止线程负荷增高,所以这里使用了线程池。
/**
* @Description:Socket通信服务器端
*/
public class Server {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
/*用于标识服务器是否正在运行*/
private static boolean shutdown = false;
private static BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
private static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 100L, TimeUnit.MINUTES, workQueue);
public static void main(String[] args) {
ServerSocket serverSocket81 = null;
final ServerSocket serverSocket80 = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
Runnable runnableTask80 = new Runnable() {
@Override
public void run() {
while(!shutdown){
Socket socket = null;
InputStream inputStream = null;
OutputStream outputStream = null;
/*serverSocket建立连接后使用socket传值*/
socket = serverSocket80.accept();
inputStream = socket.getInputStream();
/*根据传入的请求流构建Request对象*/
Request request = new Request(inputStream);
request.parse();
/*创建Response对象用于返回*/
Response response = new Response(outputStream);
response.setRequest(request);
response.sendStaticResource();
/*关闭Socket*/
socket.close();
}
}
};
executorService.execute(runnableTask80);
}
}
复制代码
主要负责从请求流中获取URL;
/**
* @CreatedBy:成江灿
* @Date:2019/12/22/16:17
* @Description:将Socket中传入的请求流转为请求对象
*/
public class Request {
private InputStream inputStream;
private String uri;
public Request(InputStream inputStream){
this.inputStream = inputStream;
}
public void parse(){
/*从输入流中获取请求信息*/
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try{
/*将输入流写入到缓冲区*/
i = inputStream.read(buffer);
}
catch (IOException e){
e.printStackTrace();
i = -1;
}
for(int j=0; j<i; j++){
request.append((char)buffer[j]);
}
uri = parseUri(request.toString());
}
private String parseUri(String requestString){
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
/*取出HTTP请求的第一行中涉及到URL的第二部分*/
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public String getUri(){
return uri;
}
}
复制代码
/**
* @CreatedBy:成江灿
* @Date:2019/12/22/16:28
* @Description:主要作用是将请求的文件返回
*/
public class Response {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(System.getProperty("user.dir") + File.separator + "webroot", request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
String errorMessage = "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>";
output.write(errorMessage.getBytes());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
复制代码
可以注意到,上面编写的服务端只能接受HTTP协议的请求并相应结果,而且上述程序是高度耦合的, 即连接的建立与请求的处理是耦合在服务器端的 ,所以一旦协议发生改变,那么连接的规则也会发生改变,那么服务器的代码也要发生改变,也许你会觉得重新复制一遍代码新建一个服务器端程序不就好了吗? 但你是否想过也许他们的请求处理程序可以是一样的吗,即处理程序它只是通过Request对象来获取信息的,所以我们是不是只需要改变一下处理请求流的规则便可以了呢?
所以我们需要将请求的连接与请求的处理在服务器端解耦合!!新版的架构图如下:
其中:(1)Connector负责协议负责各种协议连接的建立和Request和Response对象的构建;
(2)Engine则负责通过Request和Response对象对请求进行处理;
(3)Service则用于管理多个Connector与其相对应的Engine,因为基于不同的协议的请求可能会共用同一套处理程序的嘛!
写一篇博客真的要好久,写出来自己还是感觉很多地方没有说清,下次再开另一篇讲讲Engine吧~