写一个简易服务器

第一版本

效果

访问localhost:8089,返回响应报文给浏览器

 服务器收到的请求报文内容

 浏览器转圈圈,是因为一直没收到响应报文

public class Test {
    public static void main(String[] args) {
        //写一个简易服务器
        try {//IDEA在8089监听是否有连接请求
            ServerSocket serverSocket = new ServerSocket(8089);
            Socket socket = serverSocket.accept();//有连接请求会返回socket对象
            InputStream in = socket.getInputStream();//获得输入流对象,用来接收tcp报文的数据部分
            byte[] data = new byte[1024];//暂存接收数据
            int dataCount;//一次接收的数据大小(几个字节)
            //不能用while,因为客户端没用socket.shutdownOutput()禁用此套接字的输出流,
            // 这样服务端就不会收到null,就会一直跳不出循环
           /* while ((dataCount = in.read(data)) != -1) {//接收tcp报文的数据部分
                //把字节数组[0,dataCount)的字节转换成字符串
                String str=new String(data,0,dataCount);
                System.out.println(str);//str是请求报文
            }*/

            dataCount = in.read(data);
            //把字节数组[0,dataCount)的字节转换成字符串
            String str = new String(data, 0, dataCount);
            System.out.println(str);//str是请求报文
            OutputStream out = socket.getOutputStream();//用来发响应报文
            StringBuffer buffer = new StringBuffer();//类似string多用于字符拼接
            buffer.append("HTTP/1.1 404 Not Found\r\n");
            buffer.append("Content-Type:text/html\r\n");
            buffer.append("\r\n");
            buffer.append("<div style='color:red'>File Not Found</div>");
            out.write(buffer.toString().getBytes("utf-8"));//发送tcp响应报文的数据部分
            serverSocket.close();
            //一定要关闭socket,才能关闭它底层的流对象
            // 不然out发送的响应报文一直在缓存区,浏览器接收不到响应报文就会转圈圈
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

第二版本

效果

在浏览器地址栏输入想访问1.html,服务器返回的响应报文里就是1.html

请求2.html,服务器返回的响应报文里就是2.html

如果浏览器请求的资源存在,那么返回200状态码,同时将文件的内容写入到响应体中

如果不存在,那么返回404状态码

http报文的格式

GET /1.html?use=aa HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18363
Accept-Encoding: gzip, deflate
Host: localhost:8089
Connection: Keep-Alive
Cookie: Idea-3faec05=31b22dc0-e261-45ff-9a7d-37a03783321b

Java服务器收到的请求报文

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Test {
    public static void main(String[] args) {
        //写一个简易服务器
        try {
            //IDEA在8089监听是否有连接请求
            ServerSocket serverSocket = new ServerSocket(8089);
            Socket socket = serverSocket.accept();//有连接请求会返回socket对象
            InputStream in = socket.getInputStream();//获得输入流对象,用来接收tcp报文
            byte[] data = new byte[1024];//暂存接收数据
            int dataCount;//一次接收的数据大小(几个字节)
            //不能用while,因为客户端没用socket.shutdownOutput()禁用此套接字的输出流,
            // 这样服务端就不会收到null,就会一直跳不出循环
           /* while ((dataCount = in.read(data)) != -1) {//接收tcp报文的数据
                //把字节数组[0,dataCount)的字节转换成字符串
                String str=new String(data,0,dataCount);
                System.out.println(str);//str是请求报文
            }*/
            dataCount = in.read(data);
            //把字节数组[0,dataCount)的字节转换成字符串
            String str = new String(data, 0, dataCount);
            System.out.println(str);//str是请求报文

            //------------解析请求报文,得到用户想要的资源------
            //http报文的每行后面有一个换行符,可以用这个换行把请求报文分割成多行
            int i = str.indexOf(System.lineSeparator());//得到请求行最后的回车所在下标(回车占两个字符)
            //获得请求行(不包含回车的),GET /1.html HTTP/1.1
            String requestLine = str.substring(0, i);//切割区间[0,i)
            //请求行数据之间用空格分隔
            String[] parts = requestLine.split(" ");
            String method = parts[0];//请求方式
            String url = parts[1];//请求URL
            String protocol = parts[2];//通信的协议和版本
            //判断请求URL有没有携带参数,有就去掉
            int j = url.indexOf("?");
            if (j != -1) {// 因为?后面的请求参数对用文件名搜索文件没有帮助
                url = url.substring(0, j);
            }
            //去掉url前面的/,/1.html
            String fileName = url.substring(1);//截取第一个字符及以后的内容

            OutputStream out = socket.getOutputStream();//用来发响应报文
            StringBuffer buffer = new StringBuffer();//类似string多用于字符拼接
            //在服务器的磁盘中搜索目标文件
            File file = new File(fileName);
            if (file.exists() && !file.isDirectory()) {//确保文件存在,并且不是一个目录
                //制作响应报文
                buffer.append("HTTP/1.1 200 OK\r\n");
                buffer.append("Content-Type:text/html\r\n");
                buffer.append("\r\n");
                out.write(buffer.toString().getBytes("utf-8"));//把响应报文头放入输出流缓冲区
                FileInputStream fileInput = new FileInputStream(file);
                byte[] bytes = new byte[1024];
                int length;
                while ((length = fileInput.read(bytes)) != -1) {
                    out.write(bytes, 0, length);//把响应报文body放入输出流缓冲区内
                }
                fileInput.close();
            } else {
                //制作响应报文,文件不存在,状态码404
                buffer.append("HTTP/1.1 404 Not Found\r\n");
                buffer.append("Content-Type:text/html\r\n");
                buffer.append("\r\n");
                buffer.append("<div style='color:red'>File Not Found</div>");
                out.write(buffer.toString().getBytes("utf-8"));//把响应报文放入输出流缓冲区
            }

            serverSocket.close();
            //一定要关闭socket,才能关闭它底层的流对象
            // 不然out发送的响应报文一直在缓存区,浏览器接收不到响应报文就会转圈圈
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

   
}

第三版本

服务器可以一直接收并处理请求,如果单线程模式下,直接在代码外面加一层while,当程序在处理请求时(恰好里面有Thread.sleep()),有新请求过来,因为没有进程在socket.accept()处一直阻塞监听,会遗漏请求,所有至少有两个线程,主线程负责监听请求,子线程负责处理请求

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Test {
    public static void main(String[] args) {
        //写一个简易服务器
        try {
            //IDEA在8089监听是否有连接请求
            ServerSocket serverSocket = new ServerSocket(8089);
            Socket socket = serverSocket.accept();//有连接请求会返回socket对象
            //每当有一个客户端连接进来,那么就会生成一个新的子线程去处理该客户端的请求信息
           new Thread(new Runnable() {
               @Override
               public void run() {
                   Request request = new Request(socket);
                   try {
                       OutputStream out = socket.getOutputStream();//用来发响应报文
                       StringBuffer buffer = new StringBuffer();//类似string多用于字符拼接
                       //在服务器的磁盘中搜索目标文件
                       File file = new File(request.getRequestResources());
                       if (file.exists() && !file.isDirectory()) {//确保文件存在,并且不是一个目录
                           //制作响应报文
                           buffer.append("HTTP/1.1 200 OK\r\n");
                           buffer.append("Content-Type:text/html\r\n");
                           buffer.append("\r\n");
                           out.write(buffer.toString().getBytes("utf-8"));//把响应报文头放入输出流缓冲区
                           FileInputStream fileInput = new FileInputStream(file);
                           byte[] bytes = new byte[1024];
                           int length;
                           while ((length = fileInput.read(bytes)) != -1) {
                               out.write(bytes, 0, length);//把响应报文body放入输出流缓冲区内
                           }
                           fileInput.close();
                       } else {
                           //制作响应报文,文件不存在,状态码404
                           buffer.append("HTTP/1.1 404 Not Found\r\n");
                           buffer.append("Content-Type:text/html\r\n");
                           buffer.append("\r\n");
                           buffer.append("<div style='color:red'>File Not Found</div>");
                           out.write(buffer.toString().getBytes("utf-8"));//把响应报文放入输出流缓冲区
                       }
                       //一定要关闭socket,才能关闭它底层的流对象
                       // 不然out发送的响应报文一直在缓存区,浏览器接收不到响应报文就会转圈圈
                       socket.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

每个请求报文的格式雷同,可以创建一个Request类,类的成员变量是请求报文解析后的结果

Request类中有专门解析请求报文的方法

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/*每个请求报文的格式雷同,可以创建一个Request类,类的成员变量是请求报文解析后的结果,
Request类中有专门解析请求报文的方法*/
public class Request {
    private Socket socket;

    //保存解析之后的HTTP请求报文
    private String requestString;

    //HTTP请求方法
    private String method;

    //HTTP请求资源
    private String requestResources;

    //HTTP请求版本协议
    private String protocol;

    //map用来存放请求头键值对
    private Map<String, String> requestHeaders = new HashMap<>();

    public Request(Socket socket) {
        this.socket = socket;
        try {
            InputStream in = socket.getInputStream();//获得输入流对象,用来接收tcp报文
            byte[] data = new byte[1024];//暂存接收数据
            int dataCount;//一次接收的数据大小(几个字节)
                   /* 不能用while,因为客户端没用socket.shutdownOutput() 禁用此套接字的输出流,
                    这样服务端就不会收到null,就会一直跳不出循环*/
                   /* while ((dataCount = in.read(data)) != -1) {//接收tcp报文的数据
                        把字节数组[0, dataCount)的字节转换成字符串
                        String str = new String(data, 0, dataCount);
                        System.out.println(str);//str是请求报文
                    }*/
            dataCount = in.read(data);
            //把字节数组[0,dataCount)的字节转换成字符串
            requestString = new String(data, 0, dataCount);
            System.out.println(requestString);//requestString是请求报文
            //------------解析请求报文------
            parseRequestLine();//封装请求行
            parseRequestHeaders();//封装请求头
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void parseRequestLine() {//解析请求报文的请求行
        //http报文的每行后面有一个换行符,可以用这个换行把请求报文分割成多行
        int i = requestString.indexOf(System.lineSeparator());//得到请求行最后的回车所在下标(回车占两个字符)
        //获得请求行(不包含回车的),GET /1.html HTTP/1.1
        String requestLine = requestString.substring(0, i);//切割区间[0,i)
        //请求行数据之间用空格分隔
        String[] parts = requestLine.split(" ");
        method = parts[0];//请求方式
        String url = parts[1];//请求URL
        protocol = parts[2];//通信的协议和版本
        //判断请求URL有没有携带参数,有就去掉
        int j = url.indexOf("?");
        if (j != -1) {// 因为?后面的请求参数对用文件名搜索文件没有帮助
            url = url.substring(0, j);
        }
        //去掉url前面的/,/1.html
        requestResources = url.substring(1);//截取第一个字符及以后的内容
    }

    private void parseRequestHeaders() {//解析请求头
        //http报文的每行后面有一个换行符
        int i = requestString.indexOf(System.lineSeparator());//得到请求报文第一行的回车所在下标(回车占两个字符)
        int j = requestString.indexOf(System.lineSeparator() + System.lineSeparator());//定位到请求头最后一行的回车处
        //得到请求头
        String substring = requestString.substring(i + 2, j);
        String[] heads = substring.split(System.lineSeparator());
        //取出请求头的key-value
        for (String head : heads) {
            int index = head.indexOf(":");
            String key = head.substring(0, index);
            String value = head.substring(index + 1);
            requestHeaders.put(key, value);
        }
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public String getRequestString() {
        return requestString;
    }

    public void setRequestString(String requestString) {
        this.requestString = requestString;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getRequestResources() {
        return requestResources;
    }

    public void setRequestResources(String requestResources) {
        this.requestResources = requestResources;
    }

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public Map<String, String> getRequestHeaders() {
        return requestHeaders;
    }

    public void setRequestHeaders(Map<String, String> requestHeaders) {
        this.requestHeaders = requestHeaders;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值