手撕JavaWeb服务器01——对一个请求的简单响应

前言

2024年对于自己来说是一个新的开始,从今年开始我将会专注于技术能力的提升,以博客的方式记录自己成长的脚步。今年的主线就是这个专栏------手撕 JavaWeb 服务器系列。我将会以一个后端开发者的视角去探索 Web 服务器的底层运行机制,通过猜想->实践->验证的基本流程去学习,即以 JavaWeb 服务器所具备的各种能力,去猜想它的实现原理,然后通过实践去实现,最后去看看已有的开源项目(tomcat、jboss等)的源码并思考如此设计的原因。感兴趣的朋友可以关注一下,并在疑惑的地方提出您的宝贵意见,一起探讨进步!

1. 从网络讲起

谈到 Web 服务器,就离不开网络。通常,我们需要先通过网络发送一个请求到 Web 服务器,Web 服务器根据我们的请求信息再通过网络返回对应的响应信息。信息是怎么通过网络传输地呢?接下来我们简单地聊一下(ps:讲的很浅,请耐心看完哈)

1.1. 寄快递

我们可以把网络传输数据的过程想象成物流公司运送快递,其中的很多原理是相通的。对于普通人来说,快递是怎么包装、运输、拆卸的,他们不关心,他们只需要找到快递驿站去寄送快递和取快递。同样,对于web服务器来说,它只管从指定的地方去发送和取数据即可。

寄过快递的同学应该清楚,我们在寄快递的时候需要填写自己的地址和目的地地址,类比到服务器也是一样,需要指明数据的目的地。那么在网络中如何定位一个网络设备?

1.2. 地址

相信这个问题难不倒大家,就是 ip 地址,我们可以通过 ip 地址去定位一个网络设备。这里我们也不做展开,有兴趣的同学可以自己去搜索相关的资料。我们可以把 ip 地址看作是网络中的 xxx 省 xxx 市 xxx 区,按照一定的规则可以通过这串字符定位到唯一的一个网络设备。

广义上来说,接入网络的设备都可以称作为网络设备,包括我们的个人电脑、手机、公司的服务器等等。找到了网络设备我们是不是就能直接发送数据了呢?

1.3. 端口

答案是:否。在一个网络设备中往往存在多个进程,打个比方把同一台电脑中的 qq 和 微信 当作两个不同的进程。如果只通过 ip 地址去区分数据,那么我的 qq 的数据有可能发到微信,微信的也有可能发送到 qq。因此在 ip 地址的基础上,同一个网络设备还需要去区分该设备中的多个应用。那么如何区分呢?这个时候就要用到端口了。

端口,大家可以简单的理解为网络设备中派送数据的门口。每个进程守在一个门口前面,网络设备会对这些"门"进行编号,根据数据包上的端口号找到对应的门,把数据包丢过去。

2. Socket 编程

通过上面的叙述,我们知道了网络数据传输数据的逻辑过程,具体的实现有时间大家可以去研究一下,这里我们主要讲一下代码层面的东西。

既然我们需要守着一个端口,在 Java 里面我们怎么实现呢?

java 复制代码
    public static void main(String[] args) throws IOException {
        //监听8080端口
        ServerSocket serverSocket = new ServerSocket(8080);
    }

在上面的程序中,我们可以通过创建 ServerSocket 类,并且在构造方法中传入端口号的方式去监听指定的端口。关于端口号,我们需要再解释一点,那就是它的取值范围,一般端口号的取值范围是 1到65535,用了 16 位二进制来表示端口号。

2.1. 接收数据

现在我们已经监听了 8080 端口,我们如何获取传输过来的数据呢?

java 复制代码
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket accept = serverSocket.accept();
        InputStream inputStream = accept.getInputStream();
        int i;
        while((i=inputStream.read())!=-1){
            System.out.print((char)i);
        }

创建 serverSocket 对象之后,我们通过调用 accept() 方法去获取 Socket 对象,这个方法会阻塞等待直到有数据到达我们监听的端口。数据就被封装在 IO 流中,我们可以通过 Socket 对象去获取输入流获取,以操作输入流的方式去获取网络中的数据。

2.2. 返回响应

同样,我们也可以通过操作输出流去向客户端返回响应的数据。

java 复制代码
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket accept = serverSocket.accept();
        InputStream inputStream = accept.getInputStream();
        byte[] bytes = new byte[1024];
        int i;
        //输出请求信息
        while((i=inputStream.read(bytes,0,bytes.length))!=-1){
            System.out.println(new String(bytes,0,i,StandardCharsets.UTF_8));
            if (i<1024){
                break;
            }
        }
        //响应内容
        String responseContext = "HTTP/1.1 200 OK\n" +
                "Content-Type: text/plain\n" +
                "Content-Length: 11\n" +
                "\n" +
                "hello world\n";
        OutputStream outputStream = accept.getOutputStream();
        outputStream.write(responseContext.getBytes(StandardCharsets.UTF_8));
        //清空缓存区,刷新到目的空间
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    }

这段代码比较关键的地方就是 flush 方法,如果不调用这个方法,浏览器是不会立即得到响应信息的,该方法的作用就是将缓存区的数据立即返回。

上面就是我们本次博客要介绍的完整代码了。我们返回了一个 http 报文格式的响应信息,内容是 hello world。在启动项目之后,我们使用浏览器访问 http://localhost:8080/ 即可得到下面的结果

3. 总结

  1. 首先我们浅谈了一下在应用服务器,网络以及数据之间的关系
  2. 然后我们通过 Java 代码的方式实现了对于指定端口数据的接收和响应

到这里,手撕 JavaWeb 服务器的第一篇就结束了,是不是很简单?接下来我将会一边学习,一边分享,然后将自己的学习感悟和心得以博客的方式分享出来,感兴趣的同学也可以关注我,以评论和私信的方式和我一起讨论。

相关推荐
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring
数据小小爬虫4 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
暮春二十四4 小时前
关于用postman调用接口成功但是使用Java代码调用却失败的问题
java·测试工具·postman