3.5.3 发送和接收

通过流发送消息非常简单,只需要创建消息,调用toWire()方法,添加适当的成帧信息,再写入流。当然,接收消息就要按照相反的顺序执行。这个过程适用于TCP协议,而对于UDP协议,不需要显式地成帧,因为UDP协议中保留了消息的边界信息。为了对发送与接收过程进行展示,我们考虑投票服务的如下几点:1)维护一个候选人ID与其获得选票数的映射,2)记录提交的投票,3)根据其获得的选票数,对查询指定的候选人和为其投票的消息做出响应。首先,我们实现一个投票服务器所用到的服务。当接收到投票消息时,投票服务器将调用VoteService类的handleRequest()方法对请求进行处理。

VoteService.java

figure_0077_0090

figure_0077_0091

1.创建候选人ID与选票数量的映射:第6行

对于查询请求,给定的候选人ID用来在映射中查询其获得的选票数量。对于投票请求,增加后的选票数又存回映射。

2.handleRequest():第8~24行

·返回响应:第9~12行

如果投票消息已经是一个响应信息,则直接发回而不对其进行处理和修改。否则,对其响应消息标志进行设置。

·查找当前获得的选票总数:第13~18行

根据候选人ID从映射中获取其获得的选票总数。如果该候选人ID在映射中不存在,则将其获得的选票数设为0。

·如果有新的投票,则更新选票总数:第19~21行

如果之前候选人不存在,则创建新的映射,否则,只是简单地修改已有的映射。

·设置选票总数并返回消息:第22~23行

下面我们将展示如何实现一个TCP投票客户端,该客户端通过TCP套接字连接到投票服务器,在一次投票后发送一个查询请求,并接收查询和投票结果。

VoteClientTCP.java

figure_0078_0092

figure_0078_0093

figure_0079_0094

figure_0079_0095

1.参数处理:第9~14行

2.创建套接字,获取输出流:第16~17行

3.创建二进制编码器和基于长度的成帧器:第20~22行

我们将使用一个编码器对投票消息进行编码和解码,这里为我们的协议选择的是二进制编码器。其次,由于TCP协议是一个基于流的服务,我们需要提供字节的帧。在此,我们使用LengthFramer类,它为每条消息添加一个长度前缀。注意,我们只需要改变具体的类,就能方便地转换成基于定界符的成帧方法和基于文本的编码方式,这里将VoteMsgCoder和Framer换成VoteMsgTextCoder和DelimFramer即可。

4.创建和发送消息:第24~37行

创建,编码,成帧和发送查询请求,后面是为相同候选人的投票消息。

5.获取和解析响应:第39~50行

我们使用nextMsg()方法用于返回下一条编码后的消息,并通过fromWire()方法对其进行解析/解码。

6.关闭套接字:第52行

下面我们示范TCP版本的投票服务器。该服务器反复地接收新的客户端连接,并使用VoteService类为客户端的投票消息作出响应。

VoteServerTCP.java

figure_0079_0096

figure_0080_0097

1.为服务器端建立编码器和投票服务:第15~17行

2.反复地接收和处理客户端连接:第19~37行

·接收新的客户端,打印客户端地址:第20~21行

·为客户端创建成帧器:第23行

·从客户端获取消息并对其解码:第26~28行

反复地向成帧器发送获取下一条消息的请求,直到其返回null,即指示了消息的结束。

·处理消息,发送响应信息:第28~29行

将解码后的消息传递给投票服务,以进行下一步处理。编码、成帧和回发响应消息。

UDP版本的投票客户端与TCP版本非常相似。需要注意的是,在UDP客户端中我们不需要使用成帧器,因为UDP协议为我们维护了消息的边界信息。对于UDP协议,我们使用基于文本的编码方式对消息进行编码,不过只要客户端与服务器能达成一致,也能够很方便地改成其他编码方式。

VoteClientUDP.java

figure_0081_0098

figure_0081_0099

1.设置DatagramSocket和连接:第10~20行

通过调用connect()方法,我们不必为发送的每个数据报文指定远程地址和端口,也不必测试接收到的每个数据报文的源地址。

2.创建选票和编码器:第22~26行

这次使用的是文本编码器,但我们也可以很容易地换成二进制编码器。注意这里我们不需要成帧器,因为只要每次发送都只有一个投票消息,UDP协议就已经为我们保留了边界信息。

3.向服务器发送请求消息:第28~34行

4.接收,解码和打印服务器响应信息:第36~45行

在创建DatagramPacket时,我们需要知道消息的最大长度,以避免数据被截断。当然,在对数据报文进行解码时,我们只使用数据报文中包含的实际字节,因此调用了Arrays.copyOfRange()方法来复制返回的数据报文中数组的子序列。

最后是UDP投票服务器,同样,也与TCP版本非常相似。

VoteServerUDP.java

figure_0082_0100

figure_0082_0101

figure_0083_0102

1.设置:第17~20行

为服务器创建接收缓存区,编码器以及投票服务。

2.反复地接收和处理客户端的投票消息:第22~39行

·为接收数据报文创建DatagramPacket:第23行

在每次迭代中将数据区重置为输入缓存区。

·接收数据报文,抽取数据:第24~25行

UDP替我们完成了成帧的工作!

·解码和处理请求:第30~31行

服务将响应返回给消息。

·编码并发送响应消息:第32~35行