【Java TCP/IP Socket】应用程序协议中消息的成帧与解析(含代码)
程序间达成的某种包含了信息交换的形式和意义的共识称为协议,用来实现特定应用程序的协议叫做应用程序协议。大部分应用程序协议是根据由字段序列组成的离散信息定义的,其中每个字段中都包含了一段以位序列编码(即二进制字节编码,也可以使用基于文本编码的方式,但常用协议如: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】应用程序协议中消息的成帧与解析(含代码)的更多相关文章
- 【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)(转)
书上示例 在第一章<基本套接字>中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去. 书上客户端代码如下: 1 2 3 ...
- 【Java TCP/IP Socket】构建和解析自定义协议消息(含代码)
在传输消息时,用Java内置的方法和工具确实很用,如:对象序列化,RMI远程调用等.但有时候,针对要传输的特定类型的数据,实现自己的方法可能更简单.容易或有效.下面给出一个实现了自定义构建和解析协议消 ...
- 【Java TCP/IP Socket】UDP Socket(含代码)
UDP的Java支持 UDP协议提供的服务不同于TCP协议的端到端服务,它是面向非连接的,属不可靠协议,UDP套接字在使用前不需要进行连接.实际上,UDP协议只实现了两个功能: 1)在IP协议的基础上 ...
- 【Java TCP/IP Socket】TCP Socket(含代码)
TCP的Java支持 协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构.交换方式.包含的意义以及怎样对报文所包含的信息进行解析,TCP/IP协议族有IP协议.TCP协议和UDP协议.现 ...
- 一个项目看java TCP/IP Socket编程
前一段时间刚做了个java程序和网络上多台机器的c程序通讯的项目,遵循的是TCP/IP协议,用到了java的Socket编程.网络通讯是java的强项,用TCP/IP协议可以方便的和网络上的其他程序互 ...
- 《Java TCP/IP Socket 编程 》读书笔记之十一:深入剖析socket——TCP套接字的生命周期
转载请注明出处:http://blog.csdn.net/ns_code/article/details/16113083 建立TCP连接 新的Socket实例创建后,就立即能用于发送和接收 ...
- 【Java TCP/IP Socket】深入剖析socket——TCP套接字的生命周期
建立TCP连接 新的Socket实例创建后,就立即能用于发送和接收数据.也就是说,当Socket实例返回时,它已经连接到了一个远程终端,并通过协议的底层实现完成了TCP消息或握手信息的交换. ...
- 【Java TCP/IP Socket】深入剖析socket——数据传输的底层实现
底层数据结构 如果不理解套接字的具体实现所关联的数据结构和底层协议的工作细节,就很难抓住网络编程的精妙之处,对于TCP套接字来说,更是如此.套接字所关联的底层的数据结构集包含了特定Socket实例所关 ...
- 【Java TCP/IP Socket】深入剖析socket——TCP通信中由于底层队列填满而造成的死锁问题(含代码)
基础准备 首先需要明白数据传输的底层实现机制,在http://blog.csdn.net/ns_code/article/details/15813809这篇博客中有详细的介绍,在上面的博客中,我们提 ...
随机推荐
- XDB基于Library的备份及恢复
基于standalone全备份 语句: xdb backup --federation xhive://localhost:1235 --standalone --file E:\xdbData\xD ...
- MongoDB在java中的使用
在一年前就开始在项目中使用Mongodb作为爬虫(crawler)待下载URL.下载成功URL等的存储库,最近对项目进行版本更新,根据Mongodb的最近升级情况,也对项目中的Mongodb进行了相关 ...
- jwt 登录
/* eslint-disable */ 'use strict'; const Controller = require('egg').Controller; const jwt = require ...
- docker系列之基础命令-1
1.docker基础命令 docker images 显示镜像列表 docker ps 显示容器列表 docker run IMAGE_ID 指定镜像, 运行一个容器 docker start/sto ...
- 气泡小角的css实现
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" dir="ltr"> ...
- Hive 将本地数据导入hive表中
# 导入 load data local inpath '/root/mr/The_Man_of_Property.txt' insert into table article; # 提示 FAILE ...
- git2--常用命令
Git 命令详解及常用命令 Git作为常用的版本控制工具,多了解一些命令,将能省去很多时间,下面这张图是比较好的一张,贴出了看一下: 关于git,首先需要了解几个名词,如下: ? 1 2 3 4 Wo ...
- 【04】在 PR 中关闭 issue
[04]在 PR 中关闭 issue 似乎要给别人PR. 比如你在创建一个 pull request 去修复 issue #234.那你可在 PR 输入「fixes #234」,就可以自动 ...
- JMeter学习笔记21-如何添加思考时间
本文来介绍,JMeter如何插入思考时间.前面介绍过一个真实的性能测试场景,是需要加入思考时间,来模拟真实用户行为.本文就来介绍,如何在三个请求之间添加思考时间. 1. 在Test Plan下新建一个 ...
- TOJ 4095: love168yk的选美大赛
4095: love168yk的选美大赛 Time Limit(Common/Java):1000MS/3000MS Memory Limit:65536KByteTotal Submit: ...