【Java TCP/IP Socket】UDP Socket(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/14128987

UDP的Java支持

UDP协议提供的服务不同于TCP协议的端到端服务,它是面向非连接的,属不可靠协议,UDP套接字在使用前不需要进行连接。实际上,UDP协议只实现了两个功能:

1)在IP协议的基础上添加了端口;

2)对传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据。

Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocket的send()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;接收信息时,Java程序首先创建一个DatagramPacket实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给DatagramSocket实例的receive()方法。在创建DatagramPacket实例时,要注意:如果该实例用来包装待接收的数据,则不指定数据来源的远程主机和端口,只需指定一个缓存数据的byte数组即可(在调用receive()方法接收到数据后,源地址和端口等信息会自动包含在DatagramPacket实例中),而如果该实例用来包装待发送的数据,则要指定要发送到的目的主机和端口。

UDP的通信建立的步骤

UDP客户端首先向被动等待联系的服务器发送一个数据报文。一个典型的UDP客户端要经过下面三步操作:

1、创建一个DatagramSocket实例,可以有选择地对本地地址和端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据;

2、使用DatagramSocket实例的send()和receive()方法来发送和接收DatagramPacket实例,进行通信;

3、通信完成后,调用DatagramSocket实例的close()方法来关闭该套接字。

由于UDP是无连接的,因此UDP服务端不需要等待客户端的请求以建立连接。另外,UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。一个典型的UDP服务端要经过下面三步操作:

1、创建一个DatagramSocket实例,指定本地端口号,并可以有选择地指定本地地址,此时,服务器已经准备好从任何客户端接收数据报文;

2、使用DatagramSocket实例的receive()方法接收一个DatagramPacket实例,当receive()方法返回时,数据报文就包含了客户端的地址,这样就知道了回复信息应该发送到什么地方;

3、使用DatagramSocket实例的send()方法向服务器端返回DatagramPacket实例。

UDP Socket Demo

这里有一点需要注意:

UDP程序在receive()方法处阻塞,直到收到一个数据报文或等待超时。由于UDP协议是不可靠协议,如果数据报在传输过程中发生丢失,那么程序将会一直阻塞在receive()方法处,这样客户端将永远都接收不到服务器端发送回来的数据,但是又没有任何提示。为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来制定receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端。

下面给出一个客户端服务端UDP通信的Demo(没有用多线程),该客户端在本地9000端口监听接收到的数据,并将字符串"Hello UDPserver"发送到本地服务器的3000端口,服务端在本地3000端口监听接收到的数据,如果接收到数据,则返回字符串"Hello UDPclient"到该客户端的9000端口。在客户端,由于程序可能会一直阻塞在receive()方法处,因此这里我们在客户端用DatagramSocket实例的setSoTimeout()方法来指定receive()的最长阻塞时间,并设置重发数据的次数,如果最终依然没有接收到从服务端发送回来的数据,我们就关闭客户端。

客户端代码如下:

