程序间达成的某种包含了信息交换的形式和意义的共识称为协议,用来实现特定应用程序的协议叫做应用程序协议。大部分应用程序协议是根据由字段序列组成的离散信息定义的,其中每个字段中都包含了一段以位序列编码(即二进制字节编码,也可以使用基于文本编码的方式,但常用协议如:TCP、UDP、HTTP等在传输数据时,都是以位序列编码的)的特定信息。应用程序协议中明确定义了信息的发送者应该如何排列和解释这些位序列,同时还要定义接收者应该如何解析,这样才能使信息的接收者能够抽取出每个字段的意义。TCP/IP协议唯一的约束:信息必须在块中发送和接收,而块的长度必须是8位的倍数,因此,我们可以认为TCP/IP协议中传输的信息是字节序列。

由于协议通常处理的是由一组字段组成的离散的信息,因此应用程序协议必须指定消息的接收者如何确定何时消息已被完整接收。成帧技术就是解决接收端如何定位消息首尾位置问题的,  由于协议通常处理的是由一组字段组成的离散的信息,因此应用程序协议必须指定消息的接收者如何确定何时消息已被完整。主要有两种技术使接收者能够准确地找到消息的结束位置:

1、基于定界符:消息的结束由一个唯一的标记指出,即发送者在传输完数据后显式添加的一个特定字节序列,这个特殊标记不能在传输的数据中出现(这也不是绝对的,应用填充技术能够对消息中出现的定界符进行修改,从而使接收者不将其识别为定界符)。该方法通常用在以文本方式编码的消息中。

2、显式长度:在变长字段或消息前附加一个固定大小的字段,用来指示该字段或消息中包含了多少字节。该方法主要用在以二进制字节方式编码的消息中。

由于UDP套接字保留了消息的边界信息,因此不需要进行成帧处理(实际上,主要是DatagramPacket负载的数据有一个确定的长度,接收者能够准确地知道消息的结束位置),而TCP协议中没有消息边界的概念,因此,在使用TCP套接字时,成帧就是一个非常重要的考虑因素(在TCP连接中,接收者读取完最后一条消息的最后一个字节后,将受到一个流结束标记,即read()返回-1,该标记指示出已经读取到了消息的末尾,非严格意义上来讲,这也算是基于定界符方法的一种特殊情况)。

下面给出一个自定义实现上面两种成帧技术的Demo(书上的例子),先定义一个Framer接口,它由两个方法:frameMag()方法用来添加成帧信息并将指定消息输出到指定流,nextMsg()方法则扫描指定的流,从中抽取出下一条消息。

import java.io.IOException;
import java.io.OutputStream; public interface Framer {
void frameMsg(byte[] message, OutputStream out) throws IOException;
byte[] nextMsg() throws IOException;
}

下面的代码实现了基于定界符的成帧方法,定界符为换行符“\n”,frameMsg()方法并没有实现填充,当成帧的字节序列中包含有定界符时,它只是简单地抛出异常;nextMsg()方法扫描刘,直到读取到了定界符,并返回定界符前面所有的字符,如果流为空则返回null,如果直到流结束也没找到定界符,程序将抛出一个异常来指示成帧错误。

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class DelimFramer implements Framer { private InputStream in; // 数据来源
private static final byte DELIMITER = '\n'; // 定界符 public DelimFramer(InputStream in) {
this.in = in;
} public void frameMsg(byte[] message, OutputStream out) throws IOException {
for (byte b : message) {
if (b == DELIMITER) {
//如果在消息中检查到界定符,则抛出异常
throw new IOException("Message contains delimiter");
}
}
out.write(message);
out.write(DELIMITER);
out.flush();
} public byte[] nextMsg() throws IOException {
ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream();
int nextByte; while ((nextByte = in.read()) != DELIMITER) {
//如果流已经结束还没有读取到定界符
if (nextByte == -1) {
//如果读取到的流为空,则返回null
if (messageBuffer.size() == 0) {
return null;
} else {
//如果读取到的流不为空,则抛出异常
throw new EOFException("Non-empty message without delimiter");
}
}
messageBuffer.write(nextByte);
} return messageBuffer.toByteArray();
}
}

