Socket编程

网络编程入门

软件结构

  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
  • 特点: 客户端和服务器是分开的,需要下载客户端,对网络要求相对低, 服务器压力小,开发和维护成本高,相对稳定

image-20230927191311154

B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。

特点:没有客户端,只有服务器,不需要下载客户端,直接通过浏览器访问, 对网络要求相对高, 服务器压力很大,相对不稳定,开发和维护成本低,

image-20230927191315436

两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机在网络中的通信的程序。

网络编程三要素

协议

网络通信协议:通信协议是计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。

java.net 包中提供了两种常见的网络协议的支持:

  • TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
  • TCP协议特点: 面向连接,传输数据安全,传输速度低
  • 例如: 村长发现张三家的牛丢了
  • TCP协议: 村长一定要找到张三,面对面的告诉他他家的牛丢了 打电话: 电话一定要接通,并且是张三接的
    • 连接三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
      • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。 你愁啥?
      • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。我愁你咋地?
      • 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。你再愁试试

image-20220726134219054

​ 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等

  • UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中**,例如视频会议、QQ聊天等。**
  • UDP特点: 面向无连接,传输数据不安全,传输速度快
  • 例如: 村长发现张三家的牛丢了
  • UDP协议: 村长在村里的广播站广播一下张三家的牛丢了,信息丢失,信息发布速度快

IP地址

  • IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

**IP地址分类 **

  • IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。

  • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。

    为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

常用命令

  • 查看本机IP地址,在控制台输入:
1
ipconfig
  • 检查网络是否连通,在控制台输入:
1
2
3
ping 空格 IP地址
ping 220.181.57.216
ping www.baidu.com

特殊的IP地址

  • 本机IP地址:127.0.0.1localhost

端口号

网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

  • 端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。

小结

  • 协议: 计算机在网络中通信需要遵守的规则,常见的有TCP,UDP协议
    • TCP: 面向连接,传输数据安全,传输速度慢
    • UDP: 面向无连接,传输不数据安全,传输速度快
  • IP地址: 用来标示网络中的计算机设备
    • 分类: IPV4 IPV6
    • 本地ip地址: 127.0.0.1 localhost
  • 端口号: 用来标示计算机设备中的应用程序
    • 端口号: 0–65535
    • 自己写的程序指定的端口号要是1024以上
    • 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

InetAddress类

InetAddress类的概述

  • 一个该类的对象就代表一个IP地址对象。

InetAddress类的方法

  • static InetAddress getLocalHost() 获得本地主机IP地址对象
  • static InetAddress getByName(String host) 根据IP地址字符串或主机名获得对应的IP地址对象
  • String getHostName();获得主机名
  • String getHostAddress();获得IP地址字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception {
// 获取本地ip地址对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println("ip1: "+ip1);// DESKTOP-U8Q5F96/192.168.0.100

// 获取百度ip地址对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println("ip2:"+ip2);// www.baidu.com/182.61.200.7

// 获得本地的主机名
String hostName = ip1.getHostName();
System.out.println("hostName:"+hostName);// DESKTOP-U8Q5F96

// 获得本地的ip地址
String hostAddress = ip1.getHostAddress();
System.out.println("hostAddress:"+hostAddress);//192.168.0.100
}
}

TCP通信程序

TCP协议概述

TCP概述

  • TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收器端建立逻辑连接,然后再传输数据。它提供了两台计算机之间可靠无差错的数据传输。TCP通信过程如下图所示:

image-20230927191331953

TCP协议相关的类

  • Socket : 一个该类的对象就代表一个客户端程序。
    • Socket(String host, int port) 根据ip地址字符串和端口号创建客户端Socket对象
      * 注意事项:只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。
      如果连接成功,则表示三次握手通过。
    • OutputStream getOutputStream(); 获得字节输出流对象
    • InputStream getInputStream();获得字节输入流对象
    • void close();关闭Socket, 会自动关闭相关的流,关闭通过Socket获得流,也会关闭Socket
  • ServerSocket : 一个该类的对象就代表一个服务器端程序。
    • ServerSocket(int port); 根据指定的端口号开启服务器。
    • Socket accept(); 等待客户端连接并获得与客户端关联的Socket对象 如果没有客户端连接,该方法会一直阻塞
    • void close();关闭ServerSocket,一般不关闭

