使用Socket实现HttpServer(二)
使用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(二)的更多相关文章
- Linux学习之socket编程(二)
Linux学习之socket编程(二) 1.C/S模型——UDP UDP处理模型 由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实 ...
- C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装
原文:C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 1.SocketAsyncEventArgs介绍 SocketAsyncEventArgs是微软提供的高性能 ...
- 使用Socket实现HttpServer(三)
使用Socket实现HttpServer(三) 这一章继续对我们的服务器进行优化,引入 NIO package com.fengsir.network.step4; import java.io.IO ...
- 使用Socket实现HttpServer(一)
使用Socket实现HttpServer(一) Socket 编程 socket 翻译过来叫插槽,一张图你就明白 socket 就插在 TCP 也就是传输层上,对用户的请求和服务器的响应进行处理. 下 ...
- Python基础篇【第8篇】: Socket编程(二)SocketServer
SocketServer 在上一篇文章中我们学习了利用socket模块创建socket通信服务,但细心学习后就会发现利用socket模块创建的服务无法进行多进程的处理,当需要进行大量请求处理时,请求就 ...
- C# Socket学习笔记二
小记:昨天咱们已经了解了Socket的通信原理,可是点对点的一次通信并不是我们想要的,那么今天那我们就继续学习异步通信,简单来说就是服务器端和客户端可以进行多次 互发信息的通信而不用担心通道会关闭.在 ...
- Python 基础之socket编程(二)
Python 基础之socket编程(二) 昨天只是对socket编程做了简单的介绍,只是把socket通信的框架搭建起来,要对其中的功能进行进一步的扩充,就来看看今天的料哈! 一.基于tcp的套接字 ...
- 网络编程基础【day09】:简单socket实例(二)
本节内容 1.概述 2.socket实例 3.总结 一.概述 之前我们只是介绍了soket的概念和一些逻辑图表,下面我们来看看,socket的客户端和服务端到底是怎么用的? 二.socket实例 2. ...
- socket编程之二:两种链接类型tcp和udp
前面一篇文章说到了一些计算机网络的基础知识.引入了socket.从这节開始,就进入正题了. 一 概述 TCP:Transimission Control Protocol传输控制协议. UPD:Use ...
随机推荐
- nginx反向代理配置(conf文件中的nginx)
########### 每个指令必须有分号结束.##################user administrator administrators; #配置用户或者组,默认为nobody nob ...
- AC+AP组网无线WiFi网速超慢延迟卡顿问题解决
AP是什么? AP是Access Point的简称,即无线接入点,其作用是把局域网里通过双绞线传输的有线信号(即电信号)经过编译,转换成无线电信号传递给电脑.手机等无线终端,与此同时,又把这些无线终端 ...
- 《Shader入门精要》中MVP变换的Projection矩阵与《GAMES101图形学入门》中的区别
game101的透视投影的投影矩阵是这样的 正交投影是这样的 而shader入门精要的透视投影矩阵是这样子 正交投影矩阵是这样子 game101的透视投影是这样得到的 而正交投影的时候并没有假设中心点 ...
- 面向服务开发(SOA)
面向服务的体系结构是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台.操作系统和编程语言 ...
- linux下yum无法安装lrzsz,Error: Failed to download metadata for repo ‘appstream‘: Cannot prepare internal
镜像下载.域名解析.时间同步请点击阿里云开源镜像站 linux虚拟机上准备安装一下rz sz,执行yum命令后提示如下: [root@tony001 ~]# yum install lrzsz Cen ...
- 运营商的三大数据域——B域,O域,M域
O域(运营域).B域(业务域).M域(管理域)特指电信行业大数据领域的三大数据域. B域(业务域)= business support system的数据域, O域(运营域)= operation s ...
- 提升组件库通用能力 - NutUI 在线主题定制功能探索
开发背景 NutUI 作为京东风格的组件库,已具备 H5 和多端小程序开发能力.随着业务的不断发展,组件库的应用场景越来越广.在公司内外面临诸如科技.金融.物流等各多个大型团队使用时,单一的京东 AP ...
- 台式机ATX电源:各接口定义、启动方法、电源特点
ATX,英文全称:Advanced Technology Extended,是一种由Intel公司在1995年公布的PC机主板结构规范. ATX电源作用是把交流220V的电源转换为计算机内部使用的直流 ...
- Flutter入门教程(一)Flutter简介
这是Flutter系列第一篇文章,后续会持续更新Flutter相关知识,本篇就主要对于Flutter技术做一个简单的入门介绍 一.Flutter简介 Flutter是谷歌的移动UI框架,可以快速在iO ...
- Could not find the main class
最近开发了一个短信报警的服务,打成程序包之后,再本地windows启动(start.bat)没有问题,但是发到生产环境,报如下错: Could not find the main class 莫名其妙 ...