Serie de código fuente del cliente RabbitMQ - Conexión

prefacio

Esta vez, Fangmu tiene la intención de compartir una serie de análisis de código fuente directamente en el lado del RabbitMQ Javacliente ( com.rabbitmq:amqp-client:4.8.3)

pd: Recientemente recibí la tarea de la compañía para leer y analizar spring-rabbit, amqp-clientasí que planeo compartirla con ustedes amqp-client. Porque RabbitMQes Erlangel desarrollo del lenguaje (no hay ningún plan para compartir esta pieza por el momento)

Recordatorio amistoso: este intercambio es adecuado para personas que necesitan RabbitMQtener una cierta comprensión de

No hay muchas tonterías, ¡comencemos! ! !

Demostración de conexión de cliente Java

Veamos primero un sitio web oficial provistoJava Client Connecting to RabbitMQ Demo

ConnectionFactory factory = new ConnectionFactory();
// "guest"/"guest" by default, limited to localhost connections
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);

Connection conn = factory.newConnection();
Channel channel = connection.createChannel();

byte[] messageBodyBytes = "Hello, world!".getBytes();
channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, messageBodyBytes);
channel.close();
connection.close();
复制代码

Proceso de interacción del protocolo AMQP

Los estudiantes que han usado RabbitMQ, creen que no son extraños, por lo que describiré brevemente el proceso de RabbitMQ Brokerestablecer Connectiony Channel, luego de enviar un mensaje, y cerrar Connectiony Channel. La siguiente figura es el proceso interactivo de usar la Wiresharkcaptura de paquetes ver el AMQPprotocolo completo para este proceso ( 172.30.0.74para el cliente, es decir, la ip local; 192.168.17.160para RabbitMQ Brokerla ip de)

El cliente y el corredor crean la conexión, el canal y envían mensajes

Cliente y broker envían heartbeat (Heartbeat), cierran Connection, Channel

为了让读者更容易看得源码,我先给大家描述下 clientbroker 之间 AMQP 协议的交互流程描述(AMQP 协议中 不少命令都是成对存在的,抓包协议中 Info 里的命令是 -,而代码里的是 驼峰式 此处以代码为准):

  1. AMQP 0-9-1 的连接头写入底层套接字,包含指定的版本信息(客户端告诉 broker 自己使用的协议及版本,底层使用 java 自带的 socket)

  2. 客户端等待 broker 发送的 Connection.Start (broker 告诉客户端 通信的协议和版本、SASL认证机制(详细见)、语言环境以及RabbitMQ的版本信息和支持能力)

  3. 客户端接收后 发送 Connection.StartOk (客户端告诉 broker 连接使用的帐号和密码、认证机制、语言环境、客户的信息以及能力)

  4. 客户端等待 broker 发送的 Connection.Tune (broker 与 客户端 进行参数协商)

  5. 客户端接收后 发送 Connection.TuneOk (客户端 参数 [ChannelMax、FrameMax、Heartbeat] 协商完成后告诉 broker)

  6. 客户端发送 Connection.Open (客户端 告诉 broker 打开一个连接,并请求设置_virtualHost [vhost])

  7. broker 接收到后返回 Connection.OpenOk (client 对 vhost 进行验证,成功则返回如下此信息)

  8. 客户端发送 Channel.Open,broker 接收到后返回 Channel.OpenOk (客户端 创建通道;broker 收到并创建通道完成)

  9. El cliente envía Confirm.Selecty el corredor regresa después de recibirlo Confirm.SelectOk(el cliente le dice al corredor que el mensaje debe usar el mecanismo de confirmación, y el corredor recibe y responde)

  10. El cliente envía un mensaje Basic.Publishy el corredor respondeBasic.Ack

  11. Durante este período, el cliente y el corredor verificarán los latidos del corazón del otro.heartbeat

  12. El cliente cierra el canal Channel.Closey el corredor respondeChannel.CloseOk

  13. El cliente cierra la conexión Connection.Closey el corredor respondeConnection.CloseOk

Análisis de código fuente

Después de estar familiarizado con AMQPel proceso de interacción del protocolo, es fácil entender el código fuente más adelante. Esta vez presentamos principalmente el código fuente relacionado con la conexión:ConnectionFactory.newConnection --> AMQConnection.start

ConnectionFactory.nuevaConexión()

public Connection newConnection(ExecutorService executor, AddressResolver addressResolver, String clientProvidedName)
        throws IOException, TimeoutException {
        if(this.metricsCollector == null) {
            this.metricsCollector = new NoOpMetricsCollector();
        }
        // make sure we respect the provided thread factory
  			// 创建 socketFactory 和 初始化相应的配置
        FrameHandlerFactory fhFactory = createFrameHandlerFactory();
  			// 初始化 Connection 涉及到的参数
        ConnectionParams params = params(executor);
        // set client-provided via a client property
        if (clientProvidedName != null) {
            Map<String, Object> properties = new HashMap<String, Object>(params.getClientProperties());
            properties.put("connection_name", clientProvidedName);
            params.setClientProperties(properties);
        }

  			// 这块逻辑属于 rabbit提供自动回复连接的逻辑
        if (isAutomaticRecoveryEnabled()) {
            // see com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory#newConnection
            AutorecoveringConnection conn = new AutorecoveringConnection(params, fhFactory, addressResolver, metricsCollector);

            conn.init();
            return conn;
        } else {
            List<Address> addrs = addressResolver.getAddresses();
            Exception lastException = null;
            for (Address addr : addrs) {
                try {
                    // 创建、连接 socket 并封装成 返回 SocketFrameHandler (socket 不采用Negale算法[Negale算法,大家有兴趣可以了解下这块针对socket缓存性能的优化])
                    FrameHandler handler = fhFactory.create(addr);
                    // 初始化配置、_channel0、_channelManager等等
                    AMQConnection conn = createConnection(params, handler, metricsCollector);
                  	// 启动 AMQConnection 后续会进行详细介绍
                    conn.start();
                    this.metricsCollector.newConnection(conn);
                    return conn;
                } catch (IOException e) {
                    lastException = e;
                } catch (TimeoutException te) {
                    lastException = te;
                }
            }
            if (lastException != null) {
                if (lastException instanceof IOException) {
                    throw (IOException) lastException;
                } else if (lastException instanceof TimeoutException) {
                    throw (TimeoutException) lastException;
                }
            }
            throw new IOException("failed to connect");
        }
    }
复制代码

AMQPLa lógica en el proceso de interacción 1~6del pertenece a AMQConnection.start()la lógica clave de , y también es el punto principal que se le presenta esta vez.

public void start()
            throws IOException, TimeoutException {
  			// 初始化工作线程
        initializeConsumerWorkService();
  			// 初始化心跳发送
        initializeHeartbeatSender();
  			// 将 Connection标志位 启动
        this._running = true;

  			// 确认客户端 第一件事 发送header头部协议
        AMQChannel.SimpleBlockingRpcContinuation connStartBlocker =
            new AMQChannel.SimpleBlockingRpcContinuation();

  			// 进入Rpc队列进行阻塞,等待broker返回 connection.start method
        _channel0.enqueueRpc(connStartBlocker);
        try {
            // The following two lines are akin to AMQChannel's
            // transmit() method for this pseudo-RPC.
            _frameHandler.setTimeout(handshakeTimeout);
          	// 1. 发送header头部协议 AMQP 0-9-1
            _frameHandler.sendHeader();
        } catch (IOException ioe) {
            _frameHandler.close();
            throw ioe;
        }

  			// 初始化启动 startMainLoop -- 为了接收和处理broker发送的消息
        this._frameHandler.initialize(this);

        AMQP.Connection.Start connStart;
        AMQP.Connection.Tune connTune = null;
        try {
          	// 2. 客户端等待 broker 发送的 Connection.Start
            connStart =
                    (AMQP.Connection.Start) connStartBlocker.getReply(handshakeTimeout/2).getMethod();

          	// 通信的协议和版本、SASL认证机制(详细见)、语言环境以及RabbitMQ的版本信息和支持能力
            _serverProperties = Collections.unmodifiableMap(connStart.getServerProperties());

            Version serverVersion =
                    new Version(connStart.getVersionMajor(),
                                       connStart.getVersionMinor());
						
          	// 版本比对
            if (!Version.checkVersion(clientVersion, serverVersion)) {
                throw new ProtocolVersionMismatchException(clientVersion,
                                                                  serverVersion);
            }

            String[] mechanisms = connStart.getMechanisms().toString().split(" ");
            SaslMechanism sm = this.saslConfig.getSaslMechanism(mechanisms);
            if (sm == null) {
                throw new IOException("No compatible authentication mechanism found - " +
                                              "server offered [" + connStart.getMechanisms() + "]");
            }

            String username = credentialsProvider.getUsername();
            String password = credentialsProvider.getPassword();
            LongString challenge = null;
            LongString response = sm.handleChallenge(null, username, password);

            do {
                // 3. 客户端接收后 发送 `Connection.StartOk`
                Method method = (challenge == null)
                                        ? new AMQP.Connection.StartOk.Builder()
                                                  .clientProperties(_clientProperties)
                                                  .mechanism(sm.getName())
                                                  .response(response)
                                                  .build()
                                        : new AMQP.Connection.SecureOk.Builder().response(response).build();

                try {
                    Method serverResponse = _channel0.rpc(method, handshakeTimeout/2).getMethod();
                    if (serverResponse instanceof AMQP.Connection.Tune) {
                        // 4. 客户端等待 broker 发送的 Connection.Tune
                        connTune = (AMQP.Connection.Tune) serverResponse;
                    } else {
                        challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
                        response = sm.handleChallenge(challenge, username, password);
                    }
                } catch (ShutdownSignalException e) {
                    Method shutdownMethod = e.getReason();
                    if (shutdownMethod instanceof AMQP.Connection.Close) {
                        AMQP.Connection.Close shutdownClose = (AMQP.Connection.Close) shutdownMethod;
                        if (shutdownClose.getReplyCode() == AMQP.ACCESS_REFUSED) {
                            throw new AuthenticationFailureException(shutdownClose.getReplyText());
                        }
                    }
                    throw new PossibleAuthenticationFailureException(e);
                }
            } while (connTune == null);
        } catch (TimeoutException te) {
            _frameHandler.close();
            throw te;
        } catch (ShutdownSignalException sse) {
            _frameHandler.close();
            throw AMQChannel.wrap(sse);
        } catch(IOException ioe) {
            _frameHandler.close();
            throw ioe;
        }

        try {
          	// 最大通道数
            int channelMax =
                negotiateChannelMax(this.requestedChannelMax,
                                    connTune.getChannelMax());
            _channelManager = instantiateChannelManager(channelMax, threadFactory);
						
          	// 帧最大的大小
            int frameMax =
                negotiatedMaxValue(this.requestedFrameMax,
                                   connTune.getFrameMax());
            this._frameMax = frameMax;

          	// 心跳
            int heartbeat =
                negotiatedMaxValue(this.requestedHeartbeat,
                                   connTune.getHeartbeat());

            setHeartbeat(heartbeat);

            // 5. 客户端接收后 发送 Connection.TuneOk
            _channel0.transmit(new AMQP.Connection.TuneOk.Builder()
                                .channelMax(channelMax)
                                .frameMax(frameMax)
                                .heartbeat(heartbeat)
                              .build());
            // 6. 客户端发送 Channel.Open
            _channel0.exnWrappingRpc(new AMQP.Connection.Open.Builder()
                                      .virtualHost(_virtualHost)
                                    .build());
        } catch (IOException ioe) {
            _heartbeatSender.shutdown();
            _frameHandler.close();
            throw ioe;
        } catch (ShutdownSignalException sse) {
            _heartbeatSender.shutdown();
            _frameHandler.close();
            throw AMQChannel.wrap(sse);
        }

        // We can now respond to errors having finished tailoring the connection
        this._inConnectionNegotiation = false;
    }
复制代码

finalmente

El propósito de este intercambio es primero permitir que los RabbitMQ Clientlectores RabbitMQ Brokertengan una comprensión general del proceso de interacción con el protocolo AMQP, y tener una cierta comprensión basada en el análisis del código fuente de Connection. Hay muchos códigos fuente Connectiondetallados que los lectores necesitan para entender lentamente.


Mi cuenta pública de WeChat: Programación avanzada Java Architect
Concéntrese en compartir productos secos de tecnología Java, ¡esperamos su atención!

Supongo que te gusta

Origin juejin.im/post/7080141024403816456
Recomendado
Clasificación