udp协议基础(转自疯狂java讲义)

第17章  网络编程

17.4  基于UDP协议的网络编程

UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象。Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报。

17.4.1  UDP协议基础

UDP协议是英文User Datagram Protocol的缩写,即用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。UDP协议从问世至今已经被使用了很多年,虽然UDP协议目前应用不如TCP协议广泛,但UDP协议依然是一个非常实用和可行的网络传输层协议。尤其是在一些实时性很强的应用场景中,比如网络游戏、视频会议等,UDP协议的快速更具有独特的魅力。

UDP协议是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说UDP协议是一种不可靠的协议。UDP协议适用于一次只传送少量数据、对可靠性要求不高的应用环境。

与前面介绍的TCP协议一样,UDP协议直接位于IP协议之上。实际上,IP协议属于OSI参考模型的网络层协议,而UDP协议和TCP协议都属于传输层协议。

因为UDP协议是面向非连接的协议,没有建立连接的过程,因此它的通信效率很高;但也正因为如此,它的可靠性不如TCP协议。

UDP协议的主要作用是完成网络数据流和数据报之间的转换--在信息的发送端,UDP协议将网络数据流封装成数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据内容。

可以认为UDP协议的Socket类似于码头,数据报则类似于集装箱;码头的作用就是负责发送、接收集装箱,而DatagramSocket的作用则是发送、接收数据报。因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器端的概念。

UDP协议和TCP协议简单对比如下。

TCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。

UDP协议:不可靠,差错控制开销较小,传输大小限制在64KB以下,不需要建立连接。

17.4.2  使用DatagramSocket发送、接收数据(1)

Java使用DatagramSocket代表UDP协议的Socket,DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java使用DatagramPacket来代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。

先看一下DatagramSocket的构造器。

DatagramSocket():创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、本机所有可用端口中随机选择的某个端口。

DatagramSocket(int prot):创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、指定端口。

DatagramSocket(int port, InetAddress laddr):创建一个DatagramSocket实例,并将该对象绑定到指定IP地址、指定端口。

通过上面三个构造器中的任意一个构造器即可创建一个DatagramSocket实例,通常在创建服务器时,创建指定端口的DatagramSocket实例--这样保证其他客户端可以将数据发送到该服务器。一旦得到了DatagramSocket实例之后,就可以通过如下两个方法来接收和发送数据。

receive(DatagramPacket p):从该DatagramSocket中接收数据报。

send(DatagramPacket p):以该DatagramSocket对象向外发送数据报。

从上面两个方法可以看出,使用DatagramSocket发送数据报时,DatagramSocket并不知道将该数据报发送到哪里,而是由DatagramPacket自身决定数据报的目的地。就像码头并不知道每个集装箱的目的地,码头只是将这些集装箱发送出去,而集装箱本身包含了该集装箱的目的地。

下面看一下DatagramPacket的构造器。

DatagramPacket(byte[] buf,int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。

DatagramPacket(byte[] buf, int length, InetAddress addr, int port):以一个包含数据的数组来创建DatagramPacket对象,创建该DatagramPacket对象时还指定了IP地址和端口--这就决定了该数据报的目的地。

DatagramPacket(byte[] buf, int offset, int length):以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多放length个字节。

DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):创建一个用于发送的DatagramPacket对象,指定发送buf数组中从offset开始,总共length个字节。

当Client/Server程序使用UDP协议时,实际上并没有明显的服务器端和客户端,因为两方都需要先建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket对象作为传输数据的载体。通常固定IP地址、固定端口的DatagramSocket对象所在的程序被称为服务器,因为该DatagramSocket可以主动接收客户端数据。

在接收数据之前,应该采用上面的第一个或第三个构造器生成一个DatagramPacket对象,给出接收数据的字节数组及其长度。然后调用DatagramSocket 的receive()方法等待数据报的到来,receive()将一直等待(该方法会阻塞调用该方法的线程),直到收到一个数据报为止。如下代码所示:

  1. // 创建一个接收数据的DatagramPacket对象
  2. DatagramPacket packet=new DatagramPacket(buf, 256);
  3. // 接收数据报
  4. socket.receive(packet);

在发送数据之前,调用第二个或第四个构造器创建DatagramPacket对象,此时的字节数组里存放了想发送的数据。除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的send()方法实现的,send()方法根据数据报的目的地址来寻径以传送数据报。如下代码所示:

  1. // 创建一个发送数据的DatagramPacket对象
  2. DatagramPacket packet = new DatagramPacket(buf, length, address, port);
  3. // 发送数据报
  4. socket.send(packet);

