网络编程

网络编程是什么?

在网络通信协议下,不同计算机上进行的编写程序,进行相互的数据交互。

一 网络的编程概念

1 网络通信

(1)概念

两台设备之间通过网络实现数据传输;

(2)网络通信

将数据通过网络从一台设备传输到另一台设备;

Java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信。

2 网络

(1)概念

两台或多台设备通过一定物理设备连接起来构成了网络。

(2)基于网络的覆盖范围进行分类

局域网:覆盖范围最小,仅仅覆盖一个教师或一个机房;

城域网:覆盖范围较大,可以覆盖一个城市;

广域网:覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表。

3 IP地址

(1)概念:用于唯一表示网络中的每台计算机/主机;

(2)查看ip地址:ipconfig;

(3)ip地址的表示形式:点分十进制 xx.xx.xx.xx;

(4)每一个十进制数的范围:0-255;

(5)ip地址的组成 = 网络地址 + 主机地址 ,比如:192.128.16.69;

(6)IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号可以称为全世界的每一粒沙子编上一个地址;

(7)由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍。

总结:ip地址最大的意义就是确定了网络中的唯一主机!(相当于主机在互联网中的逻辑地址)

4 域名

(1)示例

www.baidu.com

(2)好处

为了方便记忆,解决记IP的困难。

(3)概念

将IP地址映射成域名。

总结:ip地址不好记,所以把IP地址映射为域名,域名跟ip地址的本质也是k-v键值对,输入域名,获取到值ip地址(透过DNS解析返回ip),从而可以访问该网站。

5 端口号

(1)概念

用于表示计算机上某个特定的网络程序。

(2)表示形式

以整数形式,端口范围065535【2个字节表示端口0(2的16次方)-1】

(3)常见的网络程序端口号

0~1024已经被占用,比如ssh 22,ftp 21,smtp 25,http 80;

tomcat 8080,mysql 3306,oracle 1521,sqlserver 1433。

总结:端口号指定了主机中的某个程序,到底跟哪一个程序进行网络通讯取决于端口号的选择。跟tomcat通讯,端口号就填8080。

6 网络通信协议

(1)协议(tcp/ip)

TCP/IP(Transmission Control Protocol/Internet Protocol)的简写。中文名为传输控制协议/因特网互联协议,又叫网络通讯协议、这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单来说,就是由网络层的IP协议和传输层的TCP协议组成的。
还有很多其他的传输层协议比如TLS、QUIC等等(自行了解)。

(2) OSI模型(Open System Interconnection Reference Model)

OSI七层模型如图,OSI七层模型只是作为参考,现实上我们用的更多的是OSI四层模型
Alt text
OSI四层模型
Alt text
然后我们还要了解一下传输过程中数据的层层封装形式,为了方便理解后面的传输过程的详解做个准备。
Alt text
接下来是比较实际的部分,我们来研究一下通信的流程。比如客户端要发送数据给服务器的完整流程。

首先是应用层的应用数据,也就是我们常说的报文,客户端向服务器准备发送这部分数据。

Alt text
这里得说明一下数据发送之前是向下层层封装的,接收的话就反过来向上拆封。所有的网络数据最终都要通过物理层进行传输。

有了报文,接下来就来到了传输层,传输层会在报文的基础上添加上源端口+目标端口,再次封装成段

Alt text

再下来就到了网络层,网络层会在段的基础上添加源ip地址+目标ip地址,封装成包

Alt text

到了数据链路层,如果不是同一网段的情况下,客服端主机首次给服务器发送数据的话,需要先知道默认网关MAC地址才能封装成帧,再由默认网关处理后才能向互联网发送数据进行网络传输。所以需要客户端与默认网关进行一次通讯。让默认网关知道客户端的MAC地址和ip地址,将其关联为一台主机。此时客户端主机会先将上面的包数据先暂时缓存,与默认网关通信后,再重新进行发送。

首先客户端将会给默认网关发送包数据(客户端ip+网关ip),再加上自己的MAC地址+目标MAC(此处是广播MAC,全为0)封装成帧,使用ARP协议进行广播,通过交换机在局域网中所有主机进行发送此以太帧。
Alt text
接收到以太帧数据的默认网关,将会对帧数据进行拆封,获取客户端的MAC地址,再将帧数据里的包进行拆封,获取到客户端ip和目标ip,发现目标ip跟自己的一模一样,说明是发送给自己的。此时默认网关就知道了客户端IP地址+客户端MAC地址,将其关联为一台主机(将逻辑地址即ip和物理地址即MAC对应起来。本质也是逻辑对物理的映射,以ARP表的形式暂时保存下来。)。默认网关接下来就可以客户端发送报包数据(网关ip+客户端ip),再加上默认网关的MAC地址+客户端MAC地址,进行单播响应。至此,客户端就知道了默认网关的ip地址和MAC地址了。可以继续封装成帧向默认网关发送数据,再由默认网关向互联网发送数据。
Alt text

