客户端connect过程分析

BootStrap.connect()解析
本文详细剖析了BootStrap.connect()方法的工作流程,从初始化和注册到连接远程地址的具体实现,包括参数验证、事件循环调用及最终的连接操作。

转载自:https://blog.youkuaiyun.com/pzw_0612/article/details/78068194

  BootStrap.connect() 分析:

public ChannelFuture connect(String inetHost, int inetPort) {
    return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}

public ChannelFuture connect(SocketAddress remoteAddress) {
    if (remoteAddress == null) {
        throw new NullPointerException("remoteAddress");
    }

    validate();
    return doResolveAndConnect(remoteAddress, config.localAddress());
}

/**
 * @see #connect()
 */
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();

    if (regFuture.isDone()) {
        if (!regFuture.isSuccess()) {
            return regFuture;
        }
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        //...略
    }
}

  initAndRegister不再赘述,之后会调用doResolveAndConnect0doResolveAndConnect0中会进行一些参数检查, 最终调用doConnect 方法, 其实现如下:

private static void doConnect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {

    // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
    // the pipeline in its channelRegistered() implementation.
    final Channel channel = connectPromise.channel();
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (localAddress == null) {
                channel.connect(remoteAddress, connectPromise);
            } else {
                channel.connect(remoteAddress, localAddress, connectPromise);
            }
            connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
        }
    });
}

  在doConnect 中, 会在 event loop 线程中调用 Channel 的 connect 方法, 而这个channel就是 NioSocketChannel。跟进channel.connect ,它调用的实际是AbstractChannel#connect

@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return pipeline.connect(remoteAddress, promise);
}

@Override
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return tail.connect(remoteAddress, promise);
}

  tail是TailContext实例, 而TailContext是 AbstractChannelHandlerContext的子类, 因此这里调用的其实是 AbstractChannelHandlerContext#connect, 看一下这个方法的实现:

@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return connect(remoteAddress, null, promise);
}

@Override
public ChannelFuture connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

    if (remoteAddress == null) {
        throw new NullPointerException("remoteAddress");
    }
    if (isNotValidPromise(promise, false)) {
        // cancelled
        return promise;
    }

    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}

  findContextOutbound返回的是head,然后会调用到HeadContext#connect:

@Override
public void connect(
        ChannelHandlerContext ctx,
        SocketAddress remoteAddress, SocketAddress localAddress,
        ChannelPromise promise) throws Exception {
    unsafe.connect(remoteAddress, localAddress, promise);
}

  这个unsafe 其实就是 NioSocketChannelUnsafe,而NioSocketChannelUnsafe.connnect方法的实现是 AbstractNioUnsafe.connect,其代码实现:

@Override
public final void connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    boolean wasActive = isActive();
    if (doConnect(remoteAddress, localAddress)) {
        fulfillConnectPromise(promise, wasActive);
    } else {
        ...
    }
}

  AbstractNioUnsafe.connect 的实现如上代码所示, 在这个 connect 方法中, 调用了 doConnect 方法, 注意, 这个方法并不是 AbstractNioUnsafe 的方法, 而是 AbstractNioChannel 的抽象方法. doConnect 方法是在 NioSocketChannel 中实现的, 因此进入到 NioSocketChannel.doConnect 中:

@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
    if (localAddress != null) {
        javaChannel().socket().bind(localAddress);
    }

    boolean success = false;
    try {
        boolean connected = javaChannel().connect(remoteAddress);
        if (!connected) {
            selectionKey().interestOps(SelectionKey.OP_CONNECT);
        }
        success = true;
        return connected;
    } finally {
        if (!success) {
            doClose();
        }
    }
}
### 客户端 `connect` 函数的用法及错误排查 #### 一、`connect` 函数简介 在网络编程中,`connect` 是用于建立客户端与服务器之间连接的重要函数。它通过指定的目标地址和端口号发起连接请求,并等待服务器响应。如果成功,则返回 0;否则返回 -1 并设置相应的错误码。 其原型如下: ```c int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ``` - **sockfd**: 已创建的套接字描述符。 - **addr**: 目标服务器的地址结构体指针(通常为 `struct sockaddr_in` 类型)。 - **addrlen**: 地址结构体的大小。 该函数的具体实现细节可以参考站内引用中的相关内容[^1]。 --- #### 二、常见错误及其原因分析 ##### 1. 连接超时 (ETIMEDOUT) 当客户端尝试连接到目标服务器时,若长时间未能收到应答,则可能触发超时错误。这通常是由于以下原因之一引起的: - 服务器未运行或监听指定端口。 - 防火墙阻止了通信流量。 - IP 地址或端口号配置不正确。 解决方案包括验证服务器状态以及调整防火墙规则等措施[^2]。 ##### 2. 主机不可达 (EHOSTUNREACH/ECONNREFUSED) 此类错误表明目标主机不存在或者拒绝接受来自当前源IP/端口组合的数据包传输请求。具体表现为试图访问一个实际已关闭的服务实例或者是路由路径上存在问题所致的情况。 对于这种情况下的调试方法可以从以下几个方面入手考虑:确认目的机器在线状况良好并无任何物理层面上障碍存在;另外还需仔细核对待连入服务程序本身是否有启动正常并且处于可接收外部链接的状态之中[^3]. ##### 3. 套接字选项配置不当 有时即使基本参数均无明显差错但仍会出现异常现象比如无法完成三次握手过程等问题则可能是某些高级别的socket option 设置不合理所引起的结果之一. 例如TIME_WAIT状态下重试次数过多可能导致资源耗尽从而影响新建立起来的新连接效率低下甚至失败等情况的发生因此适当调节这些数值往往能够有效缓解上述症状带来的困扰[^4]. --- #### 三、代码示例 下面提供了一个简单的 C 语言版本的 TCP 客户端例子来展示如何正确调用 `connect()` 方法: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_IP "127.0.0.1" #define PORT 8080 int main() { int client_fd; struct sockaddr_in server_addr; // 创建 socket 文件描述符 if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } memset(&server_addr, '0', sizeof(server_addr)); // 配置服务器地址信息 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); // 将 IPv4 和 IPv6 地址转换成长整型数 if(inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr)<=0){ printf("\nInvalid address/ Address not supported \n"); return -1; } // 发起连接请求 if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 ){ perror("Connection Failed"); close(client_fd); return -1; } printf("Connected to the server\n"); char buffer[1024]; bzero(buffer,sizeof(buffer)); strcpy(buffer,"Hello from Client!"); send(client_fd , buffer , strlen(buffer) , 0 ); printf("Message sent\n"); recv(client_fd , buffer , 1024 , 0 ); printf("%s\n",buffer); close(client_fd); return 0; } ``` --- #### 四、总结 通过对 `connect` 的深入理解可以帮助开发者更好地构建稳定可靠的网络应用程序。同时,在遇到问题时也需综合运用日志记录、抓包工具等多种手段进行全面诊断以快速定位根本原因所在并采取相应对策加以修复改进^。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值