2

这些天我对使用 java 套接字时的 Tcp 性能感到困惑。其实java代码很简单。详情如下:

  1. 服务器打开一个端口并开始监听。
  2. 客户端请求并连接到服务器后,客户端开始写入套接字。
  3. 服务器收到请求后,它会打开一个新线程来处理这个连接。(此连接为长连接,不会超时)。
  4. 服务器将继续读取,直到它得到结束分隔符,然后给客户端一个响应并继续再次读取。
  5. 客户端收到响应后,将再次发送另一个请求。

我发现如果客户端一次写整个消息(包括结束分隔符),通信速度令人满意,速度可以达到每分钟50000条消息。但是,如果客户端分次将字节写入socket,速度会迅速下降,几乎每分钟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");
    }

如果我使用“FORMAT 1”发送消息,速度可以达到每分钟 50000 条消息,但如果使用“FORMAT 2”,速度会下降到每分钟 1400 条消息。谁说清楚原因?

我试图尽可能详细地描述,任何帮助将不胜感激。

4

1 回答 1

7

快速连续多次对套接字进行非常短的写入,然后进行读取可能会触发Nagle 算法TCP 延迟确认之间的不良交互;即使您禁用 Nagle 算法,您也会导致每个单独的写入调用都发送整个数据包(无论写入是一个字节还是一千字节,都会产生 40 多个字节的开销)。

将 a 包裹BufferedOutputStream在套接字的输出流周围应该会给您带来类似于“FORMAT 1”的性能(正是因为它将内容保存在字节数组中,直到它被填充或被刷新)。

正如John Nagle 在 Slashdot 上解释的那样

用户级解决方案是避免套接字上的写-写-读序列。写-读-写-读很好。写写写很好。但是写-写-读是一个杀手。因此,如果可以的话,缓冲您对 TCP 的少量写入并一次性发送它们。

于 2013-07-17T03:34:50.473 回答