接下里回到正题,继续发送之前的包数据,在包数据的基础上添加源MAC+目标MAC(此时目标MAC为默认网关MAC),默认网关接收到该以太帧之后拆封该以太帧,发现是发给自己的,再进行包拆封,发现目标ip在另一个网络中的,就会将该包数据发到互联网上进行路由转发。路由转发就通过物理层进行实现。所有网络数据最终都是通过光纤或者海底光缆形成互联网才得以进行数据传输。

Alt text
Alt text

当包数据在互联网上路由转发到服务器网关上的时候,服务器网关此时就会查看自己的ARP表,如果有目标主机的MAC地址,就可以直接封装成以太帧直接发送给目标主机。如果ARP表没有目标ip映射的MAC地址,也是在其局域网内使用广播,通过ARP协议获取目标主机的MAC地址之后,在将该包封装成以太帧再发送给目标主机。

Alt text

当服务器接收到该以太帧数据之后,拆封以太帧,发现包中有自己的ip,发现是发送给自己的(如果不是发给自己的包都丢弃,并更新ARP表——添加客户端主机的映射),再进行段拆封,获取到客户端口号和目标端口号,通过目标端口号,把该报文发送到服务器主机目标端口号上的应用程序上。

Alt text

应用程序处理完报文之后,将报文(应用数据)返回给客户端主机进行响应,返回的步骤也是一样的。

视频参考:https://www.bilibili.com/video/BV1EU4y1v7ju/?spm_id_from=333.999.0.0&vd_source=f242f681c7b75232ac749b73552a2012

至此,一个数据传输流程大致就这样了。如果在同一网络内,比如局域网,有二层交换机的话。可以直接交换机进行数据传输。不在同一网络内就需要走上述数据传输流程。

7 TCP和UDP

(1)TCP协议(传输控制协议)

  • 使用TCP协议前,须先建立TCP连接,形成传输数据通道;

  • 传输前,采用“三次握手”方式,确认是可靠的;

  • TCP协议进行通信的两个应用进程:客户端、服务端;

  • 在连接中可进行大数据量的传输;

  • 传输完毕,需释放已建立的连接,效率低。

(2)UDP协议(用户数据协议)

  • 将数据、源、目的封装成数据包,不需要建立连接;

  • 每个数据报的大小限制在64K内,不适合传输大量数据;

  • 因无需连接,故是不可靠的;

  • 发送数据结束时无需释放资源(因为不是面向连接的),速度快;

视频参考:https://www.bilibili.com/video/BV1kV411j7hA/?spm_id_from=333.788.recommend_more_video.1&vd_source=f242f681c7b75232ac749b73552a2012

二 InetAddress类

1 相关方法

(1)获取本机InetAddress对象 getLocalHost;

(2)根据指定主机名/域名获取IP地址对象,getByName;

(3)获取InetAddress对象的主机名 getHostName;

(4)获取InetAddress对象的地址 getHostAddress。

2 应用案例

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
public class API_ {
public static void main(String[] args) throws UnknownHostException {

//1. 获取本机的InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1

//2. 根据指定主机名 获取 InetAddress对象
InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S");
System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1

//3. 根据域名返回 InetAddress对象, 比如 www.baidu.com 对应
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4

//4. 通过 InetAddress 对象,获取对应的地址
String hostAddress = host2.getHostAddress();//IP 110.242.68.4
System.out.println("host2 对应的ip = " + hostAddress);//110.242.68.4

//5. 通过 InetAddress 对象,获取对应的主机名/或者的域名
String hostName = host2.getHostName();
System.out.println("host2对应的主机名/域名=" + hostName); // www.baidu.com

}
}

三 Socket套接字(ip地址+端口号)

1 基本介绍

(1)套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准;

(2)通讯的两端都要有Socket,是两台机器之间的通讯端点;

(3)网络通信其实就是Socket间的通信;

(4)Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输;

(5)一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端。
Alt text

