使用Socket实现HttpServer(二)

前面我们使用 Socket 实现了一个简易的 HttpServer,接下来我们将对我们的服务器进行优化:

  • 面向对象的封装
  • 优化线程模型(引入多线程)
  • Request/Response 对象抽象

Step1(面向对象的封装)

对我们之前所写的 HttpServer 进行面向对象封装。

主要封装了 listen() 和 accept() 方法。

package com.fengsir.network;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.function.Function; /**
* @Author FengZeng
* @Date 2022-01-24 13:35
* @Description TODO
*/
public class Step1Server { ServerSocket socketServer;
Function<String, String> handler; public Step1Server(Function<String, String> handler) {
this.handler = handler;
} /**
* @param port listen port
* @throws IOException
*/
public void listen(int port) throws IOException {
socketServer = new ServerSocket(port); while (true) {
accept();
}
} private void accept() throws IOException {
Socket socket = socketServer.accept();
System.out.println("a socket created"); // 拿到 socket 的请求内容,也就是 inputStream,封装成 bufferedReader,方便读取
InputStream inputStream = socket.getInputStream();
BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream)); // 按行读取,把内容放到 stringBuilder 中
StringBuilder requestBuilder = new StringBuilder();
String line = "";
// 由于有时候 readLine() 会读取到 null,就会报错 nullException,
// 所以在这里进行了一点修改
while (true) {
line = bfReader.readLine();
if (line == null || line.isBlank()) {
break;
}
requestBuilder.append(line);
}
// 打印请求内容
String request = requestBuilder.toString();
System.out.println(request); // 封装 response
BufferedWriter bfWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String response = this.handler.apply(request);
bfWriter.write(response);
// 一定要 flush
bfWriter.flush();
socket.close();
} public static void main(String[] args) throws IOException {
Step1Server step1Server = new Step1Server(res->{
return "HTTP/1.1 200 ok\n\nGood!\n";
});
step1Server.listen(8000); } }

到这里也没有什么太大的问题,不过我们的服务器还是单线程的,如果只是单纯的返回一个字符串,我们的架构也没有问题,如果此时每个请求会去做别的处理,比如去查一下数据库,那么我们的服务器性能就很低了,还会出现服务器拒绝的错误(因为操作系统的 pending Queue 已经满了),所以我们需要进一步优化。

Step2(引入多线程)

这里引入了多线程,不再是一个线程单独在跑了,试了一下 Jemeter 的压测,10000个线程的并发就会把内存拉得很高,主要还是因为创建了线程后 Java 的垃圾回收还没有启动,所以现在一般不会用这种方式去创建线程,后面会引入线程池来优化。