使用DatagramPacket接收数据时,会感觉DatagramPacket设计得过于烦琐。开发者只关心该DatagramPacket能放多少数据,而DatagramPacket是否采用字节数组来存储数据完全不想关心。但Java要求创建接收数据用的DatagramPacket时,必须传入一个空的字节数组,该数组的长度决定了该DatagramPacket能放多少数据,这实际上暴露了DatagramPacket的实现细节。接着DatagramPacket又提供了一个getData()方法,该方法又可以返回Datagram Packet对象里封装的字节数组,该方法更显得有些多余--如果程序需要获取DatagramPacket里封装的字节数组,直接访问传给 DatagramPacket构造器的字节数组实参即可,无须调用该方法。

当服务器端(也可以是客户端)接收到一个DatagramPacket对象后,如果想向该数据报的发送者"反馈"一些信息,但由于UDP协议是面向非连接的,所以接收者并不知道每个数据报由谁发送过来,但程序可以调用DatagramPacket的如下3个方法来获取发送者的IP地址和端口。

InetAddress getAddress():当程序准备发送此数据报时,该方法返回此数据报的目标机器的IP地址;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。

int getPort():当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。

SocketAddress getSocketAddress():当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的SocketAddress。

getSocketAddress()方法的返回值是一个SocketAddress对象,该对象实际上就是一个IP地址和一个端口号。也就是说,SocketAddress对象封装了一个InetAddress对象和一个代表端口的整数,所以使用SocketAddress对象可以同时代表IP地址和端口。

17.4.2  使用DatagramSocket发送、接收数据(2)

下面程序使用DatagramSocket实现了Server/Client结构的网络通信。本程序的服务器端使用循环1000次来读取DatagramSocket中的数据报,每当读取到内容之后便向该数据报的发送者送回一条信息。服务器端程序代码如下。

程序清单:codes\17\17.4\UdpServer.java

  1. public class UdpServer
  2. {
  3. public static final int PORT = 30000;
  4. // 定义每个数据报的最大大小为4KB
  5. private static final int DATA_LEN = 4096;
  6. // 定义接收网络数据的字节数组
  7. byte[] inBuff = new byte[DATA_LEN];
  8. // 以指定字节数组创建准备接收数据的DatagramPacket对象
  9. private DatagramPacket inPacket =
  10. new DatagramPacket(inBuff , inBuff.length);
  11. // 定义一个用于发送的DatagramPacket对象
  12. private DatagramPacket outPacket;
  13. // 定义一个字符串数组,服务器端发送该数组的元素
  14. String[] books = new String[]
  15. {
  16. "疯狂Java讲义",
  17. "轻量级Java EE企业应用实战",
  18. "疯狂Android讲义",
  19. "疯狂Ajax讲义"
  20. };
  21. public void init()throws IOException
  22. {
  23. try(
  24. // 创建DatagramSocket对象
  25. DatagramSocket socket = new DatagramSocket(PORT))
  26. {
  27. // 采用循环接收数据
  28. for (int i = 0; i < 1000 ; i++ )
  29. {
  30. // 读取Socket中的数据,读到的数据放入inPacket封装的数组里
  31. socket.receive(inPacket);
  32. // 判断inPacket.getData()和inBuff是否是同一个数组
  33. System.out.println(inBuff == inPacket.getData());
  34. // 将接收到的内容转换成字符串后输出
  35. System.out.println(new String(inBuff
  36. , 0 , inPacket.getLength()));
  37. // 从字符串数组中取出一个元素作为发送数据
  38. byte[] sendData = books[i % 4].getBytes();
  39. // 以指定的字节数组作为发送数据,以刚接收到的DatagramPacket的
  40. // 源SocketAddress作为目标SocketAddress创建DatagramPacket
  41. outPacket = new DatagramPacket(sendData
  42. , sendData.length , inPacket.getSocketAddress());
  43. // 发送数据
  44. socket.send(outPacket);
  45. }
  46. }
  47. }
  48. public static void main(String[] args)
  49. throws IOException
  50. {
  51. new UdpServer().init();
  52. }
  53. }

上面程序中的粗体字代码就是使用DatagramSocket发送、接收DatagramPacket的关键代码,该程序可以接收1000个客户端发送过来的数据。

