【死磕NIO】— 探索 SocketChannel 的核心原理

2023-10-26

大家好,我是大明哥,一个专注于【死磕 Java】系列创作的程序员。
死磕 Java 】系列为作者「chenssy」 倾情打造的 Java 系列文章,深入分析 Java 相关技术核心原理及源码。
死磕 Java :https://www.cmsblogs.com/group/1420041599311810560

前两篇文章我们分析了 Channel 及 FileChannel,这篇文章我们探究 SocketChannel的核心原理,毕竟下一个系列就是 死磕 Netty了。

聊聊Socket

要想掌握 SocketChannel,我们就必须先了解什么是 Socket。要想解释清楚 Socket,就需要了解下 TCP/IP。

注:本文重点在 SocketChannel,所以对 TCP和 Socket仅仅只做相关介绍,有兴趣的同学,麻烦自查专业资料

TCP/IP 体系结构

学过计算机网络的小伙伴知道,计算机网络是分层的,每层专注于一类事情。OSI 网路模型分为七层,如下:

OSI 模型是理论中的模型,在实际应用中我们使用的是 TCP/IP 四层模型,它对OSI模型重新进行了划分和规整,如下:

网络层次划分清楚了,那怎么传输数据呢?如下图:

计算机A首先在应用层将要发送的数据准备好,然后给传输层, 传输层的主要作用就是为发送端和接收端提供可靠的连接服务,传输层将数据处理完成后给网络层, 网络层的一个核心功能就是数据传输路径的选择。计算机A到计算机B有很多条路,网络层的作用就是负责管理下一步数据应该到那个路由器,选择好路径后,数据就到了网络接入层,该层主要负责将数据从一个路由器发送到另一个路由器。

上图是一个非常清晰的传输过程。但是我们思考两个个问题:

  1. 计算机A是怎么知道计算机B的具体位置的呢?
  2. 它又怎么知道将该数据包发送给哪个应用程序呢?

TCP/IP协议族已经帮我们解决了这个问题: IP地址+协议+端口

  • 网络层的“IP地址”唯一标识了网络中的主机:这样就可以找到要将数据发送给哪台主机了。
  • 传输层的“协议 + 端口”唯一标识主机中的应用程序:这样就可以找到要将数据发给那个应该程序了。

利用三元组(IP地址、协议、端口)就可以让计算机A确定将数据包发送给计算机B的应用程序了。

使用TCP/IP 协议的应用程序通常采用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言, 几乎所有的应用程序都是采用的 Socket

Socket

上面提到就目前而言,几乎所有的应用程序都是采用 Socket 来完成网络通信的。那什么是Socket呢?百度百科是这样定义的:

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

在TCP/IP四层模型中,我们并没有看到 Socket 影子,那它到底在哪里呢? 又扮演什么角色呢?

Socket 并不是属于 TCP/IP 模型中的任何一层,它的存在只是为了让应用层能够更加简便地将数据传输给传输层,应用层不需要关注TCP/IP 协议的复杂内容。我们可以将其理解成一个接口,一个把复杂的TCP/IP协议族隐藏起来的接口,对于应用层而言,他们只需要简单地调用 Socket 接口就可以实现复杂的TCP/IP 协议,就像设计模式中的门面模式( 将复杂的TCP\IP 协议族隐藏起来,对外提供统一的接口,是应用层能够更加容易地使用)。简单地说就是简单来说可以把 Socket理解成是应用层与TCP/IP协议族通信的抽象层、函数库

下图是 Socket一次完整的通信流程图:

上图设计到的Socket 相关函数:

  • socket():返回套接字描述符
  • connect():建立连接
  • bind():一个本地协议地址赋予一个套接字
  • linsten():服务器监听端口连接
  • accept():应用程序接受完成3次握手的客户端连接
  • send()recv()write()read():服务端与客户端互相发送数据
  • colse():关闭连接

探究SocketChannel

SocketChannel 是一个连接 TCP 网络Socket 的 Channel,我们可以认为它是对传统 Java Socket API的改进。它支持了非阻塞的读写。

