`

基于事件的NIO多线程服务器的问题

阅读更多

        最近看了很好的一篇博客:http://www.ibm.com/developerworks/cn/java/l-niosvr/

         于是我按照作者给的源码做实验(源码以上传到附件中NIOServer.jar),我模拟发送1000次消息,服务器做出一千次响应。

public static void main(String args[]) {
        Socket client = null;
        DataOutputStream out = null;
        DataInputStream in = null;
        try {
        	for(int i=0;i<CLIENT_NUM;i++){
        		client = new Socket("10.13.30.160", 5100);
                client.setSoTimeout(10000);
                out = new DataOutputStream( (client.getOutputStream()));
                String query = "GB";
                byte[] request = query.getBytes();
                out.write(request);
                out.flush();
                client.shutdownOutput();
                in = new DataInputStream(client.getInputStream());
                byte[] reply = new byte[40];
                in.read(reply);
               System.out.println("Time: " + new String(reply, "GBK"));
                in.close();
                out.close();
                client.close();
        	}
        }
        catch (Exception e) {
        	e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

      但是在运行过程中抛出了异常:

java.io.IOException: 您的主机中的软件放弃了一个已建立的连接。

    经过我调试代码,发现是在Response类的send(byte[] data)中SocketChannel调用write(byte[])的时候出错了。

 

        我在上面的测试代码的System.out.println("Time: " + new String(reply, "GBK"));之后让其Thread.sleep(200);一会儿发现就不会出现上面的放弃建立连接的异常了。

        我在想难道是服务器发送数据的时候,客户端已经关闭了?所以我们让客户端在关闭之前Thread.sleep(200);延迟一会儿等服务器发消息发完?

我查了一下Connect reset的原因,具体可以参考:http://wjy320.iteye.com/blog/2069518

 

        应该可以断定是服务端发送数据的时候,客户端已经关闭了(调用了close());但是客户端中的  in.read(reply)应该会阻塞呀,会一直等到服务端发来的数据后才返回呀,难道是in.read(byte[])是非阻塞的?于是好奇心促使我看了一下DataInputStream和InputStream的read(byte[])的源码。

        

//DataInputStream的read(byte[])源码
public final int read(byte b[]) throws IOException {
        return in.read(b, 0, b.length);
    }
//其中调用了InputStream的read(byte b[], int off, int len);InputStream的read(byte b[], int off, int len)的源代码如下:
public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

 我稍微对InputStream的read(...)进行一下说明:

     从所包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组 b 中。以整数形式返回实际读取的字节数。在输入数据可用、检测到文件末尾 (end of file) 或抛出异常之前,此方法将一直阻塞。
     如果 b 为 null,则抛出 NullPointerException。如果 b 的长度为零,则不读取字节并返回 0;否则试图读取至少一个字节。如果因为该流在文件末尾而无字节可用,则返回值 -1;否则至少读取一个字节并将其存储到 b 中。
     将读取的第一个字节存储到元素 b[0] 中,将下一个字节存储到 b[1] 中,依此类推。读取的字节数至多等于 b 的长度。设 k 为实际读取的字节数;这些字节将存储在从 b[0] 到 b[k-1] 的元素中,b[k] 到 b[b.length-1] 的元素不受影响。
     如果因为文件末尾以外的其他原因而无法读取第一个字节,则抛出 IOException。尤其在输入流已关闭的情况下,将抛出 IOException。

     顺便说一句,read()每次读一个byte。

 

 由此可见read(...)返回的原因有三个:

      1:给定的参数不合法。

      2:读到了文件末尾(-1) ps:一方关闭也会发送-1。

      3:缓冲区(就是那个byte[])被写满。

 

所以应该不是read(byte[])非阻塞的原因。

      看到第三条,我恍然大悟。难道是我的缓冲区开辟小了,我只开辟了40个byte;而服务器返回的信息是:“Time: 2014年5月22日 星期四 上午11时24分58秒 CST”。长度却是41个byte。所以每次read(...)返回的原因都是缓冲区被写满,而不是服务端发送完毕的文件结尾。那么第二次读的时候操作系统的缓冲区中本身就有一个byte的数据。所以在第二次读数据的时候可能服务端还没有发送完毕,但是客户端因为缓冲区写满就已经关闭了。所以服务器端会报出“放弃一个已经建立的连接”的异常。

     所以客户端每条显示少了最后一个"T",但是肯定能正常显示,不会报异常。而服务器有时候会报出以上异常,跟时机有关。

       所以解决办法就是将客户端的缓冲区开辟大一些就行了,开辟41个byte就行了,服务端就不会报错了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics