欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

简单模拟webserver

程序员文章站 2022-06-09 17:55:38
...

准备知识

本章节将按照以下内容阐述

一、网络中进程之间是如何通信

二、两种通信协议:TCP、UDP

三、什么是Socket

四、模拟一个webserver

五、请求与响应

六、代码

网络中进程之间是如何通信?

通信嘛,我理解的就是两个进程之间互相收发数据,就像打电话一样。那么进程之间是如何通信的呢,可以参考博客https://www.cnblogs.com/CheeseZH/p/5264465.html

要实现进程通信,首先解决的问题就是如何唯一标识一个进程,否则通信无从谈起,我得知道你的电话号码才能给你打电话吧!那么在本地,可以通过进程PID来标识一个进程(PID熟悉吧,linux下可以使用kill -9 PID 来杀死一个进程),而在网络中如何标识一个进程呢,TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX  BSD的套接字(套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。)来实现网络进程之间的通信。

两种通信协议:TCP、UDP

TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket(理解为电话的一端)之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。

UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。(与本主题无关,了解即可)

什么是Socket?

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,又称"套接字"。其实我理解的套接字,就是电话的一端。

Socket通讯的过程

Server端监听某个端口是否有连接请求,Client端向Server端发出连接请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。

ServerSocket(服务端)编程步骤

1) 创建套接字,将套接字绑定到一个本地地址和端口上

2) 将套接字设置为监听模式,准备接受客户请求

3) 等客户请求到来时,接受连接请求,返回一个新的对应此连接的套接字,启动线程为当前的连接服务。

4) 返回,等另一个客户请求

5) 关闭套接字

6) Socket客户端编程步骤

7) 1.创建套接字

8) 2.向服务器发起请求

9) 和服务器进行通信

10) 关闭套接字

创建Socket

java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。服务端就是一个ServerSocket,这里,我们主要讲ServerSocket

ServerSocket的构造函数有:

a. ServerSocket()throws IOException

b. ServerSocket(int port)throws IOException

c. ServerSocket(int port, int backlog)throws IOException

d. ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException

参数说明:

a. port - 端口号

b. backlog - 指的是最大等待队列长度,超过这个数量的客户端链接过来就不会让它等待而是直接拒绝了

c. bindAddr -用于绑定服务器IP,InetAddress用来描述主机地址

注意:

如果端口被占用或者没有权限使用某些端口会抛出BindException错误。譬如1~1023的端口需要管理员才拥有权限绑定。

如果设置端口为0,则系统会自动为其分配一个端口;

backlog参数将覆盖操作系统限定的队列的最大长度. 值得注意的是, 在以下几种情况中, 仍然会采用操作系统限定的队列的最大长度:

a. backlog 参数的值大于操作系统限定的队列的最大长度,

b. backlog 参数的值小于或等于0,

c. 在ServerSocket 构造方法中没有设置 backlog 参数。

常用方法:

accept()

侦听并接受到此套接字的连接。

getInetAddress()

返回此服务器套接字的本地地址。

模拟一个webserver

web server原理很简单,在服务器上运行一个ServerSocket程序,这个程序可以监控端口。比如有一个服务器的IP是10.10.40.12,其中的一个程序myserver监控的是8080端口,当浏览器发送一个请求时,这个请求里包含了所需要的页面信息index,(如: http://10.10.40.12:8080/index),请求传到服务器,myserver程序会接受这个请求,并解析请求,(因为请求就是IO流,所以可以用JAVA中的IO操作来解析),解析后,找到请求所需要的页面,页面其实就是一个文件,用JAVA的文件操作,读出这个页面,然后通过Socket写到客户端,就可以了。

简单来说访问网站的过程是这个:用户在浏览器输入一个地址,摁回车之后,就会向服务器发送一个请求,告诉服务器它想要一个页面,服务器找到这个页面,然后给浏览器发送回来。

请求与响应

请求,当客户端发起请求时,我的理解就是客户端向浏览器发送了个字符串,这个字符串包含了一些信息,比如:

  POST /examples/default.jsp HTTP/1.0

  Accept: text/plain;text/html

  Accept-Language: en-gb

  Connection:Keep-Alive

  Host: localhost

  User-Agent: Mozilla/4.0(compatible;MSIE:4.01;Widows 98)

  Content-Length: 33

  Content-Type:application/x-www-form-urlencoded

  Accept-Encodding:gzip,deflate

  lastName=Franks&firstName=Michael

第一行是请求方法 请求资源路径 协议/协议版本,中间那部分是请求头,最后一行是请求体,这里用的POST方法,所以表单的参数会在请求体中,这行和上面的用一个空行隔开,以便解析此请求的时候能知道往下就是请求体了。

响应。服务器接收到用户的请求之后,会找到用户所要的资源(页面,图片等),再发送到客户端。响应的格式如下:

  HTTP/1.1 200 OK

  Server: Microsoft-IIS/4.0

  Date: Mon, 5 Jan 2004 13:13:33 GMT

  Content-Type: text/html

  Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT

  Content-Length: 112

 

  <html>

  <head>

  <title>HTTP Response Example</title>

  </head>

  <body>

  Welcome to Software

  </body>

  </html>

第一行的意思:协议/协议版本  状态码(成功,失败等状态用数字来表示)  状态描述,接下来是告诉客户端的一些信息,再接下来就是客户请求的页面的内容。

 

下面写代码

先看一下流程

简单模拟webserver

从图中看出我们需要创建以下类来完成:

1. HttpServer 用来创建ServerSocket

2. Dispatcher 用来分发请求

3. Reqest 用来获取请求信息

4. Response 用来向客户端发送响应信息

另外,为了方便,我们还需要些一个Util类,用来写一些工具

注意:从Socket获取的inputstream/outputStream 执行关闭操作时,Socket也会关闭,所以我们要在inputStream和outputStream都完成工作时,一起关闭。

HttpServer:

package com.cn.webserver;

 

import java.io.IOException;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.UnknownHostException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

public class HttpServer {

public boolean shutdown = false;

 

public static void main(String[] args)  {

try {

new HttpServer().start();

} catch (IOException e) {

e.printStackTrace();

}

}

 

public void start() throws IOException {

int port = 8081;

int backlog = 1000;

ServerSocket serverSocket = null;

try {

serverSocket = new ServerSocket(port, backlog,InetAddress.getByName("127.0.0.1"));

while (!shutdown) {

Socket socket = serverSocket.accept();

ExecutorService es = Executors.newCachedThreadPool();

es.execute(new Dispatcher(socket));

}

} catch (IOException e) {

shutdown = true;

serverSocket.close();

e.printStackTrace();

}

}

}

Dispatcher:

package com.cn.webserver;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

import com.cn.request.Request;

import com.cn.response.Response;

import com.cn.util.Util;

 

public class Dispatcher implements Runnable{

private Socket socket;

private InputStream is;

private OutputStream os;

private int code = 200;

public Dispatcher(Socket socket){

this.socket = socket;

try {

this.is = socket.getInputStream();

this.os = socket.getOutputStream();

} catch (IOException e) {

code = 500;

e.printStackTrace();

}

}

@Override

public void run() {

try {

Request res = new Request(is);

Response resp = new Response(os);

resp.push(res.getUrl(), code);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally{

Util.closeIO(is);

Util.closeIO(os);

}

}

}

 

Request:

package com.cn.request;

 

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.Reader;

import java.util.HashMap;

import java.util.Map;

 

import com.cn.util.Util;

 

public class Request {

private InputStream in;

private Map parameterMap;

private String url;

private String method;

public Request(InputStream in){

this.in = in;

this.parameterMap = new HashMap();

parse();

}

private void parse() {

String requestHead = read();

if(requestHead==null||requestHead.length()<=0){

return ;

}

String firstLine = requestHead.substring(0, requestHead.indexOf("\r\n"));

int a = requestHead.indexOf("/");

this.method = requestHead.substring(0, a).trim();

String urlparam = null;

String parameter = null;

urlparam = firstLine.substring(a,requestHead.indexOf("HTTP/"));

if(method.equalsIgnoreCase("post")){

parameter = requestHead.substring(requestHead.lastIndexOf("\r\n"));

this.url = urlparam;

}else{

if(firstLine.contains("?")){

String []params = urlparam.split("[?]");

this.url = params[0];

if(params.length>0){

String []params1 = params[1].split("&");

if(params1.length>0){

String[] param;

String key = null;

String value = null;

for(int i = 0; i < params1.length;i++){

param = params1[i].split("=");

if(param.length>0){

key = param[0];

if(param.length>1){

value = param[1];

}else{

value = null;

}

this.parameterMap.put(key, value);

}

}

}

}

}else{

this.url=urlparam;

}

}

}

/**

 * 获取请求信息

 * @return

 */

private String read() {

StringBuffer sb = new StringBuffer();

Reader re = null;

try {

re = new InputStreamReader(this.in, "UTF-8");

char []cbuf = new char[20480];

int index = 0;

index = re.read(cbuf);

if(index>0){

sb.append(cbuf,0,index);

}

} catch (Exception e) {

e.printStackTrace();

}

return sb.toString();

}

public String getMethod() {

return method;

}

public void setMethod(String method) {

this.method = method;

}

public String getParameterMap(String key) {

return (String)parameterMap.get(key);

}

public void setParameterMap(Map parameterMap) {

this.parameterMap = parameterMap;

}

public String getUrl() {

return url;

}

public void setUrl(String url) {

this.url = url;

}

}

Response:

package com.cn.response;

 

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.util.Date;

 

import com.cn.util.Util;

 

public class Response {

private OutputStream out;

private int code;

private StringBuffer context ;

public Response(OutputStream out){

this.out = out;

context = new StringBuffer();

}

public void push(String url,int code){

this.read(url);

String head = this.createHead(context.length());

try {

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(this.out,"UTF-8"));

bw.append(head);

bw.append(this.context);

bw.flush();

} catch (IOException e) {

e.printStackTrace();

}

}

private void read(String url){

if(url==null||url.length()<0){

return;

}

url = url.substring(1);

url = System.getProperty("user.dir")+File.separatorChar+"WebRoot"+File.separatorChar+url;

File file = new File(url);

BufferedReader br = null;

if(!file.exists()){

this.code =404;

this.context.append("<h1>大写的<span style='font-size:90px'>404</span></h1>");

}else{

try {

br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

String cacheStr = br.readLine();

while(cacheStr!=null){

context.append(cacheStr);

cacheStr = br.readLine();

}

} catch (FileNotFoundException e) {

this.code =404;

e.printStackTrace();

} catch (IOException e){

e.printStackTrace();

}finally{

Util.closeIO(br);

}

}

}

private String createHead(int len){

StringBuffer headInfo =  new StringBuffer();

headInfo.append("HTTP/1.1").append(" ").append(code).append(" ");

switch (code) {

case 200:

headInfo.append("ok");

break;

case 404:

headInfo.append("NOT FOUND");

break;

case 505:

headInfo.append("SERVER ERROR");

break;

}

headInfo.append("\r\n");

//响应头

headInfo.append("Server: Webserver").append("\r\n");

headInfo.append("Date:").append(new Date()).append("\r\n");

headInfo.append("content-type:text/html;charset=UTF-8").append("\r\n");

//正文长度:字节长度

headInfo.append("Content-Length:").append(len).append("\r\n");

//分隔符

headInfo.append("\r\n");

return headInfo.toString();

}

}

Util:

package com.cn.util;

 

import java.io.Closeable;

import java.io.IOException;

 

public class Util {

public static  <T extends Closeable> void closeIO(T ...ios){

for(Closeable c :ios){

try {

if(c!=null){

c.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

 

运行效果:

 

 简单模拟webserver

 页面不存在的情况:

简单模拟webserver