Comunicación por socket mediante programación de red.

Si cree que el contenido de este blog es útil o inspirador para usted, siga mi blog para obtener los últimos artículos técnicos y tutoriales lo antes posible. Al mismo tiempo, también puedes dejar un mensaje en el área de comentarios para compartir tus pensamientos y sugerencias. ¡Gracias por tu apoyo!

1. Programación de red y comunicación por socket.

1.1 ¿Qué es la programación de redes?

La programación de redes se refiere al proceso de escribir programas de aplicación para intercambiar datos entre redes de computadoras. La programación de red puede ayudarnos a construir sistemas distribuidos, realizar la comunicación de datos entre clientes y servidores, y realizar la comunicación entre pares, etc.

1.2 ¿Qué es la comunicación por socket?

La comunicación por socket es el método de comunicación más común en la programación de redes: se basa en la pila de protocolos TCP/IP, establece una conexión entre dos computadoras a través de la red y transmite datos entre las conexiones. La comunicación por socket puede intercambiar datos entre diferentes programas que se ejecutan en diferentes computadoras y es una parte importante de la creación de aplicaciones de red.

1.3 Ventajas de la comunicación por socket

Las ventajas de la comunicación Socket incluyen:

  1. Confiabilidad: la comunicación por socket utiliza el protocolo TCP para garantizar la confiabilidad de la transmisión de datos y garantizar que los datos no se pierdan ni dañen.
  2. Flexibilidad: la comunicación por socket admite múltiples protocolos de red y métodos de transmisión, y se puede utilizar en diferentes escenarios de aplicación.
  3. Eficiencia: la comunicación por socket tiene una velocidad de transmisión de datos eficiente y baja latencia, lo que puede satisfacer las necesidades de grandes cantidades de transmisión de datos.
  4. Versatilidad: la comunicación por socket se puede utilizar no solo para transmitir datos de texto, sino también para transmitir diferentes tipos de datos, como datos multimedia y datos binarios.
  5. Programabilidad: la comunicación por socket es una interfaz de programación y los desarrolladores pueden realizar una programación personalizada de acuerdo con sus propias necesidades para lograr funciones más complejas.

Dos, protocolo de comunicación de socket

2.1 protocolo TCP

TCP es la abreviatura de Transmission Control Protocol, que es un protocolo confiable y orientado a la conexión. El protocolo TCP establece una conexión mediante un protocolo de enlace de tres vías para garantizar una transmisión de datos confiable. En la conexión TCP, los datos se dividen en varios paquetes de datos, y cada paquete de datos se marca con un número de serie y se clasifica. Después de recibir el paquete de datos, el extremo receptor confirma y vuelve a ensamblar de acuerdo con el número de serie para garantizar la precisión e integridad. de los datos sexo.

El protocolo TCP tiene las siguientes características:

  1. Fiabilidad: El protocolo TCP garantiza una transmisión de datos fiable mediante mecanismos de confirmación y retransmisión.
  2. Ordenado: el protocolo TCP divide los datos en varios paquetes de datos y los clasifica según el número de secuencia para garantizar la transmisión ordenada de los datos.
  3. Orientado a la conexión: el protocolo TCP necesita establecer una conexión antes de la comunicación y desconectarse una vez finalizada la comunicación.
  4. Inicio lento y control de congestión: el protocolo TCP evita la congestión de la red y la pérdida de datos mediante mecanismos de control de congestión y inicio lento.

El protocolo TCP se utiliza a menudo en escenarios que requieren una transmisión confiable, como la navegación web y la transferencia de archivos.

2.2 protocolo UDP

UDP es la abreviatura de User Datagram Protocol, que es un protocolo orientado sin conexión. El protocolo UDP empaqueta datos en datagramas, no garantiza una transmisión confiable ni el orden de los datos, no realiza confirmación ni retransmisión y solo envía datagramas al destino. Por lo tanto, el protocolo UDP tiene menor latencia y sobrecarga de red.

