UDP编程

UDP编程

标签(空格分隔): udp java


  1. UDP套接字:
    UDP 协议提供了一种不同于TCP协议的端到端服务。实际上UDP协议只实现两个功能:

1)在 IP 协议的基础上添加了另一层地址(端口),
2)对数据传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据。

可以进行类比的理解:

  • TCP协议与电话通信相似,而UDP协议则与邮件通信相似:你寄包裹或信件时不需要进行”连接”,但是你得为每个包裹和信件指定目的地址。
  • 在接收信息时,UDP套接字扮演的角色就像是一个信箱,从不同地址发送来的信件和包裹都可以放到里面。一旦被创建,UDP套接字就可以用来连续地向不同的地址发送信息,或从任何地址接收信息
  • UDP 套接字与TCP套接字的另一个不同点在于他们对信息边界的处理方式不同:UDP套接字将保留边界信息
  • 最后一个不同点是, UDP 协议所提供的端到端传输服务是尽力而为( best-effort)的,即 UDP 套接字将尽可能地传送信息,但并不保证信息一定能成功到达目的地址,而且信息到达的顺序与其发送顺序不一定一致(就像通过邮政部门寄信一样)。因此,使用了 UDP 套接字的程序必须准备好处理信息的丢失和重排。

使用UDP的原因:

  • 效率:如果应用程序只交换非常少量的数据,例如从客户端到服务器端的简单请求消息,或一个反方向的响应消息, TCP连接的建立阶段就至少要传输其两倍的信息量(还有两倍的往返延迟时间)
  • 另一个原因是灵活性:如果除可靠的字节流服务外,还有其他的需,UDP 协议则提供了一个最小开销的平台来满足任何需求的实现。

UDP工作流程:
与 TCP 协议发送和接收字节流不同, UDP 终端交换的是一种称为数据报文的自包含( self-contained)信息。这种信息在 Java 中表示为 DatagramPacket 类的实例。发送信息时,Java 程序创建一个包含了待发送信息的 DatagramPacket 实例,并将其作为参数传递DatagramSocket 类的 send()方法。接收信息时, Java 程序首先创建一个 DatagramPacket 实例,该实例中预先分配了一些空间(一个字节数组 byte[]),并将接收到的信息存放在该空间中。然后把该实例作为参数传递给 DatagramSocket 类的 receive()方法。除传输的信息本身外,每个 DatagramPacket 实例中还附加了地址和端口信息,其具体含义取决于该数据报文是被发送还是被接收。若是要发送的数据报文, DatagramPacket 实例中的地址则指明了目的地址和端口号,若是接收到的数据报文, DatagramPacket 实例中的地址则指明了所收信息的源地址。因此,服务器端可以修改接收到的 DatagramPacket 实例的缓存区内容,再将这个实例连同修改后的信息一起,发回给它的源地址。在DatagramPacket 的内部也有 length 和 offset 字段,分别定义了数据信息在缓存区的起始位置和字节数。

UDP客户端:
UDP 客户端首先向被动等待联系的服务器端发送一个数据报文。一个典型的 UDP 客户
端主要执行以下三步:

  • 创建一个 DatagramSocket实例,可以选择对本地地址和端口号进行设置。
  • 使用 DatagramSocket 类的 send() 和 receive()方法来发送和接收DatagramPacket实例,进行通信。
  • 通信完成后,使用 DatagramSocket 类的 close()方法来销毁该套接字。

客户端代码如下:

public class UDPClient {
    private static final int TIMEOUT = 3000;
    private static final int MAXTRIES = 5;
    //本地端口
    private static final int SOCKET_PORT = 9000;
    //接受地址端口
    private static final int PACKET_PORT = 3000;
    public static void main(String[] args) throws Exception {
        String str_send = "hello udp server";
        byte[] buff = new byte[1024];
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        DatagramSocket ds_socket  = new DatagramSocket(SOCKET_PORT);
        InetAddress server_address = InetAddress.getLocalHost();
        DatagramPacket dp_send;
        DatagramPacket dp_receive = new DatagramPacket(buff, 1024);
        ds_socket.setSoTimeout(TIMEOUT);
        int tries =0;
        boolean receiveResponse = false;
        while((!receiveResponse)&&(tries<MAXTRIES)){
            //发送数据
            System.out.println("client开始发送消息:");
//          str_send = input.readLine();
            //解决中文输入乱码问题,将str_send.length(),改为str_send.getBytes().length即可
            dp_send = new DatagramPacket(str_send.getBytes(), str_send.getBytes().length,server_address,PACKET_PORT);
            ds_socket.send(dp_send);
            try{
                ds_socket.receive(dp_receive);
                if(!dp_receive.getAddress().equals(server_address)){//check source
                    throw new Exception("received packet from unknown source");
                }
                receiveResponse = true;
            }catch(Exception e){
                tries +=1;
                System.out.println("time out,try :"+tries);
            }


        }

        if(receiveResponse){
            System.out.print("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);  
             dp_receive.setLength(1024);
        }else{
            System.out.println("no response --giving up");
        }
        ds_socket.close();
    }

}

UDP服务端
与 TCP 服务器一样, UDP 服务器的工作是建立一个通信终端,并被动等待客户端发起连接。但由于 UDP 是无连接的, UDP 通信通过客户端的数据报文初始化,并没有 TCP 中建立连接那一步。典型的 UDP 服务器要执行以下三步:
1. 创建一个 DatagramSocket 实例,指定本地端口号,并可以选择指定本地地址。此时,服务器已经准备好从任何客户端接收数据报文。
2. 使用 DatagramSocket 类的 receive()方法来接收一个 DatagramPacket 实例。当 receive()方法返回时,数据报文就包含了客户端的地址,这样我们就知道了回复信息应该发送到什么地方。
3. 使用 DatagramSocket 类的 send() 和 receive()方法来发送和接收 DatagramPackets 实例,进行通信。

public class UDPServer {

