在传输消息时,用Java内置的方法和工具确实很用,如:对象序列化,RMI远程调用等。但有时候,针对要传输的特定类型的数据,实现自己的方法可能更简单、容易或有效。下面给出一个实现了自定义构建和解析协议消息的Demo(书上例子)。

该例子是一个简单的投票协议。这里,一个客户端向服务器发送一个请求消息,消息中包含了一个候选人的ID,范围在0~1000。程序支持两种请求:一种是查询请求,即向服务器询问候选人当前获得的投票总数,服务器发回一个响应消息,包含了原来的候选人ID和该候选人当前获得的选票总数;另一种是投票请求,即向指定候选人投一票,服务器对这种请求也发回响应消息,包含了候选人ID和获得的选票数(包含了刚刚投的一票)。

在实现一个协议时,一般会定义一个专门的类来存放消息中所包含的的信息。在我们的例子中,客户端和服务端发送的消息都很简单,唯一的区别是服务端发送的消息还包含了选票总数和一个表示相应消息的标志。因此,可以用一个类来表示客户端和服务端的两种消息。下面的VoteMsg.java类展示了每条消息中的基本信息:

  • 布尔值isInquiry,true表示该消息是查询请求,false表示该消息是投票请求;
  • 布尔值isResponse,true表示该消息是服务器发送的相应消息,false表示该消息为客户端发送的请求消息;
  • 整型变量candidateID,指示了候选人的ID;
  • 长整型变量voteCount,指示出所查询的候选人获得的总选票数。

另外,注意一下几点:

  • candidateID的范围在0~1000;
  • voteCount在请求消息中必须为0;
  • voteCount不能为负数

VoteMsg代码如下:

public class VoteMsg {
private boolean isInquiry; // true if inquiry; false if vote
private boolean isResponse;// true if response from server
private int candidateID; // in [0,1000]
private long voteCount; // nonzero only in response public static final int MAX_CANDIDATE_ID = 1000; public VoteMsg(boolean isResponse, boolean isInquiry, int candidateID, long voteCount)
throws IllegalArgumentException {
// check invariants
if (voteCount != 0 && !isResponse) {
throw new IllegalArgumentException("Request vote count must be zero");
}
if (candidateID < 0 || candidateID > MAX_CANDIDATE_ID) {
throw new IllegalArgumentException("Bad Candidate ID: " + candidateID);
}
if (voteCount < 0) {
throw new IllegalArgumentException("Total must be >= zero");
}
this.candidateID = candidateID;
this.isResponse = isResponse;
this.isInquiry = isInquiry;
this.voteCount = voteCount;
} public void setInquiry(boolean isInquiry) {
this.isInquiry = isInquiry;
} public void setResponse(boolean isResponse) {
this.isResponse = isResponse;
} public boolean isInquiry() {
return isInquiry;
} public boolean isResponse() {
return isResponse;
} public void setCandidateID(int candidateID) throws IllegalArgumentException {
if (candidateID < 0 || candidateID > MAX_CANDIDATE_ID) {
throw new IllegalArgumentException("Bad Candidate ID: " + candidateID);
}
this.candidateID = candidateID;
} public int getCandidateID() {
return candidateID;
} public void setVoteCount(long count) {
if ((count != 0 && !isResponse) || count < 0) {
throw new IllegalArgumentException("Bad vote count");
}
voteCount = count;
} public long getVoteCount() {
return voteCount;
} public String toString() {
String res = (isInquiry ? "inquiry" : "vote") + " for candidate " + candidateID;
if (isResponse) {
res = "response to " + res + " who now has " + voteCount + " vote(s)";
}
return res;
}
}

接下来,我们要根据一定的协议来对其进行编解码,我们定义一个VoteMsgCoder接口,它提供了对投票消息进行序列化和反序列化的方法。toWrie()方法用于根据一个特定的协议,将投票消息转换成一个字节序列,fromWire()方法则根据相同的协议,对给定的字节序列进行解析,并根据信息的内容返回一个该消息类的实例。

import java.io.IOException;  

public interface VoteMsgCoder {
byte[] toWire(VoteMsg msg) throws IOException;
VoteMsg fromWire(byte[] input) throws IOException;
}

下面给出两个实现了VoteMsgCoder接口的类,一个实现的是基于文本的编码方式 ,一个实现的是基于二进制的编码方式。

