通过流发送消息非常简单,只需要创建消息,调用toWire()方法,添加适当的成帧信息,再写入流。当然,接收消息就要按照相反的顺序执行。这个过程适用于TCP协议,而对于UDP协议,不需要显式地成帧,因为UDP协议中保留了消息的边界信息。为了对发送与接收过程进行展示,我们考虑投票服务的如下几点:1)维护一个候选人ID与其获得选票数的映射,2)记录提交的投票,3)根据其获得的选票数,对查询指定的候选人和为其投票的消息做出响应。首先,我们实现一个投票服务器所用到的服务。当接收到投票消息时,投票服务器将调用VoteService类的handleRequest() 方法对请求进行处理。
VoteService.java
0 import java.util.HashMap;
1 import java.util.Map;
2
3 public class VoteService {
4
5 // Map of candidates to number of votes
6 private Map<Integer, Long> results = new
HashMap<Integer, Long>();
7
8 public VoteMsg handleRequest(VoteMsg msg) {
9 if (msg.isResponse()) { // If response, just send it back
10 return msg;
11 }
12 msg.setResponse(true); // Make message a response
13 // Get candidate ID and vote count
14 int candidate = msg.getCandidateID();
15 Long count = results.get(candidate);
16 if (count == null) {
17 count = 0L; // Candidate does not exist
18 }
19 if (!msg.isInquiry()) {
20 results.put(candidate, ++count); // If vote, increment
count
21 }
22 msg.setVoteCount(count);
23 return msg;
24 }
25 }
VoteService.java
1.创建候选人ID与选票数量的映射:第6行
对于查询请求,给定的候选人ID用来在映射中查询其获得的选票数量。对于投票请求,增加后的选票数又存回映射。
2.handleRequest():第8-24行
返回响应:第9-12行
如果投票消息已经是一个响应信息,则直接发回而不对其进行处理和修改。否则,对其响应消息标志进行设置。
查找当前获得的选票总数:第13-18行
根据候选人ID从映射中获取其获得的选票总数。如果该候选人ID在映射中不存在,则将其获得的选票数设为0.
如果有新的投票,则更新选票总数:第19-21行
如果之前候选人不存在,则创建新的映射,否则,只是简单地修改已有的映射。
设置选票总数并返回消息:第22-23行
下面我们将展示如何实现一个TCP投票客户端,该客户端通过TCP套接字连接到投票服务器,在一次投票后发送一个查询请求,并接收查询和投票结果。
VoteClientTCP.java
0 import java.io.OutputStream;
1 import java.net.Socket;
2
3 public class VoteClientTCP {
4
5 public static final int CANDIDATEID = 888;
6
7 public static void main(String args[]) throws Exception
{
8
9 if (args.length != 2) { // Test for correct # of args
10 throw new IllegalArgumentException("Parameter(s):
<Server> <Port>");
11 }
12
13 String destAddr = args[0]; // Destination address
14 int destPort = Integer.parseInt(args[1]); //
Destination port
15
16 Socket sock = new Socket(destAddr, destPort);
17 OutputStream out = sock.getOutputStream();
18
19 // Change Bin to Text for a different framing strategy
20 VoteMsgCoder coder = new VoteMsgBinCoder();
21 // Change Length to Delim for a different encoding
strategy
22 Framer framer = new
LengthFramer(sock.getInputStream());
23
24 // Create an inquiry request (2nd arg = true)
25 VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0);
26 byte[] encodedMsg = coder.toWire(msg);
27
28 // Send request
29 System.out.println("Sending Inquiry (" +
encodedMsg.length + " bytes): ");
30 System.out.println(msg);
31 framer.frameMsg(encodedMsg, out);
32
33 // Now send a vote
34 msg.setInquiry(false);
35 encodedMsg = coder.toWire(msg);
36 System.out.println("Sending Vote (" +
encodedMsg.length + " bytes): ");
37 framer.frameMsg(encodedMsg, out);
38
39 // Receive inquiry response
40 encodedMsg = framer.nextMsg();
41 msg = coder.fromWire(encodedMsg);
42 System.out.println("Received Response (" +
encodedMsg.length
43 + " bytes): ");
44 System.out.println(msg);
45
46 // Receive vote response
47 msg = coder.fromWire(framer.nextMsg());
48 System.out.println("Received Response (" +
encodedMsg.length
49 + " bytes): ");
50 System.out.println(msg);
51
52 sock.close();
53 }
54 }
VoteClientTCP.java
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
0 import java.io.IOException;
1 import java.net.ServerSocket;
2 import java.net.Socket;
3
4 public class VoteServerTCP {
5
6 public static void main(String args[]) throws Exception {
7
8 if (args.length != 1) { // Test for correct # of args
9 throw new IllegalArgumentException("Parameter(s): <Port>");
10 }
11
12 int port = Integer.parseInt(args[0]); // Receiving Port
13
14 ServerSocket servSock = new ServerSocket(port);
15 // Change Bin to Text on both client and server for different encoding
16 VoteMsgCoder coder = new VoteMsgBinCoder();
17 VoteService service = new VoteService();
18
19 while (true) {
20 Socket clntSock = servSock.accept();
21 System.out.println("Handling client at " + clntSock.
getRemoteSocketAddress());
22 // Change Length to Delim for a different framing strategy
23 Framer framer = new LengthFramer(clntSock.getInputStream());
24 try {
25 byte[] req;
26 while ((req = framer.nextMsg()) != null) {
27 System.out.println("Received message (" + req.length + " bytes)");
28 VoteMsg responseMsg = service.handleRequest(coder.fromWire(req));
29 framer.frameMsg(coder.toWire(responseMsg), clntSock.getOutputStream());
30 }
31 } catch (IOException ioe) {
32 System.err.println("Error handling client: " + ioe.getMessage());
33 } finally {
34 System.out.println("Closing connection");
35 clntSock.close();
36 }
37 }
38 }
39 }
VoteServerTCP.java
1.为服务器端建立编码器和投票服务:第15-17行
2.反复地接收和处理客户端连接:第19-37行
接收新的客户端,打印客户端地址:第20-21行
为客户端创建成帧器:第23行
从客户端获取消息并对其解码:第26-28行
反复地向成帧器发送获取下一条消息的请求,直到其返回null,即指示了消息的结束。
处理消息,发送响应信息:第28-29行
将解码后的消息传递给投票服务,以进行下一步处理。编码,成帧和回发响应消息。
UDP版本的投票客户端与TCP版本非常相似。需要注意的是,在UDP客户端中我们不需要使用成帧器,因为UDP协议为我们维护了消息的边界信息。对于UDP协议,我们使用基于文本的编码方式对消息进行编码,不过只要客户端与服务器能达成一致,也能够很方便地改成其他编码方式。
VoteClientUDP.java
0 import java.io.IOException;
1 import java.net.DatagramPacket;
2 import java.net.DatagramSocket;
3 import java.net.InetAddress;
4 import java.util.Arrays;
5
6 public class VoteClientUDP {
7
8 public static void main(String args[]) throws
IOException {
9
10 if (args.length != 3) { // Test for correct # of args
11 throw new IllegalArgumentException("Parameter(s):
<Destination>" +
12 " <Port> <Candidate#>");
13 }
14
15 InetAddress destAddr = InetAddress.getByName(args[0]);
// Destination addr
16 int destPort = Integer.parseInt(args[1]); //
Destination port
17 int candidate = Integer.parseInt(args[2]); // 0 <=
candidate <= 1000 req'd
18
19 DatagramSocket sock = new DatagramSocket(); // UDP
socket for sending
20 sock.connect(destAddr, destPort);
21
22 // Create a voting message (2nd param false = vote)
23 VoteMsg vote = new VoteMsg(false, false, candidate, 0);
24
25 // Change Text to Bin here for a different coding strategy
26 VoteMsgCoder coder = new VoteMsgTextCoder();
27
28 // Send request
29 byte[] encodedVote = coder.toWire(vote);
30 System.out.println("Sending Text-Encoded Request (" +
encodedVote.length
31 + " bytes): ");
32 System.out.println(vote);
33 DatagramPacket message = new
DatagramPacket(encodedVote, encodedVote.length);
34 sock.send(message);
35
36 // Receive response
37 message = new DatagramPacket(new
byte[VoteMsgTextCoder.MAX_WIRE_LENGTH],
38 VoteMsgTextCoder.MAX_WIRE_LENGTH);
39 sock.receive(message);
40 encodedVote = Arrays.copyOfRange(message.getData(), 0,
message.getLength());
41
42 System.out.println("Received Text-Encoded Response ("
+ encodedVote.length
43 + " bytes): ");
44 vote = coder.fromWire(encodedVote);
45 System.out.println(vote);
46 }
47 }
VoteClientUDP.java
1.设置DatagramSocket 和连接:第10-20行
通过调用connect()方法,我们不必1)为发送的每个数据报文指定远程地址和端口,也不必2)测试接收到的每个数据报文的源地址。
2.创建选票和编码器:第22-26行
这次使用的是文本编码器,但我们也可以很容易地换成二进制编码器。注意这里我们不需要成帧器,因为只要每次发送都只有一个投票消息,UDP协议就已经为我们保留了边界信息。
3.向服务器发送请求消息:第28-34行
4.接收,解码和打印服务器响应信息:第36-45行
在创建DatagramPacket时,我们需要知道消息的最大长度,以避免数据被截断。当然,在对数据报文进行解码时,我们只使用数据报文中包含的实际字节,因此调用了Arrays.copyOfRange()方法来复制返回的数据报文中数组的子序列。
最后是UDP投票服务器,同样,也与TCP版本非常相似。
VoteServerUDP.java
0 import java.io.IOException;
1 import java.net.DatagramPacket;
2 import java.net.DatagramSocket;
3 import java.util.Arrays;
4
5 public class VoteServerUDP {
6
7 public static void main(String[] args) throws
IOException {
8
9 if (args.length != 1) { // Test for correct # of args
10 throw new IllegalArgumentException("Parameter(s):
<Port>");
11 }
12
13 int port = Integer.parseInt(args[0]); // Receiving Port
14
15 DatagramSocket sock = new DatagramSocket(port); //
Receive socket
16
17 byte[] inBuffer = new
byte[VoteMsgTextCoder.MAX_WIRE_LENGTH];
18 // Change Bin to Text for a different coding approach
19 VoteMsgCoder coder = new VoteMsgTextCoder();
20 VoteService service = new VoteService();
21
22 while (true) {
23 DatagramPacket packet = new DatagramPacket(inBuffer,
inBuffer.length);
24 sock.receive(packet);
25 byte[] encodedMsg =
Arrays.copyOfRange(packet.getData(), 0,
packet.getLength());
26 System.out.println("Handling request from " +
packet.getSocketAddress() + " ("
27 + encodedMsg.length + " bytes)");
28
29 try {
30 VoteMsg msg = coder.fromWire(encodedMsg);
31 msg = service.handleRequest(msg);
32 packet.setData(coder.toWire(msg));
33 System.out.println("Sending response (" +
packet.getLength() + " bytes):");
34 System.out.println(msg);
35 sock.send(packet);
36 } catch (IOException ioe) {
37 System.err.println("Parse error in message: " +
ioe.getMessage());
38 }
39 }
40 }
41 }
VoteServerUDP.java
1.设置:第17-20行
为服务器创建接收缓存区,编码器,以及投票服务。
2.反复地接收和处理客户端的投票消息:第22-39行
为接收数据报文创建DatagramPacket:第23行
在每次迭代中将数据区重置为输入缓存区。
接收数据报文,抽取数据:第24-25行
UDP替我们完成了成帧的工作!
解码和处理请求:第30-31行
服务将响应返回给消息。
编码并发送响应消息:第32-35行
相关下载:
Java_TCPIP_Socket编程(doc)
文献来源:
UNDONER(小杰博客) :
LSOFT.CN(琅软中国) :