首页 > 代码库 > 使用ObjectOutputStream进行socket通信的时候出现固定读到四个字节乱码的问题
使用ObjectOutputStream进行socket通信的时候出现固定读到四个字节乱码的问题
问题描述:
最近在写一个通信相关的项目,服务器端和客户端通过socket进行通信。本来想利用read的阻塞特性,服务器端和客户端按照一定的流程进行文件读写。结果发现客户端或者服务器read方法一直都返回乱码。而且读到的一端可能是客户端,可能是服务器端,固定的读到前面有四个字节的乱码,后续读到的字节码都是正常的。
原因分析:
开始以为是流没有正常关闭。修改了代码确保正确关闭之后,发现即使重新启动服务器和客户端,还是会固定读到四个字节乱码。后面查资料分析才找出真正的原因:由于我实现的socket通信既有字符串通信,又有对象通信。所以我在传递字符串的时候,使用的是socket.getOutputStream得到的流。而在进行对象传输的时候,我在前面的输出流外面包裹了一层ObjectOutputStream。因为我是在一开始就对socket的输出流进行了包裹,而如果用ObjectOutputStream装饰输出流,默认的会自动在流前面带上四个字节的前缀。而因为开始我发消息只是发送字符串,所以我是直接使用socket的输出流。这就导致将前面的四个字节前缀发送出去,导致最终的乱码。具体参见下面相关代码:
1 /** 2 * Creates an ObjectOutputStream that writes to the specified OutputStream. 3 * This constructor writes the serialization stream header to the 4 * underlying stream; callers may wish to flush the stream immediately to 5 * ensure that constructors for receiving ObjectInputStreams will not block 6 * when reading the header. 7 * 8 * <p>If a security manager is installed, this constructor will check for 9 * the "enableSubclassImplementation" SerializablePermission when invoked10 * directly or indirectly by the constructor of a subclass which overrides11 * the ObjectOutputStream.putFields or ObjectOutputStream.writeUnshared12 * methods.13 *14 * @param out output stream to write to15 * @throws IOException if an I/O error occurs while writing stream header16 * @throws SecurityException if untrusted subclass illegally overrides17 * security-sensitive methods18 * @throws NullPointerException if <code>out</code> is <code>null</code>19 * @since 1.420 * @see ObjectOutputStream#ObjectOutputStream()21 * @see ObjectOutputStream#putFields()22 * @see ObjectInputStream#ObjectInputStream(InputStream)23 */24 public ObjectOutputStream(OutputStream out) throws IOException {25 verifySubclass();26 bout = new BlockDataOutputStream(out);27 handles = new HandleTable(10, (float) 3.00);28 subs = new ReplaceTable(10, (float) 3.00);29 enableOverride = false;30 writeStreamHeader();31 bout.setBlockDataMode(true);32 if (extendedDebugInfo) {33 debugInfoStack = new DebugTraceInfoStack();34 } else {35 debugInfoStack = null;36 } 37 }38 39 40 41 /**42 * The writeStreamHeader method is provided so subclasses can append or43 * prepend their own header to the stream. It writes the magic number and44 * version to the stream.45 *46 * @throws IOException if I/O errors occur while writing to the underlying47 * stream48 */49 protected void writeStreamHeader() throws IOException {50 bout.writeShort(STREAM_MAGIC);51 bout.writeShort(STREAM_VERSION);52 }
解决办法:
既然直接用ObjectOutputStream将原来的socket的输出流进行包裹之后会出现固定四个字节的乱码,那么可以考虑用原来的socket输出流进行写数据的时候,接收方固定丢弃四个字节乱码。这样虽然可以实现,但是总感觉很别扭。最终我优化了相关的读写对象方法,只是用原来socket的输出流进行对象读写,具体代码实现如下:
1 public <T> void writeObj(T obj) throws ZSocketException { 2 if (obj == null) { 3 return; 4 } 5 try(ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); 6 ObjectOutputStream objOut = new ObjectOutputStream(byteOut)) { // 这个只是为了计算出对象大小而用的中介输出流 7 objOut.writeObject(obj); 8 byte[] ObjByte = byteOut.toByteArray(); 9 Header header = new Header(StringMsgType.OBJECT, ObjByte.length);10 HeaderAnalyser analyser = new HeaderAnalyser();11 12 // 先写消息头,再写消息内容13 output.write(analyser.formatHeader(header), 0, Constants.HEADER_LEN);14 output.write(ObjByte, 0, ObjByte.length);15 output.flush();16 17 } catch (IOException e) {18 throw new ZSocketException(e);19 }20 }21 22 public <T> T readObj(long len, Class<T> clazz) throws ZSocketException {23 if (len < 0 || clazz == null) {24 throw new IllegalArgumentException("Negative read length or null object class!");25 }26 27 try(ByteArrayOutputStream out = new ByteArrayOutputStream(Constants.BUFF_SIZE)) {28 writeData(input, out, len);29 try (ByteArrayInputStream byteIn = new ByteArrayInputStream(out.toByteArray());30 ObjectInputStream objIn = new ObjectInputStream(byteIn)) {31 @SuppressWarnings("unchecked")32 T result = (T) objIn.readObject();33 return result;34 }35 } catch (Exception e) {36 throw new ZSocketException(e);37 }38 39 }
使用ObjectOutputStream进行socket通信的时候出现固定读到四个字节乱码的问题