TCP/IP系列之传输层UDP协议

来源:互联网 发布:淘宝嘉年华公告是什么 编辑:程序博客网 时间:2024/06/07 03:09
UDP是一个简单的面向数据报的传输层协议:进程的每个输出操作都正好产生一个UDP
数据报,并组装成一份待发送的IP数据报.IP数据报的数据部分放的是UDP数据报.如下图

UDP数据包的格式如下图:

一个Wireshark抓取的包如下图:

UDP长度字段指的是UDP首部和UDP数据的字节长度.该字段的最小值为8字节
(发送一份0字节的UDP数据报是OK).UDP检验和覆盖UDP首部和UDP数据,检验和是16bit字的
二进制反码和,UDP数据报的长度可以为奇数字节,但是检验和算法是把若干个16bit字相加.
解决方法是必要时在最后增加填充字节0,这只是为了检验和的计算(也就是说,可能增加的
填充字节不被传送).其目的是为了发现UDP首部和数据在发送端到接收端之间发生的任何改动.

物理网络层一般要限制每次发送数据帧的最大长度.任何
时候I P层接收到一份要发送的I P数据报时,它要判断向本地哪个接口发送数据(选路),并查
询该接口获得其MTU.I P把MTU与数据报长度进行比较,如果需要则进行分片.分片可以发
生在原始发送端主机上,也可以发生在中间路由器上.
把一份I P数据报分片以后,只有到达目的地才进行重新组装(这里的重新组装与其他网
络协议不同,它们要求在下一站就进行进行重新组装,而不是在最终的目的地).重新组装由
目的端的I P层来完成,其目的是使分片和重新组装过程对运输层(TCP和UDP)是透明的.
对于发送端发送的每份IP数据报来说,其标识字段都包含一个唯一值.该值在数据报分片时被复制
到每个片中(我们现在已经看到这个字段的用途).标志字段用其中一个比特来表示"更多的片".
除了最后一片外,其他每个组成数据报的片都要把该比特置1.

在Java中,java.util.DatagramSocket负责接收和发送UDP数据报,
java.util.DatagramPacket表示UDP数据报。每个
DatagramSocket与一个本地地址(包括本地主机的IP地址和本地UDP端
口)绑定,每个DatagramSocket可以把UDP数据报发送给任意一个远程
DatagramSocket,也可以接收来自任意一个远程DatagramSocket的UDP
数据报。在UDP数据报中包含了目的地址的信息,DatagramSocket根据
该信息把数据报发送到目的地。

UDP协议是无连接的协议,客户端的DatagramSocket与服务器端的DatagramSocket
不存在一一对应关系,两者无需建立连接,就能交换数据报。
DatagramSocket负责接收和发送数据报。
每个DatagramSocket对象都会与一个本地端口绑定,在此端口监听发送过来的数据报。
在客户程序中,一般由操作系统为DatagramSocket分配本地端口,这种端口也称为匿名端口;
sendSocket = new DatagramSocket();
在服务器程序中,一般由程序显式的为DatagramSocket指定本地端口。
receiveSocket = new DatagramSocket(port);
DatagramSocket提供了接收和发送数据报的方法
public void send(DatagramPacket src)throws IOException //发送数据报:
public void receive(DatagramPacket dst)throws IOException //接收数据报
receive在网络上没有数据报时,执行该方法的线程会进入阻塞状态,直到收到数据报为止。

DatagramPacket表示数据报,它的构造方法可以分为两类:
一类构造方法创建的DatagramPacket对象用来接收数据
还有一类构造方法创建的DatagramPacket对象用来发送数据。
两类构造方法的主要区别是,用于发送数据的构造方法需要设定数据报到达的目的地址,
byte[] bigData = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bigData, 0, bigData.length,
InetAddress.getByName(targetServer), port);
而用于接收数据的构造方法无需设定地址。
byte[] receiveByte = new byte[MAX_LENGTH];
DatagramPacket packet = new DatagramPacket(receiveByte, 0, MAX_LENGTH);
DatagramPacket的构造方法有一个参数length,它决定了要接收或发送的数据报的长度。
对于用于接收数据的DatagramPacket,如果实际接收到的数据报的长度大于DatagramPacket的长度,
那么多余的数据就会被丢弃。因此,必须为DatagramPacket选择合适的长度。
选择数据报大小的通用原则是:
如果网络非常不可靠,如分组无线电网络,则要选择较小的数据报,以减少传输中遭破坏的可能性。
如果网络非常可靠,而且传输速度很快,就应当尽可能使用大的数据报。
对于多数网络,8K是一个很好的折衷方案。
数据报中只能存放字节形式的数据。在发送方,需要把其他格式的数据转换为字节序列。
在接收方,需要把字节序列转换为原来格式的数据。
同一个DatagramPacket对象可以被重用,用来多次发送或接收数据。

值得注意的是,UDP协议提供不可靠的传输,如果数据报没有到达目的地,
send()方法不会抛出任何异常,因此发送方程序无法知道数据报是否被接收方接收到,
除非双方通过应用层的特定协议来确保接收方未收到数据报时,发送方能重发数据报。
一个完整的JAVA代码示例
public class DatagramTester {public DatagramTester(String tgtIP,int port) throws IOException {Receiver receiver = new Receiver(port);Sender sender = new Sender(tgtIP,port);receiver.start();sender.start();}public static void main(String args[]) throws IOException {args[0] = "147.151.240.234";DatagramTester tester = new DatagramTester(args[0],8005);}}class Sender extends Thread { private DatagramSocket sendSocket;private String targetServer;//"147.151.240.234";private int port;public Sender(String serverIP,int port) throws SocketException{sendSocket = new DatagramSocket(); this.targetServer = serverIP;this.port = port;}public void run() {try {send(); } catch (IOException e) {e.printStackTrace();}}public void send() throws IOException {BufferedReader localReader = new BufferedReader(new InputStreamReader(System.in));String msg = null;while ((msg = localReader.readLine()) != null) {byte[] bigData = msg.getBytes();DatagramPacket packet = new DatagramPacket(bigData, 0, bigData.length, InetAddress.getByName(targetServer), port);sendSocket.send(packet);if (msg.equals("bye"))break;}}}class Receiver extends Thread { private static final int MAX_LENGTH = 4584;private DatagramSocket receiveSocket;public Receiver(int port) throws SocketException{this.receiveSocket = new DatagramSocket(port);}public void run() {try {while(true){System.out.print(receive());}} catch (IOException e) {e.printStackTrace();}}private String receive() throws IOException {byte[] receiveByte = new byte[MAX_LENGTH];DatagramPacket packet = new DatagramPacket(receiveByte, 0, MAX_LENGTH);receiveSocket.receive(packet);int bytesReceived = packet.getLength();System.out.println("ReceiveSocket> received " + packet.getLength() + " bytes");return new String(receiveByte, 0, bytesReceived);}}



原创粉丝点击