UDP
何为UDP?
Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768描述了 UDP。
Internet 的传输层有两个主要协议,互为补充。无连接的是 UDP,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的事情。面向连接的是 TCP,该协议几乎做了所有的事情。
简单来说UDP相对于TCP它是无连接的,即发送数据前不需要建立连接,只要对方存在即可发送数据。但它却是不可靠的,因为它传输前既不需要建立通道也不需要关闭通道,只要客户端发起一次请求,服务端就会一次性把所有数据发送完毕。并且传输时它不会检查数据的完整性,在数据丢失或者数据出错时也不会要求重新传输,但因此节省了许多用于验证数据包的时间。
传输过程
实现UDP通信主要用到了两个类:DatagramPacket和DatagramSocket。
DatagramSocket
这个类用来表示发送和接收数据包的套接字
//创建一个套接字,作为本机的一个应用程序的接口(想象成通过这个套接字来访问某个应用程序吧),参数为应用程序的端口。对方发送请求时带上本机ip和端口即可
DatagramSocket socket = new DatagramSocket(PORT);
DatagramPacket
这个类用来包装传输的数据包,才用字节数组存储数据包,发送方需要提供ip和端口,而接收方只需要用数组存储即可。
//创建一个数据包,container是一个byte数组,第二个参数表示从数组的哪一个元素开始,第三个元素是长度
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String transferData = bufferedReader.readLine();
//如果出现乱码就加上StandardCharsets.UTF_8
byte[] data = transferData.getBytes();
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
过程
Server端 | Client端 | |
---|---|---|
DatagramSocket socket = new DatagramSocket(int port); | DatagramSocket socket = new DatagramSocket(int port); | |
DatagramPacket packet = new DatagramPacket(byte[] data, 0 , data.length); | DatagramPacket packet = new DatagramPacket(data, 0 , data.length, new InetSocketAddress(this.remoteIp, this.remotePort)); | |
socket.receive(packet); | socket.send(packet); | |
socket.send(packet1); | socket.receive(packet1); | |
socket.close(); | socket.close(); |
大致的过程就是我们知道了某个接口,也就是Server端的socket(UDP是没有客户端和服务端的区分的,但是为了好理解,我们就将他们分为C/S),接着Client端去“连接”服务端的接口,因为我们要得到服务端返回的数据包,所以我们自己也需要new一个socket,接着把数据放入packet再发送出去就行了,对方接收后进行处理再返回,我们再接收。
实践
既然我们已经大致了解了UDP的含义以及它的数据传输过程,那么我们就来简单地实现一下吧。
Java代码
//Server
package com.UDP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class Server implements Runnable{
private final String remoteIp;
private final int remotePort;
DatagramSocket socket;
BufferedReader bufferedReader;
public Server(String remoteIp, int remotePort, int port) {
this.remoteIp = remoteIp;
this.remotePort = remotePort;
try {
socket = new DatagramSocket(port);
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
//实现多线程的Runnable接口,并重写run方法。当然也可以直接写一个class继承Thread就行了。为什么要用多线程
//呢,因为我们是要发送和接收,因为同步的原因,发送和接收必定是有先后顺序的。所以我们不能做到任意时间发送
//和接收,因为你不可能只对一个人服务
@Override
public void run() {
while (true) {
try {
String transferData = bufferedReader.readLine();
//如果出现乱码就加上StandardCharsets.UTF_8
byte[] data = transferData.getBytes();
DatagramPacket packet = new DatagramPacket(data, 0 , data.length, new InetSocketAddress(this.remoteIp, this.remotePort));
socket.send(packet);
if(transferData.equals("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
bufferedReader.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.UDP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Client implements Runnable{
public int port;
DatagramSocket socket;
BufferedReader bufferedReader;
public Client(int port) {
this.port = port;
try {
socket = new DatagramSocket(this.port);
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
socket.receive(packet);
byte[] data = packet.getData();
String receiveData = new String(data, 0, data.length);
System.out.println(receiveData);
if(receiveData.equals("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
bufferedReader.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//ServerTest
package com.UDP;
public class ServerTest {
public static void main(String[] args) {
//将实现的接口类作为参数丢入Thread类
new Thread(new Server("localhost", 7777, 5555)).start();
new Thread(new Client(8888)).start();
}
}
//ClientTest
package com.UDP;
public class ClientTest {
public static void main(String[] args) {
//将实现的接口类作为参数丢入Thread类
new Thread(new Server("localhost", 8888, 6666)).start();
new Thread(new Client(7777)).start();
}
}