IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 200行java代码写一个简单的服务器 -> 正文阅读

[Java知识库]200行java代码写一个简单的服务器

引言

由于本人读的交通类大学的计算机科学,最近有个铁路实习,需要实现一个B/S架构的管理系统,我负责的是后端提供管理数据库的服务。
网上查阅资料后决定用Tomcat实现java服务的提供,但是网上学着学着,发现服务器本质就是通过套接字socket建立IO流传递信息的一个过程。用Tomcat虽然好,但是由于高度分装性,学不到底层原理。本着大学压榨自己的思想(别问,问就是我头铁),就干脆自己写一个简单的仅仅提供HTTP服务的服务器(确实很有成就感,哈哈哈哈哈)。

阅读本篇文章需要的知识有:
只要会java的基本语法就行,笔者本人也只学了一个学期的java (对,我是菜鸡,如果有错误的地方大家在评论区清点喷)

TCP协议介绍

说到大名鼎鼎的TCP协议,肯定马上就有人想到三次握手,四次挥手和滑动窗口等等。但是这些不是我想讲的! 在java高度封装下,只需要知道TCP协议是保证交付的一个协议就行。什么是保证交互呢?就是你发给我一个信息,我一定会告诉你我收到了你的信息,如果我没有告诉你,那么你就要重发请求。
举个简单的例子:
A对B说:晚上我们一起去吃烤肉?
情况一:
B对A说:好。
A就知道B同意了。
情况二:
B没听见A说的什么,什么也没有回应
A不知道B的想法,A再问B:你说什么?
情况三:
B对A说:好。
A没有听清楚B说什么。
A再问B:你说什么?
上面三种情况分别对应:
一:成功交付
二:A的包丢了
三:B的包丢了

根据反馈处理机制,A重新发送包,所以实现了互联网可靠传输。
因为Java实现的服务器也是需要在局域网中通信,不排除丢包的可能性,我选择浏览器和服务器传输数据建立的所有连接都是TCP连接,不需要考虑网络丢包的可能性,我们只考虑服务器发送的信息类型及如何发送信息,即实现了网络对我的透明性。

HTTP协议介绍

因为是B/S架构下的服务器,且本人网络基础知识才刚刚学完(由于中途参加了蓝桥杯国赛,偷偷用了几节网络课学算法,好多地方都是自学,感觉学的不太好,内卷失败,哈哈哈),所以就没有用HTTPS来给自己加大游戏难度,自己手动实现了精简的不能再精简的HTTP协议。好,现在步入正题,讲讲HTTP协议。

HTTP:
(Hyper Text Transfer Protocol,HTTP)就是一个传输超文本的协议,啥是超文本呢?广义来说,就是信息的载体,包括但不限于视频,音频。HTTP规定了报文的格式,方便浏览器和服务器识别报文的信息。

HTTP报文格式:
HTTP报文分为请求报文和响应报文,分别用于客户向服务器发送请求和服务器对请求产生应答。请求报文我用到了GET和POST报文格式,所以对于请求报文我暂时只讲这两种报文根式。GET就是向服务器请求一个静态文件,POST就是向服务器请求一个动态服务。

GET报文:

请求行:GET filename.xxx HTTP/1.1 
请求头:Host: xxx.xxx.xxx.xxx:8080
				。。。
	   Upgrade-Insecure-Requests: 1

请求体:message

1.GET报文分为请求行,请求头和请求体三个部分组成。 请求行说明这是一个GET报文,需要服务器向浏览器发送filename.xxx文件,采用的HTTP协议版本为1.1
2.请求头告知了服务器的一些报文的信息
3.请求体就是发送的额外信息,
GET是一个请求静态文件的报文,只需要告诉浏览器报文的名字,不需要发送额外信息,所以请求体为空。

POST报文格式:

请求行:POST programname.java HTTP/1.1
请求头:Host: xxx.xxx.xxx.xxx:8080
				。。。
	   Cookie: Idea-dd687ae7=0eebdb6c-d2c6-4dd3-941b-755e9519ebfc

请求体:message

1.POST报文也分为请求行,请求头和请求体三个部分。 请求行说明这是一个POST报文,需要服务器向浏览器提供服务的程序为programname.java,采用的HTTP协议版本为1.1
2.请求头告知了服务器的一些报文的信息
3.请求体就是发送的额外信息,
POST是一个请求动态服务的报文,有时候往数据库中存入信息需要发送额外信息,所以请求体一般不为空。
现在就讲完了我们需要用到的请求报文的格式,现在讲讲响应报文的格式。

OK报文

响应行:HTTP/1.1 200 	OK
响应头:mytomcat1.Server:apach-Coyote/1.1
	   Content-Type:text/html;charset=utf-8

响应体:message

OK报文的代码是200,表示的意义是成功找到了目标报文并发送。