SocketChannel具有如下特点

  1. 对于已经存在的socket不能创建SocketChannel。
  2. SocketChannel中提供的open接口创建的Channel并没有进行网络级联,需要使用connect接口连接到指定地址。
  3. 未进行连接的SocketChannle执行I/O操作时,会抛出NotYetConnectedException
  4. SocketChannel支持两种I/O模式:阻塞式和非阻塞式。
  5. SocketChannel支持异步关闭。如果SocketChannel在一个线程上read阻塞,另一个线程对该SocketChannel调用shutdownInput,则读阻塞的线程将返回-1表示没有读取任何数据;如果SocketChannel在一个线程上write阻塞,另一个线程对该SocketChannel调用shutdownWrite,则写阻塞的线程将抛出AsynchronousCloseException

SocketChannel 的使用

1. 创建SocketChannel

要想使用 SocketChannel我们首先得创建它。创建SocketChannel的方式有两种:

// 方式 1
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));

// 方式 2
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("www.baidu.com", 80));

2、连接校验

使用的SocketChannel必须是已连接的,如果使用一个未连接的SocketChannel,则会抛出 NotYetConnectedException。SocketChannel提供了四个方法来校验连接。

// 测试SocketChannel是否为open状态
socketChannel.isOpen();
// 测试SocketChannel是否已经被连接   
socketChannel.isConnected();
// 测试SocketChannel是否正在进行连接
socketChannel.isConnectionPending();
// 校验正在进行套接字连接的SocketChannel是否已经完成连接
socketChannel.finishConnect(); 

3、读操作

SocketChannel 提供了 read()方法用于读取数据:

public abstract int read(ByteBuffer dst) throws IOException;

public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;

public final long read(ByteBuffer[] dsts) throws IOException {
  return read(dsts, 0, dsts.length);
}

首先我们需要先分配一个 ByteBuffer,然后调用 read()方法,该方法会将数据从SocketChannel读入到 ByteBuffer中。

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

read()方法会返回一个 int 值,该值表示读取了多少数据到 Buffer 中,如果返回 -1,则表示已经读到了流的末尾。

4、写操作

调用 SocketChannel的write()方法,可以向 SocketChannel 中写数据。

public abstract int write(ByteBuffer src) throws IOException;

public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;

public final long write(ByteBuffer[] srcs) throws IOException {
    return write(srcs, 0, srcs.length);
}

5、设置 I/O 模式

SocketChannel 支持阻塞和非阻塞两种 I/O 模式,调用 configureBlocking()方法即可:

socketChannel.configureBlocking(false);

false 表示非阻塞,true 表示阻塞。

6、关闭

当使用完 SocketChannel 后需要将其关闭,SocketChannel 提供了 close()来关闭 SocketChannel 。

socketChannel.close();

SocketChannel 源码分析

上面简单介绍了 SocketChannel 的使用,下面我们再来详细分析 SocketChannel 的源码。SocketChannel 实现 Channel 接口,它有一个核心子类 SocketChannel,该类实现了 SocketChannel 的大部分功能。如下(图有删减)

创建 SocketChannel

上面提到通过调用 open()方法就可以一个 SocketChannel 实例。

    public static SocketChannel open() throws IOException {
        return SelectorProvider.provider().openSocketChannel();
    }

我们看到它是通过 SelectorProvider 来创建 SocketChannel 的,provider() 方法会创建一个 SelectorProvider 实例,SelectorProvider 是 Selector 和 Channel 实例的提供者,它提供了创建 Selector、SocketChannel、ServerSocketChannel 实例的方法,采用 SPI 的方式实现。 SelectorProvider 我们在讲解 Selector 的时候在阐述。

provider 创建完成后调用 openSocketChannel() 来创建 SocketChannel。

    public SocketChannel openSocketChannel() throws IOException {
        return new SocketChannelImpl(this);
    }

从这了就可以看出 SocketChannelImpl 为 SocketChannel 的实现者。调用 SocketChannelImpl 的构造函数实例化一个 SocketChannel 对象。

    SocketChannelImpl(SelectorProvider sp) throws IOException {
        super(sp);
        // 创建 Socket 并创建一个文件描述符与其关联
        this.fd = Net.socket(true);
        // 在注册 selector 的时候需要获取到文件描述符的值
        this.fdVal = IOUtil.fdVal(fd);
        // 设置状态为未连接
        this.state = ST_UNCONNECTED;
    }

fd:文件夹描述符对象。

fdVal:fd 的 value。

文件描述符简称 fd,它是一个抽象概念,在 C 库编程中可以叫做文件流或文件流指针,在其它语言中也可以叫做文件句柄(handler),而且这些不同名词的隐含意义可能是不完全相同的。不过在系统层,我们统一把它叫做文件描述符。

state:状态,设置为未连接。它有如下 6 个值

private static final int ST_UNINITIALIZED = -1;
private static final int ST_UNCONNECTED = 0;
private static final int ST_PENDING = 1;
private static final int ST_CONNECTED = 2;
private static final int ST_KILLPENDING = 3;
private static final int ST_KILLED = 4;
连接服务器:connect()

调用 Connect() 方法可以链接远程服务器。

    public boolean connect(SocketAddress sa) throws IOException {
        int localPort = 0;
        
        // 注意这里的加锁
        synchronized (readLock) {
            synchronized (writeLock) {
               // 确保当前 SocketChannel 是打开且未连接的
                ensureOpenAndUnconnected();
                InetSocketAddress isa = Net.checkAddress(sa);
                SecurityManager sm = System.getSecurityManager();
                if (sm != null)
                    sm.checkConnect(isa.getAddress().getHostAddress(),
                                    isa.getPort());
                // 这里的锁是注册和阻塞配置的锁
                synchronized (blockingLock()) {
                    int n = 0;
                    try {
                        try {
                            // 支持线程中断,通过设置当前线程的Interruptible blocker属性实现
                            begin();
                            // 
                            synchronized (stateLock) {
                               // 默认为 open, 除非调用了 close()
                                if (!isOpen()) {
                                    return false;
                                }
                                // 只有未绑定本地地址也就是说未调用bind方法才执行
                                if (localAddress == null) {
                                    NetHooks.beforeTcpConnect(fd,
                                                           isa.getAddress(),
                                                           isa.getPort());
                                }
                                // 记录当前线程
                                readerThread = NativeThread.current();
                            }
                            for (;;) {
                                InetAddress ia = isa.getAddress();
                                if (ia.isAnyLocalAddress())
                                    ia = InetAddress.getLocalHost();
                                // 调用 Linux 的 connect 函数实现
                                // 如果采用堵塞模式,会一直等待,直到成功或出现异常
                                n = Net.connect(fd,
                                                ia,
                                                isa.getPort());
                                if (  (n == IOStatus.INTERRUPTED)
                                      && isOpen())
                                    continue;
                                break;
                            }

                        } finally {
                            readerCleanup();
                            end((n > 0) || (n == IOStatus.UNAVAILABLE));
                            assert IOStatus.check(n);
                        }
                    } catch (IOException x) {
                        // 出现异常,关闭 Channel
                        close();
                        throw x;
                    }
                    synchronized (stateLock) {
                        remoteAddress = isa;
                        if (n > 0) {
                            // n > 0,表示连接成功
                            // 连接成功,更新状态为ST_CONNECTED
                            state = ST_CONNECTED;
                            if (isOpen())
                                
                                localAddress = Net.localAddress(fd);
                            return true;
                        }
                        // 如果是非堵塞模式,而且未立即返回成功,更新状态为ST_PENDING
                        // 由此可见,该状态只有非堵塞时才会存在
                        if (!isBlocking())
                            state = ST_PENDING;
                        else
                            assert false;
                    }
                }
                return false;
            }
        }
    }

该方法的核心方法就在于 n = Net.connect(fd,ia,isa.getPort()); 该方法会一直调用到 native 方法去:

JNIEXPORT jint JNICALL
Java_sun_nio_ch_Net_connect0(JNIEnv *env, jclass clazz, jboolean preferIPv6,
                             jobject fdo, jobject iao, jint port)
{
    SOCKADDR sa;
    int sa_len = SOCKADDR_LEN;
    int rv;
    //地址转换为struct sockaddr格式
    if (NET_InetAddressToSockaddr(env, iao, port, (struct sockaddr *) &sa,
                                  &sa_len, preferIPv6) != 0)
    {
      return IOS_THROWN;
    }
   //传入 fd 和 sockaddr,与远程服务器建立连接,一般就是 TCP 三次握手
   //如果设置了 configureBlocking(false), 不会堵塞,否则会堵塞一直到超时或出现异常
    rv = connect(fdval(env, fdo), (struct sockaddr *)&sa, sa_len);
    if (rv != 0) { 
        // 0 表示连接成功,失败时通过 errno 获取具体原因
        if (errno == EINPROGRESS) {  //非堵塞,连接还未建立(-2)
            return IOS_UNAVAILABLE;
        } else if (errno == EINTR) {  //中断(-3)
            return IOS_INTERRUPTED;
        }
        return handleSocketError(env, errno); //出错
    }
    return 1; //连接建立,一般TCP连接连接都需要时间,因此除非是本地网络,一般情况下非堵塞模式返回IOS_UNAVAILABLE比较多;
}
读数据:read()

SocketChannel 提供 read() 方法读取数据。

   public int read(ByteBuffer buf) throws IOException {
        synchronized (readLock) {
            // ...
            try {
                // ...
                for (;;) {
                    n = IOUtil.read(fd, buf, -1, nd);
                    if ((n == IOStatus.INTERRUPTED) && isOpen()) {
                        continue;
                    }
                    return IOStatus.normalize(n);
                }

            } finally {
                // ...
            }
        }
    }

核心方法就在于 IOUtil.read(fd, buf, -1, nd)

    static int read(FileDescriptor fd, ByteBuffer dst, long position,NativeDispatcher nd)
        throws IOException
    {
        if (dst.isReadOnly())
            throw new IllegalArgumentException("Read-only buffer");
        if (dst instanceof DirectBuffer)
            // 使用直接缓冲区读取数据
            return readIntoNativeBuffer(fd, dst, position, nd);

        // 当不是使用直接内存时,则从线程本地缓冲获取一块临时的直接缓冲区存放待读取的数据
        ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
        try {
            int n = readIntoNativeBuffer(fd, bb, position, nd);
            bb.flip();
            if (n > 0)
                // 将直接缓冲区的数据写入到堆缓冲区中
                dst.put(bb);
            return n;
        } finally {
            // 使用完成后释放缓冲
            Util.offerFirstTemporaryDirectBuffer(bb);
        }
    }

这里我们看到如果 ByteBuffer 是 DirectBuffer,则调用 readIntoNativeBuffer() 读取数据,如果不是则通过 getTemporaryDirectBuffer() 获取一个临时的直接缓冲区,然后调用 readIntoNativeBuffer()获取数据,然后将获取的数据写入 ByteBuffer 中。

    private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,long position, NativeDispatcher nd)
        throws IOException
    {
        int pos = bb.position();
        int lim = bb.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);

        if (rem == 0)
            return 0;
        int n = 0;
        if (position != -1) {
            n = nd.pread(fd, ((DirectBuffer)bb).address() + pos,rem, position);
        } else {
            n = nd.read(fd, ((DirectBuffer)bb).address() + pos, rem);
        }
        if (n > 0)
            bb.position(pos + n);
        return n;
    }

写数据 write()方法和 read()方法大致一样,大明哥这里就不在阐述了,有兴趣的小伙伴自己去研究下。

ServerSocketChannel 与 SocketChannel 原理大同小异,这里就不展开讲述了,下篇文章我们开始研究第三个组件: Selector

