【网络编程】——Java实现(4)——数据报(All About Datagrams)

这里是Java网络编程Java Socket编程相关的学习手记。这里按照官方的Java 8 Toturial教程的Custom Networking学习路径,对相关的一些内容进行解读(并不完全,如果有错请联系我,谢谢^ _ ^),同时在学习的过程中加入个人的理解与对代码运行的思考。

下面是整个专栏的文章链接,用于快速的导航。

简介与文章导航

翻译。原文出自Java 8官方教程,这里是Java Tutorial中的有关网络All About Datagrams的章节。


简介

之前我们讲的客户端与服务端通信都是通过先建立连接(无论是显示地建立还是隐式地建立),再进行数据通信。它们都可以被称作可靠(reliable)传输。这里我们谈到UDP数据报(UDP datagram ) ,它是一种不可靠的传输。

一些你写的用于网络通信的一些程序并不需要可靠的,点对点的由TCP提供的信道。相反,你的应用可能会得益于一种通信模式,此通信模式传递单独的数据包,而且数据是否到达目的地,到达目的地的顺序并不能保证。

UDP协议提供一种叫做datagrams的网络通信模式,通过此模式一个应用发送数据包(packets of data)给另一个应用程序。

数据报是一个单独的,独立的在网络传输的信息,它的到达与否,到达时间,还有数据是否有偏差都不能保证。在Java中java.net包中的DatagramPacketDatagramSocket类实现了独立于具体操作系统的UDP数据报通信。

下面对本文章的内容进行概要,详细的内容在下面的小结中介绍。

  • 什么是数据报(What Is a Datagram?)
    • 数据报是在网络中传输的一个单独的,独立的消息,它的到达与否,到达时间,内容差错与否都不能得到保证。
  • 客户端与服务端数据报实践(Writing a Datagram Client and Server)
    • 这部分通过例子来了解Java程序怎么使用数据报来通信。服务端是一个格言服务器( quote server ),它监听它的DatagramSocket并随时对客户端的请求回应相应的格言单(quotation )。客户端是一个简单地发送服务端请求的程序。
  • 发送广播数据报实践(Broadcasting to Multiple Recipients)
    • 此部分会修改上面所说的格言服务器,让它不仅仅只是发送格言单给单个的客户端,而是让它能够没分钟广播一个格言给尽可能多的客户端,当然这些客户端能收到并处理的前提是它们正在监听服务端的数据返回。因此客户端的程序必须也要进行修改。
    • 注意:许多的防火墙和路由器都配置了不允许UDP数据包通过。如果你/你的客户端在防火墙外连接一个服务端有问题的话,要请你的系统管理员让其放行UDP数据包

什么是数据报(What Is a Datagram?)

下面比较TCP,UDP数据传输的区别

客户端与服务端通过TCP socket的可靠传输进行通信,会有一条介于C/S之间专门的点到点的信道,或者为了方便理解你至少可以想象地认为底层是有这么一条信道。他们为了通信,会依次建立连接,传输数据,关闭连接。所有在信道上按一定顺序发送的的数据都会以相同的顺序在另一端接收到。我们认为,此信道是可靠的。

相反,应用收发的每个数据报(datagrams )则完全是单个独立的。这些客户端和服务端并没有也不需要有一条专门的点到点的信道。数据报传输最终到达与否是并不能保证的,同时也并不能保证他们到达目的地的顺序。

定义:数据包是一个在网上传输的单独的,独立的消息个体,它的到达与否,到达时间,内容差错与否都不能得到保证。

java.net包中有三个类能帮助你的Java程序在网络中使用数据报来收发数据包(packets ):

  • DatagramSocket
  • DatagramPacket
  • MulticastSocket
    一个应用程序能够通过DatagramSocket来发送,接收DatagramPackets。另外,DatagramPackets也能被广播到多个正在监听MulticastSocket的接收者。

客户端与服务端数据报实践(Writing a Datagram Client and Server)

在本章的例子中包含两个程序:client ,server。server通过一个数据报socket(datagram socket)不断地接收数据包。每次服务端接收到数据包时,就表明有个客户端请求了格言(quotation),服务端此时就会通过发送内容为"quote of the moment"的数据包回应给客户端。

客户端程序相对简单。它发送单个的数据包给服务端表示客户端逍遥接收到当前的格言(quote )。客户端然后等待数据包的回应。

这里通过两个类来实现服务端程序:QuoteServerQuoteServerThread。通过一个类实现客户端QuoteClient。下面我们研究一下这些类。

