如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6

2023-11-13

如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6

如何巧妙地设计代码结构

陈 鲁, 软件工程师, IBM
孙 妍, 软件工程师, IBM

 

简介: 当今的网络主流是 IPv4 网络,但随着 IP 地址的日益短缺,IPv6 网络开始渐渐盛行,因此传统的网络编程也需要做一些改进来适应 IPv6 和 IPv4 共存的网络环境。 本文介绍了一种设计模式来根据用户输入的地址或者域名建立合适的网络连接,并且屏蔽了网络连接细节,提供给用户一个统一的接口进行二次开发。 在文中还给出了一个基于 OpenSSL https 安全连接的应用来说明该方法的使用细节。

 

现代网络中,IPv4, IPv6 共存的情况日益增加,而这两种协议的地址格式,地址解析的 API 各不同,程序员必须面对如下两个问题并且合理地解决这些问题。

  1. 怎么准确识别用户输入的地址或者域名是属于 IPv4 网络还是 IPv6 网络? 
  2. 怎么屏蔽网络连接细节,提供给用户一个统一的接口? 

目前我们使用的第二代互联网 IPv4 技术,它的最大问题是网络地址资源有限,IPv6 是“Internet Protocol Version 6”的缩写,它是 IETF 设计的用于替代现行版本 IP 协议 -IPv4- 的下一代 IP 协议。与 IPV4 相比,IPv6 具有更大的地址空间。IPv4 中规定 IP 地址长度为 32 位;而 IPv6 中 IP 地址的长度为 128 位。

在 IPv4 网络下,网络编程主要依靠的是 socket 连接。在客户端,其基本步骤如下,创建一个 socket,使用 socket 连接服务器,最后通过 TCP 或者 UDP 协议进行数据读写。如果把这套方法移植到 IPv6 网络下,就需要在原来的基础上引入新的协议族、新的数据结构以及新的地址域名转换函数等。具体的一些差异如 图 1所示:



IPv4 与 IPv6 区别 

在这里要稍微介绍下 getaddrinfo()函数,它提供独立于协议的名称解析。函数的前两个参数分别是节点名和服务名。节点名可以是主机名,也可以是地址串 (IPv4 的点分十进制数表示或 IPv6 的十六进制数字串 )。服务名可以是十进制的端口号,也可以是已定义的服务名称,如 ftp、http 等。函数的第三个参数 hints 是 addrinfo 结构的指针,由调用者填写关于它所想返回的信息类型的线索。函数的返回值是一个指向 addrinfo 结构的链表指针 res。详见 图 2



getaddrinfo 函数说明 

getaddrinfo 函数之前通常需要对以下 6 个参数进行以下设置:nodename、servname、hints 的 ai_flags、ai_family、ai_socktype、ai_protocol。在 6 项参数中,对函数影响最大的是 nodename,sername 和 hints.ai_flag。而 ai_family 只是有地址为 v4 地址或 v6 地址的区别。而 ai_protocol 一般是为 0 不作改动。其中 ai_flags、ai_family、ai_socktype。说明如 图 3所示:



getaddrinfo 参数说明 

getaddrinfo 函数在 IPv6 和 IPv4 网络下都能实现独立于协议的名称解析,而且它返回的指向 addrinfo 结构的链表中会存放所有由输入参数 nodename 解析出的所有对应的 IP 信息,包括 IP 地址,协议族信息等。所以只要对 const struct addrinfo* hints 进行一些配置,就可以利用这个函数来识别连接目标的网络协议属性,进而根据其网络协议族而进行准确的连接操作。这样就解决了我们提出的第一个问题。具体的函数实现如下 清单 1所示:



				
 BaseSocket* BaseSocket::CreateSmartSocket(char* ipaddr) 
 { 		
    struct addrinfo *answer, hint, *curr; 
    bzero(&hint, sizeof(hint)); 
    hint.ai_family = AF_UNSPEC; 
    hint.ai_socktype = SOCK_STREAM; 
    char ipstr2[128]; 
    struct sockaddr_in  *sockaddr_ipv4; 
    struct sockaddr_in6 *sockaddr_ipv6; 
    	    
    int ret = getaddrinfo(ipaddr, NULL,&hint, &answer); 
    if (ret != 0) { 
        return NULL; 
    } 
    DeleteSmartSocket(); 

    for (curr = answer; curr != NULL; curr = curr->ai_next) { 
        switch (curr->ai_family){ 
            case AF_UNSPEC: 
                //do something here 
                break; 
            case AF_INET: 
                sockaddr_ipv4 = reinterpret_cast<struct sockaddr_in *>( curr->ai_addr); 
                inet_ntop(AF_INET, &sockaddr_ipv4->sin_addr, ipstr2,sizeof(ipstr2)); 
                smartSocketmap[typeIPv4]=new  SocketV4(ipstr2); 
                break; 
            case AF_INET6: 
                sockaddr_ipv6 = reinterpret_cast<struct sockaddr_in6 *>( curr->ai_addr); 
                inet_ntop(AF_INET6, <sockaddr_ipv6->sin6_addr, ipstr2,sizeof(ipstr2)); 
                smartSocketmap[typeIPv6]=new  SocketV6(ipstr2); 
                break; 
        } 
    } 	    
    freeaddrinfo(answer); 
    if(!smartSocketmap.empty()) 
    { 
        smartSocket=new BaseSocket(); 
    } 

    return smartSocket; 
 } 
 

 

对于用户来说,他们只想实现网络连接,而并不希望了解太多网络连接上冗繁的细节。如何屏蔽 IPv4 和 IPv6 网络的差异性,让用户使用统一的函数接口来完成操作,就成为我们的第二个课题。 程序中申明了一个基类叫 BaseSocket,继承于它的两个子类 SocketV4 和 SocketV6 分别负责有关 IPv4、IPv6 网络环境下的各种操作。详见 图 4



类继承图 

在设计 BaseSocket 类的时候,并没有把它作为一个单纯的基类来使用,而是把它设计成了一个 SocketV4 和 SocketV6 的代理类。我们都知道,C++ 支持向上类型转换(取一个对象的地址,并将其作为基类的地址使用),结合虚函数能够实现多态性,我们就在这里使用一个基类的指针使其指向不同的子类实例,并把这些指针放到一个容器内。这样设计的初衷是希望外部使用者只使用类的公共接口,享受类的服务,而无需关注类的内部实现细节。具体来说,就是在 IPv4、IPv6 同时存在的网络环境下,用户只需要享用 BaseSocket 类提供出的公共服务,而无需关注具体网络环境下网络操作的差异性。 为了达到上述的目的,BaseSocket 类的设计主要做了了以下几点处理:

  1. 把构造和析构函数隐藏起来(单件模式)

    在 BaseSocket 的函数申明中,通常作为类公共成员的构造函数和析构函数被塑造成了 protected 成员函数。而开放给用户创建真正操作对象的函数却是 CreateSmartSocket()。CreateSmartSocket() 函数会动态地根据网络环境创建合适的子类 SocketV4 或者 SocketV6,使用的方法就是调用上文中提到的 getaddrinfo() 函数。生成的子类对象都存储在静态 smartSocketmap 中。这样设计的原因是在于如果不这样做,用户就必须根据不同的网络来创建属于 IPv4 或者 IPv6 网络的 socket 子类,然后分别调用他们的成员函数,这样繁琐又不利于用户代码的维护和扩展。smartSocketmap 以这样的设计为用户构建对象创作的一个统一的接口,在不同网络下,只需要维护一套统一的代码,而无需为不同网络下的实现细节而费神。

  2. 向用户提供代理对象

    在 BaseSocket 类中申明了两个 BaseSocket 类型的指针,一个是 smartSocket,另外一个是 m_cursocket。BaseSocket* smartSocket 是一个静态的全局代理指针,用户只通过它来进行网络操作。在客户程序中,只存在一份由 CreateSmartSocket() 函数创建的 smartSocket 的副本,这是因为在每次需要网络连接时网络环境相应是固定的,不会由 v4 网络突然转变成 v6 网络,一个副本在运行时已经满足使用需求。CreateSmartSocket() 函数会先去侦测存储空间堆上是否已经存在 smartSocket 指针,存在的话就会调用 DeleteSmartSocket() 删除之前创建的副本,然后再创建一个新的 smartSocket 指针,提供给用户使用。而 m_cursocket 是指向真正操作对象(子类)的指针。值得注意的是,m_cursocket 指针是隐藏在 BaseSocket 类中的,而 smartSocket 正是 BaseSocket 类为 m_cursocket 封装的一层代理指针。用户所知的仅仅是调用了 smartSocket 的某个成员函数,而实际上,程序通过把 m_cursocket 定位到 map smartSocketmap 中的某一项,获得了真正的 SocketV4 或者 SocketV6 对象。




    对象结构图 




    获取 smartSocket 对象流程图 

    图 5和 图 6就展示了程序如何根据用户输入的地址信息判断网络类型,继而创建 smartSocket 对象的过程。

  3. 使用虚函数(实现多态性)

    在基类中,主要操作的函数都被申明为虚函数。如果编译器发现一个类中有被声明为 virtual 的函数,就会为其生成一个虚函数表,也就是 VTABLE。VTABLE 实际上是一个函数指针的数组,每个虚函数占用这个数组的一个位置。派生类有自己的 VTABLE,但是派生类的 VTABLE 与基类的 VTABLE 有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候,编译器还会在每个实例的内存布局中增加一个 vptr 字段,该字段指向本类的 VTABLE。C++ 对于虚函数的调用采用晚捆绑,从而能够实现多态性。 在程序中,m_cursocket 虽然是一个基类指针,但它指向的却是一个子类对象地址。由于这样的转换是子类向上转换,所以是安全的。指向正确的子类对象后,如果需要调用成员函数,就能通过本实例中的 vptr 指针指向本类的 VTABLE,由此获得正确的子类成员函数的地址来进行操作。




    利用 smartSocket 对象进行网络连接流程图 

图 7描述了 m_cursocket 如何进行类型转换,获得准确的子类对象,并且调用子类 Connect 函数的过程。

综上所述,通过以上三点,就可以降低用户程序和网络操作 Socket 部分的耦合性。让用户容易地实现他们所需要的网络连接,而不必要太关注网络环境的细节。同样,因为耦合性降低,有关 Socket 代码部分的更新和维护也相对方便,不会牵一发而动全身。


 