El protocolo UDP tiene las siguientes características:

  1. Sin conexión: el protocolo UDP no necesita establecer una conexión y envía el datagrama directamente al destino.
  2. Falta de confiabilidad: el protocolo UDP no garantiza la transmisión confiable y el orden de los datos, y no realiza confirmación ni retransmisión.
  3. Rapidez: el protocolo UDP tiene baja latencia y sobrecarga de red, y puede transmitir datos rápidamente.

El protocolo UDP se utiliza a menudo en escenarios de transmisión de datos en tiempo real, como voz, vídeo, juegos, etc. Dado que el protocolo UDP tiene un retraso y una sobrecarga de red bajos, puede cumplir con los requisitos en tiempo real.

2.3 Cómo elegir un protocolo

La elección del protocolo TCP o UDP depende de los requisitos y escenarios de la aplicación.

Si la aplicación requiere una transmisión y pedidos de datos confiables, entonces el protocolo TCP es una mejor opción. Por ejemplo, los escenarios de aplicaciones como la transferencia de archivos, la navegación web y el correo electrónico deben garantizar la precisión e integridad de los datos. En este momento, el mecanismo de confirmación y retransmisión del protocolo TCP puede garantizar la transmisión confiable y el orden de los datos.

Si la aplicación requiere una transmisión de datos rápida y rendimiento en tiempo real, entonces el protocolo UDP es una mejor opción. Por ejemplo, los escenarios de aplicaciones como voz, vídeo y juegos en tiempo real deben cumplir con una latencia y una sobrecarga de red bajas. En este momento, la baja sobrecarga de red y sin conexión del protocolo UDP puede cumplir con los requisitos en tiempo real.

En resumen, la elección de un protocolo debe realizarse de acuerdo con las necesidades y escenarios de la aplicación. Si la aplicación requiere una transmisión de datos confiable y ordenada, debe elegir el protocolo TCP; si la aplicación requiere una transmisión de datos rápida y rendimiento en tiempo real, debe elegir el protocolo UDP.

3. Programación de sockets en Java

3.1 clase de socket y clase ServerSocket

La programación de sockets en Java utiliza las clases Socket y ServerSocket en el paquete java.net.

La clase Socket se utiliza para establecer una conexión entre el cliente y el servidor. Proporciona dos constructores:

Socket(String host, int port) throws UnknownHostException, IOException
Socket(InetAddress address, int port) throws IOException

Entre ellos, el primer método de construcción se utiliza para especificar el nombre de host y el número de puerto del servidor, y el segundo método de construcción se utiliza para especificar la dirección IP y el número de puerto del servidor.

La clase ServerSocket se utiliza para monitorear la solicitud de conexión del cliente en el lado del servidor. Proporciona un constructor y un método aceptar():

ServerSocket(int port) throws IOException
Socket accept() throws IOException

Entre ellos, el método de construcción se usa para especificar el número de puerto del servidor y el método aceptar () se usa para esperar la solicitud de conexión del cliente. Una vez que un cliente se conecta exitosamente, el método aceptar () devolverá un nuevo Objeto de socket para comunicarse con el cliente comunicación.

3.2 Comunicación entre cliente y servidor

La comunicación entre el cliente y el servidor primero debe establecer una conexión y luego utilizar el flujo de entrada y el flujo de salida del objeto Socket para la transmisión de datos. El siguiente es un ejemplo de comunicación entre un cliente y un servidor simples:

Código del lado del servidor:

import java.io.*;
import java.net.*;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器已启动,等待客户端连接...");

        Socket socket = serverSocket.accept();
        System.out.println("客户端已连接...");

        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());

        String line = in.readLine();
        System.out.println("客户端发送的消息:" + line);

        out.println("你好,客户端!");
        out.flush();

        socket.close();
        serverSocket.close();
    }
}

Codigo del cliente:

import java.io.*;
import java.net.*;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8888);

        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());

        out.println("你好,服务器!");
        out.flush();

        String line = in.readLine();
        System.out.println("服务器返回的消息:" + line);

        socket.close();
    }
}

En este ejemplo, el servidor primero usa la clase ServerSocket para crear un socket de servidor y luego usa el método aceptar () para esperar la solicitud de conexión del cliente. Una vez que un cliente se conecta exitosamente, el método aceptar() devolverá un nuevo objeto Socket para comunicarse con el cliente.

El servidor utiliza flujos de entrada y flujos de salida para comunicarse con el cliente. El cliente crea una conexión a través de la clase Socket y luego usa el flujo de entrada y el flujo de salida para transmitir datos con el servidor.

4. Demostración de ejemplo

La programación de sockets en Java admite la comunicación mediante TCP y UDP. El uso de TCP para la comunicación puede garantizar la transmisión confiable y el orden de los datos, pero puede afectar el rendimiento de la transmisión en tiempo real; el uso de UDP para la comunicación puede mejorar el rendimiento de la transmisión en tiempo real, pero puede afectar la transmisión confiable y el orden de los datos datos.

4.1 Utilice el protocolo TCP para la comunicación por socket

Código de muestra para comunicación de socket usando TCP:

Código del lado del servidor:

import java.io.*;
import java.net.*;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器已启动,等待客户端连接...");

        Socket socket = serverSocket.accept();
        System.out.println("客户端已连接...");

        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());

        String line = in.readLine();
        System.out.println("客户端发送的消息:" + line);

        out.println("你好,客户端!");
        out.flush();

        socket.close();
        serverSocket.close();
    }
}

Codigo del cliente:

import java.io.*;
import java.net.*;

public class TCPClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8888);

        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());

        out.println("你好,服务器!");
        out.flush();

        String line = in.readLine();
        System.out.println("服务器返回的消息:" + line);

        socket.close();
    }
}

4.2 Utilice el protocolo UDP para la comunicación por socket

Código de muestra para comunicación por socket usando UDP:

Código del lado del servidor:

import java.net.*;

public class UDPServer {
    public static void main(String[] args) throws Exception {
        DatagramSocket serverSocket = new DatagramSocket(8888);
        System.out.println("服务器已启动,等待客户端连接...");

        byte[] receiveData = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

        serverSocket.receive(receivePacket);
        System.out.println("客户端已连接...");

        String line = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println("客户端发送的消息:" + line);

        InetAddress address = receivePacket.getAddress();
        int port = receivePacket.getPort();

        byte[] sendData = "你好,客户端!".getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port);
        serverSocket.send(sendPacket);

        serverSocket.close();
    }
}

Codigo del cliente:

import java.net.*;

public class UDPClient {
    public static void main(String[] args) throws Exception {
        DatagramSocket clientSocket = new DatagramSocket();

        InetAddress address = InetAddress.getByName("localhost");
        int port = 8888;

        byte[] sendData = "你好,服务器!".getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port);
        clientSocket.send(sendPacket);

        byte[] receiveData = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

        clientSocket.receive(receivePacket);
        String line = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println("服务器返回的消息:" + line);

        clientSocket.close();
    }
}

Cuando se utiliza UDP para la comunicación por socket, es necesario utilizar las clases DatagramSocket y DatagramPacket para enviar y recibir datos. El servidor primero crea un objeto DatagramSocket y luego usa el método recibir () para esperar la solicitud de conexión del cliente. Una vez que un cliente se conecta exitosamente, el servidor usa el objeto DatagramPacket para recibir los datos enviados por el cliente.

El cliente usa la clase DatagramSocket para crear un objeto DatagramPacket, coloca los datos que se enviarán en el objeto DatagramPacket y usa el método send() para enviar los datos. Después de recibir los datos, el servidor puede usar el objeto DatagramPacket para obtener la dirección y el puerto del cliente, y usar el objeto DatagramPacket para enviar datos al cliente.

4.3 Resumen

En Java, puede usar la clase Socket y la clase ServerSocket para implementar el protocolo TCP para la comunicación por socket, o puede usar la clase DatagramSocket y la clase DatagramPacket para implementar el protocolo UDP para la comunicación por socket. El protocolo TCP es adecuado para escenarios que requieren un alto orden y confiabilidad de datos, y el protocolo UDP es adecuado para escenarios que requieren una gran cantidad de datos en tiempo real.

Tanto las comunicaciones TCP como UDP en el código de muestra se modelan como una única solicitud y respuesta. En aplicaciones prácticas, generalmente es necesario implementar múltiples solicitudes y respuestas, que pueden procesarse mediante subprocesos múltiples o grupos de subprocesos. Además, se deben considerar cuestiones como la reconexión de la conexión TCP, el tiempo de espera y el procesamiento de desconexión, así como la pérdida de datos UDP y problemas de transmisión repetida, etc.

5. Algunos problemas comunes de programación de redes y sus soluciones.

5.1 Problemas de retraso y tiempo de espera de la red

Los problemas de latencia y tiempo de espera de la red son problemas frecuentes en la programación de redes. Cuando se produce un retraso o un tiempo de espera durante la comunicación de la red, es posible que el cliente no pueda recibir la respuesta del servidor o que el servidor no pueda recibir la solicitud del cliente. Para solucionar este problema se pueden adoptar las siguientes soluciones:

  • Establecer el período de tiempo de espera: establezca el período de tiempo de espera en el objeto Socket. Si no se recibe ninguna respuesta o solicitud dentro del tiempo especificado, se generará una excepción SocketTimeoutException.

Socket socket = new Socket();
socket.setSoTimeout(5000); // 设置超时时间为5秒
  • Utilice el mecanismo de latido: envíe mensajes de latido a la otra parte con regularidad para garantizar la validez de la conexión. Si no se recibe respuesta de la otra parte dentro de un período de tiempo, se puede determinar que la conexión se ha desconectado.

5.2 Problema de congestión de la red

La congestión de la red significa que la cantidad de datos enviados en la red es mayor que la capacidad de la red, lo que resulta en un cuello de botella en la red. Los problemas de congestión de la red pueden provocar retrasos, pérdidas y duplicaciones en las transmisiones de datos. Para solucionar este problema se pueden adoptar las siguientes soluciones:

  • Reduzca la cantidad de transmisión de datos: minimice la transmisión de datos innecesaria y reduzca la aparición de congestión de la red.
  • Utilice control de flujo: utilice tecnología de control de flujo para limitar la velocidad de envío de datos y evitar la congestión de la red.
  • Utilice la compresión de datos: comprima los datos para reducir la cantidad de transmisión de datos, reduciendo así la aparición de congestión de la red.

5.3 Problemas de duplicación y pérdida de paquetes

El problema de la pérdida y duplicación de paquetes de datos se refiere a la pérdida o transmisión repetida de algunos paquetes de datos durante la transmisión de datos. Este problema puede provocar transferencias de datos inexactas e incompletas. Para solucionar este problema se pueden adoptar las siguientes soluciones:

  • Utilice un protocolo de transmisión confiable: utilice un protocolo de transmisión confiable, como el protocolo TCP, para garantizar la confiabilidad de la transmisión de datos.
  • Utilice números de secuencia: Numere los paquetes con números de secuencia para identificar paquetes perdidos o duplicados.
  • Usar suma de verificación: use suma de verificación para verificar el paquete de datos y garantizar la exactitud de la transmisión de datos.

5.4 Problemas de seguridad

Los problemas de seguridad significan que durante la transmisión de la red, los piratas informáticos pueden escuchar los datos, lo que provoca fugas de datos y problemas de seguridad. Para solucionar este problema se pueden adoptar las siguientes soluciones:

  • Utilice tecnología de cifrado: cifre los datos para garantizar la seguridad de los datos.
  • Uso de tecnología de autenticación de identidad: autenticación de las identidades de los usuarios para evitar el acceso de usuarios ilegales.
  • Utilice tecnología de firewall: utilice tecnología de firewall para proteger la red y evitar piratas informáticos.

Con respecto a los problemas de programación de redes y sus soluciones, aquí acabamos de brindar una descripción general y escribiremos artículos para explicarlos más adelante.