下面分别给出这三个类的实现代码。觉得版权声明太长,也可以直接点击链接查看相应的代码QuoteServerQuoteServerThreadQuoteClient具体的代码说明不会全部按照教程给出的解释。

QuoteServer 类的实现很简单:

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 
import java.io.*;

public class QuoteServer {
    public static void main(String[] args) throws IOException {
        new QuoteServerThread().start();
    }
}

QuoteServer实现很简单,就new一个线程start就行。服务端的实现重点在下面要讲的QuoteServerThread类。

下面看到 QuoteServerThread 类:

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 
import java.io.*;
import java.net.*;
import java.util.*;

public class QuoteServerThread extends Thread {

    protected DatagramSocket socket = null;
    protected BufferedReader in = null;
    protected boolean moreQuotes = true;

    public QuoteServerThread() throws IOException {
	     this("QuoteServerThread");
    }

    public QuoteServerThread(String name) throws IOException {
        super(name);
        socket = new DatagramSocket(4445);

        try {
            in = new BufferedReader(new FileReader("one-liners.txt"));
        } catch (FileNotFoundException e) {
            System.err.println("Could not open quote file. Serving time instead.");
        }
    }

    public void run() {

        while (moreQuotes) {
            try {
                byte[] buf = new byte[256];

                // receive request
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                socket.receive(packet);

                // figure out response
                String dString = null;
                if (in == null)
                    dString = new Date().toString();
                else
                    dString = getNextQuote();

                buf = dString.getBytes();

		// send the response to the client at "address" and "port"
                InetAddress address = packet.getAddress();
                int port = packet.getPort();
                packet = new DatagramPacket(buf, buf.length, address, port);
                socket.send(packet);
            } catch (IOException e) {
                e.printStackTrace();
		moreQuotes = false;
            }
        }
        socket.close();
    }

    protected String getNextQuote() {
        String returnValue = null;
        try {
            if ((returnValue = in.readLine()) == null) {
                in.close();
		moreQuotes = false;
                returnValue = "No more quotes. Goodbye.";
            }
        } catch (IOException e) {
            returnValue = "IOException occurred in server.";
        }
        return returnValue;
    }
}

构造函数run()构成整个类的主体,另外getNextQuote()是辅助模块,用于读取文件内容的。

构造函数初始化服务端的socket,绑定监听端口,初始化文件读取类BufferedReader,用与从文件one-liners.txt中读取格言来回复给客户端

现在我们感兴趣的应该是QuoteServerThread的run方法。

首先看到getNextQuote()方法,读取文件中的一行格言,并且返回,当文件被读完的时候,moreQuotes被置为false

run()方法通过while循环,其中while循环的结束点是:当moreQuotes==false(格言文件被读完)的时候,while退出,此时线程也会结束了。

再细看run方法中的框架就是收(receive)发(send)

// receive request (接收数据包)
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);

// send the response to the client (发送数据包)
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);

当然收和发中间的数据处理过程如:buf数据的读取,数据包的IP地址端口号的获取都是很容易理解的了。

下面看到 QuoteClient 类:

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 

import java.io.*;
import java.net.*;
import java.util.*;

public class QuoteClient {
    public static void main(String[] args) throws IOException {

        if (args.length != 1) {
             System.out.println("Usage: java QuoteClient <hostname>");
             return;
        }

            // get a datagram socket
        DatagramSocket socket = new DatagramSocket();

            // send request
        byte[] buf = new byte[256];
        InetAddress address = InetAddress.getByName(args[0]);
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
        socket.send(packet);
    
            // get response
        packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet);

	    // display response
        String received = new String(packet.getData(), 0, packet.getLength());
        System.out.println("Quote of the Moment: " + received);
    
        socket.close();
    }
}

理解了服务端的代码,客户端的也很容易理解了。
这里的框架步骤是先发(send),再收(receive):
// send request
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);

// get response
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);

其它的都很容易理解啦。

下面试一试运行程序:

运行程序,以及自定义包名,在命令行下运行可能会发生的问题

因为我的程序在前面都会,声明包名package com.shengid.socket

我的程序源代码的三个文件都在com.shengid.socket包下。所以项目文件源码的都在 src/com/shengid/socket/目录下。此时会出现一些问题。

所以编译的时候我会按照如下的方式编译:
problem-1
然后我把one-liners.txt文件放在类的同级目录下,也就是说:src/com/shengid/socket/one-liners.txt。此时运行会出现如下错误:
problem-2

查看源代码,很容易知道,报的错误为QuoteServerThread 构造函数里面的FileNotFoundException错误,文件没找到。

