Java网络编程(七)—— UDP编程(二)

Java网络编程(七)—— UDP编程(二)

上一篇博客中《Java网络编程(六)—— UDP编程(一)》初步介绍了UDP编程,并且给出了一个简单的UDP通信的例子,但是由于篇幅问题,没有展开细讲,在这篇博客中我将重点介绍一下DatagramPacket类以及DatagramSocket类

DatagramPacket

构造函数

DatagramPacket的构造函数的形式需要取决于这个DatagramPacket是用于发送数据还是接收数据。第一组是用于接收的DatagramPacket,他的构造函数都至少需要两个参数,一个是保存数据报数据的byte数组,另一个参数是该数组中用于数据报数据的字节数。当socket从网络接收数据报时,它将数据报的数据存储在DatagramPacket对象的缓冲区数组中,直到达到你指定的长度。

第二组DatagramPacket构造函数用于创建通过网络发送的数据报。这些构造函数需要一个缓冲区数组和一个长度,另外还需要指定数据包发往的地址和端口。在这里,要为构造函数传递一个byte数组,其中包含想要发送的数据,另外还要为构造函数传入目标地址及端口(数据包将发送到这个指定的地址和端口)。DatagramSocket从包中读取目标地址和端口,地址和端口不存储在Socket中,这与TCP不同。

接收数据报的构造函数

下面两个函数用来构建能够从网络接受数据的DatagramPacket:

public DatagramPacket(byte[] buffer, int length)
public DatagramPacket(byte[] buffer, int offset, int length)

第一个构造函数是当Socket接收一个数据报时,它将数据报的数据部分存储在buffer,从buffer[o]开始,一直到包完全存储,或者直到向buffer写入了length字节。第二个构造函数是将从buffer[offset]开始存储。除此之外,这两个构造函数完全相同。

构造函数不关心缓冲区有多大,它甚至很乐于地让你创建几兆字节的DatagramPacket。不过,底层网络软件却不那么宽容,大多数底层UDP实现都不支持超过8192字节数据的数据报。实际上,很多基于UDP的协议(如DNS和TFTP)使用的包中,每数据报都仅有512字节或更少的数据。常用的最大数据大小是NFS所用的8192字节。你可能遇到的几乎所有UDP数据报都只有8KB或更少的数据。因此,不要创建超过8192字节数据的DatagramPacket对象。

例如,下面的代码段创建一个新的DatagramPacket,可以接受最大为8192的数据报:

byte[ ] buffer = new byte[8192];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
发送数据报的构造函数

下面四个函数用来构建通过网络发送数据的DatagramPacket:

public DatagramPacket(byte[] data,int length, InetAddress destination,int port)
public DatagramPacket(byte[] data,int offset, int length, InetAddress destination, int port)
public DatagramPacket(byte[] data, int length, SocketAddress destination)
public DatagramPacket(byte[] data, int offset, int length, SocketAddress destination)

每个构造函数都创建一个发往另一台主机的新DatagramPacket。这个包用data数组中从offset(如果没有使用offset,则为0)开始的length个字节填充。InetAddress或SocketAddress对象的destination指向包发往的目标主机,参数port是该主机上的端口。

在创建DatagramPacket前要将数据转换为byte数组并放在data中,但这不是绝对必需的。可以在数据报构造之后且发送之前修改data。在一些应用程序中,可以利用这一点。例如,可以将随着时间变化的数据存储在data中,并且每分钟发送当前的数据报(最新的数据)。

例如下面的代码块就创建一个新的用于发送的DatagramPacket,数据是"HELLO WORLD":

String s = "HELLO WORLD";
byte[] data = s.getBytes(StandardCharsets.UTF_8);
try {
     InetAddress inetAddress = InetAddress.getByName("localhost");
     int port  = 5555;
     DatagramPacket dp = new DatagramPacket(data, data.length, inetAddress, port);
     /*
         发送数据报的代码
     */
}catch (IOException E){   
}

get方法

下面介绍DatagramPacket类中6种获取数据报不同内容的方法:

public InetAddress getAddress()

getAddress()方法返回一个InetAddress对象,其中包含远程主机的地址。如果数据报是从Internet接收的,返回的地址则是发送该数据报的源地址。如果数据报是本地创建的,要发送到一个远程机器,那么这个方法会返回该数据报将发往的那个目标主机的地址。这个方法常用于确定发送UDP数据报的主机地址,使接收方可以回复。

public int getPort()

getPort()方法返回一个整数,指示远程端口。如果数据报是从Internet接收的,这就是发送包的主机上的端口。如果数据报是本地创建的,要发送到一个远程主机,那么返回的是发往的目标端口号。

public SocketAddress getSocketAddress()

getSocketAddress()方法返回一个SocketAddress对象,其中包含远程主机的IP地址和端口。与getInetAddress()情况一样,如果数据报是从Internet接收的,返回就是源地址。如果数据报是本地创建的,要发送到远程机器,这个方法会返回目标地址。

public byte[] getData()

getData()方法返回一个byte数组,其中包含数据报中的数据。为了能够在你的程序中使用,通常必须将这些字节转换为其他的某种数据形式。一种方法是将byte数组转换为一个String。例如可以把它转换为一个UTF-8 String:

String s = new String(dp.getData(),"UTF-8");
public int getLength()

getLength()方法返回数据报中数据的字节数。它不一定等于getData()返回的数组的长度。getLength()返回的int可能小于getData()返回的数组的长度。

public int getOffset()

对于getData()返回的数组,这个方法会返回该数组中的一个位置,即开始填充数据报数据的那个位置。

set方法

DatagramPacket还提供了几个方法,可以在创建数据报之后改变数据、远程地址和远程端口。如果创建和垃圾回收新DatagramPacket对象的时间会严重影响性能,这些方法就很重要——在有些情况下,重用对象比构造新对象要快得多。

public void setData(byte[] data)

setData()方法会改变UDP数据报的有效载荷。如果要向远程主机发送大文件(大于一个数据报所能包含的数据),可能会用到这个方法。你可以重复地发送相同的DatagramPacket对象,每次只改变数据。

public void setData(byte[] data, int offset, int length)

这个重载的setData()方法提供了另一个途径来发送大量的数据。与发送大量新数组不同,可以将所有数据放在一个数组中,每次发送一部分。例如,下面的循环将以512字节的块来发送一个大数组:

int offset = 0;
DatagramPacket dp = new DatagramPacket(bigarray,offset,512);
int bytesSent = 0;
while (bytesSent < bigarray. length) {
	socket.send(dp);
	bytesSent += dp.getLength();
	int bytesToSend = bigarray. length - bytesSent;
	int size = (bytesToSend > 512)? 512 : bytesToSend;
	dp.setData(bigarray, bytesSent, size);
}
public void setAddress(InetAddress remote)

setAddress()方法会修改数据报发往的地址。这允许你将同一个数据报发送给多个不同的接收方。

public void setPort(int port)

setPort()方法会改变数据报发往的端口。

public void setAddress(SocketAddress remote)

setSocketAddress()方法会改变数据报包要发往的地址和端口。在回复时可以使用这个方法。例如,下面的代码段将接收一个数据报包,用包含字符串“Hello there”的包响应同一个地址:

DatagramPacket input = new DatagramPacket(new byte[8192],8192);
socket.receive(input);
DatagramPacket output = new DatagramPacket("Hello there".getBytes("UTF-8")11);
SocketAddress address = input.getSocketAddress();
output.setAddress(address);
socket.send(output);
public void setLength(int length)

setLength()方法会改变内部缓冲区中包含实际数据报数据的字节数,而不包括未填充数据的空间。

DatagramSocket

要收发DatagramPacket,必须打开一个数据报Socket。在Java中,数据报Socket通过DatagramSocket类创建和访问, 所有数据报Socket都绑定到一个本地端口,在这个端口上监听入站数据。如果要编写一个客户端,不需要关心本地端口,可以调用一个构造函数,让系统来分配一个未使用的端口(匿名端口)。这个端口号会被放置在发出的数据报中,服务器将用它来确定响应数据报的发送地址。如果要编写一个服务器,客户端需要知道服务器在哪个端口监听入站数据报。因此,当服务器构造DatagramSocket时,要指定它监听的本地端口。不过,客户端和服务器使用的Socket是一样的:区别只在于使用匿名端口还是已知端口。客户端Socket和服务器Socket没有区别,这与TCP中不同。不存在诸如DatagramServerSocket之类的东西。

