性能问题。使用java从套接字读取字节时速度太慢

时间:2013-07-17 03:15:35

标签: java performance sockets serversocket

这些天我在使用Java套接字时对Tcp性能感到困惑。实际上java代码非常简单。详情如下:

  1. 服务器打开一个端口并开始监听。
  2. 客户端请求,连接到服务器后,客户端开始写入socket。
  3. 服务器收到请求后,将打开一个新线程来处理此连接。 (此连接是一个长时间连接,不会超时)。
  4. 服务器将继续读取,直到它到达结束分隔符,然后给客户端回复并继续继续阅读。
  5. 客户收到回复后,会再次发送另一个请求。
  6. 我发现客户端一次写入整个消息(包括结束分隔符),通信速度是否令人满意,速度可以达到每分钟50000条消息。但是,如果客户端在不同的时间内将字节写入套接字,则速度会快速下降,每分钟只有1400条消息,这是原始速度的1/40倍。我很困惑。任何人都可以帮我一臂之力?任何意见表示赞赏!

    我的模拟服务器端如下:

    public class ServerForHelp {
    
        final static int BUFSIZE = 10240;
        Socket socket;
        String delimiter = "" + (char) 28 + (char) 13;
    
        public static void main(String[] args) throws IOException {
    
                ServerSocket ss = new ServerSocket(9200);
                System.out.println("begin to accept...");
                while (true) {
                    Socket s = ss.accept();
                    Thread t = new Thread(new SocketThread1(s));
                    t.start();
                }
        }
    
        public String readUntilDelimiter() throws Exception {
            StringBuffer stringBuf = new StringBuffer();
            InputStream stream = socket.getInputStream();
            InputStreamReader reader = null;
            reader = new InputStreamReader(stream);
    
            char[] buf = new char[BUFSIZE];
    
            while (true) {
                int n = -1;
                    n = reader.read(buf, 0, BUFSIZE);
                if (n == -1) {
                    return null;  // it means the client has closed the connection, so return null.
                } else if (n == 0) {
                    continue; // continue to read the data until got the delimiter from the socket.
                }
    
                stringBuf.append(buf, 0, n);
                String s = stringBuf.toString();
    
                int delimPos = s.indexOf(delimiter);
                if (delimPos >= 0) {
                    // found the delimiter; return prefix of s up to separator and
                    // To make the thing simple, I have discarded the content after the delimiter.
                    String result = s.substring(0, delimPos);
                    sendTheResponse(socket);
                    return result;
                }
            }
        }
    
        private void sendTheResponse(Socket socket) throws IOException {
            Writer writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            writer.write("Hi, From server response");
            writer.flush();
        }
    
    }
    
    class SocketThread1 implements Runnable {
    
        Socket socket;
    
        public SocketThread1(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
            ServerForHelp server = new ServerForHelp();
            server.socket = socket;
            while (true) {
                try {
                    if (server.readUntilDelimiter() == null) // it means that the client has closed the connection, exist
                        break;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    

    这是一个正常的套接字编程。

    以下是我的客户方:

    public void execute() throws Exception{
    
            int msgCnt = 0;
            Socket socket = null;
            byte[] bufBytes = new byte[512];
            long start = 0;
            final char START_MESSAGE = 0x0B;
            final char END_MESSAGE = 0x1C;
            final char END_OF_RECORD = 0x0D;//\r
            String MESSAGE = "HELLO, TEST";
            socket = new Socket("192.168.81.39", 9200);
            OutputStream os = socket.getOutputStream();
            InputStream is = socket.getInputStream();
    
            while (System.currentTimeMillis() - start < 60000)
            {
    
                // If you send the total message at one time, the speed will be improved significantly  
    
                // FORMAT 1
                StringBuffer buf = new StringBuffer();
                buf.append(START_MESSAGE);
                buf.append(MESSAGE);
                buf.append(END_MESSAGE);
                buf.append(END_OF_RECORD);
                os.write(buf.toString().getBytes());
                // FORMAT 1 END
    
                //FORMAT 2
    //        os.write(START_MESSAGE);
    //        os.write(MESSAGES[port].getBytes());
    //        os.write(END_MESSAGE);
    //        os.write(END_OF_RECORD);
                //FORMAT 2 END
                os.flush();
                is.read(bufBytes);
                msgCnt++;
    
                System.out.println(msgCnt);
            }
            System.out.println( msgCnt + " messages per minute");
        }
    

    如果我使用“格式1”,要发送消息,速度可以达到每分钟50000条消息,但如果使用“格式2”,速度将降至每分钟1400条消息。谁清楚原因?

    我试图尽可能详细地描述,任何帮助都会受到很多赞赏。

1 个答案:

答案 0 :(得分:8)

快速连续多次向套接字写入非常短的写入然后读取可能会触发Nagle's algorithmTCP delayed acknowledgment之间的错误交互;即使您禁用Nagle算法,也会导致每个写入调用发送一个完整的数据包(开销超过40个字节,无论写入是一个字节还是一千个)。

在套接字的输出流周围包裹BufferedOutputStream应该会提供类似于“格式1”的性能(正是因为它在字节数组中保存,直到填充或刷新为止。)

作为John Nagle explained on Slashdot

  

用户级解决方案是避免套接字上的写 - 读 - 读序列。 write-read-write-read很好。写 - 写 - 写得很好。但写 - 写 - 读是一个杀手。所以,如果可以的话,将你的小写入缓冲到TCP并立即发送它们。