首先是用文本方式对消息进行编码的程序。该协议指定使用ASCII字符集对文本进行编码。消息的开头是一个所谓的”魔术字符串“,即一个字符序列,用于快速将投票协议的消息和网络中随机到来的垃圾消息区分开,投票/查询布尔值被编码为字符形似,‘v’代表投票消息,‘i’代表查询消息。是否为服务器发送的响应消息,由字符‘R’指示,状态标记后面是候选人ID,其后跟的是选票总数,它们都编码成十进制字符串。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner; public class VoteMsgTextCoder implements VoteMsgCoder {
/*
* Wire Format "VOTEPROTO" <"v" | "i"> [<RESPFLAG>] <CANDIDATE> [<VOTECNT>]
* Charset is fixed by the wire format.
*/ // Manifest constants for encoding
public static final String MAGIC = "Voting";
public static final String VOTESTR = "v";
public static final String INQSTR = "i";
public static final String RESPONSESTR = "R"; public static final String CHARSETNAME = "US-ASCII";
public static final String DELIMSTR = " ";
public static final int MAX_WIRE_LENGTH = 2000; public byte[] toWire(VoteMsg msg) throws IOException {
String msgString = MAGIC + DELIMSTR + (msg.isInquiry() ? INQSTR : VOTESTR)
+ DELIMSTR + (msg.isResponse() ? RESPONSESTR + DELIMSTR : "")
+ Integer.toString(msg.getCandidateID()) + DELIMSTR
+ Long.toString(msg.getVoteCount());
byte data[] = msgString.getBytes(CHARSETNAME);
return data;
} public VoteMsg fromWire(byte[] message) throws IOException {
ByteArrayInputStream msgStream = new ByteArrayInputStream(message);
Scanner s = new Scanner(new InputStreamReader(msgStream, CHARSETNAME));
boolean isInquiry;
boolean isResponse;
int candidateID;
long voteCount;
String token; try {
token = s.next();
if (!token.equals(MAGIC)) {
throw new IOException("Bad magic string: " + token);
}
token = s.next();
if (token.equals(VOTESTR)) {
isInquiry = false;
} else if (!token.equals(INQSTR)) {
throw new IOException("Bad vote/inq indicator: " + token);
} else {
isInquiry = true;
} token = s.next();
if (token.equals(RESPONSESTR)) {
isResponse = true;
token = s.next();
} else {
isResponse = false;
}
// Current token is candidateID
// Note: isResponse now valid
candidateID = Integer.parseInt(token);
if (isResponse) {
token = s.next();
voteCount = Long.parseLong(token);
} else {
voteCount = 0;
}
} catch (IOException ioe) {
throw new IOException("Parse error...");
}
return new VoteMsg(isResponse, isInquiry, candidateID, voteCount);
}
}

toWire()方法简单地创建一个字符串,该字符串中包含了消息的所有字段,并由空白符隔开。fromWire()方法首先检查”魔术字符串“,如果在消息最前面没有魔术字符串,则抛出一个异常。在理说明了在实现协议时非常重要的一点:永远不要对从网络中来的任何输入进行任何假设。你的程序必须时刻为任何可能的输入做好准备,并能很好的对其进行处理。

 

下面将展示基于二进制格式对消息进行编码的程序。与基于文本的格式相反,二进制格式使用固定大小的消息,每条消息由一个特殊字节开始,该字节的最高六位为一个”魔术值“010101,该字节的最低两位对两个布尔值进行了编码,消息的第二个字节总是0,第三、四个字节包含了candidateID值,只有响应消息的最后8个字节才包含了选票总数信息。字节序列格式如下图所示:

代码如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException; public class VoteMsgBinCoder implements VoteMsgCoder { // manifest constants for encoding
public static final int MIN_WIRE_LENGTH = 4;
public static final int MAX_WIRE_LENGTH = 16;
public static final int MAGIC = 0x5400;
public static final int MAGIC_MASK = 0xfc00;
public static final int MAGIC_SHIFT = 8;
public static final int RESPONSE_FLAG = 0x0200;
public static final int INQUIRE_FLAG = 0x0100; public byte[] toWire(VoteMsg msg) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(byteStream); // converts ints short magicAndFlags = MAGIC;
if (msg.isInquiry()) {
magicAndFlags |= INQUIRE_FLAG;
}
if (msg.isResponse()) {
magicAndFlags |= RESPONSE_FLAG;
}
out.writeShort(magicAndFlags);
// We know the candidate ID will fit in a short: it's > 0 && < 1000
out.writeShort((short) msg.getCandidateID());
if (msg.isResponse()) {
out.writeLong(msg.getVoteCount());
}
out.flush();
byte[] data = byteStream.toByteArray();
return data;
} public VoteMsg fromWire(byte[] input) throws IOException {
// sanity checks
if (input.length < MIN_WIRE_LENGTH) {
throw new IOException("Runt message");
}
ByteArrayInputStream bs = new ByteArrayInputStream(input);
DataInputStream in = new DataInputStream(bs);
int magic = in.readShort();
if ((magic & MAGIC_MASK) != MAGIC) {
throw new IOException("Bad Magic #: " +
((magic & MAGIC_MASK) >> MAGIC_SHIFT));
}
boolean resp = ((magic & RESPONSE_FLAG) != 0);
boolean inq = ((magic & INQUIRE_FLAG) != 0);
int candidateID = in.readShort();
if (candidateID < 0 || candidateID > 1000) {
throw new IOException("Bad candidate ID: " + candidateID);
}
long count = 0;
if (resp) {
count = in.readLong();
if (count < 0) {
throw new IOException("Bad vote count: " + count);
}
}
// Ignore any extra bytes
return new VoteMsg(resp, inq, candidateID, count);
}
}