客户端程序代码也与此类似,客户端采用循环不断地读取用户键盘输入,每当读取到用户输入的内容后就将该内容封装成DatagramPacket数据报,再将该数据报发送出去;接着把DatagramSocket中的数据读入接收用的DatagramPacket中(实际上是读入该DatagramPacket所封装的字节数组中)。客户端程序代码如下。

17.4.2  使用DatagramSocket发送、接收数据(3)

程序清单:codes\17\17.4\UdpClient.java

  1. public class UdpClient
  2. {
  3. // 定义发送数据报的目的地
  4. public static final int DEST_PORT = 30000;
  5. public static final String DEST_IP = "127.0.0.1";
  6. // 定义每个数据报的最大大小为4KB
  7. private static final int DATA_LEN = 4096;
  8. // 定义接收网络数据的字节数组
  9. byte[] inBuff = new byte[DATA_LEN];
  10. // 以指定的字节数组创建准备接收数据的DatagramPacket对象
  11. private DatagramPacket inPacket =
  12. new DatagramPacket(inBuff , inBuff.length);
  13. // 定义一个用于发送的DatagramPacket对象
  14. private DatagramPacket outPacket = null;
  15. public void init()throws IOException
  16. {
  17. try(
  18. // 创建一个客户端DatagramSocket,使用随机端口
  19. DatagramSocket socket = new DatagramSocket())
  20. {
  21. // 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
  22. outPacket = new DatagramPacket(new byte[0] , 0
  23. , InetAddress.getByName(DEST_IP) , DEST_PORT);
  24. // 创建键盘输入流
  25. Scanner scan = new Scanner(System.in);
  26. // 不断地读取键盘输入
  27. while(scan.hasNextLine())
  28. {
  29. // 将键盘输入的一行字符串转换成字节数组
  30. byte[] buff = scan.nextLine().getBytes();
  31. // 设置发送用的DatagramPacket中的字节数据
  32. outPacket.setData(buff);
  33. // 发送数据报
  34. socket.send(outPacket);
  35. // 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组中
  36. socket.receive(inPacket);
  37. System.out.println(new String(inBuff , 0
  38. , inPacket.getLength()));
  39. }
  40. }
  41. }
  42. public static void main(String[] args)
  43. throws IOException
  44. {
  45. new UdpClient().init();
  46. }
  47. }

上面程序中的粗体字代码同样也是使用DatagramSocket发送、接收DatagramPacket的关键代码,这些代码与服务器端代码基本相似。而客户端与服务器端的唯一区别在于:服务器端的IP地址、端口是固定的,所以客户端可以直接将该数据报发送给服务器端,而服务器端则需要根据接收到的数据报来决定"反馈"数据报的目的地。

读者可能会发现,使用DatagramSocket进行网络通信时,服务器端无须也无法保存每个客户端的状态,客户端把数据报发送到服务器端后,完全有可能立即退出。但不管客户端是否退出,服务器端都无法知道客户端的状态。

当使用UDP协议时,如果想让一个客户端发送的聊天信息被转发到其他所有的客户端则比较困难,可以考虑在服务器端使用Set集合来保存所有的客户端信息,每当接收到一个客户端的数据报之后,程序检查该数据报的源SocketAddress是否在Set集合中,如果不在就将该SocketAddress添加到该Set集合中。这样又涉及一个问题:可能有些客户端发送一个数据报之后永久性地退出了程序,但服务器端还将该客户端的SocketAddress保存在Set集合中……总之,这种方式需要处理的问题比较多,编程比较烦琐。幸好Java为UDP协议提供了MulticastSocket类,通过该类可以轻松地实现多点广播。

时间: 2024-11-05 16:02:27

udp协议基础(转自疯狂java讲义)的相关文章

《疯狂java讲义》读后感

<疯狂java讲义·第三版>,全书共851页,18章. 目录如下: 第1章 Java语言概述与开发环境 第2章 理解面向对象 第3章 数据类型和运算符 第4章 流程控制与数组 第5章 面向对象(上) 第6章 面向对象(下) 第7章 Java基础类库 第8章 Java集合 第9章 泛型 第10章 异常处理 第11章 AWT编程 第12章 Swing编程 第13章 MySQL数据库与JDBC编程 第14章 Annotation(注释) 第15章 输入/输出 第16章 多线程 第17章 网络编程 第

