UDP 协议(用户数据包协议)
UDP 是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接,简单来说,当客户端向接收端发送数据时,客户端不会确认接收端是否存在,就会发出数据。同样接收端在接收数据时,也不会向发送端反馈是否收到数据
由于使用 UDP 协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据传输
例如:视频会议通常采用 UDP 协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用 UDP 协议传送数据时,由于 UDP 的面向无连接性,不能保证数据的完整性,因此在传输重要数据是不建议使用 UDP 协议
客户端与服务器端图解
TCP 是基于字节流的传输层通信协议,所以 TCP 编程是基于 IO 流编程
UDP 通信编程
发送 数据
1..创建 DatagramSocket 对象
不传参,指定端口。(发送端口)
2.创建 DatagramPacket 对象
需传入指定的byte数组,长度,要发送的地址,接收的端口号。
3.发送数据
4.关闭
客户端:
package com.lpy.socketdemo2;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建 DatagramSocket 对象
DatagramSocket ds = new DatagramSocket();
String str = "hello world";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 80;
// 创建 DatagramPacket 对象
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 发送数据
ds.send(dp);
// 关闭
ds.close();
}
}
接收 数据
- 创建 DatagramSocket 对象,指定接收用的端口号
- 创建 DatagramPacket 对象,用byte数组接收,指定接收长度
- 接收数据
- 解析数据
服务端:
package com.lpy.socketdemo3;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 创建 DatagramSocket 对象,指定接收用的端口号
DatagramSocket ds = new DatagramSocket(10086);
byte[] bytes = new byte[1024];
// 创建 DatagramPacket 对象,用byte数组接收,指定接收长度
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
// 接收数据
ds.receive(dp);
// 解析数据
byte[] data = dp.getData();
int len = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("接收到的数据" + new String(data, 0, len)); // // 接收到的数据hello world
System.out.println("该数据是从" + address + "这台电脑中的" + port + "端口发送出来的"); // // 该数据是从/127.0.0.1这台电脑中的65220端口发送出来的
// 关闭
ds.close();
}
}
DatagramSocket类负责接收和发送数据报。每个DatagramSocket对象都会与一个本地端口绑定,在此端口监听发送过来的数据报。在客户程序中,一般由操作系统为DatagramSocket类分配本地端口,这种端口也被称为匿名端口。在服务器程序中,一般由程序显式地为DatagramSocket类指定本地端口。
send()方法可用于发送数据包,DatagramSocket的receive()方法负责接收一个数据报
值得注意的是,UDP提供不可靠的传输,如果数据报没有到达目的地,那么send()方法不会抛出任何异常,发送方程序就无法知道数据报是否被接收方接收到,除非双方通过应用层的特定协议来确保接收方未收到数据报时,发送方能重发数据报。 send()方法可能会抛出IOException,但是与java.uti.Socket相比,DatagramSocket的send()方法抛出IOException的可能性很小。如果发送的数据报超过了底层网络所支持的数据报的大小,就可能会抛出SocketException,它是IOException的子类。
在使用UDP实现Socket通信时,服务端与客户端都是使用DatagramSocket类,传输的数据要存放在DatagramPacket类中。 DatagramSocket类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。在DatagramSocket上总是启用UDP广播发送。为了接收广播包,应该将DatagramSocket绑定到通配符地址。在某些实现中,将DatagramSocket绑定到一个更加具体的地址时广播包也可以被接收。
DatagramPacket类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。 DatagramSocket类中的public synchronized void receive(DatagramPacket p)方法的作用是从此套接字接收数据报包。当此方法返回时,DatagramPacket的缓冲区填充了接收的数据。数据报包也包含发送方的IP地址和发送方机器上的端口号,此方法在接收到数据报前一直阻塞。数据报包对象的length字段包含所接收信息的长度。如果发送的信息比接收端包关联的byte[]长度长,该信息将被截短。如果发送信息的长度大于65507,则发送端出现异常。
DatagramSocket类中的public void send(DatagramPacket p)方法的作用是从此套接字发送数据报包。
DatagramPacket包含的信息有:将要发送的数据及其长度、远程主机的IP地址和远程主机的端口号。 DatagramPacket类中的public synchronized byte[]getData()方法的作用是返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量offset处开始,持续length长度。
在某些场合,一个DatagramSocket可能只希望与固定的另一个远程DatagramSocket通信。例如,NFS客户只接收来自与之通信的服务器的数据报。再例如,在网络游戏中,一个游戏玩家只接收他的游戏搭档的数据报。 从JDK1.2开始,DatagramSocket添加了一些方法,利用这些方法,可以使一个DatagramSocket只能与另一个固定的DatagramSocket交换数据报。
(1)public void connect(InetAddress host,int port) connect()方法实际上不建立TCP意义上的连接,但它能限制当前DatagramSocket只对参数指定的远程主机和UDP端口收发数据报。如果当前DatagramSocket试图对其他的主机或UDP端口发送数据报,send()方法就会抛出IllegalArgumentException。从参数以外的其他主机或UDP端口发送过来的数据报则被丢弃,程序不会得到任何通知,也不会抛出任何异常。
2)public void disconnect() disconnect()中止当前DatagramSocket已经建立的“连接”,这样,DatagramSocket就可以再次对任何其他主机和UDP端口收发数据报。
(3)public int getPort() 当且仅当DatagramSocket已经建立连接时,getPort()方法才返回DatagramSocket所连接的远程UDP端口,否则返回“-1”。
(4)public InetAddress getInetAddress() 当且仅当DatagramSocket已经建立连接时,getInetAddress()方法才返回DatagramSocket所连接的远程主机的IP地址,否则返回null。
(5)public SocketAddress getRemoteSocketAddress() 当且仅当DatagramSocket已经建立连接时,getRemoteSocketAddress()方法才返回一个SocketAddress对象,表示DatagramSocket所连接的远程主机以及端口的地址,否则返回null。
UDP客户程序通常只和特定的UDP服务器通信,因此可在UDP客户程序中把DatagramSocket与远程服务器连接。UDP服务器需要与多个UDP客户程序通信,因此在UDP服务器中一般不用对DatagramSocket建立特定的连接。
关闭DatagramSocket
DatagramSocket的close()方法会释放所占用的本地UDP端口。在程序中及时关闭不再需要的DatagramSocket,这是好的编程习惯。
DatagramSocket的选项
1.SO_TIMEOUT选项 ·设置该选项:public void setSoTimeout(int milliseconds) throws SocketException ·读取该选项:public int getSoTimeOut() throws SocketException DatagramSocket类的SO_TIMEOUT选项用于设定接收数据报的等待超时时间,单位为ms,它的默认值为0,表示会无限等待,永远不会超时。以下代码把接收数据报的等待超时时间设为3min:
if(socket.getTimeOut() == 0) socket.setTimeOut(60000*3);
DatagramSocket的setTimeout()方法必须在接收数据报之前执行才有效。当执行DatagramSocket的receive()方法时,如果等待超时,那么会抛出SocketTimeoutException,此时DatagramSocket仍然是有效的,尝试再次接收数据报。
2.SO_RCVBUF选项
·设置该选项:public void setReceiveBufferSize(int size) throws SocketException
·读取该选项:public int getReceiveBufferSize() throws SocketException
SO_RCVBUF表示底层网络的接收数据的缓冲区(简称接收缓冲区)的大小。对于有着较快传输速度的网络(比如以太网),较大的缓冲区有助于提高传输性能,因为可以在缓冲区溢出之前存储更多的入站数据报。与TCP相比,对于UDP,确保接收数据的缓冲区具有足够的大小更为重要,因为当缓冲区满后再到达的数据报会被丢弃。而TCP会在这种情况下要求重传数据,确保数据不会丢失。 此外,SO_RCVBUF还决定了程序接收的数据报的最大大小。在接收缓冲区中放不下的数据报会被丢弃。 setReceiveBufferSize(int size)方法设置接收缓冲区的大小,值得注意的是,许多网络都限定了接收缓冲区大小的最大值,如果参数size超过该值,那么setReceiveBufferSize(int size)方法所做的设置无效。getReceiveBufferSize()方法返回接收缓冲区的实际大小。
3.SO_SNDBUF选项
·设置该选项:public void setSendBufferSize(int size) throws SocketException
·读取该选项:public int getSendBufferSize() throws SocketException SO_SNDBUF表示底层网络的发送数据的缓冲区(简称发送缓冲区)的大小。setSendBufferSize(int size)方法设置发送缓冲区的大小,值得注意的是,许多网络都限定了发送缓冲区大小的最大值,如果参数size超过该值,那么setSendBufferSize(int size)方法所做的设置无效。getSendBufferSize()方法返回发送缓冲区的实际大小。
4.SO_REUSEADDR选项
·设置该选项:public void setResuseAddress(boolean on) throws SocketException
·读取该选项:public boolean getResuseAddress() throws SocketException
SO_REUSEADDR选项对于UDP Socket和TCP Socket有着不同的意义。对于UDP,SO_REUSEADDR决定多个DatagramSocket是否可以同时被绑定到相同的IP地址和端口。如果多个DatagramSocket被绑定到相同的IP地址和端口,那么到达该地址的数据报会被复制给所有的DatagramSocket。 setResuseAddress(boolean on)必须在DatagramSocket绑定到端口之前被调用,这意味着必须采用以下这个构造方法来创建DatagramSocket对象。
protected DatagramSocket(DatagramSocketImpl impl)
//此构造方法创建的DatagramSocket对象未与任何端口绑定
5.SO_BROADCAST选项
·设置该选项:public void setBroadCast(boolean on) throws SocketException
·读取该选项:public boolean getBroadCast() throws SocketException SO_BROADCAST选项决定是否允许对网络广播地址发送广播数据报。对于一个地址为192.168.5.*的网络,其本地网络广播地址为 192.168.5.255。UDP广播常被用于JXTA对等发现协议(JXTA Peer Discovery Protocol)、服务定位协议(Service Location Protocol)和DHCP动态主机配置协议(Dynamic Host Configuration Protocol)等协议。例如,如果需要和本地网中的服务器通信,但是预先不知道服务器的地址,就需要采用这些协议。 广播数据报一般只在本地网络中传播,路由器和网关一般不转发广播数据报。SO_BROADCAST选项的默认值为true。如果不希望发送广播数据报,那么可以调用DatagramSocket的setBroadCast(false)方法。
实现简易的通信聊天
客户端:
package com.lpy.socketdemo4;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Send {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
int port = 80;
InetAddress address = InetAddress.getByName("127.0.0.1");
while (true) {
System.out.println("请输入发送的内容:");
String str = sc.nextLine();
byte[] bytes = str.getBytes("UTF-8");
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
if (str.equals("bye")) break;
}
ds.close();
}
}
服务端:
package com.lpy.socketdemo4;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class receive {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(80);
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
while (true) {
ds.receive(dp);
byte[] data = dp.getData();
int len = dp.getLength();
String str = new String(data, 0, len, "UTF-8");
System.out.println("由" + dp.getAddress().getHostAddress() + "主机" + dp.getPort() +"端口号发来了:");
System.out.println(str);
if (str.equals("bye")) {
break;
}
}
ds.close();
}
}
效果如图:
本次分享到此结束,觉得有所帮助的朋友点点关注点点赞!