NotFound报文

响应行:HTTP/1.1 404 not found
响应头:mytomcat1.Server:apach-Coyote/1.1
	   Content-Type:text/html;charset=utf-8

响应体:file not found

NotFound报文的代码是404,表示请求的服务没有找到。

套接字Socket介绍

在这里插入图片描述
套接字由计算机的IP地址和端口号组成,相当于计算机连接互联网的接口,因为我们需要实现在局域网 (在互联网上通信需要域名,有没有好心人资助我20块钱买个域名,害,太穷了) 上通信,所以必须使用到端口号,端口号有操作系统管理,java也封装了专门的类,所以相对来说对我还是比较友好。

在这里,用java写服务器需要用到的所有理论知识就介绍完成了,现在正式开始写代码。

代码&思路

0.准备工作

这里主要是前期变量的定义,需要注意的是这里对动态服务进行了封装,用服务名称=执行服务的java类的地址 进行映射,再用java反射来实现类,从而提供服务。用到了.properties文件,来实现服务名和服务代码的映射。
在这里插入图片描述

//定义一个变量,用来存放服务端webcontent目录的绝对路径
public static String WEB_ROOT = System.getProperty("user.dir");
//定义静态变量,用于存放本次请求的静态页面的名称
private static String url = "";
//存放接收到的数据
private static StringBuffer content = null;
//定义一个静态map,用于提供动态服务
private static Map<String ,String> map=new HashMap<>();
//记录请求体
private static String message;

static {
//加载配置文件conf.properties到map中
   Properties pro = new Properties();
   try {
   		pro.load(new FileInputStream(WEB_ROOT+"\\conf.properties"));
        Set set = pro.keySet();
        Iterator it = set.iterator();
        if (it.hasNext()){
            String key = it.next().toString();
            String value = pro.getProperty(key);
            map.put(key,value);
        }
   } catch (IOException e) {
        e.printStackTrace();
   }
}

1.监听端口,等待建立IO流

无论是局域网还是互联网,信息网络中的传输都是以信息流的形式进行传输,所以必须采用IO流的形式来接受和发送信息。思路如下图:
在这里插入图片描述