转自:http://blog.csdn.net/ns_code/article/details/14229253

【Java TCP/IP Socket】构建和解析自定义协议消息(含代码)的更多相关文章

  1. 【Java TCP/IP Socket】应用程序协议中消息的成帧与解析(含代码)

    程序间达成的某种包含了信息交换的形式和意义的共识称为协议,用来实现特定应用程序的协议叫做应用程序协议.大部分应用程序协议是根据由字段序列组成的离散信息定义的,其中每个字段中都包含了一段以位序列编码(即 ...

  2. 【Java TCP/IP Socket】TCP Socket(含代码)

    TCP的Java支持 协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构.交换方式.包含的意义以及怎样对报文所包含的信息进行解析,TCP/IP协议族有IP协议.TCP协议和UDP协议.现 ...

  3. 【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)(转)

    书上示例 在第一章<基本套接字>中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去. 书上客户端代码如下: 1 2 3 ...

  4. 一个项目看java TCP/IP Socket编程

    前一段时间刚做了个java程序和网络上多台机器的c程序通讯的项目,遵循的是TCP/IP协议,用到了java的Socket编程.网络通讯是java的强项,用TCP/IP协议可以方便的和网络上的其他程序互 ...

  5. 【Java TCP/IP Socket】深入剖析socket——TCP通信中由于底层队列填满而造成的死锁问题(含代码)

    基础准备 首先需要明白数据传输的底层实现机制,在http://blog.csdn.net/ns_code/article/details/15813809这篇博客中有详细的介绍,在上面的博客中,我们提 ...

  6. 【Java TCP/IP Socket】基于NIO的TCP通信(含代码)

    NIO主要原理及使用 NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候, ...

  7. 【Java TCP/IP Socket】UDP Socket(含代码)

    UDP的Java支持 UDP协议提供的服务不同于TCP协议的端到端服务,它是面向非连接的,属不可靠协议,UDP套接字在使用前不需要进行连接.实际上,UDP协议只实现了两个功能: 1)在IP协议的基础上 ...

  8. JAVA TCP/IP Socket通信机制以及应用

    关于局域网通信(同一wifi下,自己电脑当服务端,同一网络段) 1.例如192.168.1.x,只有x位不相同视为同一网络段 2.当具备了以上条件,即可编写服务端代码,服务端的机制. 3.Server ...

  9. 《Java TCP/IP Socket 编程 》读书笔记之十一:深入剖析socket——TCP套接字的生命周期

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/16113083 建立TCP连接      新的Socket实例创建后,就立即能用于发送和接收 ...

随机推荐

  1. Spring Boot -- Idea搭建下搭建web项目

    最近公司准备使用Spring Boot框架,让小瑾先来学习一下,为了表示小瑾的办事效率,小瑾直接先学习用Idea搭建一个Spring Boot项目,哈哈哈,坐等领导夸. 废话不多说了,先来总结一下用I ...

  2. mysql存储过程详解及基于PHP使用实例

    mysql存储过程详解 1.      存储过程简介   我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的S ...

  3. docker系列之基础命令-1

    1.docker基础命令 docker images 显示镜像列表 docker ps 显示容器列表 docker run IMAGE_ID 指定镜像, 运行一个容器 docker start/sto ...

  4. 【HDU 2126】Buy the souvenirs(01背包)

    When the winter holiday comes, a lot of people will have a trip. Generally, there are a lot of souve ...

  5. Python3中super()的参数传递

    1. super([type[, object-or-type]]) super() 在使用时至少传递一个参数,且这个参数必须是一个类. 通过super()获取到的是一个代理对象,通过这个对象去查找父 ...

  6. adb 命令大全

    传送门 --> https://github.com/mzlogin/awesome-adb ADB,即 Android Debug Bridge,它是 Android 开发/测试人员不可替代的 ...

  7. x86保护模式-七中断和异常

    x86保护模式-七中断和异常 386相比较之前的cpu   增强了中断处理能力   并且引入了 异常概念 一 80386的中断和异常 为了支持多任务和虚拟存储器等功能,386把外部中断称为中断     ...

  8. PTA 09-排序1 排序 (25分)

    题目地址 https://pta.patest.cn/pta/test/15/exam/4/question/720 5-12 排序   (25分) 给定NN个(长整型范围内的)整数,要求输出从小到大 ...

  9. 30分钟学会如何使用Shiro(转自:http://www.cnblogs.com/learnhow/p/5694876.html)

    本篇内容大多总结自张开涛的<跟我学Shiro>原文地址:http://jinnianshilongnian.iteye.com/blog/2018936 我并没有全部看完,只是选择了一部分 ...

  10. 关于JS中字符串赋值的问题

    JS中不能直接  字符串不能 str[i] = 'x'     不能for循环 字符串length 然后赋值 应该 将字符串转换为数组   而且 字符x[i]=* 不是所有浏览器都兼容的 用  spl ...