connect和bind过程详解(一)

Netty 源码解析(九): connect 过程和 bind 过程分析 原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇。 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty 源码解析(三): Netty的 Future 和 Promise Netty 源码解析(四): Netty 的 ChannelPipeline Netty 源码解析(五): Netty 的线程池分析 Netty 源码解析(六): Channel 的 r 阅读详情

connect和bind过程详解(二)
Connect与Bind对比总结

Netty Connect 和 Bind 过程详解

本文档详细介绍 Netty 客户端的 Connect 过程。


一、Connect 完整调用链路(客户端)

从客户端代码 b.connect(inetHost, inetPort) 开始,整个调用链路如下:

Bootstrap.connect(host, port)
  ↓
Bootstrap.connect(SocketAddress)
  ↓
Bootstrap.doResolveAndConnect()  // 先 register,再 connect
  ↓
Bootstrap.doResolveAndConnect0()  // 地址解析
  ↓
Bootstrap.doConnect()  // 提交 connect 任务到 EventLoop
  ↓
AbstractChannel.connect()  // 交给 pipeline
  ↓
DefaultChannelPipeline.connect()  // 从 tail 开始
  ↓
TailContext.connect()
  ↓
AbstractChannelHandlerContext.findContextOutbound()  // 找下一个 Outbound handler
  ↓
AbstractChannelHandlerContext.invokeConnect()  // 调用 handler 的 connect 方法
  ↓
HeadContext.connect()  // 最终到达 head
  ↓
AbstractNioChannel.AbstractNioUnsafe.connect()  // 调用 unsafe
  ↓
NioSocketChannel.doConnect()  // 执行真正的连接
  ↓
SocketUtils.connect()  // JDK 底层 SocketChannel.connect()

二、详细代码流程解析

2.1 入口:Bootstrap.connect()

用户代码:

// 客户端启动代码
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.AUTO_READ, true);
b.handler(new MyChannelInitializer());

// 【入口】发起连接
ChannelFuture f = b.connect(inetHost, inetPort).sync();

第一步:地址转换

// Bootstrap
public ChannelFuture connect(String inetHost, int inetPort) {
    // 将 host 和 port 转换为 SocketAddress
    return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}

第二步:参数校验

// Bootstrap
public ChannelFuture connect(SocketAddress remoteAddress) {
    if (remoteAddress == null) {
        throw new NullPointerException("remoteAddress");
    }
    // 校验 Bootstrap 配置是否完整(group、channelFactory 等)
    validate();
    // 进入核心方法
    return doResolveAndConnect(remoteAddress, config.localAddress());
}

2.2 核心方法:doResolveAndConnect()

这个方法做两件事:

  1. initAndRegister():创建 Channel 并注册到 Selector(这部分前面已经详细介绍过)
  2. doResolveAndConnect0():解析地址并发起连接
// Bootstrap
private ChannelFuture doResolveAndConnect(
    final SocketAddress remoteAddress, 
    final SocketAddress localAddress) {
    
    // 【重要】先完成 Channel 的创建和注册
    // 这一步会:创建 NioSocketChannel、初始化 Pipeline、注册到 Selector
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();

    if (regFuture.isDone()) {
        // 情况1:register 已经完成(同步完成)
        if (!regFuture.isSuccess()) {
            return regFuture;  // register 失败,直接返回
        }
        // register 成功,继续 connect
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        // 情况2:register 还未完成(异步完成)
        // 添加监听器,等 register 完成后再执行 connect
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    // register 成功后,执行 connect
                    doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

关键点:

  • connect 必须在 register 之后执行
  • register 可能同步完成,也可能异步完成
  • 如果异步,通过监听器等待 register 完成

2.3 地址解析:doResolveAndConnect0()

这个方法负责解析域名(如果需要),然后调用 doConnect()。

// Bootstrap
private ChannelFuture doResolveAndConnect0(
    final Channel channel, 
    SocketAddress remoteAddress,
    final SocketAddress localAddress, 
    final ChannelPromise promise) {
    
    try {
        final EventLoop eventLoop = channel.eventLoop();
        // 获取地址解析器(用于 DNS 解析)
        final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);

        if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
            // 情况1:地址已经解析好了(比如直接传入 IP),直接 connect
            doConnect(remoteAddress, localAddress, promise);
            return promise;
        }

        // 情况2:需要解析域名(比如传入的是 "www.example.com")
        final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);

        if (resolveFuture.isDone()) {
            // 解析已完成(可能是缓存的结果)
            final Throwable resolveFailureCause = resolveFuture.cause();
            if (resolveFailureCause != null) {
                channel.close();
                promise.setFailure(resolveFailureCause);
            } else {
                // 解析成功,执行 connect
                doConnect(resolveFuture.getNow(), localAddress, promise);
            }
            return promise;
        }

        // 解析还未完成,添加监听器等待
        resolveFuture.addListener(new FutureListener<SocketAddress>() {
            @Override
            public void operationComplete(Future<SocketAddress> future) throws Exception {
                if (future.cause() != null) {
                    channel.close();
                    promise.setFailure(future.cause());
                } else {
                    // 解析成功后执行 connect
                    doConnect(future.getNow(), localAddress, promise);
                }
            }
        });
    } catch (Throwable cause) {
        promise.tryFailure(cause);
    }
    return promise;
}

关键点:

  • 如果传入的是 IP 地址,直接 connect
  • 如果传入的是域名,先进行 DNS 解析,再 connect
  • 解析过程也可能是异步的

2.4 提交任务:doConnect()

这个方法将 connect 操作提交到 EventLoop 的任务队列中执行。

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

    final Channel channel = connectPromise.channel();
    
    // 【关键】将 connect 操作提交到 EventLoop 的任务队列
    // 这样可以确保 connect 在 EventLoop 线程中执行
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (localAddress == null) {
                // 调用 Channel 的 connect 方法
                channel.connect(remoteAddress, connectPromise);
            } else {
                // 如果指定了本地地址,先绑定本地地址
                channel.connect(remoteAddress, localAddress, connectPromise);
            }
            // 添加监听器:如果 connect 失败,自动关闭 channel
            connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
        }
    });
}

关键点:

  • connect 操作必须在 EventLoop 线程中执行
  • 通过 eventLoop().execute() 提交任务
  • 添加失败监听器,自动清理资源

2.5 交给 Pipeline:AbstractChannel.connect()

// AbstractChannel
@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    // 将 connect 操作交给 pipeline 处理
    return pipeline.connect(remoteAddress, promise);
}

作用:将操作委托给 pipeline,利用责任链模式处理。


2.6 从 Tail 开始:DefaultChannelPipeline.connect()

// DefaultChannelPipeline
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    // connect 是 Outbound 事件,从 tail 开始传播
    return tail.connect(remoteAddress, promise);
}

关键点:

  • Outbound 事件从 tail 开始,向 head 方向传播
  • 这样用户自定义的 OutboundHandler 可以先处理(比如日志、编码等)

2.7 查找下一个 Handler:AbstractChannelHandlerContext.connect()

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

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

    // 【关键】从当前节点开始,向前查找下一个 Outbound 类型的 handler
    final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
    
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // 如果当前线程就是 EventLoop 线程,直接调用
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
        // 否则提交到 EventLoop 执行
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}

关键点:

  • findContextOutbound(MASK_CONNECT):查找下一个支持 connect 操作的 Outbound handler
  • 从 tail 开始,依次向前查找,最终会找到 head

2.8 调用 Handler:AbstractChannelHandlerContext.invokeConnect()