2 TCP网络通信编程

(1)基本介绍

  • 基于客户端-服务端的网络通信;

  • 底层使用的是TCP/IP协议;

  • 应用场景举例:客户端发送数据,都无端接受并显示控制台;

  • 基于Sockte的TCP编程。
    (2)应用实例·
    Alt text
    Alt text

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //Client类
    /**
    * 客户端,发送 "hello, server" 给服务端
    */
    public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
    //思路
    //1. 连接服务端 (ip , 端口)
    //解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
    Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
    System.out.println("客户端 socket返回=" + socket.getClass());
    //2. 连接上后,生成Socket, 通过socket.getOutputStream()
    // 得到 和 socket对象关联的输出流对象
    OutputStream outputStream = socket.getOutputStream();
    //3. 通过输出流,写入数据到 数据通道
    outputStream.write("hello, server".getBytes());
    //4. 关闭流对象和socket, 必须关闭
    outputStream.close();
    socket.close();
    System.out.println("客户端退出.....");
    }
    }
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
//Server类
/**
* 服务端
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续

Socket socket = serverSocket.accept();

System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度,显示内容.
}
//5.关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();//关闭

}
}

(3)应用实例2
Alt text
Alt text

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
//Client类
/**
* 客户端,发送 "hello, server" 给服务端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回=" + socket.getClass());
//2. 连接上后,生成Socket, 通过socket.getOutputStream()
// 得到 和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello, server".getBytes());
// 设置结束标记
socket.shutdownOutput();

//4. 获取和socket关联的输入流. 读取数据(字节),并显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}

//5. 关闭流对象和socket, 必须关闭
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}
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
//Server类
//**
* 服务端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续

Socket socket = serverSocket.accept();

System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度,显示内容.
}
//5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, client".getBytes());
// 设置结束标记
socket.shutdownOutput();

//6.关闭流和socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();//关闭

}
}

(4)应用实例3【使用字符流】
Alt text
Alt text

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
//Client类
/**
* 客户端,发送 "hello, server" 给服务端, 使用字符流
*/
@SuppressWarnings({"all"})
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回=" + socket.getClass());
//2. 连接上后,生成Socket, 通过socket.getOutputStream()
// 得到 和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道, 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello, server 字符流");
bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束, 注意,要求对方使用readLine()!!!!
bufferedWriter.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道


//4. 获取和socket关联的输入流. 读取数据(字符),并显示
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);

//5. 关闭流对象和socket, 必须关闭
bufferedReader.close();//关闭外层流
bufferedWriter.close();
socket.close();
System.out.println("客户端退出.....");
}
}
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
//Server类
/**
* 服务端, 使用字符流方式读写
*/
@SuppressWarnings({"all"})
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续

Socket socket = serverSocket.accept();

System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取, 使用字符流, 老师使用 InputStreamReader 将 inputStream 转成字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);//输出

//5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
// 使用字符输出流的方式回复信息
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello client 字符流");
bufferedWriter.newLine();// 插入一个换行符,表示回复内容的结束
bufferedWriter.flush();//注意需要手动的flush


//6.关闭流和socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();//关闭

}
}

(5)应用实例4
Alt text
Alt text

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
//Client类
/**
* 文件上传的客户端
*/
public class TCPFileUploadClient {
public static void main(String[] args) throws Exception {

//客户端连接服务端 8888,得到Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//创建读取磁盘文件的输入流
//String filePath = "e:\\qie.png";
String filePath = "e:\\abc.mp4";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));

//bytes 就是filePath对应的字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);

//通过socket获取到输出流, 将bytes数据发送给服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);//将文件对应的字节数组的内容,写入到数据通道
bis.close();
socket.shutdownOutput();//设置写入数据的结束标记

//=====接收从服务端回复的消息=====

InputStream inputStream = socket.getInputStream();
//使用StreamUtils 的方法,直接将 inputStream 读取到的内容 转成字符串
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);


//关闭相关的流
inputStream.close();
bos.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
//Server类
/**
* 文件上传的服务端
*/
public class TCPFileUploadServer {
public static void main(String[] args) throws Exception {

//1. 服务端在本机监听8888端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在8888端口监听....");
//2. 等待连接
Socket socket = serverSocket.accept();


//3. 读取客户端发送的数据
// 通过Socket得到输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//4. 将得到 bytes 数组,写入到指定的路径,就得到一个文件了
String destFilePath = "src\\abc.mp4";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();

// 向客户端回复 "收到图片"
// 通过socket 获取到输出流(字符)
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("收到图片");
writer.flush();//把内容刷新到数据通道
socket.shutdownOutput();//设置写入结束标记

//关闭其他资源
writer.close();
bis.close();
socket.close();
serverSocket.close();
}
}