参考资料

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【死磕NIO】— 探索 SocketChannel 的核心原理 的相关文章

  • 【死磕NIO】— 探索 SocketChannel 的核心原理

    大家好 我是大明哥 一个专注于 死磕 Java 系列创作的程序员 死磕 Java 系列为作者 chenssy 倾情打造的 Java 系列文章 深入分析 Java 相关技术核心原理及源码 死磕 Java https www cmsblogs
  • 【死磕NIO】— 跨进程文件锁:FileLock

    大家好 我是大明哥 一个专注于 死磕 Java 系列创作的程序员 死磕 Java 系列为作者 chenssy 倾情打造的 Java 系列文章 深入分析 Java 相关技术核心原理及源码 死磕 Java https www cmsblogs
  • 【死磕 Java 基础】 — 你以为异常就是 try…catch ?那你天真了

    大家好 我是大明哥 个人网站 https www cmsblogs com 前言 我敢说对于很多小伙伴来说 他们以为在 Java 中异常就是 try catch 稍微有点儿意识的还会用下 throw new Exception 真的有这么简
  • 【死磕 Redis】----- Redis 数据结构: skiplist

    原文 https www cmsblogs com category 1391389927996002304 chenssy 关于跳跃表其实在 JUC 里面有一个并发容器就是利用跳跃表来实现的 ConcurrentSkipListMap 死
  • 【死磕 Java 基础】--- 我一口气自己就动手实现一个 LRU

    大家好 我是大明哥 个人网站 https www topjava cn LRU 即 Least Recently Use 直译为 最近最少使用 它是根据数据的历史访问记录来进行数据淘汰的 淘汰掉最先访问的数据 其核心思想是 如果数据最近被访
  • 【死磕 Java 基础】— 我同事一个 select 分页语句查出来了 3000W 条数据

    大家好 我是大明哥 某天我正在工位上听着 Victory 愉快地敲着 hello world 这感觉就像我写的代码能征服世界 突然运维给我打了一个电话 说我们某台服务器 OOM 了 要我过去看下 这感觉就像 xxx 你懂的 去运维室 登录服
  • 【死磕 NIO】— 深入分析Buffer

    大家好 我是大明哥 今天我们来看看 Buffer 上面几篇文章详细介绍了 IO 相关的一些基本概念 如阻塞 非阻塞 同步 异步的区别 Reactor 模式 Proactor 模式 以下是这几篇文章的链接 有兴趣的同学可以阅读下 死磕NIO
  • 【死磕 NIO】— ServerSocketChannel 的应用实例

    大家好 我是大明哥 一个专注于 死磕 Java 的程序员 死磕 Java 系列为作者 chenssy 倾情打造的 Java 系列文章 深入分析 Java 相关技术核心原理及源码 死磕 Java https www cmsblogs com
  • 【Java】【NIO】【04】通过SocketChannel读写Socket

    package easing common java demo import lombok SneakyThrows import java net InetSocketAddress import java nio ByteBuffer
  • Java NIO 的前生今世 之二 NIO Channel 小结

    Java NIO Channel 通常来说 所有的 NIO 的 I O 操作都是从 Channel 开始的 一个 channel 类似于一个 stream java Stream 和 NIO Channel 对比 我们可以在同一个 Chan
  • SocketOutputStream和SocketChannel write方法的区别和底层实现

    Java直接内存原理提到了SocketChannel write的实现原理 通过IOUtil write将java堆内存拷贝到了直接内存 然后再把地址传给了I O函数 那么 BIO 是怎么实现往socket里面写数据的呢 BIO Socke
  • Java ServerSocketChannel SocketChannel(回调)

    我正在努力学习Java 我想实现一个简单的联网连接 4 游戏以及聊天功能 我希望我的网络逻辑是非阻塞的 所以经过大量研究 我发现 SocketChannel 就是我重新调整我的需求后的样子 仍然没有意义的是 SocketChannel 中缺
  • java:单套接字读写操作。全双工

    我必须实现使用特定源端口发送数据 同时监听该端口 全双工 有谁知道如何在java上实现它 我尝试创建单独的线程来侦听套接字输入流 但它不起作用 我无法将 ServerSocket 和客户端套接字绑定到相同的源端口 并且与 netty 相同
  • NIO SocketChannel 读取超时? [复制]

    这个问题在这里已经有答案了 如果连接建立后一段时间内没有收到数据 设置超时关闭 NIO SocketChannel 的最佳方法是什么 Either 您正在使用一个Selector 在这种情况下 您可以选择一个可以使用的超时 如果超时 sel
  • 使用 SocketChannel Android 连接到 websocket

    我编写了连接到 websocket 服务器 服务器应用程序和 android 应用程序的 android 应用程序Autobahn网络套接字库 我可以成功连接服务器并与服务器交换消息 但一段时间后 20 30 分钟后 Android 应用程
  • SSL 和 SocketChannel

    理想情况下 我只需要一个简单的SSLSocketChannel 我已经有一个可以通过普通方式读取和写入消息的组件SocketChannel 但对于其中一些连接 我必须通过网络使用 SSL 然而 这些连接上的操作是相同的 有谁知道免费的SSL
  • PubNub最佳实践:如何管理私人房间?

    我正在学习 pubnub 并阅读了他们的文档 但我只是找不到如何管理多房间聊天框 默认情况下 任何人都可以收听某个频道 订阅和发布都很容易 我想要的是拥有一个主要的公共房间 到目前为止一切都很好 但任何人都应该能够与其他人私下交谈 而不会有
  • 仅通过一个 SocketChannel 发送多条消息

    读完本教程后 http rox xmlrpc sourceforge net niotut http rox xmlrpc sourceforge net niotut 这是关于编写非阻塞服务器和客户端 我阅读了NIO部分 跳过了SSL部分
  • 有没有办法取消注册套接字通道上的选择器

    这是一个非常简单的问题 但我发现需要注销一个俯瞰我的 java 套接字通道的选择器 SocketChannel client myServer accept forks off another client socket client co
  • Java NIO 服务器/客户端聊天应用程序 - 仅通过关闭套接字来发送数据

    朋友们 我是 Java NIO 的新手 目前正在尝试制作一个非阻塞聊天应用程序 客户端连接到服务器没有问题 客户端向服务器写入一条消息或几条消息 但服务器仅在客户端代码关闭 Socket 连接时才开始读取消息 因此必须在客户端代码中为每条消