[java] view plain copy

  1. package zyb.org.UDP;
  2. import java.io.IOException;
  3. import java.io.InterruptedIOException;
  4. import java.net.DatagramPacket;
  5. import java.net.DatagramSocket;
  6. import java.net.InetAddress;
  7. public class UDPClient {
  8. private static final int TIMEOUT = 5000;  //设置接收数据的超时时间
  9. private static final int MAXNUM = 5;      //设置重发数据的最多次数
  10. public static void main(String args[])throws IOException{
  11. String str_send = "Hello UDPserver";
  12. byte[] buf = new byte[1024];
  13. //客户端在9000端口监听接收到的数据
  14. DatagramSocket ds = new DatagramSocket(9000);
  15. InetAddress loc = InetAddress.getLocalHost();
  16. //定义用来发送数据的DatagramPacket实例
  17. DatagramPacket dp_send= new DatagramPacket(str_send.getBytes(),str_send.length(),loc,3000);
  18. //定义用来接收数据的DatagramPacket实例
  19. DatagramPacket dp_receive = new DatagramPacket(buf, 1024);
  20. //数据发向本地3000端口
  21. ds.setSoTimeout(TIMEOUT);              //设置接收数据时阻塞的最长时间
  22. int tries = 0;                         //重发数据的次数
  23. boolean receivedResponse = false;     //是否接收到数据的标志位
  24. //直到接收到数据,或者重发次数达到预定值,则退出循环
  25. while(!receivedResponse && tries<MAXNUM){
  26. //发送数据
  27. ds.send(dp_send);
  28. try{
  29. //接收从服务端发送回来的数据
  30. ds.receive(dp_receive);
  31. //如果接收到的数据不是来自目标地址,则抛出异常
  32. if(!dp_receive.getAddress().equals(loc)){
  33. throw new IOException("Received packet from an umknown source");
  34. }
  35. //如果接收到数据。则将receivedResponse标志位改为true,从而退出循环
  36. receivedResponse = true;
  37. }catch(InterruptedIOException e){
  38. //如果接收数据时阻塞超时,重发并减少一次重发的次数
  39. tries += 1;
  40. System.out.println("Time out," + (MAXNUM - tries) + " more tries..." );
  41. }
  42. }
  43. if(receivedResponse){
  44. //如果收到数据,则打印出来
  45. System.out.println("client received data from server:");
  46. String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) +
  47. " from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();
  48. System.out.println(str_receive);
  49. //由于dp_receive在接收了数据之后,其内部消息长度值会变为实际接收的消息的字节数,
  50. //所以这里要将dp_receive的内部消息长度重新置为1024
  51. dp_receive.setLength(1024);
  52. }else{
  53. //如果重发MAXNUM次数据后,仍未获得服务器发送回来的数据,则打印如下信息
  54. System.out.println("No response -- give up.");
  55. }
  56. ds.close();
  57. }
  58. }

服务端代码如下:

[java] view plain copy

  1. package zyb.org.UDP;
  2. import java.io.IOException;
  3. import java.net.DatagramPacket;
  4. import java.net.DatagramSocket;
  5. public class UDPServer {
  6. public static void main(String[] args)throws IOException{
  7. String str_send = "Hello UDPclient";
  8. byte[] buf = new byte[1024];
  9. //服务端在3000端口监听接收到的数据
  10. DatagramSocket ds = new DatagramSocket(3000);
  11. //接收从客户端发送过来的数据
  12. DatagramPacket dp_receive = new DatagramPacket(buf, 1024);
  13. System.out.println("server is on,waiting for client to send data......");
  14. boolean f = true;
  15. while(f){
  16. //服务器端接收来自客户端的数据
  17. ds.receive(dp_receive);
  18. System.out.println("server received data from client:");
  19. String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) +
  20. " from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();
  21. System.out.println(str_receive);
  22. //数据发动到客户端的3000端口
  23. DatagramPacket dp_send= new DatagramPacket(str_send.getBytes(),str_send.length(),dp_receive.getAddress(),9000);
  24. ds.send(dp_send);
  25. //由于dp_receive在接收了数据之后,其内部消息长度值会变为实际接收的消息的字节数,
  26. //所以这里要将dp_receive的内部消息长度重新置为1024
  27. dp_receive.setLength(1024);
  28. }
  29. ds.close();
  30. }
  31. }

如果服务器端没有运行,则receive()会失败,此时运行结果如下图所示:

如果服务器端先运行,而客户端还没有运行,则服务端运行结果如下图所示:

此时,如果客户端运行,将向服务端发送数据,并接受从服务端发送回来的数据,此时运行结果如下图所示:

几个需要注意的地方

1、UDP套接字和TCP套接字的一个微小但重要的差别:UDP协议保留了消息的边界信息。

