【TCP】Recv-Q和Send-Q 不要在有什么误解了

1,169 阅读4分钟

1 LISTEN状态:表示队列中的连接数量,非LISTEN状态为字节数量;

非 LISTEN 状态

  • Recv-Q 表示 receive queue 中的 bytes 数量;
  • Send-Q 表示 send queue 中的 bytes 数值。

2 Recv-Q 正在等待accept的连接数,Send-Q表示全连接队列容量

表示的当前等待服务端调用 accept 来三次握手的 listen backlog 数值,即图中的全连接队列值(最大值为Send_Q+1),当客户端通过connect() 去连接正在 listen() 的服务端时,这些连接会经过半连接队列,接收到ACK后,进入accept queue(全连接队列) ,在里面等待直到被服务端 accept();

Send-Q 表示的则是最大的 listen backlog 数值,这就是 min(backlog, /proc/sys/net/core/somaxconn)的值

3 详细过程如下图

4 我们实际操作下:观察Recv-Q 的变化

启动服务端执行命令:(注意:在****连接事件发生后|accept操作执行前会睡眠Thread.sleep(100000);)

使用NIO作为服务端监听TCP端口:9779

~/recv_q_send_q# /usr/bin/java NIOServer

NOIServer start run in port 9779

可以看到默认Send-Q值为50;

如果并发请求的链接数超多50,多余的怎样?感兴趣的可以自己修改代码测试mark

如果等待时间过长客户端也会收到服务端的拒绝反馈,正常情况下如果超出的连接数多不并且等待时间不多,可以得到服务端的正常响应

启动客户端执行命令:

/usr/bin/java NIOClient

等待100秒后:

5 这就解释了Recv-Q 在服务端debug时为啥会阻断后面的链接接入,因为全连接队列满了,后面的链接只能等待

参考NIO代码:

server端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {

    private int num;

    private static final int BLOCK = 4096;

    private static final ByteBuffer sendB = ByteBuffer.allocate(BLOCK);

    private static final ByteBuffer receB = ByteBuffer.allocate(BLOCK);

    private Selector selector;

    public NIOServer(int port) throws IOException {
        //开启ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //获取ServerSocket
        ServerSocket serverSocket = serverSocketChannel.socket();
        //绑定ServerSocket提供服务的端口
        serverSocket.bind(new InetSocketAddress(port));
        //开启选择器
        selector = Selector.open();
        //将ServerSocketChannel注册到选择器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NOIServer start run in port " + port);
    }

    /**
     * 监听选择器的数据
     *
     * @throws IOException
     */
    private void listen() throws IOException {
        //循环监听,事件驱动模式
        while (true) {
            //select()阻塞,等待有事件发生时唤醒
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //处理完后移除该事件
                iterator.remove();
                //处理该事件
                handleKey(selectionKey);

            }
        }
    }

    /**
     * 处理选择器的监听事件
     *
     * @param selectionKey 选择器的监听事件key
     * @throws IOException
     */
    private void handleKey(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel serverSocketChannel = null;
        SocketChannel socketChannel = null;
        int count = 0;

        //客户端新连接
        if (selectionKey.isAcceptable()) {
            //开启通道连接
            serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            try {
                System.out.println(System.currentTimeMillis() + "收到请求我在睡眠100秒...");
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            socketChannel = serverSocketChannel.accept();
            System.out.println(System.currentTimeMillis() + "accept...");

            //设置为非阻塞
            socketChannel.configureBlocking(false);
            //将通道注册到选择器
            socketChannel.register(selector, SelectionKey.OP_READ);

        } else if (selectionKey.isReadable()) {
            //获取读事件通道
            socketChannel = (SocketChannel) selectionKey.channel();
            //清除原先读缓存
            receB.clear();
            //读取通道缓存
            count = socketChannel.read(receB);
            if (count > 0) {
                //解析通道缓存数据
                String receMsg = new String(receB.array(), 0, count);
                System.out.println("receive from client " + receMsg);
                //注册切到写事件
                socketChannel.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            //获取写事件通道
            socketChannel = (SocketChannel) selectionKey.channel();
            //清除发送缓存数据
            sendB.clear();
            String sendMsg = "num " + num++;
            //设置待发送的数据
            sendB.put(sendMsg.getBytes());
            //准备写
            sendB.flip();
            int write = socketChannel.write(sendB);
            System.out.println("send to client " + sendMsg);
            //注册切到读事件
            socketChannel.register(selector, SelectionKey.OP_READ);
        }
    }

    public static void main(String[] args) throws Exception {
        new NIOServer(9779).listen();
    }
}
客户端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOClient {

    private static final int BLOCK = 4096;

    private static final ByteBuffer sendB = ByteBuffer.allocate(BLOCK);

    private static final ByteBuffer receB = ByteBuffer.allocate(BLOCK);

    private SocketChannel socketChannel;

    private Selector selector;

    public NIOClient(String ip, int port) throws IOException {
        //开启通道
        socketChannel = SocketChannel.open();
        //设置为非阻塞
        socketChannel.configureBlocking(false);
        //开启选择器
        selector = Selector.open();
        //将通道注册到选择器
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        //连接服务端
        socketChannel.connect(new InetSocketAddress(ip, port));

    }

    /**
     * 连接服务器
     */
    public void connect() throws IOException {
        Set<SelectionKey> selectionKeys;
        Iterator<SelectionKey> iterator;
        SelectionKey selectionKey;
        int index = 0;

        while (true && index < 1000) {
            index++;
            selector.select();
            selectionKeys = selector.selectedKeys();
            iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();
                handleKey(selectionKey, index);
            }
            selectionKeys.clear();
        }
        //交互10次后关闭连接
        socketChannel.close();
    }

    /**
     * 处理选择器监听事件
     *
     * @param selectionKey
     */
    public void handleKey(SelectionKey selectionKey, int index) throws IOException {
        SocketChannel client;
        int count = 0;
        // 连接事件
        if (selectionKey.isConnectable()) {
            System.out.println("client connect......");
            client = (SocketChannel) selectionKey.channel();
            if (client.isConnectionPending()) {
                client.finishConnect();
                sendB.clear();
                sendB.put("Hello, Server".getBytes());
                sendB.flip();
                client.write(sendB);
            }
            client.register(selector, SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) {
            // 读事件
            client = (SocketChannel) selectionKey.channel();
            receB.clear();
            count = client.read(receB);
            if (count > 0) {
                String receMsg = new String(receB.array(), 0, count);
                System.out.println("receive from server " + receMsg);
                client.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            // 给客户端注册写事件
            client = (SocketChannel) selectionKey.channel();
            sendB.clear();
            String sendMsg = "index " + index;
            sendB.put(sendMsg.getBytes());
            sendB.flip();
            client.write(sendB);
            System.out.println("send to server " + sendMsg);
            client.register(selector, SelectionKey.OP_READ);
        }
    }

    // 模拟10个请求,并发10秒后同时执行connection
    public static void main(String[] args) throws Exception {
        for(int i = 0; i<10;i++) {
            final int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("========"+ finalI);
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        new NIOClient("127.0.0.1", 9779).connect();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }


        System.in.read();

    }

}