本文介绍了网络通讯中通用的传输控制协议(TCP)和用户数据包协议(UDP),并 且利用Java语言设计了一个简单的基于UDP 数据广播的局域网络会议程序,展示了 在Java语言中进行UDP 数据发送和接收的一般步骤。由于Java语言卓越的跨平台特 性,本系统能够不加修改的运行在Windows, Linux, Mac OS等一系列不同平台上。
介绍
随着网络技术的普及,网络会议在公司、企业和单位中的应用也越来越广。一 个网络会议系统通常包括一个服务器程序和一个客户端程序。其中服务器端负责进 行用户治理、信息交互以及表决统计;客户端则实现收听发言,公开发言,私下讨 论、投票表决等功能。在一个网络会议系统的设计和实现中,通常涉及到图形用户 界面设计,TCP/IP连接,UDP 数据广播,多线程等一系列技术。本文通过一个简单 示例程序,展示了在Java语言中进行UDP 数据发送和接收的一般步骤以及UDP 数据 广播在局域网络会议系统中的作用。
TCP (Transmission Control Protocol,传输控制协议) 是一种基于连接的通 讯协议。当两台计算机之间需要进行可靠的数据传输时,它们通过网络建立起一个 稳定的连接,这种连接通常也被称为数据链。与电话网络相类似,这种数据链是点 对点的,通讯的双方则通过这条数据链往返传输数据。在这条稳定的数据链的基础 上,TCP 协议通过信息校验能够保证接收方所接收到的数据和发送方所发送的数据 在内容和顺序上是完全一致的,从而实现了数据的可靠传输。
UDP (User Datagram Protocol,用户数据包协议)与TCP 协议之间的不同在于 UDP 不是一种基于稳定连接的通讯协议。UDP 协议将独立的数据包从一台计算机传 输到另外一台计算机,但是并不保证接受方能够接收到该数据包,也不保证接收方 所接收到的数据和发送方所发送的数据在内容和顺序上是完全一致的。因此,UDP 协议更类似于普通邮政服务,寄信人不能够保证所寄出去的信能够被收信人及时收 到,后发出的信也许会比先发出的信更早到达。
对于很多应用程序来说,在互相通讯的两台计算机之间保证一个可靠与稳定的 数据链是至关重要的。在这种情况下,就应该首先考虑使用TCP 协议在凉台计算机 之间建立起TCP/IP连接。在HTTP (Hyper-Text Transfer Protocol,超级文本传输 协议)、FTP (File Transfer Protocol, 文件传输协议)以及TELNET 应用程序中, 均要求在通讯的双方之间建立起稳定可靠的数据链,因此它们都使用了TCP 协议来 进行数据传输。
在TCP 协议中,发送方和接收方必须交换额外的信息以保证接收方已经接收到 所发送的数据包并且所接收到的数据和发送方所发送的数据在内容和顺序上是完全 一致的。这些额外的信息交换提高了数据传输的可靠度,但是也给网络带来了额外 的负担,导致数据交换的延迟,从而降低了整个网络的数据交换能力。对于某些对 实时性要求较高的应用程序来说,这样的延迟有可能是不可接受的。例如一个毫秒 级的时钟服务器按照一定的频率向客户机提供当时的时间数据,假如这些时间数据 在传输过程中受到了较大的延迟,这些过时的时间数据是完全没有意义的,即使客 户机准确无误的接收到了这些数据。相反,假如客户机所接收到的每一个数据包都 是实时的,那么即使客户机错过了一两个数据包也是可以接受的,因为他总是可以 根据后面所接收到的数据包来对自己进行校正。因此,对于对实时性要求比较高但 是对传输可靠度要求比较低的应用程序来说,UDP 协议显然是一个合适的选择。
在通用的以太网(Ehternet)构架下,计算机于计算机之间的数据交换都是通过 交换机来完成的。假如一份数据需要被传送给多个接收者,在使用TCP/IP连接的情 况下,数据发送者需要向交换机发送N 个同样的拷贝,而交换机则负责将这N 个拷 贝分发给所有的接收者;在使用UDP 数据广播的情况下,数据发送者只需要向交换 机发送一个拷贝,交换机负责将这个信息制作N 个拷贝发送给所有的机器。在这种 情况下,使用TCP/IP连接会大大的增加网络的负担。在一个普通局域网络中,可以 认为由于网络状况较差而造成数据丢失的可能性比较小,而利用UDP 数据广播进行 数据交换能够大幅度减轻网络的负担,因此设计一个基于UDP 数据广播的局域网络 会议系统式完全可行的。
通常来说,一台计算机只有一个物理界面与网络相连接,所有的应用程序均通 过该物理界面从网络接收数据或者将数据发送到网络。由于一个网络上同时存在多 台计算机,并且一台计算机上有可能同时存在多个应用程序需要与网络进行数据交 换,我们通常使用IP和端口号来识别需要进行数据交换的计算机和应用程序。每台 计算机由一个32位的IP地址来识别,在一个网络中,每台计算机的IP地址都是唯一 的,因此应用程序能够根据IP地址来将数据发送到正确的计算机。每个需要与网络 进行数据交换的应用程序均被系统分配一个16位的端口号,系统根据这个端口号将 从网络接收到的数据转发给相对应的应用程序。端口号的范围是从0 到65535 ,其 中从0 到1023被系统所保留,主要是用来提供HTTP, FTP 以及TELNET等系统服务, 因此用户自己的应用程序不应该试图去使用小于1023的端口。
Java语言的一个显著优点就是它从语言的高度上提供了对网络的支持,使得程 序员能够很轻易的构建基于网络的应用程序。在Java 1.3版的标准类库java.net中 提供了5 个接口以及21个Java类,在这些接口和类的基础上,程序员能够轻易的实 现几乎是所有的常见网络应用。例如,ServerSocket能够用来构建基于TCP/IP的服 务器程序,Socket能够用来构建基于TCP/IP的客户端程序,而DatagramPacket以及 DatagramSocket能够用来构建基于UDP 的数据广播程序。在java.net中的其他Java 库能够被用来实现域名解析、身份认证、安全许可等一系列功能。由于这些Java库 的功能和具体用法等内容已经超出了本文的讨论范围,感爱好的读者可以进一步参 考Java的文档以及Sun 公司的Java Tutorial等资料。
这个简单的程序包括如下三个模块:
数据广播与接收模块-- Broadcast.java
数据接收线程 -- Receiver.java
图形用户界面 -- Chat.java
程序设计
数据广播与接收模块Broadcast.java是本示例程序的核心部分。该类包括一个 构造方法,一个数据发送方法和一个数据接收方法。为了使这个类能够被更加广泛 的应用到其它应用程序中,作者又添加了一个端口配置方法。
在构造方法中,我们首先利用InetAddress 定义一个数据广播组,同时构造一 个用于发送数据的DatagramSocket与一个用于接收数据的MulticastSocket。 在这 里我们使用230.0.0.1 来作为数据广播组的标示符,虽然这个标示符与IP地址的格 式相同,但是它并不表示Internet上的一台机器。此外,我们在端口配置方法中分 别指定1235端口和1236端口位数据发送和数据接收端口。假如把一个UDP 数据广播 系统比喻成无线电广播系统的话,数据广播标示符可以被认为是波段,而数据接收 端口可以被认为是频率。收音机用户必须把收音机调整到相应的波段和频率才能够 接收到电台信号,我们的UDP 数据接收程序也必须加入相对应的数据广播组并且使 用正确的数据接收端口才能够正确的接收到UDP 广播数据。在构造方法中,我们利 用MulticastSocket 的构造函数指定数据接收端口(频率),并利用其joinGroup 方 法指定数据广播组(波段)。
public Broadcast()
{
GetBroadcastPorts();
try
{
// 构造数据广播组标示符 (波段)
BroadcastGroup = InetAddress.getByName("230.0.0.1");
// 构造数据发送端口
Sender = new DatagramSocket(ServerPort);
// 构造数据接收端口 (频率)
Receiver = new MulticastSocket(ClientPort);
// 指定数据接收端口的数据广播组 (波段)
Receiver.joinGroup(BroadcastGroup);
} catch (Exception e) {}
}
在数据发送方法中,我们基于用户所提供的数据以及数据广播目标端口(频率) 构造一个DatagramPacket数据包,然后利用发送数据的DatagramSocket的send方法将该数据包发送到局域网。与此相反,在数据接收方法中,我们首先构造一个空的 DatagramPacket数据包,然后利用接收数据的MulticastSocket的receive方法填充该数据包中的内容。为了避免由于数据包大小不同所造成的数据丢失等麻烦,我们特地将两个数据包的大小设置成一样的。
// 数据发送方法
public void SendData(String Msg)
{
byte[] b = new byte[1024];
DatagramPacket packet;
try
{
// 字节序列b 包括需要发送的数据
b = Msg.getBytes();
// 构造一个数据包,BroadcastGroup是数据广播组标示符(波段),
// ClientPort是数据广播目标端口(频率)。
packet = new DatagramPacket(b, b.length, BroadcastGroup, ClientPort);
// 发送数据包
Sender.send(packet);
} catch (Exception e) {}
}
// 数据接收方法
public String ReceiveData()
{
byte[] b = new byte[1024];
// 构造一个空的数据包
DatagramPacket packet = new DatagramPacket(b, 1024);
String InMsg;
try
{
// 接收数据
Receiver.receive(packet);
} catch (IOException e) {}
// 丛数据包中获得接收到的数据
b = packet.getData();
InMsg = new String(b);
return InMsg;
}