java udp socket问题_JAVA-基于UDP的Socket实现

本文详细介绍了UDP协议的特点,如非连接性、高效性等,并探讨了Java中使用DatagramPacket和DatagramSocket实现UDP通信的过程。通过示例展示了如何创建UDP客户端和服务端,包括数据发送、接收及超时重传的处理。
摘要由CSDN通过智能技术生成

UDP(User Data Protocol,用户数据报协议)

(1) UDP是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

(2) 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。

(3) UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。

(4) 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

(5)UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。

(6)UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。

UDP的Java支持

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

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

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

Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocket的send()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;接收信息时,Java程序首先创建一个Datagram实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给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程序在receive()方法处阻塞,直到收到一个数据报文或等待超时。由于UDP协议是不可靠协议,如果数据报在传输过程中发生丢失,那么程序将会一直阻塞在receive()方法处,这样客户端将永远都接收不到服务器端发送回来的数据,但是又没有任何提示。为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来制定receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端。

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

代码如下:

服务端UDPServer:

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.SocketException;

1public class UDPServer{

private static final String TAG = "UDPServer: ";

public static void main(String[] args) {

String sendStr = "客服端你好, 我是服务端";

byte[] buf = new byte[1024];

//服务端在3333端口监听接收到的数据

try{

DatagramSocket datagramSocket = new DatagramSocket( 3333 );

//接收从客户端发送过来的数据

DatagramPacket packet_receive = new DatagramPacket( buf,buf.length );

System.out.println("server is on,waiting for client to send data...");

boolean f =true;

while (f){

//服务器端接收来自客户端的数据

datagramSocket.receive( packet_receive );

System.out.println("server receive data from client");

String receiveStr = new String(packet_receive.getData(),0,packet_receive.getLength())+

" from " + packet_receive.getAddress().getHostAddress()+":"+packet_receive.getPort();

System.out.println(receiveStr);

//数据发送到客户端的3333端口

// DatagramPacket packet_send = new DatagramPacket( sendStr.getBytes(),sendStr.length(),packet_receive.getAddress(),9999 );

//sendStr.length()为求字符串的长度,它的长度与该字符串转化为字节数组的长度不一致,可能会造成数据末尾丢失,所以改为如下

DatagramPacket packet_send = new DatagramPacket( sendStr.getBytes(),sendStr.getBytes().length,packet_receive.getAddress(),9999 );

datagramSocket.send( packet_send );

//由于packet_receive在接收了数据之后,其内部消息长度值会变为实际接收消息的字节数

//所以这里要将packet_receive的内部消息长度重新设置为1024

packet_receive.setLength( 1024 );

}

datagramSocket.close();

}catch (SocketException e){

System.out.println(TAG+e.getMessage());

e.printStackTrace();

}catch (IOException e){

System.out.println(TAG+e.getMessage());

e.printStackTrace();

}

}

}

客户端:

import java.io.IOException;

import java.io.InterruptedIOException;

import java.net.*;

1public class UDPClient{

private static final String TAG = "UDPClient: ";//设置标签

private static final int TIMEOUT = 5000;//设置接收数据的超时时间

private static final int MAXNUM = 5;//设置重发数据的最多次数

public static void main(String[] args) {

String sendStr = "服务端你好,我是UDP客户端";

byte[] buf = new byte[1024];

try {

//客服端在9999端口监听接收到的数据

DatagramSocket datagramSocket = new DatagramSocket( 9999 );

InetAddress address = InetAddress.getLocalHost();

//定义用来发送数据的DatagramPacket实例

DatagramPacket packet_send = new DatagramPacket( sendStr.getBytes(),sendStr.getBytes().length ,address,3333);

//定义用来接收数据的DatagramPacket实例

DatagramPacket packet_receive = new DatagramPacket( buf,buf.length );

//数据发向本地3333端口

datagramSocket.setSoTimeout( TIMEOUT );//设置接收数据时阻塞的最长时间

int tries = 0;//重发数据的次数

boolean receiveResponse = false;//是否接收到数据的标志位

//直到接收到数据,或者重发次数达到预定值,则退出循环

while (!receiveResponse && tries < MAXNUM){

//发送数据

datagramSocket.send( packet_send );

try{

//接收从服务端发送回来的数据

datagramSocket.receive( packet_receive );

//如果接收到的数据不是来自目标地址,则抛出异常

if(!packet_receive.getAddress().equals( address )){

throw new IOException( "Received paket from an unknow source" );

}

//如果接收到数据 则将receiveResponse标志位改为true,退出循环

receiveResponse = true;

}catch (InterruptedIOException e){

//如果接收数据时阻塞超时,重发并减少一次重发的次数

tries += 1;

System.out.println("Time out,"+(MAXNUM-tries)+" more tries");

}

}

if(receiveResponse){

//如果收到数据,则打印出来

System.out.println("client received data from server: ");

String receiveStr = new String( packet_receive.getData(),0,packet_receive.getLength() )+" from "+

packet_receive.getAddress().getHostAddress()+":"+packet_receive.getPort();

System.out.println(receiveStr);

//由于packet_receive在接收了数据之后,其内部消息长度值会变为实际接收消息的字节数

//所以这里要将packet_receive的内部消息长度重新设置为1024

packet_receive.setLength( 1024 );

}else{

//如果重发MAXNUM次数据后 仍未获得服务器发送回来的数据,则打印如下信息

System.out.println("No response -- give up.");

}

datagramSocket.close();

}catch (SocketException e){

System.out.println(TAG+e.getMessage());

e.printStackTrace();

}catch (UnknownHostException e){

System.out.println(TAG+e.getMessage());

e.printStackTrace();

}catch (IOException e){

System.out.println(TAG+e.getMessage());

e.printStackTrace();

}

}

}

结果如下:

客户端显示

7b00ab9c6bb866b4431e4dcc2c1bf126.png

服务端显示如下

6e24e0a756dd8f71ca60a958aecc5811.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值