Seis, las mejores prácticas de programación de redes.

6.1 Evite el uso de bloqueo de IO

El bloqueo de IO bloqueará la ejecución del subproceso actual hasta que se complete la operación de IO o se produzca una excepción. En un escenario de alta concurrencia, el bloqueo de IO provocará el bloqueo de una gran cantidad de subprocesos, lo que afectará el rendimiento y el rendimiento del programa. Por lo tanto, trate de evitar el uso de IO de bloqueo y puede optar por utilizar IO sin bloqueo o IO asincrónica.

La implementación de IO sin bloqueo es configurar el canal Socket en modo sin bloqueo y luego usar el Selector para sondear el canal Socket.Cuando el canal Socket está listo para leer o escribir, se realiza la operación IO correspondiente. A continuación se muestra un ejemplo de IO sin bloqueo:

import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
///

SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
while (true) {
    int readyChannels = selector.select();
    if (readyChannels == 0) continue;
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
    while (keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if (key.isReadable()) {
            // 处理读操作
        } else if (key.isWritable()) {
            // 处理写操作
        }
        keyIterator.remove();
    }
}

6.2 Usar el grupo de subprocesos para administrar subprocesos

En un escenario de alta concurrencia, la creación de una gran cantidad de subprocesos provocará un desperdicio de recursos del sistema y una sobrecarga del cambio de subprocesos, lo que afectará el rendimiento y el rendimiento del programa. Por lo tanto, puede utilizar el grupo de subprocesos para administrar subprocesos, reducir el costo de creación y destrucción de subprocesos y aumentar la tasa de reutilización de subprocesos.

Java proporciona el marco del grupo de subprocesos Executor y ExecutorService, que pueden crear y administrar fácilmente grupos de subprocesos. A continuación se muestra un ejemplo que utiliza un grupo de subprocesos:

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//

ExecutorService executorService = Executors.newFixedThreadPool(10);
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
    Socket socket = serverSocket.accept();
    executorService.submit(() -> handleRequest(socket));
}

6.3 Uso de la API NIO (Nueva IO)

NIO (New IO) es un conjunto de API IO sin bloqueo proporcionadas en Java. En comparación con las API IO tradicionales, tiene mejor rendimiento y escalabilidad cuando se trata de escenarios de alta concurrencia y alto rendimiento. El núcleo de la API de NIO es Channel y Buffer, donde Channel es responsable de leer y escribir datos, y Buffer es responsable de almacenar datos.

import java.net.InetSocketAddress;
      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;

      ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
      serverSocketChannel.socket().bind(new InetSocketAddress(8080));
      serverSocketChannel.configureBlocking(false);
      Selector selector = Selector.open();
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
      while (true) {
          int readyChannels = selector.select();
          if (readyChannels == 0) continue;
          Set<SelectionKey> selectedKeys = selector.selectedKeys();
          Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
          while (keyIterator.hasNext()) {
              SelectionKey key = keyIterator.next();
              if (key.isAcceptable()) {
                  ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                  SocketChannel socketChannel = serverChannel.accept();
                  socketChannel.configureBlocking(false);
                  socketChannel.register(selector, SelectionKey.OP_READ);
              } else if (key.isReadable()) {
                  SocketChannel socketChannel = (SocketChannel) key.channel();
                  ByteBuffer buffer = ByteBuffer.allocate(1024);
                  socketChannel.read(buffer);
                  buffer.flip();
                  byte[] bytes = new byte[buffer.remaining()];
                  buffer.get(bytes);
                  String message = new String(bytes);
                  // 处理消息
              }
              keyIterator.remove();
          }
      }

Lo anterior es la mejor práctica de programación de red: al evitar el bloqueo de IO, usar el grupo de subprocesos para administrar subprocesos y usar la API NIO (New IO), se puede mejorar el rendimiento y la escalabilidad del programa.

Marco Seven, Netty y MINA

7.1 ¿Qué son Netty y MINA?