// AbstractChannelHandlerContext
private void invokeConnect(
    SocketAddress remoteAddress, 
    SocketAddress localAddress, 
    ChannelPromise promise) {
    
    if (invokeHandler()) {
        try {
            // 调用当前 handler 的 connect 方法
            ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    } else {
        // handler 还未添加完成,继续向前传播
        connect(remoteAddress, localAddress, promise);
    }
}

作用:调用 handler 的 connect 方法,让 handler 有机会处理或拦截 connect 操作。


2.9 到达 Head:HeadContext.connect()

经过 pipeline 的传播,最终会到达 head。

// HeadContext
@Override
public void connect(
    ChannelHandlerContext ctx,
    SocketAddress remoteAddress, 
    SocketAddress localAddress,
    ChannelPromise promise) {
    
    // head 负责调用 unsafe 执行真正的底层操作
    unsafe.connect(remoteAddress, localAddress, promise);
}

关键点:

  • head 是 pipeline 的第一个节点,也是最后一个处理 Outbound 事件的节点
  • head 负责调用 unsafe 执行底层操作

2.10 Unsafe 处理:AbstractNioUnsafe.connect()

这是 connect 的核心逻辑所在。

// AbstractNioChannel.AbstractNioUnsafe
@Override
public final void connect(
    final SocketAddress remoteAddress, 
    final SocketAddress localAddress, 
    final ChannelPromise promise) {
    
    if (!promise.setUncancellable() || !ensureOpen(promise)) {
        return;
    }

    try {
        if (connectPromise != null) {
            // 已经有一个 connect 正在进行中,抛出异常
            throw new ConnectionPendingException();
        }

        boolean wasActive = isActive();
        
        // 【核心】执行真正的 connect 操作
        if (doConnect(remoteAddress, localAddress)) {
            // 情况1:connect 立即成功(比如连接本地地址)
            fulfillConnectPromise(promise, wasActive);
        } else {
            // 情况2:connect 正在进行中(TCP 三次握手需要时间)
            connectPromise = promise;
            requestedRemoteAddress = remoteAddress;

            // 设置连接超时定时器
            int connectTimeoutMillis = config().getConnectTimeoutMillis();
            if (connectTimeoutMillis > 0) {
                connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                    @Override
                    public void run() {
                        ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                        ConnectTimeoutException cause =
                            new ConnectTimeoutException("connection timed out: " + remoteAddress);
                        if (connectPromise != null && connectPromise.tryFailure(cause)) {
                            close(voidPromise());
                        }
                    }
                }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
            }

            // 添加取消监听器
            promise.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isCancelled()) {
                        if (connectTimeoutFuture != null) {
                            connectTimeoutFuture.cancel(false);
                        }
                        connectPromise = null;
                        close(voidPromise());
                    }
                }
            });
        }
    } catch (Throwable t) {
        promise.tryFailure(annotateConnectException(t, remoteAddress));
        closeIfClosed();
    }
}

关键点:

  1. 防止重复连接:检查是否已有 connect 正在进行
  2. 执行 doConnect():调用底层的连接方法
  3. 处理两种结果:
    • 立即成功:直接完成 Promise
    • 正在进行:设置超时定时器,等待连接完成
  4. 超时处理:使用 EventLoop 的定时任务功能

2.11 底层连接:NioSocketChannel.doConnect()

这是真正执行 JDK 底层连接的地方。

// NioSocketChannel
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
    // 如果指定了本地地址,先绑定
    if (localAddress != null) {
        doBind0(localAddress);
    }

    boolean success = false;
    try {
        // 【核心】调用 JDK 的 SocketChannel.connect()
        boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
        
        if (!connected) {
            // 连接未立即完成,注册 OP_CONNECT 事件
            // NioEventLoop 会监听这个事件,当连接完成时会收到通知
            selectionKey().interestOps(SelectionKey.OP_CONNECT);
        }
        success = true;
        return connected;  // 返回是否立即连接成功
    } finally {
        if (!success) {
            doClose();  // 连接失败,关闭 channel
        }
    }
}

关键点:

  1. 可选的本地地址绑定:如果指定了 localAddress,先绑定
  2. 调用 JDK 底层方法:SocketUtils.connect(javaChannel(), remoteAddress)
  3. 返回值含义:
    • true:连接立即成功
    • false:连接正在进行中(非阻塞模式下的正常情况)
  4. 注册 OP_CONNECT:如果连接未立即完成,注册 OP_CONNECT 事件,等待连接完成

2.12 JDK 底层调用:SocketUtils.connect()

// SocketUtils
public static boolean connect(final SocketChannel socketChannel, final SocketAddress remoteAddress)
        throws IOException {
    try {
        // 调用 JDK 的 SocketChannel.connect() 方法
        return AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
            @Override
            public Boolean run() throws IOException {
                return socketChannel.connect(remoteAddress);
            }
        });
    } catch (PrivilegedActionException e) {
        throw (IOException) e.getCause();
    }
}

作用:

  • 封装 JDK 的 SocketChannel.connect() 方法
  • 使用 AccessController 处理安全权限

JDK SocketChannel.connect() 的行为:

  • 阻塞模式:会阻塞直到连接成功或失败
  • 非阻塞模式(Netty 使用的):
    • 立即返回 true:连接立即成功(比如连接本地地址)
    • 立即返回 false:连接正在进行中,需要等待 OP_CONNECT 事件

三、Connect 未立即完成的后续处理

当 doConnect() 返回 false 时,连接正在进行中,后续流程如下:

3.1 注册 OP_CONNECT 事件

// 在 NioSocketChannel.doConnect() 中
if (!connected) {
    selectionKey().interestOps(SelectionKey.OP_CONNECT);
}

3.2 NioEventLoop 监听事件

// NioEventLoop.run() 方法会不断循环
protected void run() {
    for (;;) {
        // 1. select 操作,等待事件就绪
        select(wakenUp.getAndSet(false));
        
        // 2. 处理就绪的事件(包括 OP_CONNECT)
        processSelectedKeys();
        
        // 3. 处理任务队列
        runAllTasks();
    }
}

3.3 处理 OP_CONNECT 事件

// NioEventLoop.processSelectedKey()
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
    int ops = k.interestOps();
    // 移除 OP_CONNECT,避免重复触发
    ops &= ~SelectionKey.OP_CONNECT;
    k.interestOps(ops);
    
    // 调用 unsafe.finishConnect() 完成连接
    unsafe.finishConnect();
}

3.4 完成连接:finishConnect()

// AbstractNioChannel.AbstractNioUnsafe
@Override
public final void finishConnect() {
    try {
        boolean wasActive = isActive();
        // 调用 JDK 的 finishConnect() 完成连接
        doFinishConnect();
        // 完成 Promise,触发监听器
        fulfillConnectPromise(connectPromise, wasActive);
    } catch (Throwable t) {
        fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
    } finally {
        // 取消超时定时器
        if (connectTimeoutFuture != null) {
            connectTimeoutFuture.cancel(false);
        }
        connectPromise = null;
    }
}

3.5 触发 channelActive 事件

// AbstractNioChannel.AbstractNioUnsafe.fulfillConnectPromise()
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
    // ...
    
    // 设置 Promise 为成功
    promise.trySuccess();
    
    // 如果之前不是 active,现在变成 active 了
    if (!wasActive && isActive()) {
        // 触发 channelActive 事件(Inbound 事件,从 head 开始传播)
        pipeline().fireChannelActive();
    }
}

3.6 开始监听 OP_READ

// DefaultChannelPipeline.HeadContext.channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) {
    // 向后传播 channelActive 事件
    ctx.fireChannelActive();
    
    // 开始读取数据(注册 OP_READ 事件)
    readIfIsAutoRead();
}

四、完整流程图

用户代码: bootstrap.connect("localhost", 8080)
         ↓
Bootstrap.connect(String, int)
         ↓
Bootstrap.connect(SocketAddress)
         ↓