下面我们展示一个网络连接实例,在这个实例中,我们会使用到 SSL 连接。众所周知,有些 server 或者网站会启用 SSL 进行安全连接,那么对于这一类的网络连接就不是简单的使用 socket 可以解决的,我们必须借用 OpenSSL 来帮助我们实现。通常我们的底层数据是用 OpenSSL 的 BIO 对象来处理的,借助 BIO_new_ssl(), BIO_new_accept() 等函数轻松实现 IPv4 环境下的网络安全连接。然而这些方法在 IPv6 的环境下却没有实现很好的支持。为此,我们需要另辟蹊径来达成我们的目标。经过一段时间对 OpenSSL 文档的研究,我们发现以下方法既可以实现我们安全连接的目的,又可以同时支持 IPv4 和 IPv6 两种网络环境,具有比较好的可扩展性。这个方法十分简单,那就是手工创建一个 socket,该 socket 连接了一个 IPv6 或者 IPv4 地址,然后将 socket 绑定到某个 SSL 对象上就可以实现 SSL 的连接了。



  1. 网络连接

    这个部分首先调用 BaseSocket::GetSmartSocket()创建了一个 HTTPsSocket,HTTPsSocket 得到的就是代理指针 smartSocket 的地址,然后试图调用 HTTPsSocket->sockConnect() 连接 SSL 端口。从调用过程可见用户根本不用关心他现在所处的网络是 IPv4 还是 IPv6 网络,只要调用统一的函数接口就可以了。如 清单 2所示。




    						
     
     /* Create a HTTPsSocket */ 
     if((HTTPsSocket=BaseSocket::GetSmartSocket())==NULL) 
        HTTPsSocket =BaseSocket::CreateSmartSocket(const_cast<char *>(SPName.c_str())); 
     if(HTTPsSocket==NULL) 
     { 
        return -1; 
     } 
    
     /* Connect the HTTPsSocket to SSLport */ 
     if (HTTPsSocket->isConnected()) 
     { 
        HTTPsSocket->disconnect(); 
     } 
     if(HTTPsSocket->sockConnect(SSLport)) 
     { 
        return SOCKETERR; 
     } 
     // END OF CONNECT TO THE SSLport 
    			
  2. 初始化 SSL

    这里是为了实现安全连接,对 openSSL 对象进行创建和初始化配置。如下 清单 3所示。




    						
     /* Lets get nice error messages */ 
     SSL_load_error_strings(); 
    
     /* Setup all the global SSL stuff */ 
     SSL_library_init(); 	
     ctx=SSL_CTX_new(SSLv23_client_method()); 
     if(ctx==NULL) 
     { 
        return -1; 
     } 
    
     /* Lets make a SSL structure */ 
     if (!(ssl = SSL_new(ctx))) 
     { 
        return -1; 
     } 
    			
  3. 绑定 smartSocket 到 SSL

    通过把 smartSocket 的系统 fd 绑定到生成的 ssl 对象上,就可以在 HTTPsSocket 上加载 openSSL 协议了。最后使用 SSL_connect() 函数进行连接。如 清单 4所示。




    						
     /* Lets bind socket fd to the SSL structure */ 
     int fd=HTTPsSocket->getSocketFD(); 
     SSL_set_fd(ssl, fd); 
    
     /* Lets use a SSL connect */ 
     if (SSL_connect (ssl) <= 0 ) 
     { 
        return -1; 
     } 
    			
  4. 接收数据

    利用先建立起的 smartSocket 连接进行数据接收。如 清单 5所示。




    						
     int each = 0; 	
     /*If it is openSSL enabled connection, then use SSL_read to get the message, 
     otherwise use  default HTTPSocket->sockRecv() function*/ 	
     if(SSLenabled) 
     { 	
        each = SSL_read(ssl, buff, len ); 
     } 
     else 
     { 
        each = HTTPSocket->sockRecv((unsigned char *)buff, len, 0 ); 
     } 
     return each; 	
    			
  5. 发送数据

    利用先建立起的 smartSocket 连接进行数据发送。如 清单 6所示。




    						
     /*If it is openSSL enabled connection, then use SSL_write to send the message, 
     otherwise use  default HTTPSocket->sockSend() function*/ 	
     int trans = 0, each = 0; 
     if(SSLenabled) 
     { 
        while (trans < len) 
        { 
            if((each = SSL_write(ssl, buf + trans, len - trans)) == 0) 
            { 
                return SOCKETERR ; 
            } 
            trans += each ; 
        } 
     } 
     else 
     { 
        while (trans < len) 
        { 
            if((each=HTTPSocket->sockSend((unsigned char *)buf+trans,len-trans,0))==0) 
            { 
                return SOCKETERR ; 
            } 
            trans += each ; 
        } 
     } 
     return trans; 
    			

 

本文介绍了一种屏蔽 socket 网络连接细节的代码设计,该方法可以很好地适应 IPv4 和 IPv6 的网络环境,而且它提供给用户一个统一的接口,把用户从 v4 或者 v6 网络的连接细节中解放出來。


学习

获得产品和技术

  • 下载 IBM 软件试用版,体验强大的 DB2®,Lotus®,Rational®,Tivoli®和 WebSphere®软件。

讨论

 

 

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

如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6 的相关文章

  • 是否可以在新的密钥库中安装现有的私钥和 ssl 证书?

    我们在服务器故障期间丢失了用于生成 CSR 的原始密钥库 我们有私钥 key 文件 和原始 CSR csr 文件 的备份 是否可以用这些重建密钥库 由于创建证书链的所有说明都需要原始密钥库 这适用于 Tomcat 7 0 27 Thanks
  • SSLHandshakeException:不存在主题备用名称

    我通过 java 代码调用 HTTPS SOAP Web 服务 我已经在 jre cacerts 密钥库中导入了自签名证书 现在我得到 com sun xml internal ws com client ClientTransportEx
  • NodeJS TLS会话ID

    我正在使用 TLS 使用 node js 库创建会话 Node js 是否提供了一种方法来检索已建立的 TLS 连接的会话 ID 它是 openssl 中 SSL ctx 的一部分 不使用 connect express 或geddy 可以
  • AWS 上的多租户应用程序 - 多个 SSL 证书安装策略

    我正在为 Rails 多租户应用程序做一些规划 并且想知道处理自定义域证书的最佳方法是什么 应用程序是相当沼泽标准 ELB 应用程序服务器和多租户数据库 在我当前的用例中 每个租户都有一个自己独有的应用程序子域 这通常是通过通配符证书来处理
  • Mechanize 收到“Errno::ECONNRESET:连接由对等方重置 - SSL_connect”

    我无法让 Mechanize 加载曾经正常工作的页面 它确实失败了 并显示Errno ECONNRESET Connection reset by peer SSL connect信息 关于我应该尝试什么或我应该查看的细节有什么建议吗 请参
  • 将 SSL 与 MAMP PRO 4 结合使用

    由于我已更新到 chrome 63 我在 localhost 中的网站被重定向到 https 因此我决定将 SSL 与 MAMP PRO 一起使用 但我无法使其工作 chrome 说您的连接不是私有的 在 MAMP 的 SSL 面板中 我生
  • 重定向时需要 SSL 吗?

    我从一位我不擅长的客户那里继承了一种奇怪的情况 该客户端有 3 个域名 主要域名是 例如domain a com 他们还将domain b com 和domain c com 重定向到domain a com 所有域都位于同一服务器上 所以
  • Java 应用程序中 PKIX 路径构建失败

    将应用程序从 Windows 2000 迁移到 Windows 2008 R2 Server 后 我花了将近一周的时间才让我的应用程序正常运行 步骤 安装Java JDK 1 7 0 25 设置系统环境变量JAVA HOME to C Pr
  • 在SSL模式下使用apache kafka

    我正在尝试在 SSL 1 way 模式下设置 kafka 我已经阅读了官方文档并成功生成了证书 我将记下两种不同情况的行为 此设置只有一名经纪人和一名动物园管理员 案例 1 经纪人间通信 明文 我的相关条目server properties
  • 德尔福:idHttp+SSL

    请解释一下如何使用 SSL https 从服务器下载文件 我在互联网上没有找到合适的答案 每个人都说 TIdSSLIOHandlerSocket 但我只有 TIdSSLIOHandlerSocketOpenSSL 如果我使用 TIdSSLI
  • 如何在 Android 4.1+ 上将自签名 SSL 证书导入到 Volley

    我开发的android应用程序使用Volley 所有通信均通过HTTPS联系 因为我是在本地环境测试的 所以我对Tomcat使用自签名证书 之前我只有安卓2 3 and 3 0设备 现在我也有了4 1和4 4 我的实现使用这种方法 http
  • SSL 和 Tkinter 不存在于 Python 3.5.2、Debian Linux 的源代码构建中

    我刚刚将 Python 3 5 2 下载到我的 Debian 机器上并使用以下命令构建它 configure make make test sudo make install 一切正常 但在make test输出中 它显示安装程序由于未安装
  • 当 SSL_get_fd 返回 -1 时,这意味着什么?

    我正在使用 frida 分析一个程序 该程序肯定通过 SSL 连接到某个服务器 当我打电话时SSL get fd 它返回 1 从文档中我看到这意味着ssl不包装套接字 BIO 已明确配置 因为我可以通过SSL get rbio openss
  • 如果Jetty的密钥库中有多个证书,它如何选择?

    我们的系统中有一些代码用于自动将自签名证书生成到密钥库中 然后由 Jetty 使用 如果给定主机的密钥已经存在 那么什么也不会发生 但如果它不存在 我们会生成一个新密钥 如下所示 public void generateKey String
  • 如何通过 SSL 将文件直接上传到 S3?

    我已经使用基于浏览器的 Amazon S3 直接 POST 上传有一段时间了 最 近想开始通过 HTTPS 发布 普通的 HTTP 帖子就可以正常工作 但是 当我将相同的表格发布到https s3 amazonaws com https s
  • Node.js 中的 HTTPS 代理服务器

    我正在开发一个node js代理服务器应用程序 我希望它支持HTTP and HTTPS SSL 协议 作为服务器 我目前正在使用node http proxy https github com nodejitsu node http pr
  • tomcat 7 + ssl 不工作 - ERR_SSL_VERSION_OR_CIPHER_MISMATCH

    Ubuntu 14 tomcat 7 java 7 our crt our key 和 gd bundle g2 g1 crt 由 godaddy 提供 该捆绑包中有 3 个证书 通过查看文件可以看出 请注意 我们的密钥和 crt 在 no
  • 向 Python 2.6 添加 SSL 支持

    我尝试使用sslPython 2 6 中的模块 但我被告知它不可用 安装OpenSSL后 我重新编译2 6 但问题仍然存在 有什么建议么 您安装了 OpenSSL 开发库吗 我必须安装openssl devel例如 在 CentOS 上 在
  • 同时使用 SSL 加密和 NTLM 身份验证的 HttpClient 失败

    我尝试在使用 SSL 加密 https 以及 NTLM 身份验证的 Sharepoint 2010 服务器上执行简单的 REST 调用 当服务器设置为不需要 SSL 仅用于测试时 服务器在生产中将需要 SSL 时 我的 NTLM 身份验证和
  • Tomcat 托管具有多个 SSL 证书的多个虚拟主机

    例如 我有一台使用 Tomcat 7 托管多个网站的服务器 a abc com b abc com c def com d def com 使用tomcat的虚拟主机功能 因此它们各自可能属于不同的webapps文件夹 我们现在正在尝试为每

随机推荐

  • python+selenium自动化测试框架实例_顶Python+Selenium搭建UI自动化测试框架

    Python语言是非常强大的编程语言 很多时候也拿来当脚本语言用 Selenium是web应用测试工具 支持Java Python等多种语言脚本 支持Chrome Firefox等多种主流浏览器 主要实现的就是模拟人使用web应用 自动的打
  • 解决Windows11能登录QQ微信,但不可以使用浏览器上网

    解决浏览器不能上网 报错ERR PROXY CONNECTION FAILED 首先我们看到我们的以太网显示连接正常 但是 但我们打开谷歌浏览器是这样的 当我们打开IE是这样的 然后它们都报同样一个错误 ERR PROXY CONNECTI
  • 企业级Nginx+keepalived 双主架构笔记参考

    对于主备架构 始终存在一台服务器处于空闲状态 对于企业来说 资源分配就不很合理 如果要将两台服务器都利用起来 可以使用主主架构的方式 同时2台都对外服务 拥有2个VIP地址 同时接收用户的请求 双主架构的实现步骤参考 master1 反向代
  • leetcode 1812. Determine Color of a Chessboard Square(python)

    描述 You are given coordinates a string that represents the coordinates of a square of the chessboard Below is a chessboar
  • phpqrcode固定二维码大小

    我们都知道一个二维码PHP API接口使用phpqrcode php文件编写很容易就可以写出一个API接口 网页所出现的二维码图像一般都是随内容的增加而增加 我想要一个固定的png图像尺寸大小 怎么办 一般网上都会给出答案说确定img的尺寸
  • Android 内存泄露实践分析

    http wetest qq com lab view 161 html from adsout qqtips past2 sessionUserType BFT PARAMS 199168 TASKID ADUIN 576439313 A
  • Unity发布WebGL如何本地打开

    https blog csdn net blastblade article details 81170020
  • 淘宝镜像的使用

    1 切换淘宝镜像 npm config get registry 2 设置淘宝镜像 npm config set registry https registry npmirror com
  • 用java写一个多线程可断点续传的下载方法

    好的 我可以向你提供一个基本的多线程可断点续传的下载方法 首先 你需要创建一个线程类 用于下载文件的指定部分 这个线程类需要记录文件的开始和结束位置 并且在运行时使用这些位置来设置 HTTP 请求的 Range 头部 然后 你可以创建一个主
  • 搭建jeesite开发环境

    jeesite tomcat7 mysql eclipse maven jeesite下载路径 https github com thinkgem jeesite 下载jeesite并解压 在D 01 Projects 06 jeesite
  • 程序员必知的23种设计模式之模板模式

    文章目录 1 模式引出 豆浆制作问题 2 模板方法模式基本介绍 2 1 模板方法模式的原理类图 2 2 模板方法模式的钩子方法 3 方案实现 4 模板方法模式在Spring4框架的应用 5 模板方法模式的注意事项和细节 1 模式引出 豆浆制
  • 拍乐云首发音视频「分组讨论」开放能力,开启线上群聊互动新玩法

    苹果CEO库克近日宣布 因疫情反复 苹果将无限期推迟员工重返办公室计划 疫情常态化之下 在线工作 在线学习 在线娱乐也都变得常态化 我们似乎开始接受这种 All in在线 的模式 并对在线互动的体验提出了更高要求 当我们想要实现一个校招的网
  • java boolean空,Java检查布尔值是否为空

    How do you check if a boolean is null or not So if I know hideInNav is null How do I stop it from further executing Some
  • Jupyterlab使用plotly图片不显示解决方法

    问题 anaconda装好了plotly库 进入lab画图 发现不管怎样都显示空白 无法显示图片 解决方法 这是因为没有安装渲染支持的插件 在命令行输入 首先需要安装nodejs12 0 0以上版本 conda install nodejs
  • Pod 启动时命令和参数 与 dockerfile镜像区别

    主要是记录问题 如下文档 为容器设置启动时要执行的命令和参数 KubernetesDockerfile 中的 CMD 与 ENTRYPOINT sparkdev 博客园 K8s Pod Command 与容器镜像 Cmd 启动优先级详解 腾
  • Intellj IDE出现 java.lang.NoClassDefFoundError:

    在Intellj Idea中 主程序依赖了一个库modules 在模块中有一个dependence选项卡 把相关的依赖库选项的provided改为compile即可
  • python获取上一层文件夹路径

    https www cnblogs com yajing zh p 6807968 html
  • 零基础程序员自学编程的6种方法,你知道吗?

    随着互联网日趋迅猛 编程已经在我们生活当中无处不在了 众所周知 程序员的工资都很不错 于是越来越多的人 都想加入到编程的行业中来 那么如何加入到程序员的行业当中 PHP从入门到放弃 C语言从入门到放弃 Java从入门到放弃 Python从入
  • flutter创建项目指定语言

    每次新建Flutter项目都会去用命令来创建 因为不熟悉android的kottlin语言 每次都会抗拒一下 所以每次创建项目我都会选择去选用我熟悉的Java跟OC语言 直接上方法 注意 现在flutter创建的项目默认是swift跟kot
  • 如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6

    如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6 如何巧妙地设计代码结构 陈 鲁 软件工程师 IBM 孙 妍 软件工程师 IBM 简介 当今的网络主流是 IPv4 网络 但随着 IP 地址的日益短缺 IPv6 网络开