3 UDP网络通信编程

(1)基本介绍
Alt text
(2)基本流程
Alt text
(3)示意图
Alt text
(4)应用案例
Alt text
Alt text

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
//ReceiverA类
/**
* UDP接收端
*/
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在9999接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet对象
//老师提示: 当有数据包发送到 本机的9999端口时,就会接收到数据
// 如果没有数据包发送到 本机的9999端口, 就会阻塞等待.
System.out.println("接收端A 等待接收数据..");
socket.receive(packet);

//4. 可以把packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
byte[] data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);


//===回复信息给B端
//将需要发送的数据,封装到 DatagramPacket对象
data = "好的, 明天见".getBytes();
//说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);

socket.send(packet);//发送

//5. 关闭资源
socket.close();
System.out.println("A端退出...");

}
}
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
//UDPSenderB类
/**
* 发送端B ====> 也可以接收数据
*/
@SuppressWarnings({"all"})
public class UDPSenderB {
public static void main(String[] args) throws IOException {

//1.创建 DatagramSocket 对象,准备在9998端口 接收数据
DatagramSocket socket = new DatagramSocket(9998);

//2. 将需要发送的数据,封装到 DatagramPacket对象
byte[] data = "hello 明天吃火锅~".getBytes(); //

//说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);

socket.send(packet);

//3.=== 接收从A端回复的信息
//(1) 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//(2) 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet对象
//老师提示: 当有数据包发送到 本机的9998端口时,就会接收到数据
// 如果没有数据包发送到 本机的9998端口, 就会阻塞等待.
socket.receive(packet);

//(3) 可以把packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);

//关闭资源
socket.close();
System.out.println("B端退出");
}
}

HTTP (Hypertext Transfer Protocol)

HTTP,超文本传输协议,它是基于TCP协议的应用层传输协议,简单来说就是客户端和服务端进行数据传输的一种规则。
视频参考:https://www.bilibili.com/video/BV1zb4y127JU/?spm_id_from=333.999.0.0&vd_source=f242f681c7b75232ac749b73552a2012

HTTP URL

HTTP URL 包含了用于查找某个资源的详细信息, 格式如下:

HTTP请求

ALTText
http请求由请求行,请求头(消息报头),请求体(请求正文)三部分构成。

请求行

请求行由请求Method, URL 字段和HTTP Version三部分构成, 总的来说请求行就是定义了本次请求的请求方式, 请求的地址, 以及所遵循的HTTP协议版本例如:
GET /example.html HTTP/1.1 (CRLF)

HTTP协议的方法有: GET: 请求获取Request-URI所标识的资源 POST: 在Request-URI所标识的资源后增加新的数据 HEAD: 请求获取由Request-URI所标识的资源的响应消息报头 PUT: 请求服务器存储或修改一个资源,并用Request-URI作为其标识 DELETE: 请求服务器删除Request-URI所标识的资源 TRACE: 请求服务器回送收到的请求信息,主要用于测试或诊断 CONNECT: 保留将来使用 OPTIONS: 请求查询服务器的性能,或者查询与资源相关的选项和需求

请求头

请求头由一系列的键值对组成,允许客户端向服务器端发送一些附加信息或者客户端自身的信息,主要包括:

Header 解释 示例
Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip
Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes
AuthorizationHTTP 授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
Connection 表示是否需要持久连接。 (HTTP 1.1默认进行持久连接)Connection: close
CookieHTTP 请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new;
Content-Length 请求的内容长度 Content-Length: 348
Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded
Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 请求的特定的服务器行为 Expect: 100-continue
From 发出请求的用户的Email From: user@email.com
Host 指定请求的服务器的域名和端口号 Host: www.rfc-editor.org
If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10
Pragma 用来包含实现特定的指令 Pragma: no-cache
Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只请求实体的一部分,指定范围 Range: bytes=500-999
Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: www.rfc-editor.org/rfc/rfc9110.html
TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5Upgrade向服务器指定某种传输协议以便服务器进行转换(如果支持)Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11User-Agent
User-Agent 的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning

请求体

只有在发送POST请求时才会有请求正文,GET方法并没有请求正文。
完整Http请求格式如下
ALTText

