投票系统的设计与实现
2014-12-23 11:19
393 查看
说实话,通过这个案例我喜欢上了《Java TCP/IP Socket编程》(原书第2版)这本书。要是让我自己写这个程序的话也可能很快写出来,但是健壮性,安全性,可复用性和面向对象的编程思想不可能用的这么全面。而且作者考虑的思路更是值得借鉴,这不仅仅是一本将Socket编程的书了!
既然是投票,那么首先要有投票的信息啊!定义VoteMsg.java类,负责记录这条信息是否是服务器发给客户端的结果信息,如果是客户端发送给服务器,是进行查询还是进行投票,当然还有候选人的ID,如果返回结果的话则需要当前得票数。
有了投票的信息类,那么就是编码问题了,在网络之间传输肯定无法通过VoteMsg类本身进行传输,这就要对信息进行编码,就是使通信的时候可以理解,为了使该信息具有特殊性(否则双发不知道接收到的信息是否是和投票有关的信息),在信息前面需要添加头信息。那么就需要对消息进行编码和解码,定义接口VoteMsgCoder.java完成功能。
import java.io.IOException;
public interface VoteMsgCoder {
byte[] toWire(VoteMsg msg) throws IOException;//负责对VoteMsg编码
VoteMsg fromWire(byte[] input) throws IOException;//负责将输入解码成VoteMsg
}
这里介绍两种方式:
基于文本的表示方法,很简单,就是把VoteMsg中携带的信息按照文本的方式进行编码,也就是将如下格式作为发送的信息。
"VOTEPROTO" <"v" | "i"> [<RESPFLAG>] <CANDIDATE> [<VOTECNT>] 其中"VOTEPROTO"为头信息。代码VoteMsgTextCoder.java。
二进制表示方法,顾名思义,就是把信息编程二进制的消息传递出去。代码VoteMsgBinCoder.java。
既然现在已经有了消息和编码能转化为一种双方都能接受的一种格式,下面要做的就是在网络中真正的传输,同时要使接收方知道什么时候才算接受结束,知道什么时候算完成了一条消息的接受工作。
成帧技术解决了接收端如何定位信息的首尾位置的问题。如果接受者试图从套接字中读取比消息本身更多的字节,将发生两种情况:
没有更多信息,阻塞等待,发送者也在等待反馈,造成死锁
读到了其他发送者的信息,造成错误。
对于如何确定消息的结束位置,有两种办法:
基于定界符:返回一个特殊字节序列,但是不能包含定界符,使用填充技术,但是发送者和接受者都得扫描信息。代码DelimFramer.java。
显示长度:要知道消息长度的上限,小于256用1个字节,小于65536需要2个字节。代码LengthFramer.java。
定义一个接口Framer来完成相应消息提取信息和添加帧信息的功能:
import java.io.IOException;
import java.io.OutputStream;
public interface Framer {
void frameMsg(byte[] message, OutputStream out) throws IOException;//负责添加帧信息
byte[] nextMsg() throws IOException;//负责从输入中去除帧信息,获得真正消息
}
万事俱备,只欠东风,既然已经有了这么多的工具,我们很快就能完成相应的具体功能了。整理一下思路:
服务器端建立serverSocket等待用户
客户端建立clientSocket和服务器链接
客户端新建投票信息voteMessage
客户端通过VoteMsgCoder.toWire(voteMessage)对该信息进行编码成message,同时添加头信息
客户端通过Framer.frameMsg(message, clientSocket.getOutputStream)发送给相应的服务器端
服务器端通过Framer.nextMsg()方法取得带解码的byte数组message
服务器端通过VoteMsgCoder.fromWire(message),获得相应的投票信息voteMessage。
服务器此时应该处理投票信息voteMessage,给客户端一个反馈信息。
此时应该添加一个处理投票的类。明显应该有一个Map<Integer,
Long>来存储候选人的得票数。这个类所要处理的很简单,如果本身传过来的就是一个反馈信息,那么直接返回;如果是查询信息,则查找相应Integer对应的Long值,如果没人为他投过票,相应的值为空,那么为它初始化;如果是投票信息,则将相应的值加1后返回。具体代码见VoteService.java。
那么基本流程如下图所示。
了解了过程,那么客户端和服务器端就很好实现啦!
服务器端:VoteServerTCP.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class VoteServerTCP {
public static void main(String args[]) throws Exception {
if (args.length != 1) { // Test for correct # of args
throw new IllegalArgumentException("Parameter(s): <Port>");
}
int port = Integer.parseInt(args[0]); // Receiving Port
ServerSocket servSock = new ServerSocket(port);
// Change Bin to Text on both client and server for different encoding
VoteMsgCoder coder = new VoteMsgBinCoder();
VoteService service = new VoteService();
while (true) {
Socket clntSock = servSock.accept();
System.out.println("Handling client at " + clntSock.getRemoteSocketAddress());
// Change Length to Delim for a different framing strategy
Framer framer = new LengthFramer(clntSock.getInputStream());
try {
byte[] req;
while ((req = framer.nextMsg()) != null) {
System.out.println("Received message (" + req.length + " bytes)");
VoteMsg responseMsg = service.handleRequest(coder.fromWire(req));
framer.frameMsg(coder.toWire(responseMsg), clntSock.getOutputStream());
}
} catch (IOException ioe) {
System.err.println("Error handling client: " + ioe.getMessage());
} finally {
System.out.println("Closing connection");
clntSock.close();
}
}
}
}
客户端:VoteClientTCP.java
import java.io.OutputStream;
import java.net.Socket;
public class VoteClientTCP {
public static final int CANDIDATEID = 888;
public static void main(String args[]) throws Exception {
if (args.length != 2) { // Test for correct # of args
throw new IllegalArgumentException("Parameter(s): <Server> <Port>");
}
String destAddr = args[0]; // Destination address
int destPort = Integer.parseInt(args[1]); // Destination port
Socket sock = new Socket(destAddr, destPort);
OutputStream out = sock.getOutputStream();
// Change Bin to Text for a different framing strategy
VoteMsgCoder coder = new VoteMsgBinCoder();
// Change Length to Delim for a different encoding strategy
Framer framer = new LengthFramer(sock.getInputStream());
// Create an inquiry request (2nd arg = true)
VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0);
byte[] encodedMsg = coder.toWire(msg);
// Send request
System.out.println("Sending Inquiry (" + encodedMsg.length + " bytes): ");
System.out.println(msg);
framer.frameMsg(encodedMsg, out);
// Now send a vote
msg.setInquiry(false);
encodedMsg = coder.toWire(msg);
System.out.println("Sending Vote (" + encodedMsg.length + " bytes): ");
framer.frameMsg(encodedMsg, out);
// Receive inquiry response
encodedMsg = framer.nextMsg();
msg = coder.fromWire(encodedMsg);
System.out.println("Received Response (" + encodedMsg.length
+ " bytes): ");
System.out.println(msg);
// Receive vote response
msg = coder.fromWire(framer.nextMsg());
System.out.println("Received Response (" + encodedMsg.length
+ " bytes): ");
System.out.println(msg);
sock.close();
}
}
运行截图如下:
既然是投票,那么首先要有投票的信息啊!定义VoteMsg.java类,负责记录这条信息是否是服务器发给客户端的结果信息,如果是客户端发送给服务器,是进行查询还是进行投票,当然还有候选人的ID,如果返回结果的话则需要当前得票数。
有了投票的信息类,那么就是编码问题了,在网络之间传输肯定无法通过VoteMsg类本身进行传输,这就要对信息进行编码,就是使通信的时候可以理解,为了使该信息具有特殊性(否则双发不知道接收到的信息是否是和投票有关的信息),在信息前面需要添加头信息。那么就需要对消息进行编码和解码,定义接口VoteMsgCoder.java完成功能。
import java.io.IOException;
public interface VoteMsgCoder {
byte[] toWire(VoteMsg msg) throws IOException;//负责对VoteMsg编码
VoteMsg fromWire(byte[] input) throws IOException;//负责将输入解码成VoteMsg
}
这里介绍两种方式:
基于文本的表示方法,很简单,就是把VoteMsg中携带的信息按照文本的方式进行编码,也就是将如下格式作为发送的信息。
"VOTEPROTO" <"v" | "i"> [<RESPFLAG>] <CANDIDATE> [<VOTECNT>] 其中"VOTEPROTO"为头信息。代码VoteMsgTextCoder.java。
二进制表示方法,顾名思义,就是把信息编程二进制的消息传递出去。代码VoteMsgBinCoder.java。
既然现在已经有了消息和编码能转化为一种双方都能接受的一种格式,下面要做的就是在网络中真正的传输,同时要使接收方知道什么时候才算接受结束,知道什么时候算完成了一条消息的接受工作。
成帧技术解决了接收端如何定位信息的首尾位置的问题。如果接受者试图从套接字中读取比消息本身更多的字节,将发生两种情况:
没有更多信息,阻塞等待,发送者也在等待反馈,造成死锁
读到了其他发送者的信息,造成错误。
对于如何确定消息的结束位置,有两种办法:
基于定界符:返回一个特殊字节序列,但是不能包含定界符,使用填充技术,但是发送者和接受者都得扫描信息。代码DelimFramer.java。
显示长度:要知道消息长度的上限,小于256用1个字节,小于65536需要2个字节。代码LengthFramer.java。
定义一个接口Framer来完成相应消息提取信息和添加帧信息的功能:
import java.io.IOException;
import java.io.OutputStream;
public interface Framer {
void frameMsg(byte[] message, OutputStream out) throws IOException;//负责添加帧信息
byte[] nextMsg() throws IOException;//负责从输入中去除帧信息,获得真正消息
}
万事俱备,只欠东风,既然已经有了这么多的工具,我们很快就能完成相应的具体功能了。整理一下思路:
服务器端建立serverSocket等待用户
客户端建立clientSocket和服务器链接
客户端新建投票信息voteMessage
客户端通过VoteMsgCoder.toWire(voteMessage)对该信息进行编码成message,同时添加头信息
客户端通过Framer.frameMsg(message, clientSocket.getOutputStream)发送给相应的服务器端
服务器端通过Framer.nextMsg()方法取得带解码的byte数组message
服务器端通过VoteMsgCoder.fromWire(message),获得相应的投票信息voteMessage。
服务器此时应该处理投票信息voteMessage,给客户端一个反馈信息。
此时应该添加一个处理投票的类。明显应该有一个Map<Integer,
Long>来存储候选人的得票数。这个类所要处理的很简单,如果本身传过来的就是一个反馈信息,那么直接返回;如果是查询信息,则查找相应Integer对应的Long值,如果没人为他投过票,相应的值为空,那么为它初始化;如果是投票信息,则将相应的值加1后返回。具体代码见VoteService.java。
那么基本流程如下图所示。
了解了过程,那么客户端和服务器端就很好实现啦!
服务器端:VoteServerTCP.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class VoteServerTCP {
public static void main(String args[]) throws Exception {
if (args.length != 1) { // Test for correct # of args
throw new IllegalArgumentException("Parameter(s): <Port>");
}
int port = Integer.parseInt(args[0]); // Receiving Port
ServerSocket servSock = new ServerSocket(port);
// Change Bin to Text on both client and server for different encoding
VoteMsgCoder coder = new VoteMsgBinCoder();
VoteService service = new VoteService();
while (true) {
Socket clntSock = servSock.accept();
System.out.println("Handling client at " + clntSock.getRemoteSocketAddress());
// Change Length to Delim for a different framing strategy
Framer framer = new LengthFramer(clntSock.getInputStream());
try {
byte[] req;
while ((req = framer.nextMsg()) != null) {
System.out.println("Received message (" + req.length + " bytes)");
VoteMsg responseMsg = service.handleRequest(coder.fromWire(req));
framer.frameMsg(coder.toWire(responseMsg), clntSock.getOutputStream());
}
} catch (IOException ioe) {
System.err.println("Error handling client: " + ioe.getMessage());
} finally {
System.out.println("Closing connection");
clntSock.close();
}
}
}
}
客户端:VoteClientTCP.java
import java.io.OutputStream;
import java.net.Socket;
public class VoteClientTCP {
public static final int CANDIDATEID = 888;
public static void main(String args[]) throws Exception {
if (args.length != 2) { // Test for correct # of args
throw new IllegalArgumentException("Parameter(s): <Server> <Port>");
}
String destAddr = args[0]; // Destination address
int destPort = Integer.parseInt(args[1]); // Destination port
Socket sock = new Socket(destAddr, destPort);
OutputStream out = sock.getOutputStream();
// Change Bin to Text for a different framing strategy
VoteMsgCoder coder = new VoteMsgBinCoder();
// Change Length to Delim for a different encoding strategy
Framer framer = new LengthFramer(sock.getInputStream());
// Create an inquiry request (2nd arg = true)
VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0);
byte[] encodedMsg = coder.toWire(msg);
// Send request
System.out.println("Sending Inquiry (" + encodedMsg.length + " bytes): ");
System.out.println(msg);
framer.frameMsg(encodedMsg, out);
// Now send a vote
msg.setInquiry(false);
encodedMsg = coder.toWire(msg);
System.out.println("Sending Vote (" + encodedMsg.length + " bytes): ");
framer.frameMsg(encodedMsg, out);
// Receive inquiry response
encodedMsg = framer.nextMsg();
msg = coder.fromWire(encodedMsg);
System.out.println("Received Response (" + encodedMsg.length
+ " bytes): ");
System.out.println(msg);
// Receive vote response
msg = coder.fromWire(framer.nextMsg());
System.out.println("Received Response (" + encodedMsg.length
+ " bytes): ");
System.out.println(msg);
sock.close();
}
}
运行截图如下:
相关文章推荐
- 【PHP+MySQL】投票系统的设计和实现
- PHP+MySQL投票系统的设计和实现分享
- PHP+MySQL投票系统的设计和实现分享
- 《WebGIS系统的设计与实现》的补充说明
- ASP.NET系统用户权限设计与实现
- 人事档案信息管理系统的面向对象的分析、设计和实现
- ASP.NET系统用户权限设计与实现(转摘)
- 基于MapXtreme的WebGPS系统的设计与实现
- Internet 即时通信系统的设计与实现
- 系统设计和现实世界!关于点,圆,球的实现
- 观点:关于游戏系统的规划、设计与实现。
- 一个典型的嵌入式系统设计和实现
- 投票系统原代码(JSP实现)
- 网站采集器系统设计(产品已实现)
- 权限系统设计 -1 初步和实现方案
- 企业级服务器设计与实现经验之插件系统--功能插件
- ASP.NET系统用户权限设计与实现(转)
- 企业级服务器设计与实现经验之系统框架(二)--功能/应用服务器主体框架
- ASP.NET系统用户权限设计与实现
- 负载均衡--大型在线系统实现的关键(下篇)(服务器集群架构的设计与选择)