TCP通信案例1

需求

  • 客户端向服务器发送字符串数据

路径

  • 客户端实现步骤
    • 创建客户端Socket对象并指定服务器地址和端口号
    • 调用Socket对象的getOutputStream方法获得字节输出流对象
    • 使用字节输出流对象的write方法往服务器端输出数据
    • 关闭Socket对象断开连接。
  • 服务器实现步骤
    • 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)
    • 调用ServerSocket对象的accept方法等待客端户连接并获得对应Socket对象
    • 调用Socket对象的getInputStream方法获得字节输入流对象
    • 调用字节输入流对象的read方法读取客户端发送的数据

实现

  • 客户端代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Client {
    public static void main(String[] args) throws Exception{
    // 客户端:
    // 1.创建Socket对象,指定要连接的服务器的ip地址和端口号
    Socket socket = new Socket("127.0.0.1",6666);
    System.out.println(socket);// 封装了服务器的ip地址和端口号

    // 2.通过Socket对象获得输出流
    OutputStream os = socket.getOutputStream();

    // 3.写出数据到服务器
    os.write("服务器,你好,今晚约吗?".getBytes());

    // 4.关闭流,释放资源
    socket.close();

    }
    }

  • 服务端代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Server {
    public static void main(String[] args) throws Exception{
    // 服务器:
    // 1.创建ServerSocket对象,指定服务器的端口号(6666)
    ServerSocket ss = new ServerSocket(6666);

    // 2.调用accept()方法,接收客户端的请求,返回Socket对象
    Socket socket = ss.accept();
    System.out.println(socket);// 封装了客户端的ip地址和端口号

    // 3.使用返回的Socket对象获得输入流
    InputStream is = socket.getInputStream();

    // 4.读取客户端写过来的数据
    byte[] bys = new byte[8192];
    int len = is.read(bys);
    System.out.println(new String(bys,0,len));

    // 5.关闭服务器(一般不关闭)
    ss.close();

    }
    }

TCP通信案例2

需求

  • 客户端向服务器发送字符串数据,服务器回写字符串数据给客户端(模拟聊天)

路径

  • 客户端实现步骤
    • 创建客户端Socket对象并指定服务器地址和端口号
    • 调用Socket对象的getOutputStream方法获得字节输出流对象
    • 使用字节输出流对象的write方法往服务器端输出数据
    • 调用Socket对象的getInputStream方法获得字节输入流对象
    • 调用字节输入流对象的read方法读取服务器端返回的数据
    • 关闭Socket对象断开连接。
  • 服务器实现步骤
    • 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)
    • 调用ServerSocket对象的accept方法等待客端户连接并获得对应Socket对象
    • 调用Socket对象的getInputStream方法获得字节输入流对象
    • 调用字节输入流对象的read方法读取客户端发送的数据
    • 调用Socket对象的getOutputStream方法获得字节输出流对象
    • 调用字节输出流对象的write方法往客户端输出数据
    • 关闭Socket和ServerSocket对象

实现

  • TCP客户端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
TCP客户端代码实现步骤
* 创建客户端Socket对象并指定服务器地址和端口号
* 调用Socket对象的getOutputStream方法获得字节输出流对象
* 调用字节输出流对象的write方法往服务器端输出数据
* 调用Socket对象的getInputStream方法获得字节输入流对象
* 调用字节输入流对象的read方法读取服务器端返回的数据
* 关闭Socket对象断开连接。
*/
public class Client {
public static void main(String[] args) throws Exception{
// 创建Socket对象,指定服务器ip和端口号
Socket socket = new Socket("127.0.0.1",6666);
while (true) {
// 通过socket对象获得输出流
OutputStream os = socket.getOutputStream();
// 写出数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入向服务器发送的数据:");
String str = sc.nextLine();
os.write(str.getBytes());

// 通过Socket对象获得输入流
InputStream is = socket.getInputStream();
// 定义一个byte数组,用来存储读取到的字节数据
byte[] bys = new byte[1024];
int len = is.read(bys);
// 打印数据
System.out.println(new String(bys,0,len));
}

// 关闭流,释放资源
//socket.close();
}
}
  • 服务端代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
TCP服务器端代码实现步骤
* 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)
* 调用ServerSocket对象的accept方法等待客端户连接并获得对应Socket对象
* 调用Socket对象的getInputStream方法获得字节输入流对象
* 调用字节输入流对象的read方法读取客户端发送的数据
* 调用Socket对象的getOutputStream方法获得字节输出流对象
* 调用字节输出流对象的write方法往客户端输出数据
* 关闭Socket和ServerSocket对象
*/
public class Server {
public static void main(String[] args) throws Exception{
// 创建ServerSocket对象,并指定端口号
ServerSocket ss = new ServerSocket(6666);
// 调用accept()方法等待客户端连接,连接成功返回Socket对象
Socket socket = ss.accept();

while (true) {
// 通过Socket对象获得输入流
InputStream is = socket.getInputStream();
// 定义一个byte数组,用来存储读取到的字节数据
byte[] bys = new byte[1024];
int len = is.read(bys);
// 打印数据
System.out.println(new String(bys,0,len));

// 通过socket对象获得输出流
OutputStream os = socket.getOutputStream();
// 写出数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入向客户端发送的数据:");
String str = sc.nextLine();
os.write(str.getBytes());
}

// 关闭资源
//socket.close();
//ss.close();// 服务器一般不关闭
}
}

综合案例

文件上传案例

需求

  • 使用TCP协议, 通过客户端向服务器上传一个文件

分析

  1. 【客户端】输入流,从硬盘读取文件数据到程序中。

  2. 【客户端】输出流,写出文件数据到服务端。

  3. 【服务端】输入流,读取文件数据到服务端程序。

  4. 【服务端】输出流,写出文件数据到服务器硬盘中。

  5. 【服务端】获取输出流,回写数据。

  6. 【客户端】获取输入流,解析回写数据。

image-20230927191345138

实现

拷贝文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建输入流对象,关联数据源文件路径
FileInputStream fis = new FileInputStream("day12\\aaa\\hb.jpg");

// 2.创建Socket对象,指定要连接的服务器的ip地址和端口号
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

// 3.通过Socket对象获得输出流,关联连接通道
OutputStream os = socket.getOutputStream();

// 4.定义变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 5.循环读取
while ((len = fis.read(bys)) != -1) {
// 6.在循环中,写出数据到通道中
os.write(bys,0,len);
}
// 7.释放资源
socket.close();
fis.close();
}
}
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,指定端口号 8888
ServerSocket ss = new ServerSocket(8888);

// 2.使用ServerSocket对象调用accept()方法,接收请求,建立连接,返回Socket对象
Socket socket = ss.accept();

// 3.通过返回的Socket对象获得输入流,关联连接通道
InputStream is = socket.getInputStream();

// 4.创建输出流对象,关联目的地文件路径
FileOutputStream fos = new FileOutputStream("day12\\aaa\\hbCopy2.jpg");

// 5.定义变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 6.循环读取
while ((len = is.read(bys)) != -1) {
// 7.在循环中,写出数据目的文件中
fos.write(bys,0,len);
}
// 8.释放资源
fos.close();
socket.close();
}
}

文件上传成功后服务器回写字符串数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 客户端
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建输入流对象,关联数据源文件路径
FileInputStream fis = new FileInputStream("day12\\aaa\\hb.jpg");

// 2.创建Socket对象,指定要连接的服务器的ip地址和端口号
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

