Java - 网络编程 TCP 和 UDP

七层网络模型

  • OSI (Open System Interconnect),即开放式系统互联,是 ISO (国际标准化组织) 组织在 1985 年研究的网络互联模型。

  • OSI 七层模型和 TCP/P 五层划分如下:
    image-20220115231540247

  • 当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去

  • 当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来。

相关的协议

协议的概念

计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议,通信协议可以对速率、传输代码、代码结构、传输控制步骤、岀错控制等制定统 - 的标准。

TCP 协议

  • 传输控制协议(Transmission Control Protoco),是一种面向连接的协议,类似于打电话。
    • 建立连接 => 进行通信 => 断开连接
    • 在传输前采用” 三次握手” 方式。
    • 在通信的整个过程中全程保持连接,形成数据传输通道。
    • 保证了数据传翰的可靠性和有序性。
    • 是一种全双工的字节流通信方式,可以进行大数据量的传输。
    • 传输完毕后需要释放已建立的连接,发送数据的效率比较低。

TCP 三次握手

为什么需要三次握手?
在通信的整个过程中全程保持连接,形成数据传输通道。并保证了数据传翰的可靠性和有序性

image

UDP 协议

  • 用户数据报协议 (User Datagram Protocol),是一种非面向连接的协议,类似于写信。
    • 在通信的整个过程中不需要保持连接,其实是不需要建立连接。
    • 不保证数据传输的可靠性和有序性。
    • 是一种全双工的数据报通信方式,每个数据报的大小限制在 64K 内。
    • 发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快。

基于 tcp 协议的编程模型

C/S 架构的简介

  • 在 CS 模式下客户向服务器发出服努请求,服务器接收请求后提供服。
  • 例如:在一个酒店中,顾客想服务员点菜服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户,这就是一种 C/S 工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器。这种系统分工和协同工作的方式就是 C/S 的工作方式。
  • 客户端部分:为每个用户所专有的,负责执行前台功能。
  • 服务器部分:由多个用户共享的信息与功能,招待后台服务。

编程模型

  • 服务端
    1. 创建 ServerSocket 类型的对象并提供端口号;
    2. 等待客户端的连接请求,调用 accept () 方法;
    3. 使用输入输出流进行通信;
    4. 关闭 Socke;
  • 客户端
    1. 创建 Socket 类型的对象并提供服务器的 P 地址和端口号;
    2. 使用输入输出流进行通信;
    3. 关闭 Socket;

image

相关类和方法的解析

ServerSocket 类

  • java.net.ServerSocket 类主要用于描述服务器套接字信息 (大插排)。
  • 常用的方法如下:
方法声明功能介绍
ServerSocket(int port)参数指定的端口号来构造对象
Socket accept()侦听并接收到此套接字的连接请求
void close()用于关闭套接字

注意事项

  • 客户端 Socket 与服务器端 Socket 对应,都包含输入和输出流。
  • 客户端的 socket.getlnputstream () 连接于服务器 socket.getOutputStream ()
  • 客户端的 socket.getOutputStream () 连接于服务器 socket.getInputStream ()