疯狂Java讲义(第4版) PDF 电子书 百度云 网盘下载

java电子书推荐理由:1)作者提供用于学习和交流的配套网站及作者亲自在线的答疑微信群.QQ群. 2)DVD光盘中含 1500分钟图书部分内容的相关视频 图书配套代码 Java面试题真题 PPT课件 设计模式电子书 有助于完成课后练习的大量完整案例 3)<疯狂Java讲义>历时十年沉淀,现已升级到第4版,经过无数Java学习者的反复验证,被包括北京大学在内的大量985.211高校的优秀教师引荐为参考资料.选作教材. 4)<疯狂Java讲义>曾翻译为中文繁体字版,在宝岛台湾上市发行.

《疯狂Java讲义》(三十五)---- 网络编程

Java网络通信非常简单,服务器端通过ServerSocket建立监听,客户端通过Socket连接到指定服务器后,通信双方就可以通过IO流进行通信. IP地址用于唯一地标识网络中的一个通信实体.端口用于表示数据交给哪个通信程序处理. 公认端口从0到1023,紧密绑定一些特定的服务.注册端口从1024到49151,应用程序通常应该使用这个范围的端口.动态端口从49152到65535,是应用程序使用的动态端口,应用程序一般不会主动使用这些端口. package com.ivy.net; import

疯狂java讲义——继承

本文章只是记录我在学习疯狂java讲义里面,对之前java知识查缺补漏进行的总结. 方法重写 方法重写要遵循"两同两小一大"规则."两同"即方法名相同.形参列表相同:“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出异常类应比父类方法声明抛出异常类更小或相等;"一大"指的是子类方法访问权限应比父类方法访问权限更大或相等.尤其指出,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法

疯狂java 讲义第三章练习题---画圆

public class PaintRound{ /** * @author Dream_e. * @version v1.0 */ private int r;//圆的半径 PaintRound(int r){ this.r = r; } public void paint(){ int y = 2*r;//y的最大值为半径的2倍 int x = 0; int c = 0; int z = 2;//坐标轴递减量. for(int j = y; j >= 0; j-=z){ x = getX(r

疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条

http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程java任务timerstring 2009-01-16 21:12 6722人阅读 评论(0) 收藏 举报  分类: J2SE(63)  版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天讲了Swing基本的控件,今天开始讲特殊控件.一天讲2个吧,首先讲用JProgressBar,Pro

学在前面——《疯狂JAVA讲义》学习笔记

一直放弃JAVA,觉得身为前端掌握html.css.js就可以胜任工作了,但是最近组里后台太忙了,有时候感觉只要在后台改一点东西就能满足前台所需要的数据了,但是自己却不会改,后台还木有空,于是觉得,嗯,身为一个IT的新新人才,当然也要回基本的JAVA了,不要求会写,但是基本的会改还是必要的~~ 于是看了各种JAVA从入门到精通的帖子,选中了<疯狂JAVA讲义>这本书,尽管书有点老了,但是感觉很全面,讲的很好,希望能够有所收获~俗话说,带着问题去学习,发现尽管很久以前了解过JAVA,但是前言里面

疯狂Java讲义(1) -- Java语言概述

学生提问:不是说JVM是运行Java程序的虚拟机吗?那JRE和JVM的关系是怎样的呢?答:简单的说,JRE包含JVM.JVM是运行Java程序的核心虚拟机,而运行Java程序不仅需要核心虚拟机,还需要其他的类加载器.字节码校验器以及大量的基础类库.JRE除了包含JVM之外,还包含运行Java程序的其他环境支持. 学生提问:为什么不安装公共JRE呢?答:公共JRE是一个独立的JRE系统,会单独安装在系统的其他路径下.公用JRE会向Internet Explorer浏览器以及系统中注册Java运行时

《疯狂Java讲义》(十八)---- JAR文件

使用JAR文件 JAR文件全称Java ARchive File, java档案文件. 通常JAR文件是一种压缩文件,通常称为JAR包.JAR文件和ZIP文件的区别是JAR文件默认包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件时在生成JAR文件时由系统自动创建的. 当开发了一个应用程序后,这个应用程序包含了很多类,如果需要把这个应用程序提供给别人使用,通常会将这些类文件打包给一个JAR文件,把这个jar文件提供给别人使用. 只要别人在系统的CLASSPATH环境变