使用 Java 多播图像

使用 Java 多播图像 

截图 Compiz


在网络内分发图像对于演示或仅在会议室中缺少投影仪时非常有用。本文展示了如何多播图像和屏幕截图,以便网络中的任何人都可以查看它们。

Multcasting 是基于UDP 数据包的。UDP 提供(与TCP不同)没有流量控制。这意味着不能保证包裹按正确的顺序交付,或者可能根本不会交付。计算机可以加入由 IP 号码和端口表示的所谓多播组。
组播组的每个成员将接收属于该组的组播数据包。
 

概述

在这个示例应用程序中,我们将多播屏幕截图和图像,以便它们可以在网络中的任何地方显示。
该应用程序由发送方和接收方 Java 应用程序组成。

如果您不热衷于了解所有详细信息,您可以直接跳到下面的运行应用程序部分并测试应用程序。
 

基础

接收组播包

组播报文的接收可以分为以下几个步骤:

  1. 在第一步中,通过调用MulticastSocket()创建多播套接字 ,该方法将套接字绑定到的端口作为参数。
  2. 下一步是加入组播组。
    多播组由范围从224.0.0.0239.255.255.255的 IP 号表示。要加入组,调用joinGroup()方法。
  3. 创建一个字节缓冲区、一个 DatagramPacket 对象并调用 MulticastSocket 方法receive() 以接受数据报包。
  4. 通过 在 DatagramPacket 上调用getData()接收和处理数据报包后, 最后一步是离开多播组并关闭多播套接字。
    这是通过调用 多播套接字上的leaveGroup() 和close()方法来完成的。


以下代码说明了这四个步骤。
 

/* Step one */
int port = 4444;
MulticastSocket ms = new MulticastSocket(port);

/* Step two */
String multicastAddress = "225.4.5.6";
InetAddress ia = InetAddress.getByName(multicastAddress);
ms.joinGroup(ia);

/* Step three */
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ms.receive(dp);
byte[] data = dp.getData();

/* Step four */
ms.leaveGroup(ia);
ms.close();
Receiving a Multicast Packet


 

传输组播数据包

TTL - 生存时间

指定在丢弃数据包之前要通过的路由器数量(跳数)。
每个通过数据包的路由器都会将 TTL 减一。TTL 为零的数据包将被丢弃。

对于 MulticastSockets(在 Java 中),TTL 默认为 1,这意味着所有数据包都将保留在同一网络中。

可以通过在 MulticastSocket 上调用setTimeToLive()来更改该参数


 
生存时间 (TTL)

传输组播数据包比接收数据包要容易一些。
如果只是发送数据包,则不必加入组。

这些是传输多播数据包所需的步骤:

  1. 在第一步中,为指定的多播组 IP 地址创建一个 InetAddress。
  2. 下一步是构造一个多播套接字和一个数据报数据包。
  3. 现在唯一剩下的就是通过多播套接字发送数据报并关闭套接字。


这四个步骤如下所示。
 

/* Step one */
String multicastAddress = "225.4.5.6";
InetAddress ia = InetAddress.getByName(multicastAddress);

/* Step two */
int port = 4444;
ms = new MulticastSocket();
DatagramPacket dp = new DatagramPacket(imageData, imageData.length,
                    ia, port);

/* Step three */
ms.send(dp);
ms.close();

Transmitting a Multicast Packet



现在我们知道如何传输和接收多播数据包,我们将看看图像是如何创建的。
 

创建屏幕截图

通过网络发送屏幕截图相当有趣,因为它使我们可以向其他人展示演示文稿或屏幕内容。

在 Java 中创建屏幕截图非常简单。从 Java 1.3 开始,Robot 类可用,这也使我们可以创建屏幕截图。 Robot 类的createScreenCapture()
方法采用一个矩形来指定要捕获的屏幕部分。在我们的例子中,我们将始终捕获整个屏幕。 下面是 用于在我们的程序中获取屏幕截图的方法getScreenshot()的源代码。


 

public static BufferedImage getScreenshot() throws AWTException,
            ImageFormatException, IOException {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension screenSize = toolkit.getScreenSize();
        Rectangle screenRect = new Rectangle(screenSize);

        Robot robot = new Robot();
        BufferedImage image = robot.createScreenCapture(screenRect);

        return image;
    }
Creating a Screenshot


 

从文件系统读取图像