    public static void main(String[] args) throws Exception {
        final int ECHOMAX =1024;
        String str_send = "hello client";

        byte[] buffer = new byte[ECHOMAX];
        //服务端在3000端口监听接收到的数据  
        DatagramSocket ds = new DatagramSocket(3000);
        DatagramPacket dp_receive = new DatagramPacket(buffer, ECHOMAX);
        System.out.println("server is on ,waiting for client");
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        DatagramPacket dp_send ;
        while(true){
            ds.receive(dp_receive);
             String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) +   
                        " from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();  
            System.out.print("server接收到的消息:");
            System.out.println(str_receive);
            System.out.println("server开始发送消息:");
//          str_send = input.readLine();
             dp_send = new DatagramPacket(str_send.getBytes(), str_send.getBytes().length,dp_receive.getAddress(),dp_receive.getPort());
            ds.send(dp_send);
            //重置缓存区
            dp_receive.setLength(ECHOMAX);
        }

    }

}

效果如下:
此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

需要注意的地方

  • 一个微小但重要的差别是UDP协议保留了消息的边界信息
    DatagramSocket 的每一次 receive()调用最多只能接收调用一次 send()方法所发送的数据。而且,不同的 receive()方法调用绝不会返回同一个 send()方法调用所发送的数据
    当在 TCP 套接字的输出流上调用 write()方法返回后,所有调用者都知道数据已经被复制到一个传输缓存区中,实际上此时数据可能已经被发送,也有可能还没有被传送,而 UDP 协议没有提供从网络错误中恢复的机制,因此,并不对可能需要重传的数据进行缓存。这就意味着,当send()方法调用返回时,消息已经被发送到了底层的传输信道中。
  • UDP 数据报文所能负载的最多数据,亦及一次传送的最大数据为 65507 个字节
    当消息从网络中到达后,其所包含的数据被 TCP 的 read()方法或 UDP 的 receive()方法返回前,数据存储在一个先进先出的接收数据队列中。对于已经建立连接的 TCP 套接字来说,所有已接受但还未传送的字节都看作是一个连续的字节序列。然而,对于 UDP 套接字来说,接收到的数据可能来自不同的发送者,一个 UDP 套接字所接受的数据存放在一个消息队列中,每个消息都关联了其源地址信息,每次 receive()调用只返回一条消息。如果 receive()方法在一个缓存区大小为 n 的 DatagramPacket 实例中调用,而接受队里中的第一条消息的长度大于 n,则 receive()方法只返回这条消息的前 n 个字节,超出部分会被自动放弃,而且对接收程序没有任何消息丢失的提示!

出于这个原因,接受者应该提供一个有足够大的缓存空间的 DatagramPacket 实例,以完整地存放调用 receive()方法时应用程序协议所允许的最大长度的消息。一个 DatagramPacket 实例中所允许传输的最大数据量为 65507 个字节,也即是 UDP 数据报文所能负载的最多数据。因此,可以用一个 65600 字节左右的缓存数组来接受数据。
- DatagramPacket 的内部消息长度值在接收数据后会发生改变,变为实际接收到的数据的长度值。
每一个 DatagramPacket 实例都包含一个内部消息长度值,其初始值为 byte 缓存数组的长度值,而该实例一旦接受到消息,这个长度值便会变为接收到的消息的实际长度值,这一点可以用 DatagramPacket 类的 getLength()方法来测试。如果一个应用程序使用同一个 DatagramPacket 实例多次调用 receive()方法,每次调用前就必须显式地将其内部消息长度重置为缓存区的实际长度,以免接受的数据发生丢失。
- 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 对象的方法:

new String(dp_receive.getData(),dp_receive.getOffset(),
dp_receive.getOffset() + dp_receive.getLength());

(出自Java Tcp Ip Socket编程(中文版))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值