Tanto Netty como MINA son frameworks de programación de redes desarrollados en base al lenguaje Java, que proporcionan un alto grado de reutilización, escalabilidad y rendimiento. Netty es un marco cliente/servidor NIO (New IO) que permite desarrollar rápidamente servidores y clientes de protocolo mantenibles y de alto rendimiento. MINA es un marco de red asíncrono impulsado por eventos y su objetivo es proporcionar un marco de servidor/cliente flexible y de alto rendimiento para aplicaciones de red.

7.2 Ventajas de Netty y MINA

Tanto Netty como MINA tienen las siguientes ventajas:

  • Alta reutilización: tanto Netty como MINA proporcionan un conjunto de API fáciles de usar y altamente reutilizables, lo que permite a los desarrolladores crear rápidamente aplicaciones de red eficientes y fáciles de mantener.
  • Escalabilidad: Tanto Netty como MINA tienen buena escalabilidad y pueden personalizarse y desarrollarse de acuerdo con las necesidades de las aplicaciones reales, para lograr un mayor rendimiento y una mejor confiabilidad.
  • Alto rendimiento: tanto Netty como MINA utilizan un modelo de E/S asincrónico y sin bloqueo, lo que evita el problema de bloqueo de subprocesos de E/S en el modelo de E/S de bloqueo tradicional, mejorando así el rendimiento de la aplicación.
  • Compatibilidad con múltiples protocolos: tanto Netty como MINA admiten múltiples protocolos, como TCP, UDP, HTTP, etc., que pueden satisfacer las necesidades de diversas aplicaciones.
  • Soporte multiplataforma: Tanto Netty como MINA son multiplataforma y pueden ejecutarse en una variedad de sistemas operativos.

7.3 Cómo elegir Netty o MINA

Al elegir Netty o MINA, debe elegir según las necesidades de aplicaciones específicas. Si necesita desarrollar un servidor o cliente de protocolo de alto rendimiento y necesita admitir múltiples protocolos, puede elegir Netty. Si necesita desarrollar una aplicación de red asíncrona basada en eventos, puede elegir MINA. Al mismo tiempo, también puede considerar los escenarios de aplicación específicos y la experiencia de desarrollo del equipo para tomar una decisión. Independientemente del marco que elija, debe comprender profundamente sus características y métodos de uso, y optimizarlo y depurarlo en combinación con escenarios de aplicaciones reales para lograr el mejor rendimiento y confiabilidad.

Ocho, la arquitectura básica de Netty y MINA.

8.1 La arquitectura básica de Netty

El núcleo de Netty es un conjunto de mecanismos de procesamiento de eventos asincrónicos altamente reutilizables y fácilmente extensibles. Su arquitectura incluye los siguientes componentes:

  • Canal: un canal para la transmisión de red, a través del cual se pueden leer y escribir datos, se pueden monitorear el estado de la conexión y los eventos, y se pueden vincular/desvincular los controladores de eventos.
  • EventLoop: un mecanismo de bucle de eventos que procesa todos los eventos y los envía a los controladores de eventos correspondientes para su procesamiento.
  • ChannelPipeline: la cadena de procesador de eventos, que combina diferentes procesadores de eventos en una cadena de procesamiento completa de manera ordenada.
  • ChannelHandler: un controlador de eventos que maneja eventos de entrada/salida y ejecuta una lógica empresarial específica.

8.2 Arquitectura básica de MINA

El núcleo de MINA es un marco de E/S asíncrono controlado por eventos y su arquitectura incluye los siguientes componentes:

  • IoSession: representa una conexión TCP/IP, que encapsula las operaciones de E/S de la red subyacente y la información de estado correspondiente.
  • IoFilterChain: una cadena de controlador de eventos que consta de una serie de IoFilters, cada uno de los cuales es responsable de procesar tipos específicos de eventos o modificar las propiedades de IoSession.
  • IoFilter: procesador de eventos, responsable de procesar eventos de entrada/salida y ejecutar lógica de negocios específica.
  • IoProcessor: Responsable de gestionar las operaciones de E/S, incluidas lectura, escritura, aceptación y conexión.
  • IoAcceptor e IoConnector: utilizados en el lado del servidor y del lado del cliente respectivamente, responsables de aceptar conexiones y crear conexiones.

