All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.servicecomb.foundation.vertx.client.tcp.TcpClient Maven / Gradle / Ivy

There is a newer version: 0.5.0
Show newest version
/*
 * Copyright 2017 Huawei Technologies Co., Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.servicecomb.foundation.vertx.client.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

import io.servicecomb.foundation.common.net.URIEndpointObject;
import io.servicecomb.foundation.vertx.server.TcpParser;
import io.servicecomb.foundation.vertx.tcp.TcpConnection;
import io.servicecomb.foundation.vertx.tcp.TcpConst;
import io.servicecomb.foundation.vertx.tcp.TcpOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetSocket;

public class TcpClient extends TcpConnection {
    private static final Logger LOGGER = LoggerFactory.getLogger(TcpClient.class);

    private static AtomicLong reqId = new AtomicLong();

    public static long getAndIncRequestId() {
        return reqId.getAndIncrement();
    }

    enum Status {
        CONNECTING,
        DISCONNECTED,
        TRY_LOGIN,
        WORKING
    }

    // 是在哪个context中创建的
    private Context context;

    private NetClient netClient;

    private TcpClientConfig clientConfig;

    private InetSocketAddress socketAddress;

    private boolean remoteSupportLogin;

    private volatile Status status = Status.DISCONNECTED;

    private NetSocket netSocket;

    // 连接未建立时,临时保存发送消息的队列
    private Buffer tmpWriteBuffer = Buffer.buffer();

    // 所有的访问,都在锁的保护中,是线程安全的
    private volatile Map requestMap = new ConcurrentHashMap<>();

    public TcpClient(Context context, NetClient netClient, String endpoint, TcpClientConfig clientConfig) {
        this.context = context;
        this.netClient = netClient;
        URIEndpointObject ipPort = new URIEndpointObject(endpoint);
        this.socketAddress = ipPort.getSocketAddress();
        this.remoteSupportLogin = Boolean.parseBoolean(ipPort.getFirst(TcpConst.LOGIN));
        this.clientConfig = clientConfig;
    }

    public Context getContext() {
        return context;
    }

    public synchronized void send(TcpOutputStream os, long msTimeout, TcpResonseCallback callback) {
        requestMap.put(os.getMsgId(), new TcpRequest(msTimeout, callback));

        if (Status.WORKING.equals(status)) {
            netSocket.write(os.getBuffer());
            return;
        }

        tmpWriteBuffer.appendBuffer(os.getBuffer());
        if (Status.DISCONNECTED.equals(status)) {
            connect();
        }
    }

    private void connect() {
        this.status = Status.CONNECTING;
        LOGGER.info("connecting to address {}", socketAddress.toString());

        netClient.connect(socketAddress.getPort(), socketAddress.getHostString(), ar -> {
            if (ar.succeeded()) {
                onConnectSuccess(ar.result());
                return;
            }

            onConnectFailed(ar.cause());
        });
    }

    private synchronized void onConnectSuccess(NetSocket socket) {
        LOGGER.info("connect to address {} success", socketAddress.toString());
        this.netSocket = socket;
        socket.handler(new TcpParser(this::onReply));

        socket.exceptionHandler(this::onException);
        socket.closeHandler(this::onClosed);

        // 开始登录
        tryLogin();
    }

    private void onClosed(Void v) {
        onDisconnected(new IOException("socket closed"));
    }

    // 异常断连时,先触发onException,再触发onClosed
    // 正常断连时,只触发onClosed
    private void onException(Throwable e) {
        LOGGER.error("{} disconnected from {}, in thread {}, cause {}",
                netSocket.localAddress().toString(),
                socketAddress.toString(),
                Thread.currentThread().getName(),
                e.getMessage());
    }

    private synchronized void onDisconnected(Throwable e) {
        this.status = Status.DISCONNECTED;
        LOGGER.error("{} disconnected from {}, in thread {}, cause {}",
                netSocket.localAddress().toString(),
                socketAddress.toString(),
                Thread.currentThread().getName(),
                e.getMessage());

        clearCachedRequest(e);
    }

    protected synchronized void tryLogin() {
        if (!remoteSupportLogin) {
            LOGGER.error("remote not support login, address={}.",
                    socketAddress.toString());
            onLoginSuccess();
            return;
        }
        if (clientConfig.getTcpLogin() == null) {
            LOGGER.error("local not support login, address={}.",
                    socketAddress.toString());
            onLoginSuccess();
            return;
        }

        this.status = Status.TRY_LOGIN;
        LOGGER.info("try login to address {}", socketAddress.toString());

        try (TcpOutputStream os = clientConfig.getTcpLogin().createLogin()) {
            requestMap.put(os.getMsgId(),
                    new TcpRequest(clientConfig.getRequestTimeoutMillis(), this::onLoginResponse));
            netSocket.write(os.getBuffer());
        }
    }

    private synchronized void onLoginResponse(AsyncResult asyncResult) {
        if (asyncResult.failed()) {
            LOGGER.error("login failed, address {}", socketAddress.toString(), asyncResult.cause());
            // 在相应回调中设置状态
            netSocket.close();
            return;
        }

        if (!clientConfig.getTcpLogin().onLoginResponse(asyncResult.result().getBodyBuffer())) {
            LOGGER.error("login failed, address {}", socketAddress.toString());
            // 在相应回调中设置状态
            netSocket.close();
            return;
        }

        LOGGER.info("login success, address {}", socketAddress.toString());
        onLoginSuccess();
    }

    private synchronized void onLoginSuccess() {
        if (tmpWriteBuffer != null) {
            LOGGER.info("writting cached buffer to address {}", socketAddress.toString());
            netSocket.write(tmpWriteBuffer);
            tmpWriteBuffer = Buffer.buffer();
        }

        this.status = Status.WORKING;
    }

    private synchronized void onConnectFailed(Throwable cause) {
        // 连接失败
        this.status = Status.DISCONNECTED;
        String msg = String.format("connect to address %s failed.",
                socketAddress.toString());
        LOGGER.error(msg, cause);

        clearCachedRequest(cause);
    }

    protected synchronized void clearCachedRequest(Throwable cause) {
        // 在onSendError,用户可能发起一次新的调用,需要避免作多余的清理
        Map oldMap = requestMap;
        requestMap = new ConcurrentHashMap<>();

        for (TcpRequest request : oldMap.values()) {
            request.onSendError(cause);
        }
        oldMap.clear();
    }

    protected void onReply(long msgId, Buffer headerBuffer, Buffer bodyBuffer) {
        TcpRequest request = requestMap.remove(msgId);
        if (request == null) {
            LOGGER.error("Unknown reply msgId {}, waiting count {}", msgId, requestMap.size());
            return;
        }

        request.onReply(headerBuffer, bodyBuffer);
    }

    public void checkTimeout() {
        for (Entry entry : requestMap.entrySet()) {
            TcpRequest request = entry.getValue();
            if (request.isTimeout()) {
                // 可能正好收到reply,且被处理了,所以这里的remove不一定有效
                // 是否有效,根据remove的结果来决定
                request = requestMap.remove(entry.getKey());
                if (request != null) {
                    String msg =
                        String.format("request timeout, msgId=%d, address=%s", entry.getKey(), socketAddress);
                    LOGGER.error(msg);

                    request.onTimeout(new TimeoutException(msg));
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy