2

最近、Java ソケットを使用しているときに Tcp のパフォーマンスについて混乱しています。実際、Java コードは非常に単純です。以下のように詳細:

  1. サーバーはポートを開き、リッスンを開始します。
  2. クライアント要求とサーバーへの接続後、クライアントはソケットへの書き込みを開始します。
  3. サーバーはリクエストを受け取った後、この接続を処理するために新しいスレッドを開きます。(この接続はタイムアウトしない長い接続です)。
  4. サーバーはエンドセパレーターを取得するまで読み取りを続け、クライアントに応答を返し、再度読み取りを続けます。
  5. クライアントが応答を受け取った後、別の要求を再度送信します。

クライアントがメッセージ全体 (エンド セパレータを含む) を 1 回書き込むと、通信速度は十分に良好で、速度は毎分 50000 メッセージに達することがわかります。ただし、クライアントがバイトを別々の時間にソケットに書き込むと、速度が急速に低下し、1 分あたり約 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 のアルゴリズムを無効にしても、個々の書き込み呼び出しごとにパケット全体が送信されます (書き込みが 1 バイトであろうと 1,000 バイトであろうと、40 バイト以上のオーバーヘッドがあります)。

ソケットの出力ストリームをラップするBufferedOutputStreamと、「FORMAT 1」と同様のパフォーマンスが得られます (これは、いっぱいになるかフラッシュされるまでバイト配列に保持されるためです)。

John Nagle が Slashdot で説明したように:

ユーザーレベルの解決策は、ソケットでの書き込み-書き込み-読み取りシーケンスを避けることです。書き込み-読み取り-書き込み-読み取りは問題ありません。書き込み書き込みは問題ありません。しかし、書き込み-書き込み-読み取りはキラーです。したがって、可能であれば、小さな書き込みを TCP にバッファリングして、一度に送信してください。

于 2013-07-17T03:34:50.473 に答える