本文主要关注点:
问:TCP方式的socket通讯的接受者不知道或者说无法确定消息的长度(比如我们聊天消息 前后的消息长度可能是不一样的),如果能识别消息是否接受完成
答:主要有2中技术使接受者能够准确的找到消息的结束位置
1、基于鉴定符(Delimiter-base):消息的结束由一个唯一的标记支出,即发送者在传输完数据后显式添加一个特殊字节序列。这个特殊标记不能砸传输中数据中出现
2、显式长度(Explicit length):在边长字段货消息前附加一个固定大小的字段,用来指示该字段货消息中包含了多少个字节(这个方法简单些,不过必须知道消息的长度的上限。发送者先确定消息的长度,将长度信息存入一个整数,作为消息的前缀。消息的藏毒上限定义了编码消息藏毒所需要的字节数:如果消息的长度小于256字节,则需要1个字节;如果消息的长度消息65536字节,则需要2个字节)
追问:第一个技术中,假如消息体重出现特殊标记字符怎么办
答:填充(Stuffing)技术能够对消息中出现的定界符进行修改,聪哥使接受者不将其识别为界定符。这个有个缺点是发送者和接受者双方度必须扫描消息
2、通讯要成功并且2端都能正确解析,需要通讯的双发约定协议:即命令交互的格式。
以下文投票为例:我们需要服务端和客户端约定一套规则来识别是投票、查询还是响应
一般2中表示方式 基本文本的表示方式(投票示例,按约定规则生成和解析字符串)和二进制表示方式
package cc.leng.tcpip.step2;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
/**
* 投票编解码<br/>
* format: Voting <"v"|"i"> [<RESPFLAG> <CANDIADTEID> [VOTECNT]
* @author leng-home
*
*/
public class VoteMsgTextCoder {
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;
/**
*
* @param msg
* @return
* @throws IOException
*/
public byte[] toWire(VoteMsg msg) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append(MAGIC).append(DELIMSTR)
.append((msg.isInquiry() ? INQSTR : VOTESTR)).append(DELIMSTR)
.append(msg.isResponse() ? RESPONSESTR + DELIMSTR : "")
.append(msg.getCandidateID()).append(DELIMSTR)
.append(msg.getVoteCount());
byte[] data = sb.toString().getBytes(CHARSETNAME);
return data;
}
/**
*
* @param mesage
* @return
* @throws IOException
*/
public VoteMsg fromWire(byte[] mesage) throws IOException{
ByteArrayInputStream msgStream = new ByteArrayInputStream(mesage);
Scanner s = new Scanner(new InputStreamReader(msgStream,CHARSETNAME));
boolean isInquiry;
boolean isResponse;
int candidateID;
long voteCount;
String token;
try{
token = s.next();
if(!MAGIC.equals(token)){
throw new IOException("Bad magic string : "+ token);
}
token = s.next();
if(VOTESTR.equals(token)){
isInquiry = false;
}else if(!INQSTR.equals(token)){
throw new IOException("Bad vote/inq indicator :"+token);
}else{
isInquiry = true;
}
token = s.next();
if(RESPONSESTR.equals(token)){
isResponse = true;
token = s.next();
}else{
isResponse = false;
}
//current token is candidateID
candidateID = Integer.parseInt(token);
if(isResponse){
token = s.next();
voteCount = Long.parseLong(token);
}else{
voteCount = 0;
}
}catch(IOException e){
throw new IOException("parse error....");
}
return new VoteMsg(isResponse, isInquiry, candidateID, voteCount);
}
}
package cc.leng.tcpip.step2;
/**
* 投票信息类
* @author leng-home
*
*/
public class VoteMsg {
private boolean isInquiry;
private boolean isResponse;
private int candidateID;
private long voteCount;
public static final int MAX_CANDIDATE_ID = 1000;
public VoteMsg(boolean isResponse,boolean isInquiry,int candidateID,long voteCount){
if(voteCount !=0 && !isResponse){
//只有响应的时候voteCount才有值
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 boolean isInquiry() {
return isInquiry;
}
public void setInquiry(boolean isInquiry) {
this.isInquiry = isInquiry;
}
public boolean isResponse() {
return isResponse;
}
public void setResponse(boolean isResponse) {
this.isResponse = isResponse;
}
public int getCandidateID() {
return candidateID;
}
public void setCandidateID(int candidateID) {
this.candidateID = candidateID;
}
public long getVoteCount() {
return voteCount;
}
public void setVoteCount(long voteCount) {
this.voteCount = 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;
}
}