// 3.通过Socket对象获得输出流,关联连接通道
OutputStream os = socket.getOutputStream();

// 4.定义变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 5.循环读取
while ((len = fis.read(bys)) != -1) {
// 6.在循环中,写出数据到通道中
os.write(bys, 0, len);
}

// - 在文件上传时,客户端从文件中读不到数据,就会停止发送。
// 但是服务器端不知道客户端停止了,所以会一直等待接收数据。
// 解决办法:在客户端调用s.shutdownOutput();通知服务器端发送结束了。
socket.shutdownOutput();// 注意

System.out.println("============客户端开始接受服务器返回的数据==============");
// 7.通过Socket对象获取输入流,关联连接通道
InputStream is = socket.getInputStream();

// 8.读取服务器回写的数据
int read = is.read(bys);// 卡死 读取服务器写回的数据,但是服务器又没有写回数据
// 9.打印服务器回写的数据
System.out.println("服务器回写的数据是:" + new String(bys, 0, read));

// 10.释放资源
socket.close();
fis.close();
}
}
// 服务器
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,指定端口号 8888
ServerSocket ss = new ServerSocket(8888);

// 2.使用ServerSocket对象调用accept()方法,接收请求,建立连接,返回Socket对象
Socket socket = ss.accept();

// 3.通过返回的Socket对象获得输入流,关联连接通道
InputStream is = socket.getInputStream();

// 4.创建输出流对象,关联目的地文件路径
FileOutputStream fos = new FileOutputStream("day12\\aaa\\hbCopy5.jpg");

// 5.定义变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 6.循环读取
while ((len = is.read(bys)) != -1) {
// 7.在循环中,写出数据目的文件中
fos.write(bys, 0, len);
}

System.out.println("======服务器开始回写数据给客户端=======");
// 7.通过socket对象获取输出流,关联连接通道
OutputStream os = socket.getOutputStream();

// 8.写出数据到通道中
os.write("恭喜您,上传成功!".getBytes());

// 9.释放资源
fos.close();
socket.close();
}
}

优化文件上传案例

1
2
3
4
5
6
7
8
9
10
1.文件名固定----->优化   自动生成唯一的文件名
2.服务器只能接受一次 ----> 优化 死循环去接收请求,建立连接
3.例如:如果张三先和服务器建立连接,上传了一个2GB字节大小的文件
李四后和服务器建立连接,上传了一个2MB字节大小的文件

李四就必须等张三上传完毕,才能上传文件

优化---->多线程优化
张三上传文件,开辟一条线程
李四上传文件,开辟一条线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 服务器
public class Server {
// year+"年"+month+"月"+day+"日"+....+"毫秒"
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,指定端口号 8888
ServerSocket ss = new ServerSocket(8888);

while (true) {
// 2.使用ServerSocket对象调用accept()方法,接收请求,建立连接,返回Socket对象
Socket socket = ss.accept();

new Thread(new Runnable() {
@Override
public void run() {

try {
// 3.通过返回的Socket对象获得输入流,关联连接通道
InputStream is = socket.getInputStream();

// 4.创建输出流对象,关联目的地文件路径
FileOutputStream fos = new FileOutputStream("day12\\aaa\\"+System.currentTimeMillis()+".jpg");

// 5.定义变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 6.循环读取
while ((len = is.read(bys)) != -1) {
// 7.在循环中,写出数据目的文件中
fos.write(bys, 0, len);
}

System.out.println("======服务器开始回写数据给客户端=======");
// 7.通过socket对象获取输出流,关联连接通道
OutputStream os = socket.getOutputStream();

// 8.写出数据到通道中
os.write("恭喜您,上传成功!".getBytes());

// 9.释放资源
fos.close();
socket.close();

} catch (IOException e) {

}
}
}).start();

}
}
}
// 客户端
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建输入流对象,关联数据源文件路径
FileInputStream fis = new FileInputStream("day12\\aaa\\hb.jpg");