Bootstrap.doResolveAndConnect()
         ├─→ initAndRegister() [创建 Channel、注册到 Selector]
         └─→ doResolveAndConnect0()
                  ↓
         Bootstrap.doConnect() [提交任务到 EventLoop]
                  ↓
         AbstractChannel.connect()
                  ↓
         DefaultChannelPipeline.connect() [从 tail 开始]
                  ↓
         TailContext.connect()
                  ↓
         findContextOutbound() [查找下一个 Outbound handler]
                  ↓
         自定义 OutboundHandler.connect() [如果有]
                  ↓
         HeadContext.connect()
                  ↓
         AbstractNioUnsafe.connect()
                  ↓
         NioSocketChannel.doConnect()
                  ↓
         SocketUtils.connect() [JDK SocketChannel.connect()]
                  ↓
         ┌────────┴────────┐
         ↓                 ↓
    立即成功          正在进行中
         ↓                 ↓
  fulfillConnectPromise   注册 OP_CONNECT
         ↓                 ↓
  触发 channelActive    NioEventLoop 监听
         ↓                 ↓
  注册 OP_READ         收到 OP_CONNECT 事件
                          ↓
                     finishConnect()
                          ↓
                  fulfillConnectPromise
                          ↓
                  触发 channelActive
                          ↓
                     注册 OP_READ

五、关键点总结

5.1 Connect 的前置条件

  1. 必须先 register:Channel 必须先注册到 Selector,才能进行 connect
  2. 在 EventLoop 线程中执行:connect 操作必须在 EventLoop 线程中执行

5.2 Connect 的两种结果

结果返回值场景后续处理
立即成功true连接本地地址直接完成 Promise,触发 channelActive
正在进行中false连接远程地址(常见)注册 OP_CONNECT,等待事件,调用 finishConnect()

5.3 Pipeline 的传播方向

  • Outbound 事件(connect、bind、write):tail → head
  • Inbound 事件(channelActive、channelRead):head → tail

5.4 异步处理机制

  1. 提交任务到 EventLoop:eventLoop().execute()
  2. 注册感兴趣的事件:selectionKey().interestOps(OP_CONNECT)
  3. EventLoop 监听事件:select() + processSelectedKeys()
  4. 事件就绪后处理:finishConnect()
  5. 触发回调:fulfillConnectPromise() → channelActive

5.5 超时处理

// 使用 EventLoop 的定时任务功能
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
    @Override
    public void run() {
        // 超时后,尝试将 Promise 设置为失败
        if (connectPromise != null && connectPromise.tryFailure(cause)) {
            close(voidPromise());
        }
    }
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);

六、常见问题

Q1:为什么 connect 要在 EventLoop 线程中执行?

A:

  1. 线程安全:避免多线程并发操作 Channel
  2. 事件顺序:保证事件按提交顺序执行
  3. 简化模型:所有 IO 操作都在同一个线程中,无需加锁

Q2:为什么 connect 可能立即成功?

A:

  • 连接本地地址(localhost、127.0.0.1)时,不需要网络传输,可以立即完成
  • 某些操作系统优化,对于本地连接会立即返回成功

Q3:如果 connect 超时会怎样?

A:

  1. 定时器触发,调用 connectPromise.tryFailure()
  2. 如果 Promise 还未完成,设置为失败状态
  3. 关闭 Channel
  4. 用户通过 ChannelFuture 可以获取到超时异常

Q4:connect 失败后会自动重试吗?

A:

  • Netty 本身不会自动重试
  • 需要用户自己实现重试逻辑,通常通过监听 ChannelFuture 实现
ChannelFuture future = bootstrap.connect("localhost", 8080);
future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture f) {
        if (!f.isSuccess()) {
            // 连接失败,延迟后重试
            f.channel().eventLoop().schedule(() -> {
                bootstrap.connect("localhost", 8080);
            }, 5, TimeUnit.SECONDS);
        }
    }
});

七、总结

