`

【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()的最长阻塞时间,并设置重发数据的次数,如果最终依然没有接收到从服务端发送回来的数据,我们就关闭客户端。

客户端代码如下:

package zyb.org.UDP;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {
	private static final int TIMEOUT = 5000;  //设置接收数据的超时时间
	private static final int MAXNUM = 5;      //设置重发数据的最多次数
	public static void main(String args[])throws IOException{
		String str_send = "Hello UDPserver";
		byte[] buf = new byte[1024];
		//客户端在9000端口监听接收到的数据
		DatagramSocket ds = new DatagramSocket(9000);
		InetAddress loc = InetAddress.getLocalHost();
		//定义用来发送数据的DatagramPacket实例
		DatagramPacket dp_send= new DatagramPacket(str_send.getBytes(),str_send.length(),loc,3000);
		//定义用来接收数据的DatagramPacket实例
		DatagramPacket dp_receive = new DatagramPacket(buf, 1024);
		//数据发向本地3000端口
		ds.setSoTimeout(TIMEOUT);              //设置接收数据时阻塞的最长时间
		int tries = 0;                         //重发数据的次数
		boolean receivedResponse = false;     //是否接收到数据的标志位
		//直到接收到数据,或者重发次数达到预定值,则退出循环
		while(!receivedResponse && tries<MAXNUM){
			//发送数据
			ds.send(dp_send);
			try{
				//接收从服务端发送回来的数据
				ds.receive(dp_receive);
				//如果接收到的数据不是来自目标地址,则抛出异常
				if(!dp_receive.getAddress().equals(loc)){
					throw new IOException("Received packet from an umknown source");
				}
				//如果接收到数据。则将receivedResponse标志位改为true,从而退出循环
				receivedResponse = true;
			}catch(InterruptedIOException e){
				//如果接收数据时阻塞超时,重发并减少一次重发的次数
				tries += 1;
				System.out.println("Time out," + (MAXNUM - tries) + " more tries..." );
			}
		}
		if(receivedResponse){
			//如果收到数据,则打印出来
			System.out.println("client received data from server:");
			String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) + 
					" from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();
			System.out.println(str_receive);
			//由于dp_receive在接收了数据之后,其内部消息长度值会变为实际接收的消息的字节数,
			//所以这里要将dp_receive的内部消息长度重新置为1024
			dp_receive.setLength(1024);   
		}else{
			//如果重发MAXNUM次数据后,仍未获得服务器发送回来的数据,则打印如下信息
			System.out.println("No response -- give up.");
		}
		ds.close();
	}  
} 


服务端代码如下:
package zyb.org.UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer { 
	public static void main(String[] args)throws IOException{
		String str_send = "Hello UDPclient";
		byte[] buf = new byte[1024];
		//服务端在3000端口监听接收到的数据
		DatagramSocket ds = new DatagramSocket(3000);
		//接收从客户端发送过来的数据
		DatagramPacket dp_receive = new DatagramPacket(buf, 1024);
		System.out.println("server is on,waiting for client to send data......");
		boolean f = true;
		while(f){
			//服务器端接收来自客户端的数据
			ds.receive(dp_receive);
			System.out.println("server received data from client:");
			String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) + 
					" from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();
			System.out.println(str_receive);
			//数据发动到客户端的3000端口
			DatagramPacket dp_send= new DatagramPacket(str_send.getBytes(),str_send.length(),dp_receive.getAddress(),9000);
			ds.send(dp_send);
			//由于dp_receive在接收了数据之后,其内部消息长度值会变为实际接收的消息的字节数,
			//所以这里要将dp_receive的内部消息长度重新置为1024
			dp_receive.setLength(1024);
		}
		ds.close();
	}
}


  如果服务器端没有运行,则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());

以上几个比较重要的知识点,笔者均已做过测试。
分享到:
评论

相关推荐

    TCP/UDP socket 调试工具

    TCP/UDP socket 调试工具 TCP/UDP socket 调试工具 SocketTool调试软件是一款网络TCP/UDP通信调试工具,本工具集功能强大与简单易用为一体,是无需安装的免费绿色软件。她可以帮助网络编程人员、网络维护人员检查所...

    Java TCP_IP Socket编程源代码书籍以及附带源码

    本资源包含一本关于Java TCP/IP Socket编程的书籍及其配套源代码,对于深入理解Socket编程至关重要。 书籍《Java TCP/IP Socket编程原书第2版》详细阐述了如何在Java环境中利用Socket进行网络通信。书中涵盖的知识...

    TCP/UDP Socket调试工具

    TCP/UDP Socket调试工具 V2.3是网络软件测试开发必备工具,对网络开发测试起到很大的帮助作用。可设置成TCP/UDP的Server端和Client端

    C# tcp/ip Socket Programmer

    《C# TCP/IP Socket程序员实战指南》是一本深入讲解如何在C#环境下使用TCP/IP协议进行网络编程的书籍。在当今互联网技术飞速发展的时代,掌握网络编程技能,特别是使用C#进行TCP/IP通信,对于软件开发者来说至关重要...

    sockettool 测试工具 tcp/ip/udp

    SocketTool是一款功能强大的网络通信测试工具,主要用于TCP/IP和UDP协议的测试与调试。它能够帮助开发者、网络管理员以及IT专业人员快速建立服务器端和客户端的连接,验证网络通信的有效性和稳定性。在本文中,我们...

    基于TCP/IP协议的Socket编程

    Java实现的Socket编程是基于TCP/IP协议的,它提供了一个可靠的数据流服务,可以实现客户端和服务器之间的数据交换。Java中的Socket类和ServerSocket类是两种基本的套接字类,Socket类用于建立客户端和服务器之间的...

    Socket网络TCP/UDP通信调试工具

    SocketTool调试工具官方版是一款网络TCP/UDP通信调试工具。SocketTool调试工具最新版可以有效地帮助网络管理员检测各种网络通信状永久,集成了TCP服务器/客户端、UDP服务器/客户端和UDP广播/组播等功能。SocketTool...

    TCP/IP和UDP的socket网络编程源码和文档

    本资源包提供的是基于TCP/IP和UDP的socket网络编程源码及文档,适用于实验平台mini2440开发板和Ubuntu操作系统。 TCP/IP协议栈是一个分层的通信模型,包含四层:链路层、网络层、传输层和应用层。TCP(Transmission...

    TCP/UDP Socket 调试工具

    首先,TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种主要的传输层协议,它们在Internet协议族(TCP/IP协议栈)中扮演着关键角色。 TCP是一种面向连接的、可靠的协议,它保证了数据的...

    基于Windows的TCP/IP及UDP通信类库

    在压缩包中,"src"目录通常包含源代码文件,这些文件可能包含了实现TCP/IP和UDP通信功能的类和函数,以及MFC界面的相关代码。"bin (win32)"目录则可能包含编译好的二进制文件,如动态链接库(.dll)或静态链接库(....

    中国科学技术大学TCP/IP Socket网络编程的PPT

    【TCP/IP Socket网络编程概述】 TCP/IP Socket网络编程是中国科学技术大学教授的一门课程,主要针对初学者,旨在介绍网络通信的基础知识,特别是TCP/IP协议及其在Socket编程中的应用。这门课程涵盖了84页的PPT内容...

    tcp/udp socket调试工具

    Socket编程在IT行业中是网络通信的基础,特别是在TCP/IP协议栈中扮演着至关重要的角色。TCP(传输控制协议)和UDP(用户数据报协议)是两种主要的传输层协议,它们各自有着独特的特性和应用场景。本篇文章将围绕"tcp...

    北京邮电大学TCP/IP Socket网络编程

    【TCP/IP Socket网络编程】是北京邮电大学提供的学习材料,对网络编程的学习者具有很大帮助。本课程主要涵盖了以下几个核心知识点: 1. **Internet与TCP/IP协议**:Internet起源于1950年代的美国,最初是为了解决...

    Android socket局域网的UDP广播自动连接及TCP/ip通讯与心跳检测

    简单的Android Socket ...客户端通过UDP广播获取到服务器端的IP地址,通过TCP/IP协议与服务器建立连接。代码实现心跳检测,当服务器断网或是结束进程,客户端可重新连接(重连部分自己实现);代码有点凌乱,仅供参考!

    Java_TCP_IP_Socket编程(原书第2版)

    以上只是Java TCP/IP Socket编程的一些基本概念和关键点,实际应用中还会涉及到更多高级话题,如SSL/TLS安全套接层、多播Socket、UDP(用户数据报协议)编程等。通过深入学习和实践,你可以掌握构建高效、稳定的网络...

    C# TCP/IP通信小例子

    在C#中,我们通常使用System.Net命名空间下的Socket类来处理TCP/IP通信。 首先,让我们来看看服务端的实现。服务端需要监听特定的端口,等待客户端的连接请求。在C#中,可以创建一个Socket实例,并调用Bind方法绑定...

    TCP/UDP网络调试助手

    private void Form1_Load(object sender, EventArgs e)//初始化为UDP Server模式 { cobProtocol.SelectedIndex = 0; txtIP.Text = GetAddressIP(); Control.CheckForIllegalCrossThreadCalls = false; } ...

    基于TCP/IP的Socket多线程通信(服务器和客户端)

    在计算机网络编程中,TCP/IP协议族是连接网络应用的核心,而Socket是其接口,它提供了应用程序与网络通信的能力。本主题将深入探讨基于TCP/IP的Socket如何实现多线程通信,包括服务器端和客户端的设计与实现。 1. *...

Global site tag (gtag.js) - Google Analytics