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服务器的更多相关文章

  1. Python超简单的HTTP服务器

    Python超简单的HTTP服务器 安装了python就可以 python -m SimpleHTTPServer 执行这一个命令即可实现一个HTTP服务器,将当前目录设为HTTP服务目录,可以通过h ...

  2. Asp.Net读取服务器EXE文件的方法!(超简单实例)

    Asp.Net读取服务器EXE文件的方法!(超简单实例) Process process = new Process(); process.StartInfo.FileName = "d:\ ...

  3. python超简单的web服务器

    今天无意google时看见,心里突然想说,python做web服务器,用不用这么简单啊,看来是我大惊小怪了. web1.py   1 2 3 #!/usr/bin/python import Simp ...

  4. 超简单Mac安装Tomcat服务器

    超简单Mac安装Tomcat服务器 1.首先下载tomcat 下载连接 2. 解压并改名 3.把Tomcat复制到系统中的资源库或者Library 4.打开shell,进入Tomcat下面的bin目录 ...

  5. 实现超简单的http服务器

    想在Linux下实现一个简单的web Server并不难.一个最简单的HTTP Server不过是一个高级的文件服务器,不断地接收客户端(浏览器)发送的HTTP请求,解析请求,处理请求,然后像客户端回 ...

  6. mysql主从复制(超简单)

      mysql主从复制(超简单) 怎么安装mysql数据库,这里不说了,只说它的主从复制,步骤如下: 1.主从服务器分别作以下操作:  1.1.版本一致  1.2.初始化表,并在后台启动mysql  ...

  7. 这些优化 Drupal 网站速度的超简单办法,你忽略了多少?

    “怎么样能让我的 Drupal 网站更快一些?”是我们最常遇到的一个问题.站点速度确实非常重要,因为它会影响你的 SEO排名效果.访客是否停留以及你自己管理网站所需要的时间. 今天我们就来看看那些通过 ...

  8. 运用socket实现简单的服务器客户端交互

    Socket解释: 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制,取后一种意 ...

  9. tomcat解析之简单web服务器(图)

    链接地址:http://gogole.iteye.com/blog/587163 之前有javaeyer推荐了一本书<how tomcat works>,今天晚上看了看,确实不错,第一眼就 ...

随机推荐

  1. laravel 关闭 csrf 验证 TokenMismatchException

    csrf验证失败 注释掉kernel.php 的 csrf 行代码

  2. excel打开csv格式的文件,数字末尾都变成零,解决方式

    excel打开csv格式的文件,数字末尾都变成零,解决方式

  3. understand 在windows 以及 unbuntu 下的安装

    1.win7 64位下安装 1)下载Understand.4.0.908.x64.rar. 2)解压之,直接运行里面的Understand-4.0.908-Windows-64bit.exe. 3)选 ...

  4. Delphi 的 FireDAC 连接管理与配置过程

    Delphi 的 FireDAC 连接管理与配置过程: 使用 FireDAC 技术连接 数据库,主要是使用  TFDConnection ,其中有一参数是选择  ConnectionDefFile. ...

  5. 用WMI监控IIS

    参考网站:http://blog.chinaunix.net/uid-7910284-id-5774420.html 参考官方文档:https://docs.microsoft.com/en-us/p ...

  6. Java 处理 XML

    DOM 优缺点:实现 W3C 标准,有多种编程语言支持这种解析方式,并且这种方法本身操作上简单快捷,十分易于初学者掌握.其处理方式是将 XML 整个作为类似树结构的方式读入内存中以便操作及解析,因此支 ...

  7. 制作ecc证书(linux命令行)

    生成ECC证书.Debian:/home/test# openssl ecparam -out EccCA.key -name prime256v1 -genkeyDebian:/home/test# ...

  8. docker拉取镜像报错:net/http: TLS handshake timeout.

    docker拉取镜像报错:net/http: TLS handshake timeout. 启动一个后台的busybox容器 [yunva@node1 network-scripts]$ docker ...

  9. PHP一维数组转二维数组正则表达式

    2017年11月20日17:17:08 array(1 => '哈哈')  变成  array('id' => 1, 'name' => '哈哈') 查找目标:  (\d)\s=&g ...

  10. 8-Images

    HTML Image Tags Tag Description <img> Defines an image <map> Defines an image-map <ar ...