// 2.创建Socket对象,指定要连接的服务器的ip地址和端口号
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

// 3.通过Socket对象获得输出流,关联连接通道
OutputStream os = socket.getOutputStream();

// 4.定义变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 5.循环读取
while ((len = fis.read(bys)) != -1) {
// 6.在循环中,写出数据到通道中
os.write(bys, 0, len);
}

// 想办法,告诉服务器,我客户端写完了数据,我再也不会写数据了
socket.shutdownOutput();// 注意

System.out.println("============客户端开始接受服务器返回的数据==============");
// 7.通过Socket对象获取输入流,关联连接通道
InputStream is = socket.getInputStream();

// 8.读取服务器回写的数据
int read = is.read(bys);
// 9.打印服务器回写的数据
System.out.println("服务器回写的数据是:" + new String(bys, 0, read));

// 10.释放资源
socket.close();
fis.close();
}
}

模拟B\S服务器 扩展

需求

  • 模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。

分析

  1. 准备页面数据,web文件夹。
  2. 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问,查看网页效果

实现

浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public class Demo {
public static void main(String[] args) throws Exception {
// 通过读取浏览器端的请求信息,获取浏览器需要访问的页面的路径
// 1.创建ServerSocket对象,指定端口号为9999
ServerSocket ss = new ServerSocket(9999);

while (true) {
// 2.调用accept()方法,接收请求,建立连接,返回Socket对象
Socket socket = ss.accept();

new Thread(new Runnable() {
@Override
public void run() {
try {
// 3.通过返回的Socket对象获取字节输入流,关联连接通道
InputStream is = socket.getInputStream();

// 4.把字节输入流转换为字符输入流
InputStreamReader isr = new InputStreamReader(is);

// 5.创建字符缓冲输入流
BufferedReader br = new BufferedReader(isr);

// 6.使用字符缓冲输入流读取第一行数据
String line = br.readLine();

// 7.使用空格对读取到的第一行数据进行分割
String[] arr = line.split(" ");

// 8.获取分割后数组中索引为1的元素,对其进行截取
String path = arr[1].substring(1);
System.out.println("浏览器需要访问的页面路径是:" + path);

// 服务器把浏览器需要访问的页面响应给浏览器
// 9.创建一个字节输入流,关联数据源文件路径
FileInputStream fis = new FileInputStream(path);

// 10.通过Socket对象获得输出流,关联连接通道
OutputStream os = socket.getOutputStream();

// 11.定义一个变量,用来存储读取到的字节数据
byte[] bys = new byte[8192];
int len;

// 响应页面的时候需要同时把以下响应过去给浏览器
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
os.write("\r\n".getBytes());


// 12.循环读取
while ((len = fis.read(bys)) != -1) {
// 13.在循环中,写出数据给浏览器
os.write(bys, 0, len);
}

// 关闭Socket对象,释放资源
fis.close();
socket.close();
} catch (IOException e) {

}
}
}).start();
}
}

/**
* 1.读取到浏览器端的请求信息
*
* @return
* @throws IOException
*/
private static Socket method01() throws IOException {
// 1.读取到浏览器端的请求信息
// 1.1 创建ServerSocket对象,指定端口号为9999
ServerSocket ss = new ServerSocket(9999);
// 1.2 调用accept()方法,接收请求,建立连接,返回Socket对象
Socket socket = ss.accept();
// 1.3 通过返回的Socket对象获取输入流,关联连接通道
InputStream is = socket.getInputStream();
// 1.4 使用输入流去读取数据
byte[] bys = new byte[8192];
int len = is.read(bys);
// 1.5 打印读取到的数据
System.out.println(new String(bys, 0, len));
/*
GET /day12/web/index.html HTTP/1.1
Host: localhost:9999
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,**;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: Idea-7071e3d8=cc177568-5581-4562-aeac-26fcb6ca7e56
*/
return socket;
}
}

访问效果:

image-20230927191353982

图解:

image-20230927191357507