Connect 过程的核心要点:

  1. 调用链路长但清晰:从 Bootstrap 到 Pipeline 到 Unsafe 到 JDK
  2. 异步处理是常态:通过 EventLoop + Selector + interestOps 实现
  3. Pipeline 是核心:所有操作都通过 Pipeline 传播,支持用户自定义处理
  4. 两种结果处理:立即成功和正在进行中,分别有不同的处理流程
  5. 完善的异常处理:超时、取消、失败都有相应的处理机制

理解 connect 过程,就理解了 Netty 异步 IO 的核心机制!

c#中connect函数_(十一)socket、connect、bind函数详解 一、socket函数1、头文件:#include /* See NOTES */#include 2、函数原型:int socket(int domain, int type, int protocol);socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。dimain:域,网络域,网络地址范围(... 阅读详情

相关推荐

Fiddler教程:fiddler抓包时,出现的 tunnel to

解读tunnel to 在抓包的时候,有时候会遇到很多的tunnel to,图标是一把锁的形状,使用的方法是Connect,如下图: 在某主的优快云博客中,查到的资料如下: connect是为了建立http tunnel,connect是http众多方法中的其中一种,它跟post、get、put、options方法是并列的。但是它的使用场景很特殊。只有在受限制的网络环境中(防火...

雨水的早晨的博客 11万+

《UNIX网络编程》卷一第四章学习笔记

《UNIX网络编程》卷一第四章学习笔记 4.2 socket函数 #include<sys/socket.h> int socket(int family,int type,int protocol); //返回值:出错返回-1,否则返回非负描述符  famliy参数指明协议族: 取值 含义 AF_INET IPV4协议 AF_INET6 IPV6协议 AF_LOCAL Unix域协议 AF_ROUTE 路由套接字 AF_KEY 密钥套接字  type参

fakepku的博客 254

电影票购票小程序搭建(源码部署)

答:为用户提供便捷的购票方式,包括授权登录、个性化推荐、在线选座、订单管理等功能,同时在界面设计上注重用户友好性,简化用户的购票流程。答:通过展示电影院的优惠活动和票价信息,同时提供电影推荐功能,根据用户的历史购票记录和电影评分推荐个性化的电影列表,吸引用户购票。编写页面逻辑代码:使用JavaScript编写小程序页面的逻辑代码,实现电影列表的获取、电影详情的展示、座位选择的逻辑等。设计小程序界面:使用WXML和WXSS编写小程序的界面代码,实现电影列表的展示、电影详情的样式、座位选择页面的布局等。

2301_79072569的博客 2068

TCP连接的状态详解以及故障排查(三)

当Server端调用socket函数调用时,相当于Server端产生了一个处于Closed状态的监听套接字,Server端调用bind操作,将监听套接字与指定的地址和端口关联,然后又调用listen函数,系统会为其分配未完成队列和完成队列,此时的监听套接字可以接受Client的连接,监听套接字状态处于LISTEN状态。这是三次握手过程中的报文1。:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

ybigbear的博客 1010

从零搭建医疗化验单识别系统:深度学习+OCR实战教程(附数据集)

本文详细介绍了如何从零开始构建一个医疗化验单智能识别系统。通过融合深度学习和OCR技术,系统能够应对版式多样、内容专业的医疗单据,实现从文本检测、识别到关键信息结构化抽取的全流程。文章提供了数据集处理、模型选型、训练微调及工程化部署的实战指南,并分享了避坑经验与性能优化策略,为开发者解决医疗信息化痛点提供了完整的技术方案。

k9l0m1的博客 493

随机子空间集成方法原理与scikit-learn实践

集成学习通过组合多个基学习器的预测结果来提升模型性能,是机器学习中的核心方法之一。随机子空间方法作为集成学习的重要变体,其核心原理是通过对特征空间进行随机子采样来增加模型多样性。这种方法特别适合处理高维数据,能有效缓解维度灾难问题,常与决策树、K近邻等对特征选择敏感的基学习器配合使用。在scikit-learn中,可以通过BaggingClassifier和BaggingRegressor实现随机子空间集成,关键参数包括max_features和n_estimators。实际应用中,该方法在金融风控、医疗诊

weixin_33773084的博客 377

socket、listen 等函数的打电话隐喻

如果用电话来隐喻 TCP 的连接过程中用到的函数, 那么:1. socket 函数意味着有电话可用;2. bind 告诉其他用户自己的号码, 以便其他用于打给它;3. listen 是电话的响铃, 当电话来的时候会通知你;4. connect 是主动打别人的电话,前提是你知道号码;5. accept 返回一个新的连接套接字, 用于并行的操作。 转载于:https://www.cnblo...

baochuanying9531的博客 168

【新华三】DHCP+TFTP+ZTP自动开局实战

设备运行ZTP功能,可以自动获取并自动加载开局文件,实现设备的免现场配置和部署,从而降低人力成本,提升部署效率。使用TFTP服务器工具进行文件的上传和下载,网上很多TFTP server软件,大家可以自行下载。3CDaemon等,可以根据使用自取所需。大家应该都了解ZTP,但是目前发现除了控制器之外仅使用ZTP开局的兄弟并不是很多,因此分享此过程方便大家批量开局,留出更多时间学习更多技术。电脑打开DHCP服务器,TFTP服务器,并通过网线连接到设备的管理口,若设备较多可以通过带外交换机等进行连接,

五大连池的镜子 4983

udp通讯中的connect()和bind()函数

udp是一个基于无连接的通讯协议,通讯基本模型如下: 可以看出,不论是在客户端还是服务器,connect()似乎用不上,bind()在客户端也用不上,但是事实并非如此。1. udp客户端使用connect()函数udp客户端建立了socket后可以直接调用sendto()函数向服务器发送数据,但是需要在sendto()函数的参数中指定目的地址/端口,但是可以调用connect()函数先指明目的地

echo_bright_的博客 5万+

Comsol 管道导波裂纹检测:从建模到求解的完整流程解析

本文详细解析了使用Comsol Multiphysics进行管道导波裂纹检测的完整仿真流程。从几何建模、材料定义、物理场设置到网格划分与求解后处理,系统阐述了如何构建高保真虚拟模型以模拟导波与裂纹的相互作用。重点分享了激励信号设置、频散效应处理及参数化扫描等实战技巧,旨在帮助工程师与研究人员高效优化无损检测方案,降低实验成本。

weixin_29207439的博客 74

Oracle EBS Interface/API(29) - AP发票API

续上编文章《Oracle EBS Interface/API(28) - 客制化开发AP付款API》背景,介绍如调用官方提供标准AP发票API调用 用户界面 Invoices(Payables Manager->Invoices->Entry->Invoices) Open Interface Invoices(Payables Manager->Invoices->Entry->Open Interface Invoices) 快速参考 参考点 内容.

陈显平的专栏-Oracle EBS 1151

socket学习一、socket、bind/connect、listen函数详解

Socket本意是插座,在计算机通信领域中被翻译为“套接字”,是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。通过Socket,两台计算机可以通过网络进行信息的传递。

个人工作中学到的专业技术与职业发展知识笔记 3682

【DeepSeek R1构建本地RAG知识库】数据结构化清洗-知识图谱制作、使用、dify集成

本文探讨了在构建本地RAG(Retrieval-Augmented Generation)系统时,如何通过引入知识图谱来增强系统的知识理解能力。传统的向量数据库和嵌入模型虽然能处理语义匹配,但无法有效捕捉知识间的关联性。知识图谱通过“实体-关系-属性”的结构化表示,能够更好地支持推理、推荐和多跳问答等高级功能。文章详细介绍了知识图谱的基本概念、制作思路,以及如何使用Neo4j图数据库进行知识图谱的构建和查询。通过实例展示了从PDF文件解析到知识图谱入库的全过程,强调了知识图谱在提升RAG系统性能中的重要作用

少说,多做 567

设置connect超时时间_Netty 源码解析(九): connect 过程和 bind 过程分析

原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注今天是猿灯塔“365篇原创计划”第九篇。接下来的时间灯塔君持续更新Netty系列一共九篇Netty 源码解析(一): 开始Netty 源码解析(二): Netty 的 ChannelNetty 源码解析(三): Netty的 Future 和 PromiseNetty 源码解析(四): Netty 的 ChannelPipelineNetty...

weixin_39955953的博客 287

微信电脑版本不会自动下载图片吗?——没有找到解决方法。——只会默认下载doc等文件,图片不行,需要自己另存为——有什么办法解决吗?

微信电脑版本不会自动下载图片吗?——没有找到解决方法。——只会默认下载doc等文件,图片不行,需要自己另存为——有什么办法解决吗?此外,用户还可以选择将接收到的图片、视频和文件保存到指定的文件夹中。‌用户可以在微信设置中调整这一设置。

qq_15963745的博客 5092

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

connect和bind过程详解(二)

connect和bind过程详解(二)

java_rutu的博客 182

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

使用bind()和connect()函数

socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理;而客户端要用 connect() 函数建立连接。 bind() 函数 bind() 函数的原型为: int bind(int sock, struct sockaddr *addr, sockl

芝麻软件工作室的专栏 1万+

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

socket、listen、connect、bind、accept函数详解

socket函数 为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议 #include<sys/socket.h> int socket(int family,int type,int protocol) 返回成功则返回非负文件描述符,若出错则为-1 其中family参数指明协议族,它是某些常量AF_INET:IPv4、AF_INET:Ipv6等...

Patrick的博客 2133

Netty+源码解析(九)_+connect+过程和+bind+过程分析

Netty 源码分析系列: Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty 源码解析(三): Netty 的 Future 和 Promise Netty 源码解析(四): Netty 的 ChannelPipeline Netty 源码解析(五): Netty 的线程池分析 Netty 源码解析(六): Channel 的 register 操作 Netty 源码解析(七): NioEventLoop 工作流程 Netty 源码解析(八): 回

不朽丶 296

socket、connect、bind、listen函数

如下给出了在一对 TCP 客户和服务器进程之间发生的一些典型事件的时间表。

YangLei253的博客 2163

UDP协议为什么要使用connect和bind

转载自https://www.cnblogs.com/kex1n/p/8342416.html UDP中使用bind和connect的作用 1:UDP中可以使用connect系统调用 2:UDP中connect操作与TCP中connect操作有着本质区别。 TCP中调用connect会引起三次握手,client与server建立连结.UDP中调用connect内核仅仅把对端ip&por...

jtabibito's blog 1157

connect和bind

考虑以下情形:我们使用UDP写一个echo程序,kehu

GetnextWindow的专栏 2249

socket编程 -- socket、bind、accept、connect函数

socketsocket函数原型#include <sys/types.h> /* See NOTES */ #include <sys/socket.h>int socket(int domain, int type, int protocol);参数说明 domain: AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址 AF_INET6 与上面

未来之歌 1万+

Linux深入理解TCP协议(connect、bind、listen、accept)及其源码

文章目录TCP建立连接的三次握手过程探究使用Linux Socket api建立TCP连接的过程bind,listenconnect从源码角度分析TCP三次握手的过程对tcp_v4_connect的部分源码分析 主要探讨的问题就是connect及bind、listen、accept背后的三次握手相关问题 TCP建立连接的三次握手过程 首先还是老生常谈的三次握手问题,还是照惯例先来再温习一遍 第一次握手:客户端尝试连接服务器,向服务器发送syn(全称是同步序列编号)报文,syn=i,客户端进入SYN_SE

“逛丢一只鞋”的博客 5245
上一篇: ChannelPipeline事件传播详解-executionMask机制
下一篇: connect和bind过程详解(二)
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值