正确的做法是把文件放在src目录下,也就是与com同级的目录下

|--src
|-----com
|-----one-liners.txt

下面运行服务端:
problem-3
运行客户端:
problem-04

发送广播数据报实践(Broadcasting to Multiple Recipients)

除了DatagramSocket能够发送数据包给其它应用程序之外,还有java.net中的MulticastSocket类。此socket类在客户端用来监听服务器发送的多播数据包。

让我们来重写上面的格言服务器(quote server),让它广播DatagramPackets 数据包给多个接收端,而不是发送数据给单个特定的客户端,新实现的服务器要以一定的时间间隔定期地广播格言数据。同样,客户端也需要修改,使其能够顺利地监听MulticastSocket上的格言数据。

同样这个例子中也有类: MulticastServer, MulticastServerThread, MulticastClient

MulticastServer类:

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 

public class MulticastServer {
    public static void main(String[] args) throws java.io.IOException {
        new MulticastServerThread().start();
    }
}

MulticastServer实现很简单,仍然和以前一样的处理方式。

下面说一下MulticastServerThread类,这里是继承自QuoteServerThread

public class MulticastServerThread extends QuoteServerThread {
    // ...
}

extends QuoteServerThread让其能使用父类的构造函数,getNextQuote

下面是代码实现:

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 

import java.io.*;
import java.net.*;
import java.util.*;

public class MulticastServerThread extends QuoteServerThread {

    private long FIVE_SECONDS = 5000;

    public MulticastServerThread() throws IOException {
        super("MulticastServerThread");
    }

    public void run() {
        while (moreQuotes) {
            try {
                byte[] buf = new byte[256];

                // construct quote
                String dString = null;
                if (in == null)
                    dString = new Date().toString();
                else
                    dString = getNextQuote();
                buf = dString.getBytes();

                // send it
                InetAddress group = InetAddress.getByName("230.0.0.1");
                DatagramPacket packet = new DatagramPacket(buf, buf.length, group, 4446);
                socket.send(packet);

                // sleep for a while
                try {
                    sleep((long)(Math.random() * FIVE_SECONDS));
                } catch (InterruptedException e) { }
            } catch (IOException e) {
                e.printStackTrace();
                moreQuotes = false;
            }
        }
        socket.close();
    }
}

通过上面的代码,我们看到,它并不需要先接收信息,再返回信息给客户端,而是每个5秒发送多播消息出去。

联想到之前的单播消息,我们发送消息时设置socket的ip地址,端口号都是通过收到的socket消息来提取的。而这里数据包的ip地址,端口号需要我们服务端以硬编码(hard-coded)的方式来设置。其中ip地址"203.0.113.0"是一个组播ip地址而非单个主机的地址。而在我们的多播业务场景中,组播地址是可以从保留地址中任意选取的。如此一来:创建的DatagramPacket数据包就能到达所有监听此端口,组播地址为"203.0.113.0"的客户端

那么下面我们查看客户端的实现,下面是MulticastClient 类的实现

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 

import java.io.*;
import java.net.*;
import java.util.*;

public class MulticastClient {

    public static void main(String[] args) throws IOException {

        MulticastSocket socket = new MulticastSocket(4446);
        InetAddress address = InetAddress.getByName("230.0.0.1");
        socket.joinGroup(address);

        DatagramPacket packet;

        // get a few quotes
        for (int i = 0; i < 5; i++) {

            byte[] buf = new byte[256];
            packet = new DatagramPacket(buf, buf.length);
            socket.receive(packet);

            String received = new String(packet.getData(), 0, packet.getLength());
            System.out.println("Quote of the Moment: " + received);
        }

        socket.leaveGroup(address);
        socket.close();
    }

}

MulticastSocket调用方法socket.joinGroup(address);用来标识组播地址的组( identifies the group

下面我们来运行一下:开启服务端,开启多个客户端

运行

编译因为MulticastServerThread继承自QuoteServerThread,所以QuoteServerThread也需要编译,因为在上面章节我已经编译了,下面就不进行编译了,不过要注意这一点。
multi-01

运行服务端。 运行没有报任何错误,没出错。
multi-02

运行三个客户端。运行服务端后,我们再运行三个客户端,没有问题,三个都能接收到5条消息,然后推出程序。
multi-client1
multi-client2
multi-client3

注意到上面的client1的数据和client2,client3的不同。原因很简单,启动时间不同。

总结

从简单的URL,Connection到Socket,再到本章的数据报。一步步,我们对Java平台的网络编程有了更深的了解。

当然后面还有许多值得探索的,期待。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值