关闭

JAXB+Socket的一个Bug(或者算一个Feature吧)

1372人阅读 评论(0) 收藏 举报

把Socket和JAXB结合起来用的时候会有问题: 当我们让JAXB 去 unmarshal  InputStream或者marshal outputStream的时候,它会自动close掉InputStream,这样Socket就会处于半关闭状态,而Socket处于这种状态会自动关闭,这样用Socket只能单向通讯一次就结束了。一般的原则是,谁打开了stream,谁就应该关闭它,但是JAXB底层使用的是JAXP特别是SAX,而SAX让parser负责关闭流(原因请参见后附的在Kohsuke Kawaguchi's Blog摘抄下来的英文说明),所以使用完JAXB的操作后,socket就会莫名的关闭了。按照Kohsuke Kawaguchi所说的我重新改了代码,写了NoCloseInputStream和NoCloseOutputStream,他们复写了父类中的close():

 

package edu.jlu.fuliang.net;

import java.io.FilterInputStream;
import java.io.InputStream;

public class NoCloseInputStream extends FilterInputStream {
    
public NoCloseInputStream(InputStream in) {
        
super(in);
    }

    
    
public void close() {} // ignore close
}


package edu.jlu.fuliang.net;

import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class NoCloseOutputStream extends FilterOutputStream {
    
public NoCloseOutputStream(OutputStream out) {
        
super(out);
    }

    
    
public void close() {} // ignore close
}

这样就不会关闭Stream了,从而也不会关闭Socket了:

Server端:
//Write
CatalogTypeUtils.buildXml(catalog,new NoCloseOutputStream(socket.getOutputStream()));
socket.shutdownOutput();
//Read
catalog = CatalogTypeUtils.buildObject(new NoCloseInputStream(socket.getInputStream()));
System.out.println(
"Server: " + catalog.getPublisher());
Client端:
/*Read*/
CatalogType catalog 
= CatalogTypeUtils.buildObject(new NoCloseInputStream(socket.getInputStream()));
System.out.println(
"Client:" + catalog.getPublisher());
/*Write*/
CatalogTypeUtils.buildXml(catalog, 
new NoCloseOutputStream(socket.getOutputStream()));
socket.shutdownOutput();


他们最后通过关闭Socket来关闭流:
socket.close();

package edu.jlu.fuliang.test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import edu.jlu.fuliang.net.NoCloseInputStream;



public class CatalogTypeUtils {
    
private static JAXBContext jaxbContext = null;
    
private static Marshaller marshaller = null;
    
private static ObjectFactory factory = null;

    
public static void buildXml(CatalogType catalogType, OutputStream output) {

        
try {
            jaxbContext 
= JAXBContext.newInstance("edu.jlu.fuliang.test");
            marshaller 
= jaxbContext.createMarshaller();
            ObjectFactory factory 
= new ObjectFactory();
            JAXBElement
<CatalogType> catalogElement = factory.createCatalog(catalogType);
            marshaller.marshal(catalogElement,output);
        }
 catch (JAXBException e) {
            e.printStackTrace();
        }

    }


    
public static CatalogType buildObject(InputStream input) {
        JAXBElement
<CatalogType> catalogElement = null;
        
try {
            JAXBContext jaxbContext 
= JAXBContext.newInstance("edu.jlu.fuliang.test");
            Unmarshaller unMarshaller 
= jaxbContext.createUnmarshaller();
            catalogElement 
= (JAXBElement<CatalogType>) unMarshaller.unmarshal(input);
        }
 catch (Exception e) {
            e.printStackTrace();
        }

        
return catalogElement.getValue();
    }

}

Kohsuke Kawaguchi's Blog:

How did I write "Socket + XML = pitfall"?

Posted by kohsuke on July 22, 2005 at 11:40 AM | Permalink | Comments (1)

Well, I kind of know the XML code inside out ;-), so I knew beforehand that a SAX parser closes the stream it reads. So I just mostly wrote a simple program to confirm the socket behavior. Also, I think I know about TCP probably more than average developers. That probably have helped, too.

Mostly I just verified that calling socket.getInputStream().close() brings down the whole connection. This you can quickly check by attempting to socket.getOutputStream().write(0); afterward. While no authoritative document was found on this issue, there's overwhelming side evidence to back up this hypothesis; for example, JDK added the partial close support only in 1.3. That must mean that any socket method that pre-date JDK 1.3 cannot possibly do partial close (or else why they needed to add it later?)

Ethereal could have been used. When I was studying TCP I used it a lot. But in this case, since I was just interested in if the connection was still alive, netstat would have been just fine.

I think most of the times I do tricky trouble-shooting by tracing into JDK and seeing the code in action by myself using a debugger. One thing I hate is that JDK core class libraries are compiled without the debug info. This is also done for a reason (to reduce the size of JRE), but one of those days I'll recompile everything in src.zip to create my custom rt.jar with full debug info!

Socket + XML = pitfall

Posted by kohsuke on July 15, 2005 at 12:04 PM | Permalink | Comments (10)

Yesterday, one of the JAXB users sent me an e-mail, asking for how to solve the problem he faced.

The scenario was like this; you have a client and a server, and you want a client to send an XML document to a server (through a good ol' TCP socket), then a server sends back an XML document. A very simple use case that should just work.

The problem he had is that unless the client sends the "EOS" (end of stream) signal to the server, the server keeps blocked. When he modified his code to send EOS by partial-closing the TCP socket (Socket.shutdownOutput), the server somehow won't be able to send back the response saying the socket is closed.

What's Happening?

So, what's happening and who's fault is this?

When you tell JAXB to unmarshal from InputStream, it uses JAXP behind the scene, in particular SAX, for parsing the document. Normally in Java, the code who opened the stream is responsible for closing it, but SAX says a parser is responsible for closing a stream.

Call it a bug or a feature, but this is done for a reason. People often assume that a parser only reads a stream until it hits the end tag for the root element, but the XML spec actually requires a parser to read the stream until it hits EOS. This is because a parser needs to report an error if anything other than comment, PI, or whitespace shows up. Given that, I'd imagine SAX developers thought "well, if a parser needs to read until EOS, why not have a parser close it? after all, the only thing you can do with a fully read stream is to close it!" In a sense, it makes sense. In any case, it's too late to change now.

So, the net effect is that when you pass in an InputStream from Socket.getInputStream to a JAXB unmarshaller, the underlying SAX parser will call the InputStream.close automatically.

Now, what happens when a socket InputStream is closed? The JDK javadoc really doesn't seem to say definitively, but a little exeriment reveals that it actually fully closes the TCP session. That explains why our guy couldn't write back the response --- by the time he read an input, his socket was already closed!

This seems like a surprising behavior. It would have been better if closing a stream only closes a socket partially in that direction, and you would need to close both InputStream and OutputStream to fully shutdown a socket. It would have made a lot of sense. I guess the reason why it's not done this way is because of the backward compatibility. The Socket class was there since the very first JDK 1.0, but the notion of partial close is only added in JDK 1.3. JDK 1.3 of course couldn't change the behavior of earlier JDKs, no matter how undesirable it is.

By putting those two behaviors, we now know what has happened. At server, a SAX parser who was reading a request is terminating a connection too prematurely.

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:280856次
    • 积分:3897
    • 等级:
    • 排名:第8139名
    • 原创:108篇
    • 转载:25篇
    • 译文:0篇
    • 评论:69条
    最新评论
    文章收藏