HTTP响应

与HTTP请求类似,先上一张图:
Alt text
HTTP响应也由三部分组成,包括响应行,响应头(消息报头),响应体(响应正文)。

HTTP响应行

状态行也由三部分组成,包括HTTP协议的版本,状态码,以及对状态码的文本描述。例如:
HTTP/1.1 200 OK (CRLF)

HTTP响应状态码

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

  • 1xx:指示信息 - 表示请求已接收,继续处理
  • 2xx:成功 - 表示请求已被成功接收、理解、接受
  • 3xx:重定向 - 要完成请求必须进行更进一步的操作
  • 4xx:客户端错误 - 请求有语法错误或请求无法实现 *
  • 5xx:服务器端错误 - 服务器未能实现合法的请求

常见状态代码、状态描述、说明:

  • 200: OK - 客户端请求成功
  • 400: Bad Request - 客户端请求有语法错误,不能被服务器所理解
  • 401: Unauthorized - 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403: Forbidden - 服务器收到请求,但是拒绝提供服务
  • 404: Not Found - 请求资源不存在,eg:输入了错误的URL
  • 500: Internal Server Error - 服务器发生不可预期的错误
  • 503: Server Unavailable - 服务器当前不能处理客户端的请求,一段时间后,可能恢复正常

响应头

响应头由一系列的键值对组成,允许服务器端向客户端发送一些附加信息或者服务器端自身的信息,主要包括

Header 解释 示例
Accept-Ranges 表明服务器是否支持指定范围请求及哪种类型的分段请求 Accept-Ranges: bytes
Age 从原始服务器到代理缓存形成的估算时间(以秒计,非负) Age: 12
Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD
Cache-Control 告诉所有的缓存机制是否可以缓存及哪种类型 Cache-Control: no-cache
Content-Encoding web服务器支持的返回内容压缩编码类型。 Content-Encoding: gzip
Content-Language 响应体的语言 Content-Language: en,zh
Content-Length 响应体的长度 Content-Length: 348
Content-Location 请求资源可替代的备用的另一地址 Content-Location: /index.html
Content-MD5 返回资源的MD5校验值 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range 在整个返回体中本部分的字节位置 Content-Range: bytes 21010-47021/47022
Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8
Date 原始服务器消息发出的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag 请求变量的实体标签的当前值 ETag: “737060cd8c284d8af7ad3082f209582d”
Expires 响应过期的日期和时间 Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified 请求资源的最后修改时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 Location: www.rfc-editor.org/rfc/rfc9110.html
Pragma 包括实现特定的指令,它可应用到响应链上的任何接收方 Pragma: no-cache
Proxy-Authenticate 它指出认证方案和可应用到代理的该URL上的参数 Proxy-Authenticate: Basic
refresh 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) Refresh: 5; url=www.zcmhi.com/archives/94…
Retry-After 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 Retry-After: 120
Server web服务器软件名称 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer 指出头域在分块传输编码的尾部存在 Trailer: Max-Forwards
Transfer-Encoding 文件传输编码 Transfer-Encoding:chunked
Vary 告诉下游代理是使用缓存响应还是从原始服务器请求 Vary: *
Via 告知代理客户端响应是通过哪里发送的 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 警告实体可能存在的问题 Warning: 199 Miscellaneous warning
WWW-Authenticate 表明客户端请求实体应该使用的授权方案 WWW-Authenticate: Basic

Content-Type常见的值

在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。
常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

另外一种常见的媒体格式是上传文件之时使用的:

  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

以上就是我们在日常的开发中,经常会用到的若干content-type的内容格式。

响应体

响应头之后紧跟着一个空行,然后接响应体。响应体就是Web服务器发送到客户端的实际内容。除网页外,响应体还可以是诸如Word、Excel或PDF等其他类型的文档,具体是哪种文档类型由Content-Type指定的MIME类型决定。
完整Http响应格式如下:
Alt text

HTTP协议详解

HTTP的五大特点

  • 支持客户/服务器模式。
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。早期这么做的原因是请求资源少,追求快。后来通过Connection: Keep-Alive实现长连接
  • 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

非持久连接和持久连接

在实际的应用中,客户端往往会发出一系列请求,接着服务器端对每个请求进行响应。对于这些请求|响应,如果每次都经过一个单独的TCP连接发送,称为非持久连接。反之,如果每次都经过相同的TCP连接进行发送,称为持久连接。