package com.fengsir.network;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.function.Function; /**
* @Author FengZeng
* @Date 2022-01-24 14:01
* @Description TODO
*/
public class Step2Server { ServerSocket socketServer;
Function<String, String> handler; public Step2Server(Function<String, String> handler) {
this.handler = handler;
} /**
* @param port listen port
* @throws IOException
*/
public void listen(int port) throws IOException {
socketServer = new ServerSocket(port); while (true) {
accept();
}
} private void accept() throws IOException {
// 如果不把 accept 提到这里,那么上面的 while循环会一直创建线程
// 这也体现了 accept() 是 blocking 的
Socket socket = socketServer.accept();
System.out.println("a socket created");
new Thread(()->{
try {
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} private void handler(Socket socket) throws IOException { // 拿到 socket 的请求内容,也就是 inputStream,封装成 bufferedReader,方便读取
InputStream inputStream = socket.getInputStream();
BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream)); // 按行读取,把内容放到 stringBuilder 中
StringBuilder requestBuilder = new StringBuilder();
String line = "";
while (true) {
line = bfReader.readLine();
if (line == null || line.isBlank()) {
break;
}
requestBuilder.append(line);
}
// 打印请求内容
String request = requestBuilder.toString();
System.out.println(request); // 封装 response
BufferedWriter bfWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String response = this.handler.apply(request);
bfWriter.write(response);
// 一定要 flush
bfWriter.flush();
socket.close();
} public static void main(String[] args) throws IOException {
Step2Server step2Server = new Step2Server(res->{
return "HTTP/1.1 200 ok\n\nGood!\n";
});
step2Server.listen(8000); }
}

Step3(封装Request/Response)

由于 step2 中,我们使用的是 Function 对象来实现的请求和响应,我们进一步抽象封装,创建 IHandlerInterface 接口:

package com.fengsir.network.step3;

import java.io.IOException;

/**
* @Author FengZeng
* @Date 2022-01-24 14:26
* @Description TODO
*/
@FunctionalInterface
public interface IHandlerInterface {
/**
* 处理响应
* @param request request
* @param response response
* @throws IOException
*/
void handler(Request request, Response response) throws IOException;
}

然后我们创建 Request 类:

package com.fengsir.network.step3;

import org.apache.commons.httpclient.HttpParser;

import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* @Author FengZeng
* @Date 2022-01-24 14:27
* @Description TODO
*/
public class Request {
// 使用正则表达式提取请求的方法
static Pattern methodRegex = Pattern.compile("(GET|PUT|POST|DELETE|OPTIONS|TARCE|HEAD)"); private final String body;
private final String method;
private final HashMap<String, String> headers; public String getBody() {
return body;
} public String getMethod() {
return method;
} public HashMap<String, String> getHeaders() {
return headers;
} public Request(Socket socket) throws IOException { // 这是 DataInputStream 和 InputStream 的区别
// DataInputStream -> primitives(char,float)
// InputStream -> bytes
DataInputStream iptStream = new DataInputStream(socket.getInputStream());
BufferedReader bfReader = new BufferedReader(new InputStreamReader(iptStream)); // 从第一行读取请求的方法,HttpParser 是我引入的 commons-httpClient 包中的对象
String methodLine = HttpParser.readLine(iptStream,"UTF-8");
Matcher matcher = methodRegex.matcher(methodLine);
matcher.find();
String method = matcher.group(); // 解析请求头
// Content-Type: xxxx
var headers = HttpParser.parseHeaders(iptStream, "UTF-8");
HashMap<String, String> headMap = new HashMap<>();
for (var h : headers) {
headMap.put(h.getName(), h.getValue());
} // 解析请求体
var bufferReader = new BufferedReader(new InputStreamReader(iptStream));
var body = new StringBuilder(); char[] buffer = new char[1024];
while (iptStream.available() > 0) {
bufferReader.read(buffer);
body.append(buffer);
} this.body = body.toString();
this.method = method;
this.headers = headMap; }
}

创建 Response 类:

package com.fengsir.network.step3;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.HashMap; /**
* @Author FengZeng
* @Date 2022-01-24 14:27
* @Description TODO
*/
public class Response {
Socket socket;
private int status;
static HashMap<Integer, String> codeMap;
public Response(Socket socket) {
this.socket = socket;
if (codeMap == null) {
codeMap = new HashMap<>();
codeMap.put(200, "ok");
}
} /**
* http 标准响应
* @param msg message
* @throws IOException
*/
public void send(String msg) throws IOException {
this.status = 200;
var resp = "HTTP/1.1 " + this.status + " " + codeMap.get(this.status) + "\n";
resp += "\n";
resp += msg;
this.sendRaw(resp);
} /**
* 发送原始的响应
* @param msg message
* @throws IOException
*/
public void sendRaw(String msg) throws IOException {
var bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write(msg);
bufferedWriter.flush();
bufferedWriter.close();
} }

到此,我们再重构一下主函数:

package com.fengsir.network.step3;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
* @Author FengZeng
* @Date 2022-01-24 14:28
* @Description TODO
*/
public class Step3Server {
ServerSocket serverSocket;
IHandlerInterface httpHandler; public Step3Server(IHandlerInterface httpHandler) {
this.httpHandler = httpHandler;
} public void listen(int port) throws IOException {
serverSocket = new ServerSocket(port);
while (true) {
this.accept();
}
} private void accept() throws IOException {
Socket socket = serverSocket.accept();
new Thread(() -> {
try {
this.handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} private void handler(Socket socket) throws IOException {
Request request = new Request(socket);
Response response = new Response(socket);
this.httpHandler.handler(request, response);
} public static void main(String[] args) throws IOException {
var server = new Step3Server((req,resp)->{
System.out.println(req.getHeaders());
resp.send("Greetings\n");
});
server.listen(8000); } }

可以看到,我们现在的主函数已经很简短了,其实大多数的框架都是从这么一个过程过来的,所以抽象,封装,重构能力是很重要的,优秀的程序员在coding的时候,都会去往这几个方面去想,下一章我打算引入NIO继续优化我们的 Http Server

使用Socket实现HttpServer(二)的更多相关文章

  1. Linux学习之socket编程(二)

    Linux学习之socket编程(二) 1.C/S模型——UDP UDP处理模型 由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实 ...

  2. C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装

    原文:C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 1.SocketAsyncEventArgs介绍 SocketAsyncEventArgs是微软提供的高性能 ...

  3. 使用Socket实现HttpServer(三)

    使用Socket实现HttpServer(三) 这一章继续对我们的服务器进行优化,引入 NIO package com.fengsir.network.step4; import java.io.IO ...

  4. 使用Socket实现HttpServer(一)

    使用Socket实现HttpServer(一) Socket 编程 socket 翻译过来叫插槽,一张图你就明白 socket 就插在 TCP 也就是传输层上,对用户的请求和服务器的响应进行处理. 下 ...

  5. Python基础篇【第8篇】: Socket编程(二)SocketServer

    SocketServer 在上一篇文章中我们学习了利用socket模块创建socket通信服务,但细心学习后就会发现利用socket模块创建的服务无法进行多进程的处理,当需要进行大量请求处理时,请求就 ...

  6. C# Socket学习笔记二

    小记:昨天咱们已经了解了Socket的通信原理,可是点对点的一次通信并不是我们想要的,那么今天那我们就继续学习异步通信,简单来说就是服务器端和客户端可以进行多次 互发信息的通信而不用担心通道会关闭.在 ...

  7. Python 基础之socket编程(二)

    Python 基础之socket编程(二) 昨天只是对socket编程做了简单的介绍,只是把socket通信的框架搭建起来,要对其中的功能进行进一步的扩充,就来看看今天的料哈! 一.基于tcp的套接字 ...

  8. 网络编程基础【day09】:简单socket实例(二)

    本节内容 1.概述 2.socket实例 3.总结 一.概述 之前我们只是介绍了soket的概念和一些逻辑图表,下面我们来看看,socket的客户端和服务端到底是怎么用的? 二.socket实例 2. ...

  9. socket编程之二:两种链接类型tcp和udp

    前面一篇文章说到了一些计算机网络的基础知识.引入了socket.从这节開始,就进入正题了. 一 概述 TCP:Transimission Control Protocol传输控制协议. UPD:Use ...

随机推荐

  1. nginx反向代理配置(conf文件中的nginx)

    ########### 每个指令必须有分号结束.##################user administrator administrators;  #配置用户或者组,默认为nobody nob ...

  2. AC+AP组网无线WiFi网速超慢延迟卡顿问题解决

    AP是什么? AP是Access Point的简称,即无线接入点,其作用是把局域网里通过双绞线传输的有线信号(即电信号)经过编译,转换成无线电信号传递给电脑.手机等无线终端,与此同时,又把这些无线终端 ...

  3. 《Shader入门精要》中MVP变换的Projection矩阵与《GAMES101图形学入门》中的区别

    game101的透视投影的投影矩阵是这样的 正交投影是这样的 而shader入门精要的透视投影矩阵是这样子 正交投影矩阵是这样子 game101的透视投影是这样得到的 而正交投影的时候并没有假设中心点 ...

  4. 面向服务开发(SOA)

    面向服务的体系结构是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台.操作系统和编程语言 ...

  5. linux下yum无法安装lrzsz,Error: Failed to download metadata for repo ‘appstream‘: Cannot prepare internal

    镜像下载.域名解析.时间同步请点击阿里云开源镜像站 linux虚拟机上准备安装一下rz sz,执行yum命令后提示如下: [root@tony001 ~]# yum install lrzsz Cen ...

  6. 运营商的三大数据域——B域,O域,M域

    O域(运营域).B域(业务域).M域(管理域)特指电信行业大数据领域的三大数据域. B域(业务域)= business support system的数据域, O域(运营域)= operation s ...

  7. 提升组件库通用能力 - NutUI 在线主题定制功能探索

    开发背景 NutUI 作为京东风格的组件库,已具备 H5 和多端小程序开发能力.随着业务的不断发展,组件库的应用场景越来越广.在公司内外面临诸如科技.金融.物流等各多个大型团队使用时,单一的京东 AP ...

  8. 台式机ATX电源:各接口定义、启动方法、电源特点

    ATX,英文全称:Advanced Technology Extended,是一种由Intel公司在1995年公布的PC机主板结构规范. ATX电源作用是把交流220V的电源转换为计算机内部使用的直流 ...

  9. Flutter入门教程(一)Flutter简介

    这是Flutter系列第一篇文章,后续会持续更新Flutter相关知识,本篇就主要对于Flutter技术做一个简单的入门介绍 一.Flutter简介 Flutter是谷歌的移动UI框架,可以快速在iO ...

  10. Could not find the main class

    最近开发了一个短信报警的服务,打成程序包之后,再本地windows启动(start.bat)没有问题,但是发到生产环境,报如下错: Could not find the main class 莫名其妙 ...