【JAVA网络编程】JAVA使用UDP实现简单SOCKET通信实例 您所在的位置:网站首页 javaudp发送视频 【JAVA网络编程】JAVA使用UDP实现简单SOCKET通信实例

【JAVA网络编程】JAVA使用UDP实现简单SOCKET通信实例

2023-09-22 10:31| 来源: 网络整理| 查看: 265

之前有记录一篇基于TCP的socket通信: https://blog.csdn.net/qq_41358574/article/details/117716047

文章目录 介绍DatagramSocket的选项 测试用例 客户端使用UDP发送字符串服务端客户端Datagram Packet类中常用API的使用测试用例 使用上述API发送数据 UDP单播重用DatagramPacket

介绍

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)方法。

测试用例 客户端使用UDP发送字符串 服务端 public class Net { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(8888); byte[] byteArray = new byte[12]; // 构造方法第2个参数也要写上10个,代表要接收数据的长度为10 // 和客户端发送数据的长度要一致 DatagramPacket myPacket = new DatagramPacket(byteArray, 10); socket.receive(myPacket); socket.close(); System.out.println("包中数据的长度:" + myPacket.getLength()); System.out.println(new String(myPacket.getData(), 0, myPacket.getLength())); } } 客户端 public class Client { // 客户端要发送的数据字节长度为10 // 所以服务端只能最大取得10个数据 public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(); socket.connect(new InetSocketAddress("localhost", 8888)); String newString = "1234567890"; byte[] byteArray = newString.getBytes(); DatagramPacket myPacket = new DatagramPacket(byteArray, byteArray.length); socket.send(myPacket); socket.close(); } }

我电脑打开了两个项目,然后先运行服务端再运行客户端,结果: (显示在服务端的项目控制台) 在这里插入图片描述

Datagram Packet类中常用API的使用 DatagramPacket类中的public synchronized void setData(byte[]buf)方法的作用是为此包设置数据缓冲区。将此DatagramPacket的偏移量设置为0,长度设置为buf的长度。DatagramPacket类中的public synchronized void setData(byte[]buf,int offset,int length)方法的作用是为此包设置数据缓冲区。此方法设置包的数据、长度和偏移量。DatagramPacket类中的public synchronized int getOffset()方法的作用是返回将要发送或接收到的数据的偏移量。DatagramPacket类中的public synchronized void setLength(int length)方法的作用是为此包设置长度。包的长度是指包数据缓冲区中将要发送的字节数,或用来接收数据的包数据缓冲区的字节数。长度必须小于或等于偏移量与包缓冲区长度之和。 测试用例 使用上述API发送数据

SERVER:

public class Net{ public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(8888); byte[] byteArray = new byte[10]; DatagramPacket myPacket = new DatagramPacket(byteArray, byteArray.length); socket.receive(myPacket); socket.close(); byteArray = myPacket.getData(); System.out.println(new String(byteArray, 0, myPacket.getLength())); } }

客户端:

public class Client2 { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(); socket.connect(new InetSocketAddress("localhost", 8888)); String newString = "我是员工"; byte[] byteArray = newString.getBytes(); DatagramPacket myPacket = new DatagramPacket(new byte[]{}, 0); myPacket.setData(byteArray, 2, 6); System.out.println("myPacket.getOffset()=" + myPacket.getOffset()); socket.send(myPacket); socket.close(); } } UDP单播

“单播”就是将数据报文让1台计算机知道。 注意,想要让Linux接收UDP信息,必须使用root管理员角色执行命令关闭防火墙: systemctl stop firewalld.service 例如,将计算机A中的Windows 操作系统的IP设置为192.168.0.150,将计算机B中的CentOS操作系统的IP地址设置为192.168.0.105,最后在控制台或终端互ping就可以了。 例:

public class Server { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(8888); byte[] byteArray = new byte[10]; DatagramPacket packet = new DatagramPacket(byteArray, byteArray.length); socket.receive(packet); byteArray = packet.getData(); System.out.println(new String(byteArray, 0, packet.getLength())); socket.close(); } } public class Client { public static void main(String[] args) throws IOException { DatagramSocket socket = new DatagramSocket(); socket.connect(new InetSocketAddress("192.168.0.150", 8888)); byte[] byteArray = "1234567890".getBytes(); DatagramPacket packet = new DatagramPacket(byteArray, byteArray.length); socket.send(packet); socket.close(); } } 重用DatagramPacket

同一个DatagramPacket对象可以被重用,用来多次发送或接收数据。下面创建两个线程,sender发送数据,receiver接收数据

import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; public class DatagramTester { private int port = 8000; private DatagramSocket sender; private DatagramSocket receiver; private static final int maxlength = 3584; public DatagramTester() throws SocketException { sender = new DatagramSocket(); receiver = new DatagramSocket(); senderThread.start(); receiverThread.start(); } public static byte[] longtobyte(long[]data)throws IOException { byte[]data1 = new byte[1024]; return data1;//用于把long类型转化未byte类型 } public static long[] bytetolong(byte[]data) throws IOException{ long[]data2 = new long[1024]; return data2; }//这两个函数自己随便写的 public void send(byte[] bigData) throws IOException{ DatagramPacket packet = new DatagramPacket(bigData,0,512, InetAddress.getByName("localhost"),port); int bytesSent = 0;//已经发送的字节数 int count = 0;//已经发送的次数 while(bytesSent byte[]bigData = new byte[maxlength]; DatagramPacket packet = new DatagramPacket(bigData,0,maxlength); int byteReceiver = 0;//已经接收的字节数 int count = 0; long bigTime = System.currentTimeMillis(); while(byteReceiver public void run(){//发送者线程 long[]longArray = new long[maxlength/8]; for(int i = 0;i e.printStackTrace(); } } }; public Thread receiverThread = new Thread(){ public void run() {//接收者线程 try{ long[]longArray = bytetolong(receive()); for(int i = 0;i DatagramTester datagramTester = new DatagramTester(); } }

send()方法通过一个while循环来发送数据,在每次循环中,最多只发送512字节。当剩余的未发送的字节数remain小于512,就发送remain字节,否则就发送512字节。send()方法开始创建的DatagramPacket对象一直被重用,通过调用它的setData(bigData,bytesSent,length)方法,可以改变下一次要发送的数据的在bigData缓冲区内的起始位置和长度。

在receive()方法中,局部变量bigData缓冲区用来存放接收的数据,receive()方法通过一个while循环来接收数据。在每次循环中,接收到的数据都被放在bigData缓冲区内。receive()方法开始创建的DatagramPacket对象一直被重用,通过调用它的setData(bigData,bytesReceived,MAX_LENGTH-bytesReceived)方法,可以改变下一次要接收的数据存放在bigData缓冲区内的起始位置和长度。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有