服务器和客户端的连接

  • 服务端:

    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
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;

    public class ServerSocketTest {
    public static void main(String[] args) {
    Socket socket = null;
    ServerSocket ss = null;
    try {
    // 创建ServerSocket对象并提供端口号
    ss = new ServerSocket(8888);
    System.out.println("等待客户端连接...");
    // 当没有客户端连接时,则服务器阻塞在accept方法调用
    socket = ss.accept();
    System.out.println("客户端连接成功!");
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (null != ss) {
    ss.close();
    }
    if (null != socket) {
    socket.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
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
- 客户端:

```java
import java.io.IOException;
import java.net.Socket;

public class ClientServerTest {
public static void main(String[] args) {
Socket socket = null;

try {
// 创建Socket对象并提供服务器ip和端口号
socket = new Socket("127.0.0.1", 8888);
// 使用输入输出流进行通讯
} catch (IOException e) {
e.printStackTrace();
}finally {
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务端与客户端通信

  • 服务端
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
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class ServerSocketTest {
public static void main(String[] args) {
Socket socket = null;
ServerSocket ss = null;
Scanner scanner = null;
PrintStream ps = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
// 创建ServerSocket对象并提供端口号
ss = new ServerSocket(8888);
System.out.println("等待客户端连接...");
// 当没有客户端连接时,则服务器阻塞在accept方法调用
socket = ss.accept();
System.out.println("客户端连接成功!");
// 使用输入输出流进行通讯
// 将InputStream 转换为 Reader
isr = new InputStreamReader(socket.getInputStream());
// 使用缓冲流读取数据
br = new BufferedReader(isr);
// 输出流
ps = new PrintStream(socket.getOutputStream());

scanner = new Scanner(System.in);
while (true) {
// 接收一行消息
String s = br.readLine();
System.out.println("接收到客户端发送过来的数据:" + s);
System.out.print("请输入你要发送的消息:");
String message = scanner.next();
if (message.equalsIgnoreCase("exit")) {
System.out.println("聊天已结束");
break;
}
ps.println(message);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br) {
br.close();
}
if (null != isr) {
isr.close();
}
if (null != ps) {
ps.close();
}
if (null != ss) {
ss.close();
}
if (null != socket) {
socket.close();
}

} catch (IOException e) {
e.printStackTrace();
}
if (null != scanner) {
scanner.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
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientServerTest {
public static void main(String[] args) {
Socket socket = null;
PrintStream ps = null;
Scanner scanner = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
// 创建Socket对象并提供服务器ip和端口号
socket = new Socket("127.0.0.1", 8888);
// 输出流
ps = new PrintStream(socket.getOutputStream());
// 将输入流转换为 Reader 类型
isr = new InputStreamReader(socket.getInputStream());
// 使用缓冲流读取数据
br = new BufferedReader(isr);
while (true) {
System.out.print("请输入你要发送的信息:");
scanner = new Scanner(System.in);
String message = scanner.next();
if (message.equalsIgnoreCase("exit")) {
break;
}
// 使用输入输出流进行通讯
// 向客户端发送字符串
ps.println(message);
System.out.println("客户端发送数据内容成功!");
// 接收服务端发过来的内容
String str = br.readLine();
System.out.println("接收到服务端发送过来的数据:" + str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br) {
br.close();
}
if (null != isr) {
isr.close();
}
if (null != scanner) {
scanner.close();
}
if (null != socket) {
socket.close();
}
if (null != ps) {
ps.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

基于 udp 协议的编程模型 (熟悉)

编程模型

  • 接收方
    1. 创健 Datagram Socket 类型的对象并提供端口号
    2. 创建 Datagram Packet 类型的对象并提供缓冲区
    3. 通过 Socket 接收数据内容存放到 Packet 中,调用 receive 方法
    4. 关闭 Socket
  • 发送方
    1. 创建 DatagramSocket 类型的对象
    2. 创建 DatagramPacket 类型的对象并提供接收方的通信地址
    3. 通过 Socket 将 Packet 中的数据内容发送出去,调用 send 方法
    4. 关闭 Socket

相关类和方法的解析

Datagram Socket 类

java.net.DatagramSocket 类主要用于描述发送和接收数据报的套接字 (邮局)
换句话说,该类就是包裹投递服务的发送或接收点.

  • 常用的方法如下:
方法声明功能介绍
DatagramSocket()使用无参的方式构造对象
DatagramSocket(int port)根据参数指定的端口号来构造对象
void receive(DatagramPacket p)用于接收数据报存放到参数指定的位置
void send(DatagramPacket p)用于将参数指定的数据报发送出去
void close()关闭 Socket 并释放相关资源

DatagramPacke 类

  • java.net.DatagramPacket 类主要用于描述数据报,数据报用来实现无连接包裹投递服务。
  • 常用的方法如下:
方法声明功能介绍
DatagramPacket(byte[] buf, int length)根据参数指定的数组来构造对象,用于接收长度为 length 的数据报
DatagramPacket(byte[] buf, int length, InetAddress address, int port)根据参数指定数组来构造对象,将数据报发送到指定地址和端口
InetAddress getAddress()用于获取发送方或接收方的通信地址
int gbtPort()用于获取发送方或接收方的端口号
int getLength()用于获取发送数据或接收数据的长度

InetAddress 类

  • java.net.InetAddress 类主要用于描述互联网通信地址信息。
  • 常用的方法如下:
方法声明功能介绍
static InetAddress getLocalHost()用于获取当前主机的通信地址
static InetAddress getByName(String host)根据参数指定的主机名获取通信地址

UDP 通信实现

  • 发送方
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
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
* 发送方
*/
public class SendTest {
public static void main(String[] args) {
DatagramSocket ds = null;
try {
// 创建 DatagramSocket 对象不需要提供端口号
ds = new DatagramSocket();
// 将字符串转换成byte的数组
byte[] barr = "hello".getBytes();
// 创建 DatagramPacket 数据包对象 并提供 数组、地址和端口号
DatagramPacket dp = new DatagramPacket(barr, barr.length, InetAddress.getLocalHost(), 8888);
// 将数据包发送出去
ds.send(dp);
System.out.println("发送数据成功!");

// 接受服务端发送的信息
byte[] barr2 = new byte[20];
// 接收数据报内容 并放入byte数组中
DatagramPacket dp2 = new DatagramPacket(barr2, barr2.length);
// 接收
ds.receive(dp2);
String message = new String(barr2, 0, dp2.getLength());
System.out.println("接收回复的消息:" + message + "!");

} catch (IOException e) {
e.printStackTrace();
}finally {
if (null != ds) {
ds.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
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
* UDP 接收方
*/
public class ReceiveTest {
public static void main(String[] args) {
DatagramSocket ds = null;
try {
// 创建 DatagramSocket 对象并提供端口号
ds = new DatagramSocket(8888);
// 创建 DatagramPacket 对象并提供缓冲区
byte[] barr = new byte[20];
// 接收数据报内容 并放入byte数组中
DatagramPacket dp = new DatagramPacket(barr, barr.length);
System.out.println("等待数据...");
// 接收
ds.receive(dp);
// 转换指定长度的字符
String msg = new String(barr, 0, dp.getLength());
System.out.println("接受到的数据是:" + msg + "!");

// 回发信息给发送方
byte[] barr2 = "I receive!".getBytes();
DatagramPacket dp2 = new DatagramPacket(barr2, barr2.length, dp.getAddress(), dp.getPort());
// 发送
ds.send(dp2);
System.out.println("回复信息成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != ds) {
ds.close();
}
}

}
}

URl 类

常用的方法

方法声明功能介绍
URL(String spec)根据参数指定的字符串信息构造对象
String geProtocol()获取协议名称
String getHost()获取主机名称
int getport()获取端口号
String getPath()获取路径信息
String getFile()获取文件名
URLConnection openConnection()获取 URLConnection 类的实例

URLConnection 类

基本概念

  • java.net.URLConnection 类是个抽象类,该类表示应用程序和 URL 之间的通信链接的所有类的超类,主要实现类有支持 HTP 特有功能的 HttpURiConnection 类。

HttpURIConnection 类的常用方法

| 方法声明 | 功能介绍 |
|Inputstream getInputstream ()| 获取输入流 |
|void disconnect ()| 断开连接 |