下面的代码实现了基于长度的成帧方法,适用于长度小于65535个字节的消息。发送者首先给出指定消息的长度,并将长度信息以big-endian顺序(从左边开始,由高位到低位发送)存入2个字节的整数中,再将这两个字节存放在完整的消息内容前,连同消息一起写入输出流;在接收端,使用DataInputStream读取整型的长度信息,readFully()方法将阻塞等待,直到给定的数组完全填满。使用这种成帧方法,发送者不需要检查要成帧的消息内容,而只需要检查消息的长度是否超出了限制。

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class LengthFramer implements Framer {
public static final int MAXMESSAGELENGTH = 65535;
public static final int BYTEMASK = 0xff;
public static final int SHORTMASK = 0xffff;
public static final int BYTESHIFT = 8; private DataInputStream in; public LengthFramer(InputStream in) throws IOException {
this.in = new DataInputStream(in); //数据来源
} //对字节流message添加成帧信息,并输出到指定流
public void frameMsg(byte[] message, OutputStream out) throws IOException {
//消息的长度不能超过65535
if (message.length > MAXMESSAGELENGTH) {
throw new IOException("message too long");
}
out.write((message.length >> BYTESHIFT) & BYTEMASK);
out.write(message.length & BYTEMASK);
out.write(message);
out.flush();
} public byte[] nextMsg() throws IOException {
int length;
try {
//该方法读取2个字节,将它们作为big-endian整数进行解释,并以int型整数返回它们的值
length = in.readUnsignedShort();
} catch (EOFException e) { // no (or 1 byte) message
return null;
}
// 0 <= length <= 65535
byte[] msg = new byte[length];
//该方法处阻塞等待,直到接收到足够的字节来填满指定的数组
in.readFully(msg);
return msg;
}
}

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

【Java TCP/IP Socket】应用程序协议中消息的成帧与解析(含代码)的更多相关文章

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

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

  2. 【Java TCP/IP Socket】构建和解析自定义协议消息(含代码)

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

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

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

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

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

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

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

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

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

  7. 【Java TCP/IP Socket】深入剖析socket——TCP套接字的生命周期

    建立TCP连接      新的Socket实例创建后,就立即能用于发送和接收数据.也就是说,当Socket实例返回时,它已经连接到了一个远程终端,并通过协议的底层实现完成了TCP消息或握手信息的交换. ...

  8. 【Java TCP/IP Socket】深入剖析socket——数据传输的底层实现

    底层数据结构 如果不理解套接字的具体实现所关联的数据结构和底层协议的工作细节,就很难抓住网络编程的精妙之处,对于TCP套接字来说,更是如此.套接字所关联的底层的数据结构集包含了特定Socket实例所关 ...

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

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

随机推荐

  1. shell脚本调试打印日志问题

    shell脚本调试打印日志问题 1. 需求 我们在编写脚本的时候,有时候需要做调试,便于我们定位问题,有时候等脚本上线之后,我们需要保留脚本执行过程中的记录.便于我们在出问题的时候,定位问题. 2. ...

  2. 监控linux各主机系统时间是否一致

    #!/bin/bashSTATE_OK=0 STATE_WARNING=1 STATE_CRITICAL=2 STATE_UNKNOWN=3PASSWD='**************'print_h ...

  3. 移动产品设计之ios系统的导航

    做道题:[不定项选择题] OS中导航设计模式有几种? A.平铺导航 B.标签导航 C.树形导航 D.模态视图导航 正确答案:A B C 讲解: 导航始终是产品设计的重头戏,往往产品设计中90%的事情就 ...

  4. (57)zabbix Slide shows幻灯片展示

    定义好screen之后,我们想了解服务器状况之时,一般会一个个screen点过去,zabbix提供了幻灯片展示方法,可以定义多个Slide,一个slide中可以包含多个screen. 创建slide ...

  5. 异步FIFO最小深度计算

    计算FIFO深度是设计FIFO中常遇到的问题.常识告诉我们,当读速率慢于写速率时,FIFO便可被用作系统中的缓冲元件或队列.因此FIFO的大小基本上暗示了所需缓存数据的容量,该容量取决于读写数据的速率 ...

  6. 《零基础入门学习Python》【第一版】视频课后答案第006讲

    python中被看作假:FALSE  none 0  ‘ ’  " "  ( ) [ ] { },其他一切都被解释为真 测试题答案: 0.Python 的 floor 除法现在使用 ...

  7. Mining of Massive Datasets-1

    given lots of data->discover patterns and models that are: valid, useful, unexpected, understanda ...

  8. Cocos2d-x学习资料集锦

    Cocos2d-x学习资料集锦: 1.Cocos2d-x官方中文文档:https://github.com/chukong/cocos-docs/blob/master/catalog/zh.md 推 ...

  9. 八、docker的常用命令

    1 Docker常用命令 1.1 存储镜像 如果要导出镜像到本地文件,可以使用 docker save 命令. docker save -o log_v140.tar docker.io/vmware ...

  10. Python基础数据类型之集合

    Python基础数据类型之集合 集合(set)是Python基本数据类型之一,它具有天生的去重能力,即集合中的元素不能重复.集合也是无序的,且集合中的元素必须是不可变类型. 一.如何创建一个集合 #1 ...