DatagramSocket的每一次receive()调用最多只能接收调用一次send()方法所发送的数据,而且,不同的receive()方法调用绝对不会返回同一个send()方法所发送的额数据。

当在TCP套接字的输出流上调用write()方法返回后,所有调用者都知道数据已经被复制到一个传输缓存区中,实际上此时数据可能已经被发送,也有可能还没有被传送,而UDP协议没有提供从网络错误中恢复的机制,因此,并不对可能需要重传的数据进行缓存。这就意味着,当send()方法调用返回时,消息已经被发送到了底层的传输信道中。

2、UDP数据报文所能负载的最多数据,亦及一次传送的最大数据为65507个字节

当消息从网络中到达后,其所包含的数据被TCP的read()方法或UDP的receive()方法返回前,数据存储在一个先进先出的接收数据队列中。对于已经建立连接的TCP套接字来说,所有已接受但还未传送的字节都看作是一个连续的字节序列。然而,对于UDP套接字来说,接收到的数据可能来自不同的发送者,一个UDP套接字所接受的数据存放在一个消息队列中,每个消息都关联了其源地址信息,每次receive()调用只返回一条消息。如果receive()方法在一个缓存区大小为n的DatagramPacket实例中调用,而接受队里中的第一条消息的长度大于n,则receive()方法只返回这条消息的钱n个字节,超出部分会被自动放弃,而且对接收程序没有任何消息丢失的提示!

出于这个原因,接受者应该提供一个有足够大的缓存空间的DatagramPacket实例,以完整地存放调用receive()方法时应用程序协议所允许的最大长度的消息。一个DatagramPacket实例中所允许传输的最大数据量为65507个字节,也即是UDP数据报文所能负载的最多数据。因此,可以用一个65600字节左右的缓存数组来接受数据。

3、DatagramPacket的内部消息长度值在接收数据后会发生改变,变为实际接收到的数据的长度值。

每一个DatagramPacket实例都包含一个内部消息长度值,其初始值为byte缓存数组的长度值,而该实例一旦接受到消息,这个长度值便会变为接收到的消息的实际长度值,这一点可以用DatagramPacket类的getLength()方法来测试。如果一个应用程序使用同一个DatagramPacket实例多次调用receive()方法,每次调用前就必须显式地将其内部消息长度重置为缓存区的实际长度,以免接受的数据发生丢失(见上面客户端代码第53行,服务端代码第29行)。

以上面的程序为例,若在服务端的receiver()后加入如下代码:System.out.println(dp_receive.getLength());则得到的输出结果为:15,即接收到的字符串数据“Hello UDPserver”的长度。

4、DatagramPacket的getData()方法总是返回缓冲区的原始大小,忽略了实际数据的内部偏移量和长度信息。

由于DatagramPacket的getData()方法总是返回缓冲数组的原始大小,即刚开始创建缓冲数组时指定的大小,在上面程序中,该长度为1024,因此如果我们要获取接收到的数据,就必须截取getData()方法返回的数组中只含接收到的数据的那一部分。

在Java1.6之后,我们可以使用Arrays.copyOfRange()方法来实现,只需一步便可实现以上功能:

byte[] destbuf = Arrays.copyOfRange(dp_receive.getData(),dp_receive.getOffset(),

dp_receive.getOffset() + dp_receive.getLength());

当然,如果要将接收到的字节数组转换为字符串的话,也可以采用本程序中直接new一个String对象的方法(见上面客户端代码第48行,服务端代码第21行):

new String(dp_receive.getData(),dp_receive.getOffset(),

dp_receive.getOffset() + dp_receive.getLength());

以上几个比较重要的知识点,笔者均已做过测试。

时间: 2024-10-14 12:42:35

【Java TCP/IP Socket】UDP Socket(含代码)的相关文章

Socket、Http、TCP/IP、UDP的联系与区别

