`

【Java TCP/IP Soket】— 消息边界的问题解决

 
阅读更多

转自:http://blog.csdn.net/dabing69221/article/details/17222595

【Java TCP/IP Soket】— 消息边界的问题解决

关于消息边界问题,在TCP套接字处理接收消息中尤为重要,所以大家一定要学会解决它!

场景:

     当接收者试图从套接字中读取比消息本身更多的字节时,将可能发生两种情况:

    1.如果套接字中没有其他消息,接收者将会阻塞等待,同时无法处理接收到的消息;如果发送者也在等待接收端的响应消息,则会形成死锁;

    2.如果套接字中有其他消息,接收者会将后面消息的一部分甚至全部读到第一条消息中去,这将产生一些协议错误;

应用:

    当我们使用TCP套接字时,处理“消息边界” 是一个重要的考虑因素;如果使用UDP套接字时,不存在这个问题,因为在DatagramPacket中存放的数据

    有一个确定的长度(DatagramPacket.getLength()方法),接收者能够精确的知道“消息边界”(或消息结束位置)

    现在介绍两个技术可以使接收者能够精确的找到“消息边界”(也就是消息的结束位置)

 

       1. 基于定界符

           消息的结束由一个唯一的标记指出,即发送者在传输消息后显示添加一个特殊标记。这个特殊标记不能在传输消息本身中出现。

           注意:

           (1)前提条件:

                使用“基于定界符”的方法来解决消息边界问题时,消息本身不能包含有“定界符”,否则接收者将提前认为消息已经结束;

           (2)特殊的实现:

                使用Socket.close( )或Socket.shutdownOutput( ) 来实现“基于定界符”的方法:在“基于定界符”中有一个特殊情况是,可以用在TCP连接上传输的最后一个消息

                上。 在发送完这个消息后,发送者就可以简单的关闭(使用socket.shutdownOutput()方法或socket.close()方法)发送端的TCP连接。接收者读取完这条消息

                的最后一个字节后,将接收到一个流结束标记(即InputStream.read() 返回 -1),该标记指出了已经读取到流的末尾;

           (2)应用场景:

               “基于定界符”的方法通常用在“以文本方式编码的消息”中,不能用在“以二进制方式编码的消息”中(例如图片、MP3),其中最大的一个原因就是:接收者需要遍历

                消息信息来查找“定界符”(定界符:其实就是使用一个特殊的字符或字符串来标识消息的结束)。假如这个消息信息是一个图片,你在图片(二进制文件)中去查找

                一个“字符”合适吗?肯定不合适,二进制肯定不能与字符来进行比较;

 

       2.显示长度

           在变长字段或消息前附加一个固定大小的字段,用来指示该字段或消息中包含了多少字节;

           注意:使用这种方式必须要知道消息的上限,但是,假如在无意间发送的消息超过了消息的上限,如果不处理妥当,将会发生消息丢失;

          

例子:

一.基于定界符的实现例子

1.使用“自定义定界符”,解决消息边界问题:

(1). 处理定界符的消息类

  1. public class DelimFramer  
  2. {  
  3.   
  4.     private static final int DELIMITER = '\n';  
  5.   
  6.     /** 
  7.      * 添加成帧信息并将信息写入到输出流 
  8.      *  
  9.      * @param message 
  10.      * @param out 
  11.      * @throws IOException 
  12.      */  
  13.     public void frameMsg(byte[] message, OutputStream out) throws IOException  
  14.     {  
  15.         for (byte b : message)  
  16.         {  
  17.             /* 
  18.              * 注意:发送的消息本身不能包含定界符。如果存在,则抛出异常 
  19.              */  
  20.             if (b == DELIMITER)  
  21.             {  
  22.                 throw new IOException("Message contains delimiter");  
  23.             }  
  24.         }  
  25.   
  26.         out.write(message);  
  27.         out.write(DELIMITER);  
  28.         out.flush();  
  29.     }  
  30.   
  31.     /** 
  32.      * 读入输入流,直到读取到了定界符,并返回定界符前面的所有字符 
  33.      *  
  34.      * 1.包含定界符的信息 2.不包含定界符的信息 
  35.      *  
  36.      * @return 
  37.      * @throws IOException 
  38.      */  
  39.     public byte[] nextMsg(InputStream in) throws IOException  
  40.     {  
  41.         ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream();  
  42.         int nextByte;  
  43.   
  44.         /* 
  45.          * 情况一:判断消息中是否包含定界符; 如果输入流读取到了定界符,则返回定界前面的所有字符(不包括定界符) 
  46.          */  
  47.         while ((nextByte = in.read()) != DELIMITER)  
  48.         {  
  49.             /* 
  50.              * 情况二:判断消息中是否不包含定界符;如果输入流读取到了-1(说明该消息中不包括定界符) 
  51.              */  
  52.             if (nextByte == -1)  
  53.             {  
  54.                 /* 
  55.                  * 判断BytaArrayOutputStream的缓冲区中是否有数据: 
  56.                  * 1.如果没有数据:说明从该输入流中没有读取到消息,就到达输入流的末尾 ; 
  57.                  * 2.如果有数据:说明从该输入流中读取的消息是一个不带分界符的非空消息; 
  58.                  */  
  59.                 if (messageBuffer.size() == 0)  
  60.                 {  
  61.                     return null;  
  62.                 }  
  63.                 else  
  64.                 {  
  65.                     throw new EOFException(  
  66.                             "Non-empty message without delimiter");  
  67.                 }  
  68.             }  
  69.             messageBuffer.write(nextByte);  
  70.         }  
  71.         return messageBuffer.toByteArray();  
  72.     }  
  73. }  

 

(2). TCP客户端类

 

  1. public class TCPClient  
  2. {  
  3.   
  4.     public static void main(String[] args) throws UnknownHostException,  
  5.             IOException  
  6.     {  
  7.         Socket client = new Socket(InetAddress.getLocalHost(), 8888);  
  8.         OutputStream output = client.getOutputStream();  
  9.         InputStream input = client.getInputStream();  
  10.         DelimFramer delimFramer = new DelimFramer();  
  11.   
  12.         byte[] msg = new String("Hello").getBytes();  
  13.         // 发送消息  
  14.         delimFramer.frameMsg(msg, output);  
  15.   
  16.         // 接收消息  
  17.         byte[] receiveByte = delimFramer.nextMsg(input);  
  18.         String receiveMsg = new String(receiveByte);  
  19.   
  20.         System.out.println("Client receive msg:" + receiveMsg);  
  21.   
  22.         input.close();  
  23.         output.close();  
  24.         client.close();  
  25.   
  26.     }  
  27. }  

 

(3).TCP服务端类

 

 

  1. public class TCPServer  
  2. {  
  3.   
  4.     public static void main(String[] args) throws IOException  
  5.     {  
  6.         DelimFramer delimFramer = new DelimFramer();  
  7.   
  8.         ServerSocket server = new ServerSocket(8888);  
  9.         OutputStream output;  
  10.         InputStream input;  
  11.   
  12.         while (true)  
  13.         {  
  14.             Socket client = server.accept();  
  15.   
  16.             System.out.println("Handing client at "  
  17.                     + client.getRemoteSocketAddress());  
  18.   
  19.             output = client.getOutputStream();  
  20.             input = client.getInputStream();  
  21.   
  22.             byte[] msg = delimFramer.nextMsg(input);  
  23.   
  24.             System.out.println("Server receive msg:" + new String(msg));  
  25.   
  26.             delimFramer.frameMsg(msg, output);  
  27.         }  
  28.     }  
  29. }  

这个例子还有一个缺点,就是只考虑了“定界符”是单字节的情况,对于多字节的情况没有考虑。自己也没有找到什么好的办法,如果大家有知道的请回复一下。

 

2.使用定界符的“特殊的实现”(close( )或shutdownOutput( )方法), 解决消息边界问题:

(1)TCP客户端

 

  1. import java.io.ByteArrayOutputStream;  
  2. import java.io.IOException;  
  3. import java.io.InputStream;  
  4. import java.io.OutputStream;  
  5. import java.net.InetAddress;  
  6. import java.net.Socket;  
  7. import java.net.UnknownHostException;  
  8.   
  9. public class TestClient  
  10. {  
  11.     public static void main(String[] args) throws UnknownHostException,  
  12.             IOException  
  13.     {  
  14.         byte[] msg = new String("Hello Server!").getBytes();  
  15.   
  16.         Socket client = new Socket(InetAddress.getLocalHost(), 8888);  
  17.         OutputStream output = client.getOutputStream();  
  18.         InputStream input = client.getInputStream();  
  19.   
  20.         output.write(msg);  
  21.         output.flush();  
  22.   
  23.         client.shutdownOutput();  
  24.   
  25.         ByteArrayOutputStream byteArray = new ByteArrayOutputStream();  
  26.         int readSize = 0;  
  27.         byte[] temp = new byte[1024];  
  28.         while ((readSize = input.read(temp)) != -1)  
  29.         {  
  30.             byteArray.write(temp, 0, readSize);  
  31.         }  
  32.   
  33.         byte[] recvByte = byteArray.toByteArray();  
  34.   
  35.         System.out.println("Client receive message:" + new String(recvByte));  
  36.   
  37.         byteArray.close();  
  38.         input.close();  
  39.         output.close();  
  40.         client.close();  
  41.     }  
  42. }  


(2)TCP服务端

 

 

  1. import java.io.ByteArrayOutputStream;  
  2. import java.io.IOException;  
  3. import java.io.InputStream;  
  4. import java.io.OutputStream;  
  5. import java.net.ServerSocket;  
  6. import java.net.Socket;  
  7.   
  8. public class TestServer  
  9. {  
  10.     public static void main(String[] args) throws IOException  
  11.     {  
  12.         ServerSocket server = new ServerSocket(8888);  
  13.         byte[] msg = new String("Hello Client!").getBytes();  
  14.         while (true)  
  15.         {  
  16.   
  17.             Socket client = server.accept();  
  18.   
  19.             System.out.println("Handling clint at:"  
  20.                     + client.getRemoteSocketAddress());  
  21.   
  22.             InputStream input = client.getInputStream();  
  23.             OutputStream output = client.getOutputStream();  
  24.   
  25.             ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
  26.             byte[] temp = new byte[1024];  
  27.             int readSize = 0;  
  28.             while ((readSize = input.read(temp)) != -1)  
  29.             {  
  30.                 byteArrayOut.write(temp, 0, readSize);  
  31.             }  
  32.             byte[] recvByte = byteArrayOut.toByteArray();  
  33.             System.out  
  34.                     .println("Server receive message:" + new String(recvByte));  
  35.   
  36.             output.write(recvByte);  
  37.             output.flush();  
  38.             client.shutdownOutput();  
  39.   
  40.             output.close();  
  41.             input.close();  
  42.         }  
  43.     }  
  44. }  

   注意:

   使用该方法 适用于 客户端与服务端的两次握手通信,一般能够瞒住大部分业务逻辑需求。两次握手通信为:客户端发送消息 服务端接收、服务端发送消息 客户端接收;

   如果要实现多次握手通信,请使用 “自定义定界符” 方式实现。

 

二. 显示长度的实现例子

   前面已经说过,使用 “显示长度” 的方式必须要知道 “消息长度的上限”,所以我们可以使用DataInputStream类来读取消息长度,它提供了两个方法,分别为:

   DataInputStream.readUnsignedByte( ): 读取此输入流的下一个字节并返回”无符号 8 位数“, 所以它的取值范围为:0 ~ 255 (2^8-1) , 所以, 消息长度上限为: 255;

   DataInputStream.readUnsignedShort():读取此输入流的下两个字节并返回” 一个无符号 16 位整数“ , 所以它的取值范围为:0 ~ 65535 (2^16-1), 所以, 消息长度上限为: 65535;

  (1). 处理定界符的消息类:

  

  1. public class LengthFramer implements Framer  
  2. {  
  3.   
  4.     public static final int MAXMESSAGELENGTH = 65535;  
  5.   
  6.     public static final int BYTEMASK = 255;  
  7.   
  8.     public static final int SHORTMASK = 65535;  
  9.   
  10.     public static final int BYTESHTFT = 8;  
  11.   
  12.     @Override  
  13.     public void frameMsg(byte[] message, OutputStream output)  
  14.             throws IOException  
  15.     {  
  16.         /** 
  17.          * 这里的接收端接收的消息长度上限为65535个byte,所以这里必须判断发送消息的长度上限。 如果超出消息长度上限,超出的部分会被忽略 
  18.          */  
  19.         if (message.length > MAXMESSAGELENGTH)  
  20.         {  
  21.             throw new IOException("message to long");  
  22.         }  
  23.         // 这里使用了Java中的移位运算与位运算,将发送的消息长度拆分为2个字节并发送(readUnsignedShort()方法:读取输入流的下两个字节,所以这里必须将消息长度拆分为2个字节发送)  
  24.         output.write((message.length >> BYTESHTFT) & BYTEMASK);  
  25.         output.write(message.length & BYTEMASK);  
  26.   
  27.         output.write(message);  
  28.         output.flush();  
  29.     }  
  30.   
  31.     @Override  
  32.     public byte[] nextMsg(InputStream input) throws IOException  
  33.     {  
  34.         int length;  
  35.         DataInputStream dataInput;  
  36.         try  
  37.         {  
  38.             /** 
  39.              * 使用readUnsignedShort()返回的最大值为65535,所以接收msg数组的长度最大为65535,所以, 
  40.              * 接收消息长度的上限为65535个字节 
  41.              */  
  42.             dataInput = new DataInputStream(input);  
  43.             length = dataInput.readUnsignedShort();  
  44.         }  
  45.         catch (EOFException e)  
  46.         {  
  47.             return null;  
  48.         }  
  49.         byte[] msg = new byte[length];  
  50.         dataInput.readFully(msg);  
  51.         return msg;  
  52.     }  
  53. }    

 

   注意:

   使用 “显示长度” 的方式 处理消息边界有一个弊端,就是必须要知道消息长度上限。但是,在实际应用中,我们发送的消息长度往往都在不经意间超出了消息长度,如果不处理妥当

 

   这时候就会造成消息的丢失,所以,这个方法也不实用,大概了解一下吧。

分享到:
评论

相关推荐

    Java+TCP%2FIP+Socket+编程+-+v1.01

    Java TCP/IP Socket编程是Java网络编程中的重要组成部分,它允许应用程序通过网络进行通信。TCP(传输控制协议)和UDP(用户数据报协议)是互联网上两种主要的传输层协议,它们各自具有不同的特点和用途。 TCP是一...

    Socket实现tcp编程

    需要注意的是,TCP是流式协议,没有消息边界,所以通常需要自定义数据格式或使用缓冲区来处理数据。 为了提高程序的健壮性和可维护性,可以使用异步I/O或者多线程/多进程模型来处理并发连接。例如,服务器可以为每...

    Socket连接-TCP测试

    值得注意的是,TCP是面向字节流的,这意味着数据可能会被分片或重组,因此在传输过程中可能需要考虑数据的边界问题。在图片传输时,通常会先发送图片的元信息(如文件长度),以便接收端能正确地拼接字节流。 为了...

    C++写的服务器与Java写的客户端通过Socket通信java代码

    为了实现跨语言的Socket通信,C++服务器和Java客户端需要遵守相同的协议,包括数据格式(如ASCII或二进制)、消息边界(如使用特定分隔符)等。这样,双方才能正确解析接收到的数据。 总结起来,C++和Java通过...

    采用TCP SOCKET技术编写C/S模式的java聊天程序

    本教程将深入讲解如何利用TCP SOCKET技术在Java中编写一个C/S(Client/Server)模式的聊天程序,该程序具备群聊和私聊功能,并能处理多个并发连接以及正确识别消息边界。 首先,TCP(Transmission Control Protocol...

    tcp传输,Java实现

    在Java中,我们主要使用`java.net.Socket`和`java.net.ServerSocket`类来实现TCP通信。服务器端通常会创建一个`ServerSocket`监听特定的端口,等待客户端的连接请求。一旦有客户端连接,服务器端就可以通过`accept()...

    c++--java,socket.zip

    在TCP/IP协议栈中,Socket接口用于实现TCP和UDP两种传输层协议,其中TCP提供面向连接的、可靠的数据传输服务,而UDP则是无连接的、不可靠的数据传输服务。在这个项目中,我们主要关注的是基于TCP的Socket通信。 C++...

    Socket通信,通过异步,解决粘包问题

    在TCP/IP协议栈中,Socket接口提供了低级别的网络通信抽象,让开发者能够构建跨平台的网络应用程序。本主题主要探讨如何利用异步机制来解决Socket通信中的“粘包”问题。 “粘包”问题在Socket通信中时常出现,特别...

    socket编程定长处理数据

    TCP/IP协议中的TCP段就是一个典型的变长数据例子,其长度由头部的字段决定,可以动态变化。 在Socket编程中处理定长数据通常涉及以下步骤: 1. 发送方将数据填充到预设长度的缓冲区中,不足的部分可能用填充字符...

    tcp socket server

    这些语言都提供了丰富的库支持,例如在Python中,我们可以使用内置的`socket`模块轻松创建TCP服务器。 总的来说,理解TCP套接字服务器的工作原理对于网络编程至关重要。它涉及到网络通信的基础知识,包括IP地址、...

    C#与Android Socket通信

    Socket通信是一种基于TCP/IP协议族的网络通信方式,它允许两个网络应用程序通过网络进行数据交换。在本场景中,我们关注的是C#作为服务器端,Android作为客户端的Socket通信实现。这种通信方式常用于移动应用与后台...

    关于socket的封装使用

    Socket是网络编程中的一个重要概念,它是基于TCP/IP通信协议实现的一种通信方式,允许两台计算机通过互联网进行双向通信。在Android开发中,我们经常会用到Socket进行客户端与服务器端的数据交换。下面将详细介绍...

    windows下socket编程上机报告

    Windows操作系统提供了一套API,即Winsock(Windows Sockets),用于支持TCP/IP协议栈,使得开发者可以创建能够接收和发送数据的网络应用程序。 首先,我们需要了解Socket的基本概念。Socket分为两种类型:流式...

    使用 socket 进行简单聊天 demo

    这个协议可能是自定义的,例如,每条消息前加上特定的标识符,以便区分消息的边界。在实际应用中,还可以考虑使用JSON、XML等结构化格式来包装消息,便于解析和处理。 在描述中提到,移动端和服务端都有,这意味着...

    xianchengchi.zip_Socket 线程池_socket池_线程池_线程池socket

    在Java等编程语言中,Socket通常被用来实现TCP/IP协议,为应用程序提供低级别的网络通信服务。 线程池是多线程编程中的一个设计模式,它维护着一组可以复用的线程,而不是每次需要时就创建新的线程。这种方式减少了...

    UNIX下socket编程

    由于TCP数据以字节流的形式传输,没有固定的边界,因此客户端需要循环读取,直到没有数据可读或发生错误。例如,代码中第19-26行展示了如何读取并显示服务器发送的数据。 4. 关闭连接:客户端通常在读取完所有数据...

    Socket_tcp多事务处理程序框架.pdf

    综上所述,构建一个多事务处理的Socket_TCP程序框架,不仅需要深入理解TCP/IP协议栈的工作原理,还需要掌握应用层协议的设计,以及多线程/多进程编程技术,以确保系统的高并发、高可用性和高性能。

    Socket案例源码

    首先,Socket是基于TCP/IP协议族的通信接口,它允许应用程序通过网络发送和接收数据。在Java中,Socket类代表了服务器和客户端之间的连接,InputStream和OutputStream则分别用于读写数据。本案例中,我们可能会看到...

    android Socket实现简单聊天功能以及文件传输

    在Java中,Socket和ServerSocket是进行TCP/IP通信的核心类。ServerSocket在服务器端运行,监听特定端口,等待客户端的连接请求;而Socket则在客户端运行,发起连接请求并与服务器建立连接。 在实现聊天功能时,我们...

Global site tag (gtag.js) - Google Analytics