非持久连接在每次请求|响应之后都要断开连接,下次再建立新的TCP连接,这样就造成了大量的通信开销。例如前面提到的往返时间(RTT) 就是在建立TCP连接的过程中的代价。

非持久连接给服务器带来了沉重的负担,每台服务器可能同时面对数以百计甚至更多的请求。持久连接就是为了解决这些问题,其特点是一直保持TCP连接状态,直到遇到明确的中断要求之后再中断连接。持久连接减少了通信开销,节省了通信量。

HTTP和HTTPS

HTTP的不足

  • 通信使用明文(不加密),内容可能会被窃听
  • 不验证通信方的身份,因此有可能遭遇伪装
  • 无法证明报文的完整性,所以有可能已遭篡改

HTTPS介绍

视频参考:https://www.bilibili.com/video/BV1KY411x7Jp/?spm_id_from=333.788&vd_source=f242f681c7b75232ac749b73552a2012
HTTPS 即”HTTP over SSL”或”HTTP over TLS”。
HTTP 协议中没有加密机制,但可以通 过和 SSL(Secure Socket Layer, 安全套接层 )或 TLS(Transport Layer Security, 安全层传输协议)的组合使用,加密 HTTP 的通信内容。属于通信加密,即在整个通信线路中加密。

HTTP + 加密 + 认证 + 完整性保护 = HTTPS(HTTP Secure )
Alt text

对称加密与非对称加密

什么是对称加密#
对称加密即加密与解密都使用相同的密钥。例如文件压缩设置密码就是一个很好的例子,使用密码加密压缩文件,使用相同的密码解压缩文件。

什么是非对称加密#
与对称加密相对,非对称加密即加密与解密使用不同的密钥对。密钥对包含公钥和私钥,通常公钥是公开的,而私钥是私人持有的,一般公钥用于加密,而私钥用于解密。相比于对称加密而言,非对称加密的性能会差很多。

单纯使用对称加密不可行,存在密钥泄露和易破解的缺点。
单纯使用非对称加密也不可行,对于不重要的文件也进行非对称加密是很不合理的。

Alt text
HTTPS 采用共享密钥加密(对称)和公开密钥加密(非对称)两者并用的混合加密机制。若密钥能够实现安全交换,那么有可能会考虑仅使用公开密钥加密来通信。但是公开密钥加密与共享密钥加密相比,其处理速度要慢。

所以应充分利用两者各自的优势, 将多种方法组合起来用于通信。 在交换密钥阶段使用公开密钥加密方式,之后的建立通信交换报文阶段 则使用共享密钥加密方式。
Alt text

HTTPS握手过程的简单描述如下:
Alt text

1.客户端(浏览器)将自己支持的一套加密规则发送给服务器(网站)。
服务器获得浏览器公钥
2.服务器(网站)从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。(私钥是永远都不会发出去的) 浏览器获得服务器公钥
3.获得服务器(网站)证书之后浏览器要做以下工作:
(a). 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
(b). 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码(接下来通信的密钥),并用证书中提供的公钥加密(共享密钥加密)。
(c) 使用约定好的HASH计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。 浏览器验证证书 -> 自己生成随机密码(预主密钥),并使用服务器的公钥加密 -> (加密后的预主密钥) -> 服务器
4.服务器(网站)接收浏览器发来的数据之后要做以下的操作:
(a). 使用自己的私钥将加密后的预主密钥进行解密取出预主密钥(因为公钥加密的消息能用私钥解密),使用预主密钥解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
(b). 使用预主密钥加密一段握手消息,发送给浏览器。
服务器用自己的私钥解出随机密码 -> 用预主密钥解密握手消息(共享密钥通信,就是之后两边通信的密钥,两边都有且一模一样的,也是参考视频里说得会话密钥)-> 验证HASH与浏览器是否一致(验证浏览器)
HTTPS的不足
5.加密解密过程复杂,导致访问速度慢
6.加密需要认向证机构付费
7.整个页面的请求都要使用HTTPS
(简而言之,总体就是先进行非对称加密、认证、生成共享密钥,进行对称加密通信)

HTTPS的特点

  • 兼容性(兼容HTTP)
  • 可扩展性(SSL/TLS可以与其他应用层协议使用,如ftp,smtp,telnet协议等)
  • 保密性(防嗅探/偷窥)
  • 完整性(防纂改)
  • 真实性(防假冒,如dns污染等)

To be continued….