HTTP协议:简单对象访问协议,对应于应用层  ,HTTP协议是基于TCP连接的 tcp协议:    对应于传输层 ip协议:     对应于网络层 TCP/IP是传输层协议,主要解决数据如何在网络中传输:而HTTP是应用层协议,主要解决如何包装数据. Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议. http连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断

TCP/IP、UDP、 Http、Socket的区别

网络由上往下分为: 表示层和应用层 :HTTP协议(基于传输层的TCP协议,主要解决如何包装数据) 会话层 传输层: TCP协议(基于网络层的IP协议).TPC/IP协议(主要解决数据如何在网络中传输) 网络层: IP 协议 数据链路层 物理层 4.socket则是对TCP/IP协议的封装和应用(程序员层面上),Socket本身并不是协议,而是一个调用接口(API,它只是提供了一个针对TCP或者UDP编程的接口),通过Socket,我们才能使用TCP/IP协议,实际上,Socket跟TCP/IP

http、TCP/IP协议与socket之间的区别

网络由下往上分为:  www.2cto.com 物理层-- 数据链路层-- 网络层--                       IP协议 传输层--                       TCP协议 会话层-- 表示层和应用层--           HTTP协议 1.TCP/IP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上. 建立起一个TCP连

Http TCP/IP协议和socket之间的区别和联系

总结,TCP/IP是传输层协议,主要解决数据如何在网路中传输,socket是TCP/IP协议的具体实现,是对TCP/IP协议的封装和应用,属于程序员层面,HTTP是应用层协议,应用层协议很多,类似的像HTTP.FTP.Telnet. 传输层协议和应用层协议的区别和分工可以用下面的一段话做总结: "我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP.FTP.T

Http、TCP/IP协议与Socket

网络由下往上分为: 物理层-- 数据链路层-- 网络层-- IP协议 传输层-- TCP协议 会话层-- 表示层和应用层-- HTTP协议 1.TCP/IP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在"无差别"的网络之上. 建立起一个TCP连接需要经过"三次握手": 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态

TCP/IP、UDP、HTTP、SPDY等的一些解释说明

文章大部分内容均是来自于网络和相关的官方文档,仅作整理和总结. 在理解这些名词以及他们之间的关系之前,有必要先理解下OSI模型.OSI七层模型详解 简单的用下面的图表试图显示不同的TCP/IP和其他的协议在最初OSI模型中的位置: 7 应用层 例如HTTP.SMTP.SNMP.FTP.Telnet.SIP.SSH.NFS.RTSP.XMPP.Whois.ENRP 6 表示层 例如XDR.ASN.1.SMB.AFP.NCP 5 会话层 例如ASAP.TLS.SSH.ISO 8327 / CCITT

【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)

书上示例 在第一章<基本套接字>中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去. 书上客户端代码如下: import java.net.Socket; import java.net.SocketException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class TCPEch

一个项目看java TCP/IP Socket编程

前一段时间刚做了个java程序和网络上多台机器的c程序通讯的项目,遵循的是TCP/IP协议,用到了java的Socket编程.网络通讯是java的强项,用TCP/IP协议可以方便的和网络上的其他程序互通消息. 先来介绍下网络协议:     TCP/IP         Transmission Control Protocol 传输控制协议         Internet Protocol 互联网协议     UDP         User Datagram Protocol 用户数据协议

【Java TCP/IP Socket】Socket编程知识点总结

简介 1.协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构.交换方式.包含的意义以及怎样对报文所包含的信息进行解析. 2.TCP/IP协议族有IP协议.TCP协议和UDP协议. 3.TCP协议和UDP协议使用的地址叫做端口号,用来区分同一主机上的不同应用程序.TCP协议和UDP协议也叫端到端传输协议,因为他们将数据从一个应用程序传输到另一个应用程序,而IP协议只是将数据从一个主机传输到另一个主机. 4.在TCP/IP协议中,有两部分信息用来确定一个指定的程序:互联网地址和端口号: