Introducción a los ejemplos de Netty y HelloWorld

¡Todo debería comenzar desde el modelo de E / S de red!

Modelo IO: BIO / NIO / Netty

BIO (Bloqueo de E / S): Bloqueo de E / S

La primera API de Java (java.net) proporcionaba la llamada función de bloqueo proporcionada por la biblioteca de sockets del sistema local. El código de muestra es el siguiente:

ServerSocket serverSocket = new ServerSocket(portNumber);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
while ((request = in.readLine()) != null) {
    if ("Done".equals(request)) {
        break;
}
response = processRequest(request);
out.println(response);
}

Este fragmento de código solo podrá manejar una conexión al mismo tiempo. Para administrar varios clientes simultáneos, debe

Socket crea un nuevo hilo, el modelo de hilo se muestra en la siguiente figura:

 

Este modelo tiene los siguientes dos problemas:

1. Puede haber una gran cantidad de subprocesos durmiendo en cualquier momento, esperando que los datos de entrada o salida estén listos, lo que puede considerarse una pérdida de recursos.

2. Necesidad de asignar memoria para la pila de llamadas de cada hilo.

3. Aunque la máquina virtual Java (JVM) puede soportar físicamente una gran cantidad de subprocesos, la sobrecarga causada por el cambio de contexto causará problemas mucho antes de que se alcance el límite.

NIO (E / S sin bloqueo): E / S sin bloqueo

La función NIO de Java se introdujo en JDK 1.4 y su estructura es la siguiente:

En esta figura se puede ver que Selector es la clave para la implementación de E / S sin bloqueo de Java. Utiliza la API de notificación de eventos.

Para determinar cuáles en un conjunto de sockets sin bloqueo están listos para operaciones relacionadas con E / S. Porque puede comprobar el estado de finalización de cualquier operación de lectura o escritura en cualquier momento. Bajo este modelo, un solo hilo puede manejar múltiples conexiones concurrentes.

Comparado con BIO, este modelo tiene las siguientes características:

1. Puede manejar muchas conexiones con menos subprocesos, reduciendo así la sobrecarga causada por la administración de memoria y el cambio de contexto.

2. Cuando no hay ninguna operación de E / S para procesar, el subproceso también se puede utilizar para otras tareas.

 

Aunque el NIO de Java es bastante superior en rendimiento que el BIO, debe ser tan correcto y seguro.

no es fácil. En particular, es una tarea tediosa y propensa a errores procesar y programar operaciones de E / S de manera confiable y eficiente bajo una carga alta, y es hora de que Netty juegue.

Pasos de comunicación NIO:

① Cree ServerSocketChannel y configure el modo sin bloqueo para él.

②Enlace el monitoreo, configure los parámetros de TCP, ingrese el tamaño del backlog, etc.

③Cree un subproceso de E / S independiente para sondear el selector de multiplexor.

④ Cree un Selector, registre el ServerSocketChannel creado antes en el Selector y configure el indicador de monitoreo SelectionKey.OP_ACCEPT.

⑤Inicie el subproceso IO, ejecute el método Selector.select () en el cuerpo del bucle y sondee los canales listos.

⑥ Al sondear el canal en el estado listo, es necesario juzgar el bit de operación. Si está en el estado ACCEPT, significa que es un nuevo acceso de cliente, y luego llamar al método accept para recibir el nuevo cliente.

⑦ Configure algunos parámetros del nuevo cliente de acceso, como no bloqueo, y continúe registrándolo en el Selector, configure la bandera de monitoreo, etc.

⑧ Si el bit de identificación del canal sondeado es READ, léalo y construya un objeto Buffer.

⑨El problema más detallado es que los datos no se han enviado y continúan enviándose ...

Marco de Netty

Netty es el marco NIO más popular. Netty de uso de alta concurrencia

Netty ha encapsulado la API NIO y el rendimiento se ha mejorado hasta cierto punto mediante los siguientes medios

1. Utilice tecnología de multiplexación para mejorar la concurrencia de conexiones de procesamiento

2. Copia cero:

1. Netty usa BUFFERS DIRECTOS para recibir y enviar datos, usando memoria directa fuera del montón para lectura y escritura de Socket, sin la necesidad de una segunda copia del búfer de bytes

2. Netty proporciona un objeto Buffer combinado, que puede agregar varios objetos ByteBuffer para una operación.

3. La transferencia de archivos de Netty utiliza el método transferTo, que puede enviar directamente los datos en el búfer del archivo al canal de destino, evitando el problema de copia de memoria causado por el método tradicional de escritura circular.

3. Grupo de memoria: para reducir la pérdida de recursos causada por la asignación y el reciclaje de memoria directa fuera del montón, Netty proporciona un mecanismo de reutilización del búfer basado en el grupo de memoria.

4. Utilice el modelo de subprocesos múltiples de Reactor maestro-esclavo para mejorar la simultaneidad

5. Se adopta el diseño sin bloqueo en serie y la operación en serie se realiza dentro del subproceso IO para evitar la degradación del rendimiento causada por la competencia de múltiples subprocesos.