除了发送屏幕截图,应用程序还可以发送存储在文件系统中的图像。
该应用程序将按随机顺序从目录中读取 JPEG 图像。
 

    public static BufferedImage getRandomImageFromDir(File dir) throws IOException {
        String[] images = dir.list(new ImageFileFilter());
        int random = new Random().nextInt(images.length);

        String fileName = dir.getAbsoluteFile() + File.separator + images[random];
        File imageFile = new File(fileName);

        return ImageIO.read(imageFile);
    }


class ImageFileFilter implements FilenameFilter
{
    public boolean accept( File dir, String name )
    {
      String nameLc = name.toLowerCase();
      return nameLc.endsWith(".jpg") ? true : false;
    }
}

Reading Imges from the Filesystem


 

传输数据

既然我们知道了如何传输多播数据包以及如何获取图像,那么我们就来仔细看看如何通过网络传输大图像。
 

UDP 数据包大小

UDP(以及 TCP)封装在 IP 数据包中。

最大 IP 数据包大小为 65535。从最大 IP 数据包大小中,我们必须减去 IP 头的 20 个字节和 UDP 头的 8 个字节。
因此,UDP 数据包中可以传输的最大数据大小为 65507 字节。
 

123

1 - IP 报头(20 字节)2 - UDP 报头(8 字节)3 - UDP 数据(最大 65507 字节)
 
UDP数据包



65507 字节的限制对于小图像来说可能足够了,但对于全屏截图来说这还不够。
可以缩小图像以使其适合单个 UDP 数据包,但这会使这些图像难以查看。

在我们的解决方案中,我们将把图像分成适当大小的块并通过网络传输它们。
如上所述,UDP 内部没有流量控制,因此我们必须自己处理。
 

交通管制

为了确保传输的图像切片以正确的顺序重新组合,一些标头被添加到 UDP 数据包中。
这将减少可以在数据包内传输的数据量,但这应该是可以接受的。
 

旗帜8 位包含 SESSION_END
和 SESSION_START 标志
会话编号8 位数据包所属的会话
数据包8 位总包数
最大数据包大小16 位每个数据包的最大大小
包号8 位当前包的数量
尺寸16 位当前包的数据大小

发送方确定每个图像大小的大小,然后通过网络将切片与上述附加头信息一起发送。

根据附加头信息中给出的信息,图像接收器可以确定图像的最终尺寸、当前图像数据的位置以及图像完成的天气。
 

图像发送器和接收器




发件人代码片段如下所示。

 

 
        while(true) {
                BufferedImage image = getScreenshot();

                image = shrink(image, scalingFactor);
                byte[] imageByteArray = bufferedImageToByteArray(image, OUTPUT_FORMAT);
                int packets = (int) Math.ceil(imageByteArray.length / (float)DATAGRAM_MAX_SIZE);


                int HEADER_SIZE = 8;
                int MAX_PACKETS = 255;
                int SESSION_START = 128;
                int SESSION_END = 64;

                if(packets > MAX_PACKETS) {
                    System.out.println("Image is too large to be transmitted!");
                    continue;
                }

                for(int i = 0; i <= packets; i++) {
                    int flags = 0;
                    flags = i == 0 ? flags | SESSION_START: flags;
                    flags = (i + 1) * DATAGRAM_MAX_SIZE > imageByteArray.length ? flags | SESSION_END : flags;

                    int size = (flags & SESSION_END) != SESSION_END ? DATAGRAM_MAX_SIZE : imageByteArray.length - i * DATAGRAM_MAX_SIZE;

                    byte[] data = new byte[HEADER_SIZE + size];
                    data[0] = (byte)flags;
                    data[1] = (byte)sessionNumber;
                    data[2] = (byte)packets;
                    data[3] = (byte)(DATAGRAM_MAX_SIZE >> 8);
                    data[4] = (byte)DATAGRAM_MAX_SIZE;
                    data[5] = (byte)i;
                    data[6] = (byte)(size >> 8);
                    data[7] = (byte)size;

                    System.arraycopy(imageByteArray, i * DATAGRAM_MAX_SIZE, data, HEADER_SIZE, size);
                    sender.sendImage(data, "225.4.5.6", 4444);

                    if((flags & SESSION_END) == SESSION_END) break;
                }

                Thread.sleep(SLEEP_MILLIS);
                sessionNumber = sessionNumber < MAX_SESSION_NUMBER ? ++sessionNumber : 0;
            }

