`
cuisuqiang
  • 浏览: 3960274 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
3feb66c0-2fb6-35ff-968a-5f5ec10ada43
Java研发技术指南
浏览量:3670107
社区版块
存档分类
最新评论

ByteBuffer 到底怎么用?网络编程中一点总结!

    博客分类:
  • JDK
阅读更多

做tcp网络编程,要解析一批批的数据,可是数据是通过Socket连接的InputStream一次次读取的,读取到的不是需要转换的对象,而是要直接根据字节流和协议来生成自己的数据对象。

按照之前的编程思维,总是请求然后响应,当然Socket也是请求和响应,不过与单纯的请求响应是不同的。

这里Socket连接往往是要保持住的,也就是长连接,然后设置一个缓冲区,网络流不断的追加到缓冲区。然后后台去解析缓冲区的字节流。

如图所示,网络的流一直在传递,我们收到也许是完成的数据流,也可能是没有传递完的。这里就需要监视管道,不断读取管道中的流数据,然后向缓冲区追加。程序从头开始解析,如果目前缓冲区包含了数据,则解析,没有则放弃继续读取管道流。

就算管道中包含了数据,也不一定包含了完成的数据。例如,100个字节是一个数据体,可是目前缓冲区内包含了120个字节,这就是说缓冲区包含了一条数据,但是还有没有传递完的字节流。那么就要把前100个字节拿出来解析,然后从缓冲区清除这100个字节。那缓冲区就剩下20个字节了,这些数据可能在下次流中补充完成。

如何建立缓冲?

/**
 * 全局MVB数据缓冲区 占用 1M 内存
 */
private static ByteBuffer bbuf = ByteBuffer.allocate(10240);

/**
 * 线程安全的取得缓冲变量
 */
public static synchronized ByteBuffer getByteBuffer() {
	return bbuf;
}

 写一个Socket客户端,该客户端得到Socket连接,然后读取流,一直向缓冲中追加字节流,每次追加后调用一个方法来解析该流

public void run() {
	Socket socket = GlobalClientKeep.mvbSocket;
	if (null != socket) {
		try {
			// 获得mvb连接引用
			OutputStream ops = socket.getOutputStream();
			InputStream ips = socket.getInputStream();
			while (true) {
				if (null != ops && null != ips) {
					// 接收返回信息
					byte[] bt = StreamTool.inputStreamToByte(ips);
					ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();
					// 设置到缓冲区中
					bbuf.put(bt);
					// ////////////////////////////////////////////////////////////////////////
					// 拆包解析方法
					splitByte(ops);
					ops.flush();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	} else {
		// 如果连接存在问题,则必须重新建立
		GlobalClientKeep.initMvbSocket();
	}
}

 

关于如何读取流,我有一篇博客专门讲解了所以这里是直接调用方法

byte[] bt = StreamTool.inputStreamToByte(ips);

 那么解析方法是如何做的?

解析方法首先获得该缓冲中的所有可用字节,然后判断是否符合一条数据条件,符合就解析。如果符合两条数据条件,则递归调用自己。其中每次解析一条数据以后,要从缓冲区中清除已经读取的字节信息。

/**
 * @说明 拆包解析方法
 */
public static void splitByte(OutputStream ops) {
	try {
		ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();
		int p = bbuf.position();
		int l = bbuf.limit();
		// 回绕缓冲区 一是将 curPointer 移到 0, 二是将 endPointer 移到有效数据结尾
		bbuf.flip();
		byte[] byten = new byte[bbuf.limit()]; // 可用的字节数量
		bbuf.get(byten, bbuf.position(), bbuf.limit()); // 得到目前为止缓冲区所有的数据
		// 进行基本检查,保证已经包含了一组数据
		if (checkByte(byten)) {
			byte[] len = new byte[4];
			// 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度
			System.arraycopy(byten, 0, len, 0, 4);
			int length = StreamTool.bytesToInt(len); // 每个字节流的最开始肯定是定义本条数据的长度
			byte[] deco = new byte[length]; // deco 就是这条数据体
			System.arraycopy(byten, 0, deco, 0, length);
			// 判断消息类型,这个应该是从 deco 中解析了,但是下面具体的解析内容不再啰嗦
			int type = 0;
			// 判断类型分类操作
			if (type == 1) {
				
			} else if (type == 2) {
				
			} else if (type == 3) {
				
			} else {
				System.out.println("未知的消息类型,解析结束!");
				// 清空缓存
				bbuf.clear();
			}
			// 如果字节流是多余一组数据则递归
			if (byten.length > length) {
				byte[] temp = new byte[bbuf.limit() - length];
				// 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度
				System.arraycopy(byten, length, temp, 0, bbuf.limit() - length);
				// 情况缓存
				bbuf.clear();
				// 重新定义缓存
				bbuf.put(temp);
				// 递归回调
				splitByte(ops);
			}else if(byten.length == length){ // 如果只有一条数据,则直接重置缓冲就可以了
				// 清空缓存
				bbuf.clear();
			}
		} else {
			// 如果没有符合格式包含数据,则还原缓冲变量属性
			bbuf.position(p);
			bbuf.limit(l);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

 

代码只是一个参考,主要讲解如何分解缓冲区,和取得缓冲区的一条数据,然后清除该数据原来站的空间。

至于缓冲区的属性,如何得到缓冲区的数据,为什么要清空,bbuf.flip();是什么意思。下面来说一下关于ByteBuffer 的一下事情。

 ByteBuffer 中有几个属性,其中有两个很重要。limit和 position。position开始在0,填充数据后等于数据的长度,而limit是整个缓冲可用的长度。bbuf.flip();之后,position直接变为0,而limit直接等于position。JDK源码如下:

    /**
     * Flips this buffer.  The limit is set to the current position and then
     * the position is set to zero.  If the mark is defined then it is
     * discarded.
     *
     * <p> After a sequence of channel-read or <i>put</i> operations, invoke
     * this method to prepare for a sequence of channel-write or relative
     * <i>get</i> operations.  For example:
     *
     * <blockquote><pre>
     * buf.put(magic);    // Prepend header
     * in.read(buf);      // Read data into rest of buffer
     * buf.flip();        // Flip buffer
     * out.write(buf);    // Write header + data to channel</pre></blockquote>
     *
     * <p> This method is often used in conjunction with the {@link
     * java.nio.ByteBuffer#compact compact} method when transferring data from
     * one place to another.  </p>
     *
     * @return  This buffer
     */
    public final Buffer flip() {
	limit = position;
	position = 0;
	mark = -1;
	return this;
    }

 这样,在position和limit之间的数据就是我们要的可用数据。

但是position和limit是ByteBuffer在put和get时需要的属性,所以在使用后要么还原,要么像上面代码一样,清除一些字节信息然后重置

 ByteBuffer 的get和put不是我们平常的取值和设值一样,他会操纵一些属性变化。

 

请您到ITEYE看我的原创:http://cuisuqiang.iteye.com

或支持我的个人博客,地址:http://www.javacui.com

 

8
2
分享到:
评论
6 楼 allen.lei 2015-10-27  
博主好,
看到您的帖子,http://cuisuqiang.iteye.com/blog/1443212
这个里面的 ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer(); 这个GlobalCommonObjectKeep有吗?具体做什么操作啊?

求GlobalCommonObjectKeep。
多谢

我的qq 25897555
邮箱 25897555@qq.com

另外,我的tcp socket长链接数据收不全,接收貌似分包了:
我的客户端接收代码:
while (!done && thread == readerThread){
while(connection.byreader.available()>3){
Log.d("summercoolAPP", "PacketReader 2");

int length = connection.byreader.read();

Log.d("summercoolAPP", "PacketReader 3 length="+length);
if (connection.byreader.available() < length){
Log.d("summercoolAPP", "PacketReader 5");
return;
}
Log.d("summercoolAPP", "PacketReader 6");
//
byte[] buffer = new byte[length];
try {
connection.byreader.read(buffer);
Object recMsg = null;
try{
recMsg = SERIALIZER.deserialize(buffer);
}catch(Exception e){
recMsg = null;
e.printStackTrace();
}
if (recMsg == null) {
continue;
}
if (recMsg instanceof byte[]) {
recMsg = CustomSerializer.decode((byte[]) recMsg);

}
System.out.println(recMsg);
Log.d("summercoolAPP", "PacketReader 7 recMsg="+recMsg);
//
// 如果接收到Server发送的握手反馈消息,则回送握手完成消息
if (recMsg instanceof HandshakeAck) {
Log.d("summercoolAPP", "PacketReader 8");
// 构造握手完成消息包
HandshakeFinish finish = new HandshakeFinish(groupName);
byte[] hfBytes = SERIALIZER.serialize(finish);
// 发送
connection.sendSpecial4HandshakeAck(new String(hfBytes,"UTF-8"));
}

else if(recMsg instanceof Heartbeat){
byte[] htBytes = SERIALIZER.serialize(Heartbeat.getSingleton());
connection.sendSpecial4HandshakeAck(new String(htBytes,"UTF-8"));
}

else if(recMsg instanceof HandshakeRequest){
}
else if(recMsg instanceof HandshakeFinish){

}
else{//具体业务parser4bs
parser.setInput(new StringReader((String)recMsg));
parser4bs();
}
} catch (Exception e) {
Log.d("summercoolAPP", "PacketReader 9");
e.printStackTrace();
done = true;
}
}
}

服务端:
发送字节流,
格式是:
length(4字节)+ID(1字节)+content
length的值是ID1字节+content的长度

我的服务端发送一段完整业务数据给客户端时,客户端接收是分段的,且没啥规律
对了,我的服务端在发送数据时,起了一个现成定时去ping客户端,也会发送ping包,我在客户端接收业务数据时,也看到了分包的中间穿插了ping包,数据
求如何接收完整业务数据啊?
多谢了
5 楼 allen.lei 2015-10-27  

这个里面的 ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();   这个GlobalCommonObjectKeep有吗?具体做什么操作啊?

求GlobalCommonObjectKeep。
多谢

我的qq 25897555
邮箱 25897555@qq.com
4 楼 cuisuqiang 2012-03-09  
canghailan 写道
现在JavaEye的大牛基本都归隐了。。。

只有我们这些小辈的瞎折腾了
3 楼 canghailan 2012-03-08  
现在JavaEye的大牛基本都归隐了。。。
2 楼 cuisuqiang 2012-03-08  
canghailan 写道
引用
/** 
 * 全局MVB数据缓冲区 占用 1M 内存 
 */  
private static ByteBuffer bbuf = ByteBuffer.allocate(10240);  
  
/** 
 * 线程安全的取得缓冲变量 
 */  
public static synchronized ByteBuffer getByteBuffer() {  
    return bbuf;  
}

获取方法加了synchronized,但是获取的ByteBuffer不是线程安全的。
想要线程安全的话:
(1)使用ThreadLocal
(2)每次分配重新分配
(3)每次使用bbuf时synchronized或者加锁

这么牛逼的人物竟然是新申请的号,难道是专门出来灭我们的
1 楼 canghailan 2012-03-07  
引用
/** 
 * 全局MVB数据缓冲区 占用 1M 内存 
 */  
private static ByteBuffer bbuf = ByteBuffer.allocate(10240);  
  
/** 
 * 线程安全的取得缓冲变量 
 */  
public static synchronized ByteBuffer getByteBuffer() {  
    return bbuf;  
}

获取方法加了synchronized,但是获取的ByteBuffer不是线程安全的。
想要线程安全的话:
(1)使用ThreadLocal
(2)每次分配重新分配
(3)每次使用bbuf时synchronized或者加锁

相关推荐

    Android中的ByteBuffer解析

    2. **网络通信**:在Android网络编程中,ByteBuffer用于处理Socket通信中的字节流。 3. **图像处理**:在Android图形处理中,如OpenGL ES,ByteBuffer用于传递像素数据。 六、性能优化 1. **选择适当的缓冲区类型...

    Java NIO学习笔记——ByteBuffer用法

    在阅读或实现`byteBufferTest.java`时,注意观察如何利用ByteBuffer的特性进行高效的数据传输,例如与FileChannel配合读写文件,或者在网络编程中与SocketChannel交互。同时,理解源码可以帮助深入理解Java NIO的...

    ByteBuffer.zip

    在IT行业中,ByteBuffer是一个非常重要的概念,特别是在网络通信和数据处理领域。ByteBuffer是Java平台提供的一种高效的数据操作接口,它允许我们以字节为单位进行读写操作,这对于处理二进制数据尤其有用。在...

    网络编程中服务端的优化解析

    本篇文章将深入探讨网络编程中服务端优化的关键点,特别是针对"粘包分包"问题的解决策略。 "粘包分包"现象在网络通信中常常出现,主要发生在基于TCP协议的通信中。TCP协议为了提高传输效率,会将多个小的数据段合并...

    易语言汇编版ByteBuffer源码

    ByteBuffer作为一个易语言组件,可以帮助开发者更好地实现网络编程中的数据处理任务。 通过阅读和理解`content.txt`文件中的源代码,开发者可以学习到如何在易语言中实现类似Java的ByteBuffer功能,从而提升自己的...

    Netty网络编程视频教程

    近百节视频详细讲解,...4. 网络编程 5. NIO vs BIO 二. Netty 入门 1. 概述 2. Hello World 3. 组件 4. 双向通信 三. Netty 进阶 1. 粘包与半包 2. 协议设计与解析 3. 聊天室案例 四. 优化与源码 1. 优化 2. 源码分析

    网络编程之Netty一站式精讲.rar

    3. 高效的缓冲区:Netty使用ByteBuf代替Java的ByteBuffer,提供了更高效的数据读写操作。 4. 统一的API:Netty为不同的传输类型(TCP、UDP等)提供了统一的API,简化了开发过程。 5. 扩展性和灵活性:Netty允许...

    java网络编程试卷及答案.pdf

    Java网络编程是Java开发中的重要领域,主要涉及网络通信、数据传输和协议处理等方面。这份“java网络编程试卷及答案.pdf”很可能包含了关于这个主题的各种问题和解答,旨在帮助学习者检验和提升自己的技能。以下是...

    protobuf+long+bytebuffer

    从网络或者存储中获取到protobuf编码的二进制数据后,可以创建一个`ByteBuffer`实例,然后调用protobuf编译器生成的类的解析方法,传入`ByteBuffer`实例进行解码。`ByteBuffer`会根据protobuf编码规则正确地读取和...

    C#下的组播网络编程

    组播网络编程是一种高效的数据传输方式,特别是在音视频会议系统中,它可以将数据发送到多个接收者,而无需为每个接收者单独建立连接。在C#环境下,利用.NET框架提供的Socket类,我们可以轻松实现组播功能。下面我们...

    网络编程需要的2个包

    在Java编程语言中,进行网络编程时,通常会用到两个核心的包:`java.net`和`java.nio`。这两个包提供了丰富的类和接口,帮助开发者构建基于TCP/IP协议的网络应用程序,如服务器、客户端、数据传输等。现在,让我们...

    Android在JNI中使用ByteBuffer的方法

    总结来说,Android在JNI中使用ByteBuffer主要是为了提高数据传输的效率,通过直接访问内存,减少数据拷贝,从而提升应用性能。理解并熟练运用ByteBuffer的创建、操作和JNI交互机制,对于优化Android系统的低层功能和...

    ios-byteBuffer:在objective-c中重写一个类

    ios-byteBuffer [![CI状态]( Lee / ios-byteBuffer.svg?style = flat)]( Lee / ios-byteBuffer ) 用法 #分配 ByteBuffer *buffer = [ByteBuffer initWithOrder: ByteOrderLittleEndian]; #输入数据 - ( ...

    深入理解Apache Mina (6)---- Java Nio ByteBuffer与Mina ByteBuffer的区别

    在Mina中,ByteBuffer的使用是至关重要的,因为它提供了高效的数据读写机制。本篇将深入探讨Java NIO(非阻塞I/O)中的ByteBuffer和Mina库自定义的ByteBuffer之间的区别。 Java NIO的ByteBuffer是Java标准库提供的...

    NIO(byteBuffer)按行读取文件

    使用nio byteBuffer 实现按行读取文件(大文件) 在window/linux/macOS上均测试通过 对于中文乱码也已处理成功 完整注释,可随需求更改 有问题请邮件:mly610865580@126.com

    【IT十八掌徐培成】Java基础第26天-05.ByteBuffer-mark-pos-limit-cap-flip.zip

    在Java编程语言中,`ByteBuffer`是Java NIO(New IO)框架中的核心类之一,它提供了一种高效处理字节数据的方式。本教程重点讲解了`ByteBuffer`的几个关键属性:mark、position、limit和capacity,以及重要的操作...

    java网络编程rar

    总的来说,这份"Java网络编程"的电子书将引导初学者深入理解网络通信的原理,并学会使用Java进行实际的网络编程。从简单的Socket通信到复杂的HTTP/HTTPS应用,从基础的I/O操作到高效的NIO技术,都是学习者需要掌握的...

    后端netty网络编程

    后端Netty网络编程是构建高性能、高效率网络应用的核心技术之一,特别是在Java领域中,Netty因其高效、易用和强大的特性而备受推崇。Netty是一个开源的异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能...

    Netty初探:掌握高性能网络通信框架,提升Java网络编程技能

    它极大地简化了Java网络编程,尤其是对于TCP和UDP套接字,以及各种自定义二进制或基于文本的协议。在深入探讨Netty之前,我们需要先了解一些基础的网络编程概念。 网络编程主要涉及通过网络在不同计算机之间交换...

Global site tag (gtag.js) - Google Analytics