UDP(User Datagram Protocol)

UDP全称为:用户数据报协议

特点

  • 无需事先建立连接;
  • 数据不安全,容易丢;
  • 效率高;(耗费资源少)
  • 发送的数据量有大小限制;

相关类&常用方法(※)

  • 相关类:
    • DatagramSocket: 表示一个端点(套接字);如果是发送端,往往采用空参数的构造方法,而接收端必须指定固定的端口号;
    • DatagramPacket: 表示一个包装袋;如果是发送端,需要指定接收端的ip和端口号;如果是接收端,只需要指定保存数据的数组和可用长度即可;
  • 常用方法:
    • 构造方法(创建Socket对象):
      • Public DatagramSocket():创建客户端的Socket对象,系统会随机分配一个端口号
      • Public DatagramSocket(int port): 创建服务端的Socket对象,并指定端口号。
    • 构造方法(创建DatagramPacket数据包对象):
      • public DatagramPacket(byte[] arr, int length, InetAddress address, int port)
        • 创建发送的数据包对象
      • public DatagramPacket(byte[] arr, int length)
        • 创建用来接收数据的数据包对象
    • Socket实例方法(必须拿类的对象来调用):
      • public void send(DatagramPacket dp): 发送数据包
      • public void receive(DatagramPacket dp): 使用数据包接收数据
    • DatagramPacket实例方法:
      • public int getLength(): 获取数据包实际接收到的字节个数

Demo01(简单收发数据)

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
/*
练习udp入门发送数据
*/
public class myUDPSend {
public static void main(String[] args) throws Exception {
// 1, 创建发送端对象
DatagramSocket ds = new DatagramSocket();
// 2, 创建一个封装数据的对象(数据包
byte[] arr = "你好呀!".getBytes();
DatagramPacket dp = new DatagramPacket(arr, arr.length, InetAddress.getLocalHost(), 5888);
// 3, 执行发送的动作
ds.send(dp);
System.out.println("客户端发送完成");
ds.close();
}
}

/*
练习udp入门接收数据
*/
public class myUDPRecv {
public static void main(String[] args) throws Exception {
// 1,创建接收端对象(数据包
DatagramSocket ds = new DatagramSocket(5888);
// 2, 创建一个封装数据的对象(数据包
byte[] arr = new byte[1024];
DatagramPacket dp = new DatagramPacket(arr, arr.length);
// 3, 执行接收的动作, 如果发送端一直不发数据,那么这个方法将会一直阻塞
ds.receive(dp); // 接收到的数据都装到了 dp中的数组arr中了
System.out.println("接收端接收完成!");
// 解析数据
// dp.getData(); 得到的就是上面的数据,但是数组是引用数据类型,所以receive方法执行的过程中已经把数组的内容修改了,不接收返回值,也会受到影响
// 获取真是数据的有效长度
int length = dp.getLength();
String s = new String(arr, 0, length);
System.out.println("接收端收到了:" + s);
ds.close();
}
}

Demo02(持续收发数据)

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
public class udpContinueSend {
public static void main(String[] args) throws Exception {
// 1, 创建发送端对象
DatagramSocket ds = new DatagramSocket();
// 2, 创建键盘输入,准备循环输入数据
while (true) {
Scanner s = new Scanner(System.in);
System.out.println("请输入需要发送的数据(Q表示退出):");
String message = s.next();
byte[] arr = message.getBytes();
// 3, 创建发送包的对象dp(datagramPacket)只有在创建dp对象的时候才会封装端口
DatagramPacket dp = new DatagramPacket(arr, arr.length, InetAddress.getLocalHost(), 5888);
// 4, 执行发送数据的动作
ds.send(dp);
// 判断是否结束
if ("Q".equals(message)) {
System.out.println("客户端结束!");
break;
}
}
ds.close();
}
}

public class udpContinueRecv {
public static void main(String[] args) throws Exception {
// 1,创建接收端对象(这里必须绑定端口
DatagramSocket ds = new DatagramSocket(5888);
byte[] arr = new byte[1024];
// 2, 自备一个接收数据的包装袋
DatagramPacket dp = new DatagramPacket(arr, arr.length);
while (true) {
ds.receive(dp);
// 解析包装袋
String s = new String(arr, 0, dp.getLength());
System.out.println("客户端对我们说:"+s);
// 判断是否结束
if ("Q".equals(s)) {
System.out.println("服务器已经结束!");
break;
}
}
ds.close();
}
}