构造函数

public DatagramSocket() throws SocketException

这个构造函数创建一个绑定到匿名端口的Socket。、

public DatagramSocket(int port) throws SocketException

这个构造函数创建一个在指定端口(由port参数指定)监听入站数据报的Socket。可以使用这个构造函数编写在已知端口监听的服务器。TCP端口和UDP端口没有任何关联。对于两个不同的程序,如果一个使用UDP而另一个使用TCP,那么它们可以使用相同的端口号。

public DatagramSocket(int port, InetAddress interface) throwsSocketException

这个构造函数主要用于多宿主主机,它会创建在指定端口和网络接口监听入站数据报的Socket。address参数是匹配该主机某个网络接口的InetAddress对象。

public DatagramSocket(SocketAddress interface) throwsSocketException

这个构造函数与前一个相似,只是网络接口地址和端口由SocketAddress读取。例如,下面的代码段会创建一个只监听本地回送地址的Socket:

SocketAddress address = new InetSocketAddress("127.0.0.1", 9999);
DatagramSocket socket = new DatagramSocket(address);

发送和接收数据报

DatagramSocket类的任务是发送和接收UDP数据报,一个Socket可以既发送又接收数据报。事实上,它可以同时对多台主机收发数据。

public void send(DatagramPacket dp) throws lOException

一旦创建了DatagramPacket并构造了DatagramSocket,可以将包传递给Socket的send()方法来发送这个包。例如,假设theSocket是一个DatagramSocket对象,theOutput是一个DatagramPacket对象,可以如下使用theSocket发送theOutput:

theSocket.send(theOutput);
public void receive(DatagramPacket dp) throws IOException

这个方法从网络接收一个UDP数据报,存储在现有的DatagramPacket对象dp中。与ServerSocket类的accept()相似,这个方法会阻塞调用线程,直到数据报到达。如果程序除了等待数据报外还有其他操作,就应当在单独的线程中调用receive()。

public void close()

调用DatagramSocket对象的close()方法将释放该Socket占用的端口。与流和TCPSocket一样,你可能需要在一个finally块中关闭数据报Socket:

DatagramSocket server = null
try {
server = new DatagramSocket();
} catch (IOException ex) iSystem.err.println(ex);
}finally {
	try {
		if (server != null) server.close();
		} catch (IOException ex) {
}
public int getLocalPort()

DatagramSocket的getLocalPort()方法返回一个int,表示Socket正在监听的本地端口。如果创建了一个匿名端口的DatagramSocket,希望得出为Socket分配的端口,可以使用这个方法。

public InetAddress getLocalAddress()

DatagramSocket的getLocalAddress()方法返回一个InetAddress对象,表示Socket绑定到的本地地址。

public SocketAddress getLocalSocketAddress()

getLocalSocketAddress()方法返回一个SocketAddress对象,这个对象包装了Socket绑定到的本地接口和端口。

管理连接

与TCP socket不同,数据报Socket不太在意与谁对话。事实上,默认情况下它们可以与任何人对话,利用下面5个方法,你可以选择允许收发数据报的主机,而拒绝所有其他主机的包。

public void connect(InetAddress host, int port)

connect()方法并不真正建立TCP意义上的连接。不过,它确实指定了DatagramSocket只对指定远程主机和指定远程端口收发数据包。

public void disconnect()

disconnect()方法中断已连接DatagramSocket的“连接”,从而可以再次收发任何主机和端口的包。

public int getPort()

当且仅当DatagramSocket已连接时,getPort()方法返回它所连接的远程端口。否则返回-1。

public InetAddress getInetAddress()

当且仅当DatagramSocket已连接时,getInetAddress()方法返回它所连接的远程主机的地址。否则返回null。

public InetAddress getRemoteSocketAddress()

如果DatagramSocket已连接,getRemoteSocketAddress()返回它所连接的远程主机的地址。否则返回null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值