ServerSocket serverSocket;
Socket socket = null;
InputStream is = null;
OutputStream os = null;
serverSocket = new ServerSocket(8080);//对8080端口号开启信息监听
while(true) {
	//死循环是为了保证一直对该端口监听,而不是处理完一个web服务就结束程序
     socket = serverSocket.accept();//这里会一直等待,直到接受到
     								//IO流才会继续运行程序
     os = socket.getOutputStream();//得到输出流
     is = socket.getInputStream();//得到输入流

2.得到请求报文后,分析请求报文

因为网络传输给服务器的是一长串字符串,虽然该字符串为标准的HTTP请求报文格式,但是服务器仍然无法识别,需要手动分析。

if(parse(is)) {
     //判断是否为挥手信息
     if (url.indexOf(".")!=-1) {
           //GET请求
           //发送网页静态资源
           sentStaticResoure(os);
     } else {
     	   //POST请求
     	   //根据提供的服务名来向浏览器提供服务
           sentDyResourse(is,os);
      }
}

注:判断是否为分手信息是因为浏览器关闭网页时,服务器会接收到一个空信息,而服务器对空信息无法分析,所有会报错,需要分开考虑。

3.服务完成,关闭IO流

在服务完成后,如果不关闭IO流,IO流会对这个端口进行阻塞,导致服务器无法对其他浏览器的请求提供服务。

try {
     if (is != null)
            is.close();
     if (os != null)
            os.close();
     if (socket != null)
            socket.close();
} catch (IOException e) {
     e.printStackTrace();
}

4.parse函数解析

这个函数是用来分析请求报文,得到静态服务需要传输的文件名,或者请求的动态服务名称,存入url变量(字符串类型)。

private static boolean parse(InputStream is) {
        //定义一个变量,存放http协议请求数据
        content = new StringBuffer();
        //定义一个数组,存放http协议请求的原始数据,以字节流的形式存储
        byte[] buffer = new byte[2048];
        //报文长度
        int len = -1;
        try {
            //读取数据
            len = is.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (len == -1) {
            //浏览器连接关闭产生的空报文
            url = null;
            return false;
        }
        //遍历字节数组,转存在content中
        for (int i = 0; i < len; i++) {
            content.append((char) buffer[i]);
        }
        //得到请求行的URL
        System.out.println("content:\n" + content);
        url = parseURL(content.toString());
        return true;
    }

    private static String parseURL(String content) {
        int index1, index2;
        //第一个空格下标,前面是协议类型,如POST或者GET
        index1 = content.indexOf(" ");
        //第二个空格下标,前面是文件或者服务的名字
        index2 = content.indexOf(" ", index1 + 1);
        //message保存请求体
        message = content.substring(content.lastIndexOf("\n")+1);
        return content.substring(index1 + 2, index2);
    }

5.sentStaticResoure函数解析

这个函数用于向浏览器发送静态数据,比如html文件,jpg图像等等。

private static void sentStaticResoure(OutputStream os) {
        //定义一个字节数组,用于存放本次请求的静态资源html/css等等的文件
        byte[] bytes = new byte[100000];
        FileInputStream fis = null;
        try {
        	//定义文件指针
            File file = new File(WEB_ROOT, url);
            if (file.exists()) {
                //写入响应行
                os.write("HTTP/1.1 200 OK\n".getBytes(StandardCharsets.UTF_8));
                //写入响应头
                os.write("mytomcat1.Server:apach-Coyote/1.1\n".getBytes(StandardCharsets.UTF_8));
                //得到文件的后缀名
                String tail = url.substring(url.indexOf(".") + 1);
                os.write(("Content-Type:text/" + tail + ";charset=utf-8\n").getBytes(StandardCharsets.UTF_8));
                os.write("\n".getBytes(StandardCharsets.UTF_8));
                //获得文件输入流对象
                fis = new FileInputStream(file);
                int len = fis.read(bytes);
                //将文件以流的方式写入响应体
                os.write(bytes, 0, len);
                //清空缓冲区,确保数据发送完毕
                os.flush();
            } else {
            	//写入响应行
                os.write("HTTP/1.1 404 not found\n".getBytes(StandardCharsets.UTF_8));
                //写入响应头
                os.write("mytomcat1.Server:apach-Coyote/1.1\n".getBytes(StandardCharsets.UTF_8));
                os.write("Content-Type:text/html;charset=utf-8\n".getBytes(StandardCharsets.UTF_8));
                //写入响应体
                os.write("\nfile not found".getBytes(StandardCharsets.UTF_8));
                //刷新缓冲区
                os.flush();
            }
        } catch (IOException e) {
            System.out.println("文件为空");
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                //服务提供完成,关闭文件
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

6.sentDyResourse函数解析

这个函数用于向浏览器发送动态服务内容,

private static void sentDyResourse(InputStream is, OutputStream os) {
        try {
            //响应行
            os.write("HTTP/1.1 200 OK\n".getBytes(StandardCharsets.UTF_8));
            //响应头
            os.write("mytomcat1.Server:apach-Coyote/1.1\n".getBytes(StandardCharsets.UTF_8));
            os.write(("Content-Type:text/html;charset=utf-8\n").getBytes(StandardCharsets.UTF_8));
            //响应体
            os.write("\n".getBytes(StandardCharsets.UTF_8));
            //得到服务的名称
            url=url.substring(url.lastIndexOf("/")+1);
            //创建一个类的反射,用来执行服务
            Class clazz = Class.forName(map.get(url));
            //实例化该类
            Serable serable = (Serable) clazz.newInstance();
            //提供服务
            serable.server(message,os);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

7.404消息的发送

如果浏览器请求的服务不存在,或者浏览器请求的文件不存在,那么就需要返回一个404信息,告诉浏览器他访问的信息不存在。

//响应行
os.write("HTTP/1.1 404 not found\n".getBytes(StandardCharsets.UTF_8));
//响应头
os.write("mytomcat1.Server:apach-Coyote/1.1\n".getBytes(StandardCharsets.UTF_8));
os.write("Content-Type:text/html;charset=utf-8\n".getBytes(StandardCharsets.UTF_8));
//响应体
os.write("\nfile not found".getBytes(StandardCharsets.UTF_8));
//刷新缓冲区
os.flush();

8.举例一个服务

在配置文件中写好这个服务,然后就可以直接调用这个服务了。调用服务的方式就是通过java反射新建aaTest类,然后调用aaTest的方法。
在这里插入图片描述

package mytomcat2;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class aaTest implements Serable {
    public aaTest(){
        System.out.println("aa is creating");
    }

    @Override
    public void server(String message, OutputStream os) {
        try {
            os.write(("我收到了:"+message).getBytes(StandardCharsets.UTF_8));
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

然后

按照上面的代码的思路,就可以用自己的电脑实现一个简单的服务器,虽然简陋,但是在写服务器的过程中,最大的收获不是写出来了一个服务器,而是加深了对计算机网络的理解,初步接触了socket编程,还有java反射等等的知识得到了巩固。**这里我没有放数据库的代码,一个是考虑到数据库处理设计到了大量java反射,对于一些对java反射不熟练的同学可能不友好;二是数据库只是java的一种服务,没必要纠结服务类型。**抓住主要矛盾,兼顾次要矛盾,就可以学到很多东西,压力也不会很大。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-14 10:44:42  更:2021-07-14 10:44:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/17 20:25:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码