TCP(Transmission Control Protocol)

传输控制协议: 是一种需要事先建立连接,且安全,无数据大小限制的数据传输协议;

特点

  1. 需要事先建立连接(三次握手)
  2. 安全
  3. 无数据大小限制
  4. 耗费资源多,效率低

数据交互的原理

  • 通过IO流进行数据交互的;

相关类&常用方法(※)

  • Public Socket(String host, int port):

    • 根据指定的服务器IP,端口号,请求与服务端建立连接,连接通过就获得了客户端socket;
  • public ServerSocket(int port):

    • 为服务端程序注册端口;
  • 常用方法:

    • Public OutputStream getOutputStream:获取字节输出流对象
    • Public InputStream getInputStream:获取字节输入流对象
    • Public Socket accept(): 阻塞等待客户端的连接请求,一旦连接成功,则返回服务器端这边的socket对象。

Demo01(入门案例)

image-20240422220931154

Demo02(聊天案例)

  • 思路

    • 在客户端和服务器端各自准备两个线程,相互循环读写数据即可;
  • 代码

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
tcp客户端
*/
public class TCPClient {
public static void main(String[] args) throws Exception {
// 1: 创建客户端对象,需要指定和哪个服务器及端口号,建立3次握手
Socket s = new Socket(InetAddress.getLocalHost(), 58888);
DataInputStream dis = new DataInputStream(s.getInputStream());
OutputStream outputStream = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(outputStream);
// 开启两个线程,循环读
Thread du = new Thread(() -> {
try {
// 从s中获取字节输入流,转成数据流
while (true) {
String s1 = dis.readUTF();
System.out.println("我是客户端,我们收到服务器的内容是:" + s1);
if ("0".equals(s1)) {
System.out.println("客户端读数据已退出");
break;
}
}
//dis.close();
} catch (Exception e) {
e.printStackTrace();
}
});
du.start();


//开启两个线程,循环写
// 2: 通过io与服务器进行数据交互
Thread xie = new Thread(() -> {
try {
// 3: 转成数据流
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("我是客户端,请输入您想对服务器说的话:(0表示结束)");
String s1 = sc.nextLine();
dos.writeUTF(s1);
if ("0".equals(s1)) {
System.out.println("客户端写已退出");
Thread.sleep(1000);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
xie.start();
xie.join();
du.join();
// 5: 释放资源 所有我们自己new的对象,自己关
dos.close();
dis.close();
s.close();
System.out.println("mian结束了...");
}
}


/*
TCP服务器端
*/
public class TCPServer {
public static void main(String[] args) throws Exception {
// 1: 创建服务器
ServerSocket ss = new ServerSocket(58888);
// 2: 使用一个方法,接受客户端的请求;(完成三次握手)
Socket s = ss.accept();
OutputStream outputStream = s.getOutputStream();
// 3: 转成数据流
DataOutputStream dos = new DataOutputStream(outputStream);
DataInputStream dis = new DataInputStream(s.getInputStream());
Thread du = new Thread(() -> {
try {
// 从s中获取字节输入流,转成数据流
while (true) {
String s1 = dis.readUTF();
System.out.println("我是服务端,我们收到客户端的内容是:" + s1);
if ("0".equals(s1)) {
System.out.println("服务端读数据已退出");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
du.start();


//开启两个线程,循环写
// 2: 通过io与服务器进行数据交互
Thread xie = new Thread(() -> {
try {

Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("我是服务端,请输入您想对客户端说的话:(0表示结束)");
String s1 = sc.nextLine();
dos.writeUTF(s1);
if ("0".equals(s1)) {
System.out.println("服务端写已退出");
Thread.sleep(1000);
break;
}
}

} catch (Exception e) {
e.printStackTrace();
}
});
xie.start();
xie.join();
du.join();
// 5: 释放资源 所有我们自己new的对象,自己关
dis.close();
dos.close();
s.close();
ss.close();

}
}

Demo03(模拟BS架构的服务器)

  • 思路

    • 只需要按HTTP协议的格式,给浏览器响应对应的数据即可;
  • 代码

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
/*
模拟一个可以对浏览器做响应的服务器

响应的内容是: 黑马程序员666

*/
public class MyServer {
public static void main(String[] args) throws IOException {
// 1: 创建服务器,监听端口
ServerSocket ss = new ServerSocket(8099);
System.out.println("服务器已经启动成功!");
// 2: 死循环,一直等待客户端的连接
while (true){
System.out.println("服务器等待连接中........");
Socket accept = ss.accept();
//通过 accept 对象,获取客户端ip地址对象
System.out.println(accept.getRemoteSocketAddress().toString()+"来请求我了...");
// 为了提升客户端的体验,可以使用多线程给每个客户端单独响应
new Thread(()->{
try {
// 从 accept 中获取输出流,包装成打印流,并给浏览器响应数据
PrintStream ps = new PrintStream(accept.getOutputStream());
ps.println("HTTP/1.1 200 halsshao");
ps.println("Content-Type:text/html;charset=utf-8");
ps.println();
ps.println("<div style=\"color: aqua;font-size: 30px;text-align: center;border: 2px solid green\">为所欲为</div>");
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}

Demo04(群聊案例)

思路

在客户端准备两个线程,分别循环读写数据;
在服务器端,准备一个集合,每个客户端连接之后,都先将客户端对象添加到集合中;
创建一个线程,循环读客户端数据,每次读到数据之后,都遍历集合中的每个对象,分别写出去即可;

代码

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/*
tcp客户端
*/
public class TCPClient {
public static void main(String[] args) throws Exception {
// 1: 创建客户端对象,需要指定和哪个服务器及端口号,建立3次握手
Socket s = new Socket(InetAddress.getByName("192.168.31.117"), 56666);
DataInputStream dis = new DataInputStream(s.getInputStream());
OutputStream outputStream = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(outputStream);
// 开启两个线程,循环读
Thread du = new Thread(() -> {
try {
// 从s中获取字节输入流,转成数据流
while (true) {
String s1 = dis.readUTF();
System.out.println("我是客户端,我们收到服务器的内容是:" + s1);
}
//dis.close();
} catch (Exception e) {
//e.printStackTrace();
}
});
du.start();


//开启两个线程,循环写
// 2: 通过io与服务器进行数据交互
Thread xie = new Thread(() -> {
try {
// 3: 转成数据流
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("我是客户端,请输入您想对服务器说的话:(0表示结束)");
String s1 = sc.nextLine();
dos.writeUTF(s1);
if ("0".equals(s1)) {
System.out.println("客户端写已退出");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
xie.start();
xie.join();
// 让读的线程直接停止即可
du.stop();
// 5: 释放资源 所有我们自己new的对象,自己关
dos.close();
dis.close();
s.close();
System.out.println("mian结束了...");
}
}

/*
服务器端的线程任务
*/
public class MyRun implements Runnable {

private Socket s;
private ArrayList<Socket> all;

public MyRun(Socket s, ArrayList<Socket> all) {
this.s = s;
this.all = all;
}

@Override
public void run() {
try {
// 循环读
DataInputStream dis = new DataInputStream(s.getInputStream());
while (true) {
String s1 = dis.readUTF();
// 收到当前这个客户端的信息之后,需要遍历 all ,给里面的每个socket都发一次信息,内容就是 s1
for (int i = all.size() - 1; i >= 0; i--) {
Socket qt = all.get(i);
// 给 qt 写1次数据即可
DataOutputStream dos = new DataOutputStream(qt.getOutputStream());
// 判断一下,当前s客户端,说的话是什么,然后再给其他客户端响应
if ("0".equals(s1)) {
// 通知其他人
dos.writeUTF(s.getRemoteSocketAddress().toString() + "要下线了...."+"下线后的在线人数是:"+(all.size()-1));
} else {
dos.writeUTF(s.getRemoteSocketAddress().toString()+"说:"+s1+"当前在线人数是:"+all.size());
}
}
}
} catch (Exception e) {
all.remove(s);
System.out.println(s.getRemoteSocketAddress().toString() + "下线了...");
}
}
}

/*
群聊的服务器
*/
public class MyServer {
public static void main(String[] args) throws Exception {
// 1: 创建服务器对象
ServerSocket ss = new ServerSocket(56666);
// 需要提前准备一个容器,专门用于保存所有的客户端对象
ArrayList<Socket> all = new ArrayList<>();
// 2: 循环等待客户端连接
while (true) {
Socket s = ss.accept();
// 将 s 添加到 all
all.add(s);
System.out.println(s.getRemoteSocketAddress().toString() + "上线了...");
// 开启一个线程,专门读 s 的内容
new Thread(new MyRun(s,all)).start();
}
}
}