ServerSocket实现超简单HTTP服务器
1、相关知识简介
HTTP协议
HTTP是常用的应用层协议之一,是面向文本的协议。HTTP报文传输基于TCP协议,TCP协议包含头部与数据部分,而HTTP则是包含在TCP协议的数据部分,如下图
HTTP报文本质上是一个TCP报文,数据部分携带的内容为HTTP报文,HTTP报文多数情况下是一串文本,当然也可能携带二进制信息。
HTTP报文
HTTP报文包含头部和请求体,请求体内容可为空。请求头与请求体用单独的空行分隔,即”\r\n”。HTTP头部结构如下:
当报文为请求报文时,第一行信息为 {方法} {URI} {HTTP版本}
方法通常为GET, POST,URI为URL后面携带的参数信息,HTTP版本表示当前使用的HTTP版本。
当报文为响应报文时,第一行的信息为 {HTTP版本} 状态
HTTP版本同上,下面是部分常见的状态码
| 状态码 | 英文名称 | 含义 |
|---|---|---|
| 200 | OK | 请求成功 |
| 304 | Not Modified | 所请求的资源未修改 |
| 400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
| 403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
| 404 | Not Found | 服务器无法根据客户端的请求找到资源(网页),常说的404错误就是指这个 |
| 405 | Method Not Allowed | 客户端请求中的方法被禁止 |
| 502 | Bad Gateway | 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 |
报文从第二行开始均为 {字段名}: {字段值} 的格式。字段名通常是英文字母与”-“的组合,有常用的几个,有时也可以使用自定义字段名,值得注意的是字段名最好不要包含空格,虽然我Postman上模拟没问题,但在Chrome上试解析会出问题。
HTTP报文的请求体就是一段数据,没有严格的格式限制,较为随意,但如果在头部声明Content-Type为Multipart/form-data后就会有一定的格式规范,具体可以看看我之前写的一篇文章
http://blog.csdn.net/kurozaki_kun/article/details/78646960
Socket
Socket是对TCP/IP的封装,为程序员提供了面向传输层及以上层的编程。Java中关于Socket的类主要是Socket,DatagramSocket,ServerSocket,还有NIO对应的类,这里实现主要基于前三者。Socket能够建立端到端的同通信。其实总结一句话,就是使用Socket能够帮助程序员传输TCP/UDP报文。
2、基于Socket实现简单的HTTP服务器
ServerSocket监听端口
ServerSocket用于监听特定端口,调用accept()方法会阻塞当前线程,直到接收到一个Socket,而我们需要处理所接收到的Socket。下面先写出一个大致的框架
class ServerListeningThread extends Thread {
private int bindPort;
private ServerSocket serverSocket;
public ServerListeningThread(int port) {
this.bindPort = port;
}
@Override
public void run() {
try {
serverSocket = new ServerSocket(bindPort);
while (true) {
Socket rcvSocket = serverSocket.accept();
//单独写一个类,处理接收的Socket,类的定义在下面
HttpRequestHandler request = new HttpRequestHandler(rcvSocket);
request.handle();
rcvSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//最后要确保以下把ServerSocket关闭掉
if (serverSocket != null && !serverSocket.isClosed()) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class HttpRequestHandler {
private Socket socket;
public HttpRequestHandler(Socket socket) {
this.socket = socket;
}
public void handle() throws IOException {
//TODO 这里写处理接收到的socket的逻辑
}
}
回送简单的HTTP报文
接下来的关注点应该在如何处理Socket上,先从最简单的开始做起,不管socket里的是什么,都一律只回复一个响应报文,上面的handle()方法处理应该如下
class HttpRequestHandler {
private Socket socket;
public HttpRequestHandler(Socket socket) {
this.socket = socket;
}
public void handle() throws IOException {
socket.getOutputStream().
write(("HTTP/1.1 200 OK\r\n" + //响应头第一行
"Content-Type: text/html; charset=utf-8\r\n" + //简单放一个头部信息
"\r\n" + //这个空行是来分隔请求头与请求体的
"<h1>这是响应报文</h1>\r\n").getBytes());
}
}
然后来试试效果,在main函数调用一下,这里监听8888端口
public static void main(String[] args) {
new ServerListeningThread(8888).start();
}
用浏览器打开 127.0.0.1:8888 或 localhost:8888,能够显示下面结果
可以见到刚才通过socket回送的响应报文被浏览器成解析了,红色箭头位置是自己添加的头部信息。
读取请求并回送
一个HTTP请求真正处理起来还是比较繁琐的,这里只介绍下简单的情景,例如请求报文带有POST参数,先读取socket的数据,并控制台输出一下HTTP请求的报文是什么样的
class HttpRequestHandler {
//此处代码省略
public void handle() throws IOException {
//获取输入流,读取数据
StringBuilder builder = new StringBuilder();
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
char[] charBuf = new char[1024];
int mark;
while ((mark = isr.read(charBuf)) != -1) {
builder.append(charBuf, 0, mark);
if (mark < charBuf.length) {
break;
}
}
System.out.println(builder.toString());
socket.getOutputStream().
write(("HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"\r\n" +
"<h1>这是响应报文</h1>\r\n").getBytes());
}
}
使用postman向8888端口发送一个携带POST参数的HTTP请求,如下
控制台输出结果为
其中三个提交的参数在body的表现形式为 参数名=值,多个参数用&连接成字符串,该字符串占一行。下面可以使用字符串操作将这些信息解析出来,并且将解析结果回送回去。
class HttpRequestHandler {
//此处代码省略...
public void handle() throws IOException {
StringBuilder builder = new StringBuilder();
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
char[] charBuf = new char[1024];
int mark = -1;
while ((mark = isr.read(charBuf)) != -1) {
builder.append(charBuf, 0, mark);
if (mark < charBuf.length) {
break;
}
}
if (mark == -1) {
return;
}
Map<String, String> headers = new HashMap<>();
Map<String, String> parameters = new HashMap<>();
String[] splits = builder.toString().split("\r\n");
int index = 1;
//处理header
while (splits[index].length() > 0) {
String[] keyVal = splits[index].split(":");
headers.put(keyVal[0], keyVal[1].trim());
index++;
}
String body = splits[index + 1];
String[] bodySplits = body.split("&");
//处理body的参数
for (String str : bodySplits) {
String[] param = str.split("=");
parameters.put(param[0], param[1]);
}
String respStr = "头部信息\r\n";
for (Map.Entry<String, String> entry : headers.entrySet()) {
respStr += "名称: " + entry.getKey() + ", 值: " + entry.getValue() + "<br/>";
}
respStr += "\r\nbody信息\r\n";
for (Map.Entry<String, String> entry : parameters.entrySet()) {
respStr += "名称: " + entry.getKey() + ", 值: " + entry.getValue() + "<br/>";
}
socket.getOutputStream().
write(("HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"\r\n" +
"<h1>这是响应报文</h1>\r\n" + respStr).getBytes());
}
}
使用POST方法带参访问8888端口,其返回结果如下
在这基础上,还可以根据提交参数查询数据库等等操作,一个成熟的服务器实际上已经封装好了如上的解析步骤,然后监听主机的80端口(即HTTP默认端口),真正实现一个服务器要处理的情况远比这里讲述的多,例如处理文件传输等等。
小结
这里主要使用ServerSocket和Socket来实现,实际上还可以使用NIO的ServerSocketChannel和SocketChannel。服务器处理请求的步骤通常就是 监听端口->收到请求->处理->响应请求,中间的处理会有多层的步骤。
ServerSocket实现超简单HTTP服务器的更多相关文章
- Python超简单的HTTP服务器
Python超简单的HTTP服务器 安装了python就可以 python -m SimpleHTTPServer 执行这一个命令即可实现一个HTTP服务器,将当前目录设为HTTP服务目录,可以通过h ...
- Asp.Net读取服务器EXE文件的方法!(超简单实例)
Asp.Net读取服务器EXE文件的方法!(超简单实例) Process process = new Process(); process.StartInfo.FileName = "d:\ ...
- python超简单的web服务器
今天无意google时看见,心里突然想说,python做web服务器,用不用这么简单啊,看来是我大惊小怪了. web1.py 1 2 3 #!/usr/bin/python import Simp ...
- 超简单Mac安装Tomcat服务器
超简单Mac安装Tomcat服务器 1.首先下载tomcat 下载连接 2. 解压并改名 3.把Tomcat复制到系统中的资源库或者Library 4.打开shell,进入Tomcat下面的bin目录 ...
- 实现超简单的http服务器
想在Linux下实现一个简单的web Server并不难.一个最简单的HTTP Server不过是一个高级的文件服务器,不断地接收客户端(浏览器)发送的HTTP请求,解析请求,处理请求,然后像客户端回 ...
- mysql主从复制(超简单)
mysql主从复制(超简单) 怎么安装mysql数据库,这里不说了,只说它的主从复制,步骤如下: 1.主从服务器分别作以下操作: 1.1.版本一致 1.2.初始化表,并在后台启动mysql ...
- 这些优化 Drupal 网站速度的超简单办法,你忽略了多少?
“怎么样能让我的 Drupal 网站更快一些?”是我们最常遇到的一个问题.站点速度确实非常重要,因为它会影响你的 SEO排名效果.访客是否停留以及你自己管理网站所需要的时间. 今天我们就来看看那些通过 ...
- 运用socket实现简单的服务器客户端交互
Socket解释: 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制,取后一种意 ...
- tomcat解析之简单web服务器(图)
链接地址:http://gogole.iteye.com/blog/587163 之前有javaeyer推荐了一本书<how tomcat works>,今天晚上看了看,确实不错,第一眼就 ...
随机推荐
- srping mvc 集成CXF 导致类初始化两遍
cxf依赖于spring的ContextLoaderListener,而spring mvc 则依赖于DispatcherServlet. 初始化DispatcherServlet的时候会依赖初始化一 ...
- 获取异步API数据
异步操作应该是以前学习 ajax 时才被明确提及,就目前的理解,同步就是同一时间只能做一件事,如果使用 ajax同步模式,则代码会卡在 xhr.send() 这里,只有请求响应的过程全部完成了才会执行 ...
- [机器学习] k近邻算法
算是机器学习中最简单的算法了,顾名思义是看k个近邻的类别,测试点的类别判断为k近邻里某一类点最多的,少数服从多数,要点摘录: 1. 关键参数:k值 && 距离计算方式 &&am ...
- .NET中制做对象的副本(一)
.NET中对于复杂对象制作副本比较困难,闲暇之时写了这个方法,和大家分享. 本案例用于大型对象的副本制作,常见的应用场景就是树形对象节点的拷贝,但是也有局限性,目前使用于类里有基本类型(int sti ...
- 10分钟搭建Kubernetes容器集群平台【转】
官方提供3种方式部署Kubernetes minikube Minikube是一个工具,可以在本地快速运行一个单点的Kubernetes,尝试Kubernetes或日常开发的用户使用.不能用于生产环境 ...
- addEventListener() 方法,事件监听
知识点1:addEventListener() 方法,事件监听,可以使用 removeEventListener() 方法来移除事件的监听. 语法 element.addEventListener(e ...
- git与eclipse集成之文件回退
1.1. 文件回退 1.1.1. 添加或修改文件回退,选择要回退的文件,右键Overwrite 1.1.2. 删除文件回退 选择要回退的文件,右键Overwrite 文件变 ...
- 数字证书及CA的扫盲介绍
★ 先说一个通俗的例子 考虑到证书体系的相关知识比较枯燥.晦涩.俺先拿一个通俗的例子来说事儿. ◇ 普通的介绍信 想必大伙儿都听说过介绍信的例子吧?假设 A 公司的张三先生要到 B 公司去拜访,但是 ...
- LightOJ1004
#include<bits/stdc++.h> using namespace std; int Map[106][106]; int Vis[106][106]; int Num[106 ...
- 【原创】大数据基础之Logstash(1)简介、安装、使用
Logstash 6.6.2 官方:https://www.elastic.co/products/logstash 一 简介 Centralize, Transform & Stash Yo ...