6. Utilice el marco de serialización de Protobuf de forma predeterminada

7. Configuración de parámetros TCP flexible

El marco RPC de Ali Dubbo usa netty en la capa inferior

Composición de la arquitectura Netty:

 

Pasos de comunicación de Netty:

① Cree dos grupos de subprocesos NIO, uno dedicado al procesamiento de eventos de red (que acepta conexiones de clientes) y el otro para leer y escribir comunicaciones de red.

②Cree un objeto ServerBootstrap y configure una serie de parámetros de Netty, como el tamaño del búfer para recibir datos salientes.

③Cree un ChannelInitializer de clase para el procesamiento real de datos y prepárese para la inicialización, como establecer el conjunto de caracteres y el formato para aceptar datos salientes y la interfaz para procesar los datos.

④Enlaza el puerto, ejecuta el método de bloqueo síncrono y espera a que se inicie el servidor.

Terminal de servicio:

public class Server {
 
    private int port;
 
    public Server(int port) {
        this.port = port;
    }
 
    public void run() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理服务器端接收客户端连接
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //进行网络通信(读写)
        try {
            ServerBootstrap bootstrap = new ServerBootstrap(); //辅助工具类,用于服务器通道的一系列配置
            bootstrap.group(bossGroup, workerGroup) //绑定两个线程组
                    .channel(NioServerSocketChannel.class) //指定NIO的模式
                    .childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new ServerHandler());
                        }
                    })
                    /**
                     * 对于ChannelOption.SO_BACKLOG的解释:
                     * 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端
                     * 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到
                     * 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept
                     * 从B队列中取出完成了三次握手的连接。
                     * A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
                     * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的
                     * 连接数并无影响,backlog影响的只是还没有被accept取出的连接
                     */
                    .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
                    .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
                    .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
                    .childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接
            ChannelFuture future = bootstrap.bind(port).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
 
    public static void main(String[] args) {
        new Server(8379).run();
    }
}

Clase ServerHandler:

public class ServerHandler  extends ChannelHandlerAdapter {
 
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
	
			//do something msg
			ByteBuf buf = (ByteBuf)msg;
			byte[] data = new byte[buf.readableBytes()];
			buf.readBytes(data);
			String request = new String(data, "utf-8");
			System.out.println("Server: " + request);
			//写给客户端
			String response = "我是反馈的信息";
			ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
			//.addListener(ChannelFutureListener.CLOSE);
			
 
	}
 
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
 
}

Cliente:

public class Client {
 
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new ClientHandler());
                    }
                });
        ChannelFuture future = bootstrap.connect("127.0.0.1", 8379).sync();
        future.channel().writeAndFlush(Unpooled.copiedBuffer("777".getBytes()));
        future.channel().closeFuture().sync();
        workerGroup.shutdownGracefully();
    }
 
}

Clase ClientHandler:

public class ClientHandler extends ChannelHandlerAdapter {
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            ByteBuf buf = (ByteBuf) msg;
            byte[] data = new byte[buf.readableBytes()];
            buf.readBytes(data);
            System.out.println("Client:" + new String(data).trim());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
 
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
 
}

Problemas de adherencia y desembalaje de TCP

TCP es un protocolo de "transmisión", que son datos genéticos sin fronteras. Puede imaginar que si el agua del río es como datos, están conectados en una sola pieza sin líneas divisorias. La capa inferior de TCP no comprende el significado específico de los datos comerciales de la capa superior. Dividirá los paquetes de acuerdo con las condiciones específicas de el búfer de TCP, es decir, en términos comerciales, un paquete completo se puede dividir en varios paquetes para su transmisión por TCP, o se pueden encapsular varios paquetes pequeños en un paquete de datos grande y enviarlos. Esto es lo que se denomina pegar / desempaquetar problema.

solución:

①La longitud del mensaje es fija. Por ejemplo, el tamaño de cada mensaje se fija en 200 bytes. Si no es suficiente, complete el espacio.

②Agregue caracteres especiales al final del paquete para la segmentación, como agregar retorno de carro.

③ Divida el mensaje en un encabezado y un cuerpo del mensaje, e incluya un campo que indique la longitud total del mensaje en el encabezado del mensaje y luego procese la lógica empresarial.

Métodos para resolver el pegado / desempaquetado de TCP en Netty:

①Clase Delimitador: DelimiterBasedFrameDecoder (delimitador personalizado)

② 定 长 : FixedLengthFrameDecoder

para resumir

Hay dos tipos de comunicación de red: E / S de bloqueo y E / S sin bloqueo. Netty ha encapsulado y mejorado NIO.

La comunicación RPC implica dos tecnologías: 1. Conversión de objetos, 2. Transmisión por socket.

Netty se usa a menudo para comunicaciones de red de alto rendimiento y se usan llamadas RPC en la arquitectura de microservicio.

Supongo que te gusta

Origin blog.csdn.net/x18094/article/details/114745014
Recomendado
Clasificación