`
san_yun
  • 浏览: 2662252 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Java Socket codec

 
阅读更多

要写好java的网络编程并不只是new几个Socket,get一下InputStream,write to OutStream这么简单的。如何定义高效,稳定的协议,如何处理TCP协议中字节的发送和接收,编码,解码问题?socket缓冲区又是什么?本文讲讨论这些问题。

 

codec

     TCP/IP 协议以字节的方式传输用户数据,并没有对其进行检查和修改。这个特点使应用程序可以非常灵活地对其传输的信息进行编码。TCP/IP 协议的唯一约束是,信息必须在块(chunks)中发送和接收,而块的长度必须是 8 位(一个字节)的倍数,因此,我们可以认为在 TCP/IP 协议中传输的信息是字节序列。鉴于此,我们可以进一步把传输的信息看作byte数组,每个数字的取值范围是 0 到 255。这与 8 位编码的二进制数值范围是一致的:00000000 代表 0,00000001 代表 1,00000010 代表 2,等等,最多到 11111111,即255。

 

基本整型

     如我们所见,TCP 和 UDP 套接字使我们能发送和接收字节序列(数组) 即范围在 0-255,之间的整数。使用这个功能,我们可以对值更大的基本整型数据进行编码,不过发送者和接收者必须先在一些方面达成共识。一是要传输的每个整数的字节大小(size)。例如,Java程序中,int 数据类型由 32 位表示,因此,我们可以使用 4 个字节来传输任意的 int 型变量或常量;short 数据类型由 16 位表示,传输 short 类型的数据只需要两个字节;同理,传输64 位的 long 类型数据则需要 8 个字节。
下面我们考虑如何对一个包含了 4 个整数的序列进行编码:

  • 一个 byte 型,
  • 一个 short 型,
  • 一个 int 型,
  • 以及一个 long 型,

     按照这个顺序从发送者传输到接收者。我们总共需要 15 个字节:第一个字节存放 byte 型数据,接下来两个字节存放 short 型数据,再后面 4 个字节存放 int 型数据,最后 8 个字节存放 long 型数据。如下图:


 

 

order of transmission:传输顺序

     我们已经做好深入研究的准备了吗?未必。对于需要超过一个字节来表示的数据类型,我们必须知道这些字节的发送顺序。显然有两种选择:从整数的右边开始,由低位到高位地发送,即 little-endian 顺序;或从左边开始,由高位到低位发送,即 big-endian 顺序。考虑长整型数 123456787654321L,其 64 位(以十六进制形式)表示为 0x0000704885F926B1。

如果我们以 big-endian 顺序来传输这个整数,其字节的十进制数值序列就如下所示:


 
如果我们以 little-endian 顺序传输,则字节的十进制数组序列为:


    关键的一点是,对于任何多字节的整数,发送者和接收者必须在使用 big-endian 顺序还是使用 little-endian 顺序上达成共识。如果发送者使用了 little-endian 顺序来发送上述整数,而接收者以 big-endian 顺序对其进行接收,那么接收者将取到错误的值,它会将这个 8 字节序列的整数解析成 12765164544669515776L。
    发送者和接收者需要达成共识的最后一个细节是:所传输的数值是有符号的(signed)还是无符号的(unsigned)。Java 中的四种基本整型都是有符号的,它们的值以二进制补码的方式存储,这是有符号数值的常用表示方式。

 

正确存入到字节数组
   为了清楚地展示需要做的步骤,我们将对如何使用"位操作(bit-diddling)"(移位和屏蔽)来显式编码进行介绍。这部分内容见:各种进制基础知识

 

DataOutputStream和DataInputStream

      如你所见,上面的强制编码方法需要程序员做很多工作:要计算和命名每个数值的偏移量和大小,并要为编码过程提供合适的参数。如果没有将 encodeIntBigEndian()方法提出来作为一个独立的方法,情况会更糟。基于以上原因,强制编码方法是不推荐使用的,而且 Java 也提供了一些更加易用的内置机制。不过,值得注意的是强制编码方法也有它的优势,除了能够对标准的 Java 整型进行编码外,encodeIntegerBigEndian() 方法对 1 到8 字节的任何整数都适用--例如,如果愿意的话,你可以对一个 7 字节的整数进行编码。

    构建本例中的消息的一个相对简单的方法是使用 DataOutputStream 类和ByteArrayOutputStream 类。DataOutputStream 类允许你将基本数据类型,如上述整型,写入一个流中:它提供了 writeByte(),writeShort(),writeInt(),以及 writeLong()方法。

ByteArrayOutputStream buf = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(buf);
out.writeByte(byteVal);
out.writeShort(shortVal);
out.writeInt(intVal);
out.writeLong(longVal);
out.flush();
byte[] msg = buf.toByteArray();

 

处理BigInteger
    对于基本整型,发送者和接收者必须在使用多大空间(字节数)来表示一个数值上达成共识。但是这又与使用 BigInteger 相矛盾,因为 BigInteger 可以是任意大小。一种解决方法是使用基于长度的Frame。

 

字符串和文本

     对于字符串只要我们指定如何对要传输的文本进行编码,我们就几乎能发送其他任何类型的数据:先将其表示成文本形式,再对文本进行编码。首先得将文本视为由符号和字符(characters)组成。实际上每个 String 实例都对应了一个字符序列(数组,char[]类型)。一个字符在 Java 内部表示为一个整数。例如,字符"a",即字母"a"的符号,与整数 97 对应;字符"X"对应了 88,而符号"!"(感叹号)则对应了 33。
       在一组符号与一组整数之间的映射称为编码字符集,或许你听说过 ASCII 编码字符集。Java 使用了一种称为 Unicode 的国际标准编码字符集来表示 char 型和 String 型值。Unicode 字符集将"世界上大部分的语言和符号"[ ]映射到整数 0 至 65535 之间,能更好地适用于国际化程序。例如,日文平假名中代表音节"o"的符号映射成了整数 12362。Unicode包含了 ASCII 码:每个 ASCII 码中定义的符号在 Unicode 中所映射整数与其在 ASCII 码中映射的整数相同。这就为 ASCII 与 Unicode 之间提供了一定程度的向后兼容性。

    发送者与接收者必须在符号与整数的映射方式上达成共识,才能使用文本信息进行通信。对于每个整数值都比 255小的一小组字符,则不需要其他信息,因为其每个字符都能够作为一个单独的字节进行编码。对于可能使用超过一个字节的大整数的编码方式,就有多种方式在线路上对其进行编码。因此,发送者和接收者还需要对这些整数如何表示成字节序列统一意见,即编码方案(encodingscheme)。编码字符集和字符的编码方案结合起来称为字符集(charset,见 RFC 2278)。你也可以定义自己的字符集,但没有理由这样做,世界上已经有大量不同的标准(standardized)字符集在使用。Java 提供了对任意字符集的支持,而且每种实现都必须支持以下至少一种字符集:US-ASCII(ASCII 的另一个名字) ISO-8859-1,UTF-8,UTF-16BE,UTF-16LE,UTF-16。

组合输入输出流

   Java 中与流相关的类可以组合起来从而提供强大的功能。例如,我们可以将一个 Socket实例的 OutputStream 包装在一个 BufferedOutputStream 实例中,这样可以先将字节暂时缓存在一起,然后再一次全部发送到底层的通信信道中,以提高程序的性能。我们还能再将这个BufferedOutputStream 实例包裹在一个 DataOutputStream 实例中,以实现发送基本数据类型的功能。以下是实现这种组合的代码

Socket socket = new Socket(server, port);
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(socket.getOutputStream()));

 

Framing

    将数据转换成在线路上传输的格式只完成了一半工作,在接收端还必须将接收到的字节序列还原成原始信息。应用程序协议通常处理的是由一组字段组成的离散的信息,Framing解决了接收端如何定位消息的首尾位置的问题。无论信息是编码成了文本、多字节二进制数、或是两者的结合,应用程序协议必须指定消息的接收者如何确定何时消息已完整接收。这是一个没有处理好Frame的例子:socket的问题

    如果一条完整的消息负载在一个 DatagramPacket 中发送,这个问题就变得很简单了:DatagramPacket 负载的数据有一个确定的长度,接收者能够准确地知道消息的结束位置。然而,如果通过 TCP 套接字来发送消息,情况将变得更复杂,因为 TCP 协议中没有消息边界的概念。如果一个消息中的所有字段都有固定的长度,同时每个消息又是由固定数量的字段组成的话,消息的长度就能够确定,接收者就可以简单地将消息长度对应的字节数读到一个 byte[]缓存区中。但是如果消息的长度是可变的(例如消息中包含了一些变长的文本字符串),我们事先就无法知道需要读取多少字节。

    如果接收者试图从套接字中读取比消息本身更多的字节,将可能发生以下两种情况之一:如果信道中没有其他消息,接收者将阻塞等待,同时无法处理接收到的消息;如果发送者也在等待接收端的响应信息,则会形成死锁(deadlock); 另一方面,如果信道中还有其他消息,则接收者会将后面消息的一部分甚至全部读到第一条消息中去,这将产生一些协议错误。因此,在使用 TCP 套接字时,成帧就是一个非常重要的考虑因素。

主要有两个技术使接收者能够准确地找到消息的结束位置:

  • 基于定界符(Delimiter-based):消息的结束由一个唯一的标记(unique marker,)指出,即发送者在传输完数据后显式添加的一个特殊字节序列。这个特殊标记不能在传输的数据中出现。
  • 显式长度(Explicit length):在变长字段或消息前附加一个固定大小的字段,用来指示该字段或消息中包含了多少字节。

关于Frame的实现,参考Frame实现

 

 

 

 

 

 

 

 

 

 

  由于 TCP 提供了一种可信赖的字节流服务,任何写入 Socket 的 OutputStream 的数据复本都必须保留,直到其在连接的另一端被成功接收。向OutputStream写数据并不意味着数据实际上已经被发送--它们只是被复制到了本地缓冲区。就算在Socket 的 OutputStream 上进行 flush()操作,也不能保证数据能够立即发送到信道。

 

     在使用 TCP socket时需要记住的最重要一点是: 不能假设在连接的一端将数据写入输出流和在另一端从输入流读出数据之间有任何一致性。尤其是在发送端由单个输出流的 write()方法传输的数据,可能会通过另一端的多个输入流的 read()方法来获取;而一个 read()方法可能会返回多个 write()方法传输的数据。下面是一个例子:

 

byte[] buffer0 = new byte[1000];
byte[] buffer1 = new byte[2000];
byte[] buffer2 = new byte[5000];
...
Socket s = new Socket(destAddr, destPort);
OutputStream out = s.getOutputStream();
...
out.write(buffer0);
...
out.write(buffer1);
...
out.write(buffer2);
...
s.close();

 

  • 大小: 7.8 KB
  • 大小: 8.3 KB
  • 大小: 8.9 KB
  • 大小: 26.5 KB
分享到:
评论

相关推荐

    Java Socket 开发框架【susu】

    Java Socket 开发框架【susu】是一个自定义的、基于异步模式的网络通信解决方案,专为Java开发者设计。在Java编程中,Socket是实现客户端与服务器之间通信的基础,它提供了进程间的网络通信能力。而将Socket编程封装...

    Mina+Socket通信

    Mina和Socket是两种常见的网络通信框架和技术,它们在Java编程环境中被广泛使用。本篇文章将深入探讨如何使用Mina与Socket实现通信,并提供客户端和服务端的实现代码概述。 Mina(全称“MINA: Minimalistic ...

    Mina Socket 源代码

    在源代码中,你可能会看到自定义的 Codec 类,它们负责将应用程序对象与网络字节流之间进行转换。 8. **事件处理** Mina 的事件驱动模型允许开发者对特定的网络事件(如连接建立、数据到达、连接关闭等)做出响应...

    SocketDebug 可执行文件

    使用开源技术搭建的TCP调试程序,Apache Mina + Swing + Apache commons-codec

    Java实现远程屏幕监视

    import java.net.Socket; public class Server { public Server() { } public void listen() throws IOException { ServerSocket server = new ServerSocket(8002); while (true) { try { ...

    常用的30个java工具类

    12. **SimpleServer和Client**: 这可能是指简单的TCP或HTTP服务器和客户端实现,比如使用`java.net.ServerSocket`和`java.net.Socket`进行网络通信。 以上就是对标题和描述中提到的Java工具类的详细说明。这些工具...

    SocketDebug 源代码

    SocketDebug是一款基于Apache MINA框架、Swing图形用户界面库以及Apache Commons Codec库开发的TCP调试工具。这个工具主要用于帮助开发者在TCP网络通信过程中进行数据包的捕获、分析和发送,是网络编程和调试过程中...

    java多线程tcpsocketserver源码-netty-learning:网络学习

    java多线程tcp socket server源码@Netty 笔记 1. 内蒂 网络应用框架 特征 设计 支持多种传输类型 事件模型 高度可定制的线程模型 表现 高吞吐量,低延迟 更少的资源消耗 最小化内存拷贝 安全SSL/TLS 支持 建筑学 核 ...

    mina 包 socket 框架

    开发者可以自定义Codec实现,将应用级别的消息转换为字节流,反之亦然。这对于处理各种数据格式,如XML、JSON或二进制协议,非常有用。 除了基本的网络通信功能,Mina还提供了许多附加特性,如心跳检测、会话管理、...

    SocketConnector连接器

    SocketConnector连接器是一种在Java编程环境中广泛使用的网络通信组件,主要用于建立基于TCP/IP协议的Socket连接。这个连接器是Java的Netty框架的一部分,Netty是一个高性能、异步事件驱动的网络应用程序框架,用于...

    java聊天程序

    本项目可能是通过Java的网络API(如Socket和ServerSocket类)来实现客户端与服务器端之间的数据交换,允许用户进行实时对话。 【描述】: 根据给出的链接,这是一个关于Java聊天程序的博客文章,但具体内容未提供...

    JAVA安全性第一部分 密码学基础

    5. SSL/TLS协议:在Java中,通过JSSE(Java Secure Socket Extension)实现SSL(Secure Sockets Layer)和TLS(Transport Layer Security)协议,用于创建安全的网络连接,确保数据在网络中的传输是加密的。...

    niosocket及其开源框架MINA学习总结收集.pdf

    NIO (Non-blocking Input/Output) 是Java中的一种I/O模型,与传统的 blocking I/O 相比,它提供了更高效的数据处理方式。在传统的Socket编程中,每个连接都会创建一个新的线程进行处理,这在连接数量较少时是可行的...

    学员信息管理系统

    【学员信息管理系统】是一个基于Java开发的项目,旨在帮助学习者深入理解Java编程语言,并实践在实际项目中的应用。这个系统包含了对学员信息的基本操作,如增加、删除、修改和查询,是学习Java编程和软件工程实践的...

    基于Java的两个通用安全模块的设计与实现.zip

    为了实现安全的数据传输,比如HTTPS,我们可以使用JSSE(Java Secure Socket Extension),它包含了SSL/TLS协议的支持。在设计加密模块时,需要考虑以下几点:选择合适的加密算法、密钥长度、以及加密模式(如CBC、...

    netty socketio全部包jar

    标题中的“netty socketio全部包jar”指的是一个包含了Netty和SocketIO相关库的Java归档(JAR)文件集合。这些文件是为构建基于Web的通信解决方案而准备的,特别是针对实时通信需求。 Netty是一个高性能、异步事件...

    lucene-codecs-4.4.0.zip

    6. 网络编程:Java提供了Socket、ServerSocket等类,用于实现TCP/IP网络通信,还有URL、URLConnection等类支持HTTP和其他网络协议。 7. 反射(Reflection):允许运行时检查类、接口、字段和方法的信息,以及动态...

    Apache Mina

    3. **Java Socket**: 掌握Java Socket编程,理解网络通信的基本原理。 4. **Java线程及并发库**: 熟练使用Java中的线程控制和并发工具,如`java.util.concurrent`包下的类和接口。 #### 架构与组件 Mina的核心架构...

    Android源码分析:VoIP.pdf

    JNI层的`create`函数调用C/C++标准库中的`::socket`函数创建socket,并将socket号通过`SetIntField`方法传递回Java层的RtpStream实例。 总结来说,Android源码中的VoIP实现涉及了RTP协议的详细处理,包括流的创建、...

    Netty简介 Netty线程模型和EventLoop Codec编码与解码 ByteBuf容器

    这些Transport实现了底层的I/O操作,如创建socket、发送和接收数据,将复杂的网络编程简化为对Channel的操作。 **Bootstrap引导** Bootstrap是Netty的启动配置类,用于配置服务器端或客户端的启动参数,如...

Global site tag (gtag.js) - Google Analytics