UDP编程基本概念
TCP/IP协议栈中,TCP协议和UDP协议的联系和区别?
联系:TCP和UDP是TCP/IP协议栈中传输层
的两个协议,它们使用网络层功能
把数据包
发送到目的地,从而为应用层提供网络服务。
区别:
1. TCP是面向连接
的传输。UDP是无连接
的传输。
2. TCP保证数据按照发送顺序
到达,UDP无法保证。
3. TCP是可靠性
传输,而UDP则是不可靠
传输。
4. UDP因为少了很多控制信息,所以传输速度比TCP速度快。
5. TCP适合用于传输大量数据,UDP适合用于传输小量数据。
6. Tcp是面向字节流
的,udp是面向报文
的;
7. Tcp只支持点对点通信
,udp支持一对一,一对多,多对多
的通信模式;
8. Tcp有拥塞控制机制
;udp没有拥塞控制
,适合媒体通信;
9. Tcp首部开销(20个字节)比udp的首部开销(8个字节)要大
举例: TCP的server和client之间通信就好比两个人打电话
。UDP的server和client之间的通信就像两个人发电报或者发短信
。
- TCP 是一种可靠的协议 —— 首先客户端和服务端需要建立连接(三次“握手”),数据发送完毕需要断开连接(四次“挥手”);如果发送数据时数据损坏或者丢失,那么 TCP 会重新发送。保证可靠的代价就是效率的降低(建立连接和断开连接就需要时间,保证数据的可靠性也需要额外的消耗)。
- 与 TCP 相对应,UDP 是面向无连接的协议,并且它不保证数据是否会到达,也不保证到达的数据是否准确和数据顺序是否正确 —— 所以相比于 TCP, UDP 的速度很快。在
不需要建立连接即可发送数据
的系统,或者保证最快的传输速度比每一位数据都正确更重要
的系统(如视频会议,丢失某个数据包只是一个画面或者声音的小干扰)中,UDP 才是正确的选择。实际上,在同一个网段,或者在信号很好的局域网,UDP 是非常可靠的。
UDP通讯协议的特点
- 将数据集封装为
数据包
,面向无连接
。 - 每个数据包大小限制在
64K
中 - 因为
无连接
,所以不可靠
- 因为
不需要建立连接
,所以速度快
5.udp 通讯是不分服务端与客户端的
,只分发送端与接收端。
使用UDP的协议:
DNS
:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。SNMP
:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。TFTP(Trival File Transfer Protocal)
:简单文件传输协议,该协议在熟知端口69上使用UDP服务。- 总的来说,相比于TCP而言,
UDP不如其广泛,但是在需要很强的实时交互性场合,如网络游戏和视频会议方面,UDP相对重要。
因为UDP协议不需要维持连接的开销,支持一对多。多对多的通信模式。
如果说TCP类似于打电话,而UDP就相当于发短信。 - 在网络编程中,必须要求可
靠数据传输的信息一般使用TCP
,而一般的数据使用UDP实现。
Java中的UDP编程实现
-
和TCP编程相比,UDP编程就简单得多,
因为UDP没有创建连接,数据包也是一次收发一个,所以没有流的概念。
-
而UDP网络编程服务端实现和TCP方式的服务器端实现类似,也是
服务器端监听某个端口,然后获得数据报文包,进行逻辑处理后将处理以后的结果反馈给客户端,最后关闭网络连接。
-
同TCP一样,java的
java.net
包中,也提供了两个类DatagramSocket
和DatagramPacket
来支持UDP的数据包(Datagram)通信。 -
其中
DatagramSocket
用于在程序之间建立传送数据包的通信通道
,DatagramPacket
则用来表示一个数据包。
,DatagramSocket发送的每个数据包都需要指定地址
,而DatagramPacket则是在首次创建时指定地址,以后所有数据包的发送都通过此socket。
-
UDP的客户端编程也是4个部分:
建立连接、发送数据、接受数据和关闭连接
。
注意: 在Java中使用UDP编程,仍然需要使用Socket,因为应用程序在使用UDP时必须指定网络接口(IP)和端口号。注意:
UDP端口和TCP端口虽然都使用0~65535,但他们是两套独立的端口,即一个应用程序用TCP占用了端口8080,不影响另一个应用程序用UDP占用端口8080
。
怎样来编写UDP
分别建立发送端和接收端,发送端发送数据包,接收端接收发送端发送的数据包
创建发送方步骤:
- 创建DatagramSocket服务
- 创建数据包DatagramPacket ,用于发送数据,并指定ip和端口以及要发送的数据
- 关闭资源
创建接收方步骤:
- 创建DatagramSocket服务,并监听指定端口
- 创建数据包DatagramPacket,用来接收数据
- 用DatagramSocket接收数据到数据包中
- 从数据包DatagramPacket中取出数据
- 关闭资源
发送端和接收端是两个独立运行的程序,任何一方断开都不会影响到对方
常用类:
- DatagramSocket:
用于发送或接收数据包
- 因为 UDP 协议并不需要建立连接,所以我们将数据
(byte数组)放入 DatagramPacket
之后,还需要将目的地(IP地址和端口)放入到 DatagramPacket 中
——DatagramSocket 的 send(DatagramPacket packet)
方法根据 packet 中指定目的地,将其包含的数据往这个目的地发送。至于数据是否能(准确)到达目的地,DatagramSocket 并不关心。 - DatagramSocket 在
接收数据包
时,我们需要为其指定一个监听的端口
。当有包含了接收端机器 IP 地址和 DatagramSocket 所监听端口
的数据包到达时,DatagramSocket 的 receive(DatagramPacket packet) 方法便会对数据包进行接收,并将接收到的数据包填入到 packet 中。
- 因为 UDP 协议并不需要建立连接,所以我们将数据
- DatagramPacket:
数据包
不需要利用IO流实现数据的传输
每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地
因为 UDP 没有服务端和客户端之分
,所以我们把两端分别定义为 发送端 和 接收端
。
DatagramSocket类
用于发送或接收数据包
方法 | 方法描述 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报包 |
void send(DatagramPacket p) | 从此套接字发送数据报包 |
setSoTimeout(int timeout) | 设置发送方法的超时时间,单位毫秒 (意思是后续接收UDP包时,等待时间最多不会超过1秒,否则在没有收到UDP包时,客户端会无限等待下去。这一点和服务器端不一样,服务器端可以无限等待,因为它本来就被设计成长时间运行。) |
connect(InetAddress address, int port) | 发送方“连接”到指定的服务器端(connect()方法不是真连接,它是为了在客户端的DatagramSocket实例中保存服务器端的IP和端口号,确保这个DatagramSocket实例只能往指定的地址和端口发送UDP包,不能往其他地址和端口发送。这么做不是UDP的限制,而是Java内置了安全检查。) |
connect(SocketAddress addr) | 发送方“连接”到指定的服务器端(同上) |
disconnect() | 用于发送方断开连接 (disconnect()也不是真正地断开连接,它只是清除了客户端DatagramSocket实例记录的远程服务器地址和端口号,这样,DatagramSocket实例就可以连接另一个服务器端) |
DatagramPacket类
数据包
构造方法 | 构造方法描述 |
---|---|
DatagramPacket(byte[] buf,int length) | 构造DatagramPacket,用于接收长度为length的数据包 |
DatagramPacket(byte[] buf,int length, IntAddress address, int port) | 构造长度为length的数据包。将数据发送到指定IP地址的指定端口号。 |
DatagramPacket(byte buf[], int length, SocketAddress address) | 构造长度为length的数据包。将数据发送到指定IP地址的指定端口号。java.net.SocketAddress的子类为InetSocketAddress,是java对IP+端口的封装 |
DatagramPacket(byte buf[], int offset, int length, SocketAddress address) | 构造长度为length,发送起始下标为offset的数据包, 将数据发送到指定IP地址的指定端口号java.net.SocketAddress的子类为InetSocketAddress,是java对IP+端口的封装 |
代码实现UDP通信
创建接收端
- 定义UDP的Socket服务。
- 定义一个数据包DatagramPacket,用于存储接收到的字节数据。数据包对象中有更多功能可以提取字节信息中的不同信息。
- 通过Socket服务的receive方法将收到的数据存储到定义好的数据包中。
- 通过数据包对象中的特有功能将数据取出。
- 对数据进行处理。
- 关闭资源。
定义UDP接收端的时候通常会监听一个端口,其实就是给这个网络应用程序定义一个数字标识。如果不定义系统会分配一个随机的。方便于明确哪些数据过来该应用程序可以处理。接收端通常会指定一个监听端口。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPRecieve {//接收端 先开启
public static void main(String[] args) throws IOException {
//1.创建服务端套接字
DatagramSocket datagramSocket = new DatagramSocket(6000);//数据包接收对象
byte[] bytes = new byte[1024];//字节数组 用来接收 数据
//2.创建接受客户端信息的空数据包
while (true) {
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
//3.接受数据包
datagramSocket.receive(datagramPacket); //接收数据 存储到dp
//4.获取客户端IP和主机名
InetAddress inet = datagramPacket.getAddress(); //获取地址对象
String ip = inet.getHostAddress(); //获取IP地址
int port = datagramPacket.getPort(); //端口号
//5.读取数据
int length = datagramPacket.getLength(); //接收数据的长度
byte[] data = datagramPacket.getData(); //接收数据的字节数组
//String dataStr = new String(data,0,length);//显示收到的数据
//String dataStr = new String(bytes, 0, length);//显示收到的数据
// 收取到的数据存储在buffer中,由packet.getOffset(), packet.getLength()指定起始位置和长度
String dataStr = new String(datagramPacket.getData(), datagramPacket.getOffset(), datagramPacket.getLength(), StandardCharsets.UTF_8);
System.out.println("IP地址:" + ip + ",端口号:" + port + ",内容:" + dataStr);
//6.释放资源
//datagramSocket.close();
}
}
}
- 接收端的receive为
阻塞式方法
。 - 如果系统没有指定发送端从哪里发送数据,系统会随机给发送端分配一个端口,代表数据是从这里发送的。如果发送端的端口用完之后没有释放则下一次发送数据的端口会按照上一次的进行顺延。
创建发送端
- 建立UDP socket服务。
- 提供数据并将数据封装到数据包中。
- 通过socket服务的发送功能将数据包发送出去。
- 关闭资源。
如果没有接收端,因此该程序发送的数据包将会丢失。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPSend {//发送端
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
//1.创建客户端套接字
DatagramSocket datagramSocket = new DatagramSocket(); //数据包发送对象
//设置发送方超时时间为1秒
//datagramSocket.setSoTimeout(1000);
//2.创建客户端发送数据包
while (true) {
System.out.println("请输入要发送的信息:");
String str = sc.nextLine();
//要发送的数据 转字节
byte[] bytes = str.getBytes();
//3. 数据包对象 (字节, 长度, 传输地址, 端口号)
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("localhost"), 6000);
//4. 发送数据包
datagramSocket.send(datagramPacket);
//结束发送循环
if("886".equals(str)){
break;
}
}
//5.释放资源
//datagramSocket.close();
}
}
-
优先启动接收端监听指定端口请求
-
发送端发送数据包
-
接收端接收数据包
-
发送端断开并不会影响到接收端
使用UDP建立简易聊天室
- 虽然实现了使用UDP进行两个窗口之间的通信,但是要想在一个窗口实现信息的接收与发送需要使用多线程技术。一个线程实现信息的发送,一个线程实现信息的接收。
创建接收端
**
* 接收端
*/
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPServer implements Runnable {
//指定端口
private int port;
public UDPServer(int port) {
super();
this.port = port;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
@Override
public void run() {
DatagramSocket ds = null;
try {
//创建UDP接收端的对象,必须指定端口
//端口最好指定在一万以上,因为八千之前的端口很多都被占用了
ds = new DatagramSocket(port);
//定义接收的字节数组
byte[] bs = new byte[1024];
System.out.println("服务器已经启动");
while (true) {
//定义接收数据包
DatagramPacket dp = new DatagramPacket(bs, bs.length);
//数据包的接收
ds.receive(dp);
//获得发送端的IP
InetAddress ia = dp.getAddress();
//获得数据包中的数据,这个数组的长度是我们自己定义的长度(1024)
byte[] bs1 = dp.getData();
//获取接收数据的长度(实际接收到数据的长度)
int len = dp.getLength();
//组装接收到的数据
String data = new String(bs1, 0, len);
//退出程序
if ("exit".equals(data)) {
System.out.println("接收端已退出");
break;
}
System.out.println(ia.getHostAddress() + "说:\r\n" + data);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭接收端
if (ds != null) {
ds.close();
}
}
}
}
创建发送端
/**
* 发送端
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient implements Runnable {
//发送目标的IP
private String ip;
private int port;
//发送端口
public UDPClient(String ip, int port) {
super();
this.ip = ip;
this.port = port;
}
@Override
public void run() {
DatagramSocket ds = null;
BufferedReader br = null;
try {
//创建控制台的输入流对象
br = new BufferedReader(new InputStreamReader(System.in));
//创建发送端端接收对象
ds = new DatagramSocket();
System.out.println("已经接入" + ip);
while (true) {
System.out.println("请输入你要发送的内容:");
//读取控制台输入的数据并且转换成字节数组
byte[] bs = br.readLine().getBytes();
//创建要发送的目的地的IP对象
InetAddress ia = InetAddress.getByName(ip);
//指定数据包
//第一个参数是打包的字节数组,第二个参数是要打包的字节长度
//第三个参数是要发送的IP对象,第四个参数是要发送的服务端
DatagramPacket dp = new DatagramPacket(bs, bs.length, ia, port);
//发送
ds.send(dp);
System.out.println("我说:\r\n" + new String(bs, 0, bs.length));
//退出程序
if ("exit".equals(new String(bs, 0, bs.length))) {
System.out.println("发送端已退出");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null) {
ds.close();
}
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
主函数
/**
* 聊天室
*/
public class ChatRoom {
public static void main(String[] args) {
//创建接收端对象的线程的实现
UDPClient uc = new UDPClient("127.0.0.1", 10000);
//创建服务端
UDPServer us = new UDPServer(10001);
//发送端的线程
Thread t = new Thread(uc);
//接收端的线程
Thread t1 = new Thread(us);
//启动线程
t.start();
t1.start();
}
}
执行结果
https://www.liaoxuefeng.com/wiki/1252599548343744/1319099802058785