Nueve, demostración de ejemplo

9.1 Uso de Netty para implementar el servidor HTTP

Usar Netty para implementar un servidor HTTP puede simplificar enormemente el desarrollo y la implementación de aplicaciones web, porque Netty proporciona un marco de programación HTTP liviano y de alto rendimiento. A continuación, demostraremos cómo utilizar Netty para implementar un servidor HTTP mediante un código de muestra simple.

Necesitamos agregar la dependencia de Netty en el archivo pom.xml:

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.65.Final</version>
        </dependency>

Código del servidor HTTP:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class HTTPServer {

    private final int port;

    public HTTPServer(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new HTTPServerInitializer());

            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("Server started and listening on " + future.channel().localAddress());

            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: " + HTTPServer.class.getSimpleName() + " <port>");
            return;
        }

        int port = Integer.parseInt(args[0]);

        new HTTPServer(port).start();
    }
}

Código de inicialización del servidor HTTP:

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

public class HTTPServerInitializer extends ChannelInitializer<SocketChannel> {

    private static final HttpServerCodec CODEC = new HttpServerCodec();
    private static final HttpObjectAggregator AGGREGATOR = new HttpObjectAggregator(1024 * 1024);
    private static final ChunkedWriteHandler CHUNKED_WRITE_HANDLER = new ChunkedWriteHandler();
    private static final HTTPServerHandler SERVER_HANDLER = new HTTPServerHandler();

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(CODEC);
        pipeline.addLast(AGGREGATOR);
        pipeline.addLast(CHUNKED_WRITE_HANDLER);
        pipeline.addLast(SERVER_HANDLER);
    }
}

Código del controlador del servidor HTTP:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;

import java.io.File;
import java.nio.file.Files;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

public class HTTPServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        String uri = request.uri();

        if (uri.equals("/")) {
            uri = "/index.html";
        }

        String path = "." + uri;

        File file = new File(path);

        if (file.exists() && file.isFile()) {
            byte[] bytes = Files.readAllBytes(file.toPath());
            ByteBuf buffer = Unpooled.wrappedBuffer(bytes);
          
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, buffer);
            response.headers().set(CONTENT_TYPE, "text/html");
            response.headers().set(CONTENT_LENGTH, buffer.readableBytes());

            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        } else {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND);
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

En el código de muestra anterior, utilizamos los ​HttpServerCodec​procesadores para​HttpObjectAggregator​ simplificar el procesamiento de solicitudes y respuestas HTTP. En​ChunkedWriteHandler​ , podemos procesar el objeto recibido, juzgar si leer el contenido del archivo de acuerdo con el URI solicitado y devolver de.​HTTPServerHandler​​FullHttpRequest​​ByteBuf​

Cabe señalar que el código de muestra anterior es solo una implementación simple del servidor HTTP, que solo es adecuada para aprendizaje y demostración. En el entorno de producción, debemos considerar más cuestiones como la seguridad, el rendimiento y la escalabilidad, y optimizar y configurar en consecuencia.

En general, Java proporciona una rica API de programación de red que permite a los desarrolladores implementar fácilmente varias aplicaciones de red. Al implementar aplicaciones de red, debemos prestar atención a las especificaciones de los protocolos de red, abordar condiciones anormales de las conexiones de red y garantizar la seguridad y confiabilidad de la transmisión de la red.

Si cree que el contenido de este blog es útil o inspirador para usted, siga mi blog para obtener los últimos artículos técnicos y tutoriales lo antes posible. Al mismo tiempo, también puedes dejar un mensaje en el área de comentarios para compartir tus pensamientos y sugerencias. ¡Gracias por tu apoyo!

Supongo que te gusta

Origin blog.csdn.net/bairo007/article/details/132595896
Recomendado
Clasificación