Image Sender



接收器检查 UDP 数据并确定图像属于当前会话的天气以及图像切片是否已经存储。
采集完所有切片后,将显示图像。
 

 
            byte[] buffer = new byte[DATAGRAM_MAX_SIZE];
            while (true) {
                DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
                ms.receive(dp);
                byte[] data = dp.getData();

                int SESSION_START = 128;
                int SESSION_END = 64;
                int HEADER_SIZE = 8;

                short session = (short)(data[1] & 0xff);
                short slices = (short)(data[2] & 0xff);
                int maxPacketSize = (int)((data[3] & 0xff) << 8 | (data[4] & 0xff)); // mask the sign bit
                short slice = (short)(data[5] & 0xff);
                int size = (int)((data[6] & 0xff) << 8 | (data[7] & 0xff)); // mask the sign bit

                if((data[0] & SESSION_START) == SESSION_START) {
                    if(session != currentSession) {
                        currentSession = session;
                        slicesStored = 0;
                        imageData = new byte[slices * maxPacketSize];
                        slicesCol = new int[slices];
                        sessionAvailable = true;
                    }
                }

                if(sessionAvailable && session == currentSession){
                    if(slicesCol != null && slicesCol[slice] == 0) {
                        slicesCol[slice] = 1;
                        System.arraycopy(data, HEADER_SIZE, imageData, slice * maxPacketSize, size);
                        slicesStored++;
                    }
                }

                if(slicesStored == slices) {
                    ByteArrayInputStream bis= new ByteArrayInputStream(imageData);
                    BufferedImage image = ImageIO.read(bis);
                    labelImage.setIcon(new ImageIcon(image));
                    windowImage.setIcon(new ImageIcon(image));

                    frame.pack();
                }
            }
Image Receiver
图像接收器


原则上就是这样。现在让我们来看看应用程序。
 

应用程序

该应用程序的工作原理如上所述,但除此之外还有一些附加功能。
这些功能在本文中没有解释,但通过阅读源代码应该很容易理解。

这些附加功能是:

  • 发送者缩放图像
  • 显示鼠标位置
  • 以全屏模式显示图像(接收器)

运行应用程序


首先下载应用程序。
ZIP 文件包含两个 jar 文件和源代码。
 

对于第一个测试,只需双击 jar 文件(ImageSender.jar 和 ImageReceiver.jar)。
这将启动发送方和接收方应用程序。

您应该会看到类似于以下屏幕截图的内容。
 

发件人

  

接收者

发送方和接收方


在一台机器上运行这两个程序没有多大意义,但这是一个测试,如果两个应用程序都在工作。
对于真正的测试,在两台不同的计算机上启动 jar 文件。发送方的屏幕截图应显示在接收方一侧。

如果运行发送器的目录中有 图像文件夹,则将发送这些图像(JPEG 格式)而不是屏幕截图。
要以全屏模式查看图像,只需在多播图像接收器窗口中按任意键即可。

以下是一些示例屏幕截图。
 

Ubuntu 上的 Eeepc 屏幕

  

eeePC 上的 Ubuntu

  

运行 Jalimo 的诺基亚 N800


截图


 

命令行参数

程序也可以通过指定命令行参数来运行。

当未指定命令行参数时,将使用以下默认值:
 

IP 多播地址: 225.4.5.6
端口: 4444
显示鼠标光标: 1
以秒为单位的更新间隔(发送方): 2
缩放系数: 0.5


以下命令行参数可用:
 

java -jar ImageSender.jar   scaling_factor update_interval_sec show_mouse port ip_address

java -jar ImageReceiver.jar  端口 ip_address



命令行选项不必完全指定,但中间不能省略任何选项。
所以指定以下选项是可以的: java -jar ImageSender.jar   0.7 3 1 2048
 

无线网络

无线网络可能是一个问题,因为并非所有路由器都支持多播到具有无线连接的计算机。
结果可能是图像没有及时显示或根本没有显示。
如果您的无线连接遇到问题,请尝试通过电缆直接将您的计算机连接到路由器。


而已。我希望你喜欢这篇文章。
如有疑问,请给我留言。

快乐编码!

面带微笑


(joschi)

版本:0.2
作者的电子邮件地址:jochen [at] fun2code.de

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值