随机推荐

  • R语言 第2章 数据对象与数据读写(2)

    重复序列的创建 rep as factor c yizi1 yinzi2 yinzi3 3 rep 是重复函数 它可以将某一向量重复若干次 使用格式 rep x times 1 length out NA each 1 其中x是预重复的序列
  • win10无法装载iso文件_win10系统打开iso格式文件的四种方法

    许多用户在win10系统中下载一个ISO文件 经常重装系统的小伙伴都知道 iso格式是一种光盘映像的文件格式 Windows10系统自带的虚拟光驱 无需任何软件就可以直接打开ISO文件 如果没有虚拟光驱该怎么打开 接下去分享win10系统打
  • python二元函数图像在线绘制_Python:绘制两个变量的二维函数

    我的功能是 def g R r return np sqrt 2 0 R r r R 1 np sqrt R np sqrt R np sqrt 2 0 r 1 r 1 r 1 0 np sqrt R np sqrt 2 0 1 R np
  • R手册(Tidy+Transform)--缺失处理(naniar and simputation)

    文章目录 naniar 缺失数据摘要 阴影矩阵 可视化缺失值变量分布关系 simputation make imputation simpler for missing data 缺失值是指粗糙数据中由于缺少信息而造成的数据的聚类 分组 删
  • cloudstack GuestNetwork Ingress-Egress rule

    Egress 1 创建 egress 规则 1 向management发出api命令 createEgressFirewallRulecmd 的create 方法 最终在cloud数据库firewall rules表中插一条state Ad
  • 阿里druid-spring-boot-starter 配置,个人整理以及遇到的问题(防止之后找不到)

    简介 什么是Druid Druid是阿里巴巴开源平台上的一个项目 整个项目由数据库连接池 插件框架和SQL解析器组成 该项目主要是为了扩展JDBC的一些限制 可以让程序员实现一些特殊的需求 比如向密钥服务请求凭证 统计SQL信息 SQL性能
  • 服务器上使用screen和linux的基本操作

    临时换源 pip install torch 1 7 1 i https pypi tuna tsinghua edu cn simple some package pip install torch 1 7 1 i http pypi d
  • MATLAB入门到精通(三):常用函数及数学应用

    合集如下 MATLAB入门到精通 一 简介及数据类型 MATLAB入门到精通 二 基本语句及绘图 MATLAB入门到精通 三 常用函数及数学应用 十一 常用函数 11 1 随机数函数 11 1 1 rand 函数 rand 函数用来产生均匀
  • 创建一个React项目实现一个计算器

    使用环境react脚手架 node js create react app 文件名 配置完这些就让我们开始把 count js import React Component from react import store from redu
  • mybatis中的typeAlias

    mybatis 的 xml 文件中需要写类的全限定名 较繁琐 可以配置自动扫描包路径给类配置别名 有两种配置方式 方式一 mybatis config xml 中配置
  • AB32VG1项目之智能晾衣架

    智能晾机架项目 开发过程 前期准备 分离工程 导入工程 安装包 安装最近的rt thread 包 AB32VG1的 SDK包 RISC V GCC工具链 下载 硬件搭建 开发板上的3 3V能否可用的问题 大体的硬件规划 软件设计 控制逻辑设
  • 关于Unrecognized Windows Sockets error: 5: socket write error 错误

    最近有个需求是从A数据库读取数据导入到B数据库 demo的数据量也就几万条 但是遇到了一个非常罕见的问题 后端框架是mybatis plus spring boot 在insertBatch到数据库B时 没有立即报错 而是执行插入了几百条数
  • 小程序通过webView打开H5页面并传参(包含webView业务域名配置)、H5页面实现返回小程序并实现传参

    小程序内嵌webview实现跳转 传参 1 小程序通过webView打开H5页面并传参 2 H5接收小程序传参 H5返回小程序并实现传参 小程序接收H5传参 目录 一 小程序通过webView打开H5页面并传参 1 业务域名 2 在小程序中
  • (转)认识SAP SD销售模式之跨公司销售

    跨公司销售 销售订单的发货工厂对应的公司和销售组织对应的公司不同 比如 9801公司为销售性公司 9901为生产性的公司 当公司9801接到订单后 直接从9901公司发货 如果不通过跨公司销售 需要9801像9901公司下虚拟的采购订单 然
  • win10 下 Linux使用方法笔记

    最近想学习一下比特币源码 官方推荐是在Linux系统下学习 且推荐在win10 下的Linux系统进行编译运行 所以下面将学习过程记录一下 1 参考了这篇文章中的方法 进行安装WSL https www cnblogs com JettTa
  • agoda获取酒店数据

    最近改了改代码 正好解决了一些报错问题 更新出来 个别处会加蜜 数据库以及线程控制 from DBUtils PooledDB import PooledDB import requests import demjson import ti
  • 堆和栈的区别

    1 1内存分配方面 堆 一般由程序员分配释放 若程序员不释放 程序结束时可能由OS回收 注意它与数据结构中的堆是两回事 分配方式是类似于链表 可能用到的关键字如下 new malloc delete free等等 栈 由编译器 Compil
  • leetcode622-设计循环队列

    本题重点 1 选择合适的数据结构 2 针对选择的数据结构判断 空 和 满 这两点是不分先后次序的 在思考时应该被综合起来 事实上 无论我们选择链表还是数组 最终都能实现题中描述的 循环队列 的功能 只不过选择不同结构时 我们面临和需要解决的
  • 不是一个PDF文件或该文件已损坏

    之前用公司电脑打开PDF文档的时候 出现了这样的一种现象 就是提示格式错误 不是一个PDF文件或该文件已被损坏 有三种解决方法 1 有可能是电脑上自带的PDF阅读软件版本太低 出现了不兼容的现象 换个最新的PDF阅读器吧 我用了福昕阅读器很
  • 【死磕NIO】— 探索 SocketChannel 的核心原理

    大家好 我是大明哥 一个专注于 死磕 Java 系列创作的程序员 死磕 Java 系列为作者 chenssy 倾情打造的 Java 系列文章 深入分析 Java 相关技术核心原理及源码 死磕 Java https www cmsblogs