IPv6 socket编程下--实现篇

本文将详细介绍IPv6 socket编程的具体实现,具体原理请点击:IPv6 socket编程上--原理篇

判断客户端可用的IP stack

原理大家都明白了,但是客户端做不同的处理的前提是需要知道客户端可用的IP协议栈。可用的IP stack类型分别是IPv4-only、IPv6-only、IPv4-IPv6 Dual stack。

我们先定义客户端可用的IP协议栈的意思是,获取客户端当前能使用的IP协议栈。例如iOS在NAT64 WIFI连接上的情况下,Mobile的网卡虽然存在IPv4的协议栈,但是系统是不允许使用的。IOS只能使用WIFI的协议栈,在NAT64 WIFI的情况下就是IPv6-only网络了。

这里还有一个问题需要讨论,如果遇到IPv6-only网络,需要把它当作NAT64来处理,在v4 IP前添加前缀64:ff9b::/96。 但是这里NAT64和IPv6-only不是等价的。IPv6-only网络可能支持NAT64,能访问v4的互联网资源,但是IPv6-only能访问v6的互联网资源,不支持NAT64。这里假设IPv6-only的网络都是支持NAT64的,对v4 IP进行64:ff9b::/96的处理。因为不支持NAT64的话,微信服务器v4地址根本就不可访问(当然如果手机系统有464XLAT服务,并且运营商支持,也是可以访问v4资源的,但是不在讨论范围了)。

获取本地IP和网关方案(iOS)

  • IOS通过sysctl获取当前网关或路由 如果只能获取IPv6网关,那当前是IPv6-only 如果只能获取IPv4网关,那当前是IPv4-only 如果同时能获取IPv6/IPv4路由,那情况就比复杂,分析如下 IOS在WIFI连接上的情况下,并不会关闭Mobile的网卡。 在WIFI是IPv6-only网络,Mobile是IPv4-only网络,下v4 socket或者v4-mapped都无法出去。 证明apple应该对TCP connect函数进行过改造,在WIFI和Mobile共存的情况下,只能走WIFI网络,和Android不一样,iOS不是通过去掉Mobile网卡的方式来做。 这样导致的一个有趣的特性:网络切换时候如果Mobile 下建立的socket不关闭可以继续使用Mobile网络。 如果程序使用bind接口绑定到Mobile的网卡下,这个时候是可以使用Mobile网络进行访问的。(这里算不算偷流量呢,当然这里是特性,具体怎么样应用是程序的问题了)。 因此我们可以考虑WIFI连接了的情况下,我们只要知道网关是对应那张网卡,就可以知道当前是不是当前支持的IP协议栈? 然而事情没有那么简单,我们先按照刚刚说的思路走下去

  • 通过getifaddr接口,可以拿到当前全部网络的IP地址(排除掉非活跃和loopback的网卡) 如果IPv4、IPv6网关都属于WIFI网卡,那当前是IPv4-IPv6 Dual stack 如果IPv4、IPv6网关都属于Mobile网卡,那当前是IPv4-IPv6 Dual stack 到这里都没有问题,但是下面的情况呢: 如果IPv4网关属于Mobile网卡,IPv6网关属于WIFI? 如果IPv4网关属于WIFI网卡,IPv6网关属于Mobile? 这里的情况还要分开,如果是正常情况下IOS在WIFI连接后是不允许使用Mobile网卡的,但是iOS又有一个特性是3G热点。 在这样的情况下IOS手机本身是走Mobile网络的,WIFI只是做桥接。

这个方案非常复杂,而且跟iOS平台的系统实现强耦合,其他平台必须重新实现,后续如果iOS进行网络逻辑的更新,这里还必须修改。因此这个的方案不太建议大家用。

DNS方案

这里的方案是直接做DNS解析,然后判断返回的IP有没有带上64:ff9b前缀来确定当前的IP协议栈。这也是唯一能够判断IPv6-only网络是否支持NAT64的方案。

//gateway
in6_addr addr6_gateway = {0};
if (0 != getdefaultgateway6(&addr6_gateway))
    return EIPv4;
if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway))
    return EIPv4;
in_addr addr_gateway = {0};
if (0 != getdefaultgateway(&addr_gateway))
    return EIPv6;
if (INADDR_NONE == addr_gateway.s_addr || INADDR_ANY == addr_gateway.s_addr )
    return EIPv6;
//getaddrinfo
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG|AI_V4MAPPED;
int error = getaddrinfo("dns.weixin.qq.com", "http", &hints, &res0);if (0 != error) {
    return EIPv4; 
}
for (res = res0; res; res = res->ai_next) {
    if (AF_INET6 == res->ai_addr.sa_family) {
        if (is_nat64_address(((sockaddr_in6&)res->ai_addr).sin6_addr)) {
            return EIPv6;
        }
    }
}
return EIPv4;

我们分析下上面的sample,前面gateway的代码是为了加速判断过程,我们知道DNS是一个网络过程,耗时很有可能是非常久的。dns.weixin.qq.com必须保证解析的域名只有v4 ip地址。hints.ai_family = PF_INET6利用了DNS64的特性,如果在纯IPv6环境下会返回NAT64映射地址的方式。AI_V4MAPPED为了在非DNS64网络下,返回v4-mapped ipv6 address,不会返回EAI_NONAME失败,导致判断不准确。AI_ADDRCONFIG返回的地址是本地能够使用的(具体可以看文档下面的介绍)。如果有NAT64前缀的v6地址返回,证明当前网络是IPv6-only NAT64网络。 不过这个方案有很多缺点,就是耗时不确定,可能因为网络失败导致错误的结果,需要网络流量,会对运营商的DNS服务器造成压力,网络切换需要立刻进行重试重连。 结论,这个方案不太合适。

socket connect的方式(支持iOS9和Android)

这里的方案是直接使用v4 IP地址和v6 IP地址进行连接,通过结果来确认当前客户端可用IP stack。

_test_connect(int pf, struct sockaddr *addr, size_t addrlen) {
    int s = socket(pf, SOCK_STREAM, IPPROTO_TCP);
    if (s < 0)
        return 0;
    int ret;
    do {
        ret = connect(s, addr, addrlen);
    } while (ret < 0 && errno == EINTR);
    int success = errno;
    do {
        ret = close(s);
    } while (ret < 0 && errno == EINTR);
    return success;
}

static int
_have_ipv6() {
    static const struct sockaddr_in6 sin6_test = {
        .sin6_len = sizeof(sockaddr_in6),
        .sin6_family = AF_INET6,
        .sin6_port = htons(0xFFFF),
        .sin6_addr.s6_addr = {
            0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    };
    sockaddr_union addr = { .in6 = sin6_test };
    return _test_connect(PF_INET6, &addr.generic, sizeof(addr.in6));
}

static int
_have_ipv4() {
    static const struct sockaddr_in sin_test = {
        .sin_len = sizeof(sockaddr_in),
        .sin_family = AF_INET,
        .sin_port = htons(0xFFFF),
        .sin_addr.s_addr = htonl(0x08080808L),  // 8.8.8.8
    };
    sockaddr_union addr = { .in = sin_test };
    return _test_connect(PF_INET, &addr.generic, sizeof(addr.in));
}

enum TLocalIPStack {
    ELocalIPStack_None = 0,
    ELocalIPStack_IPv4 = 1,
    ELocalIPStack_IPv6 = 2,
    ELocalIPStack_Dual = 3,
};

void test() {
    TLocal IPlocal_stack = 0;
    int errno_ipv4 = _have_ipv4();
    int errno_ipv6 = _have_ipv6();
    int local_stack = 0;
    if ( errno_ipv4 != EHOSTUNREACH && errno_ipv4 != ENETUNREACH) {
        local_stack |= ELocalIPStack_IPv4;
    }
    if (errno_ipv6 != EHOSTUNREACH && errno_ipv6 != ENETUNREACH) {
        local_stack |= ELocalIPStack_IPv6;
    }
}

这个方案是利用外网IP进行连接,如果返回EHOSTUNREACH的时候说明本地没有对应的路由到达目标地址,如果ENETUNREACH的时候说明本地没有相应的协议栈,这两种情况都是说明相应的协议栈不可用。 分析下这个方案的缺点,和getaddrinfo一样,耗时不确定,因为有调用connect动作,进行tcp连接。如果connect遇到EHOSTUNREACH ENETUNREACH错误是不会耗费流量和立刻返回的,因为这些都是本地网络判断。但是,如果相应网络可用,这个是要花费网络流量的,耗时也不能确定。如果我们连接一个存在的IP,这样在网络好的时候很快返回(这样会对服务器造成连接的压力),网络差的时候很久才返回。如果连接一个不存在的IP,需要很久时间才会返回(75s的连接超时)。

这样看来,这三个方案都不完美,根本不能在真实场景中使用, 有没有更加可用的方案呢?iOS 9.0 上层Objc Framework可以无缝支持,但是用bsd socket需要代码完成对应的工作。但是iOS Framework的最新源码也没有开源出来,无法知道其实现原理。 继续研究发现,getaddrinfo的AI_ADDRCONFIG flags有点像我们需要实现的功能,要去掉IP,就必须要知道当前的IP stack。它是怎么样实现的?

//Android的AI_ADDRCONFIG 功能的sample
_test_connect(int pf, struct sockaddr *addr, size_t addrlen) {
    int s = socket(pf, SOCK_DGRAM, IPPROTO_UDP);
    if (s < 0)
        return 0;
    int ret;
    do {
        ret = connect(s, addr, addrlen);
    } while (ret < 0 && errno == EINTR);
    int success = (ret == 0);
    do {
        ret = close(s);
    } while (ret < 0 && errno == EINTR);
    return success;
}

static int
_have_ipv6() {
    static const struct sockaddr_in6 sin6_test = {
        .sin6_len = sizeof(sockaddr_in6),
        .sin6_family = AF_INET6,
        .sin6_port = htons(0xFFFF),
        .sin6_addr.s6_addr = {
            0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    };
    sockaddr_union addr = { .in6 = sin6_test };
    return _test_connect(PF_INET6, &addr.generic, sizeof(addr.in6));
}

static int
_have_ipv4() {
    static const struct sockaddr_in sin_test = {
        .sin_len = sizeof(sockaddr_in),
        .sin_family = AF_INET,
        .sin_port = htons(0xFFFF),
        .sin_addr.s_addr = htonl(0x08080808L),  // 8.8.8.8
    };
    sockaddr_union addr = { .in = sin_test };
    return _test_connect(PF_INET, &addr.generic, sizeof(addr.in));
}

enum TLocalIPStack {
    ELocalIPStack_None = 0,
    ELocalIPStack_IPv4 = 1,
    ELocalIPStack_IPv6 = 2,
    ELocalIPStack_Dual = 3,
};

void test() {
    TLocal IPlocal_stack = 0;
    int have_ipv4 = _have_ipv4();
    int have_ipv6 = _have_ipv6();
    int local_stack = 0;
    if ( have_ipv4) {
        local_stack |= ELocalIPStack_IPv4;
    }
    if (have_ipv6) {
        local_stack |= ELocalIPStack_IPv6;
    }
}

这里的代码构造成和第一个socket connect的类型,只修改一部分代码。 可以看到,和第一个例子的区别是socket(pf, SOCK_DGRAM, IPPROTO_UDP)用了UDP进行连接,UDP可以进行connect,只是进行绑定服务器地址的动作,并不会有网络数据的产生,后续可以直接使用send接口,不需要使用sendto接口(每次都需指定服务器的地址)。 经过测试iOS和Android都能检测出当前可用的IP stack。我们再做一些思考,如果connect接口在UDP的时候,应该是除了TCP发送syn包外的全部事情都做了的。如果这样考虑的话,这个方案成立的依据还是足够的。

混合的方案(Mac OS,iOS,Linux,Android都支持,Windows/wp待测试)

发现在iOS8/Mac OS上述方案会有点问题(iOS9正常),就是iOS8上IPv6-only网络也会有169.254.x.x的自组网的IPv4 stack(其实iOS9上也有,但不影响测试结果),这样会导致IPv4 stack的udp socket能够connect成功(haveipv4()返回1)。应对这种情况,我们可以用前面getdefaultgateway的方案,把自组网排除出没有网关的情况。当然,有手机网的时候,IPv4网关是可以获取到的,还是会走到haveipv4的路径。当然,如果haveipv4和haveipv6只有一个返回1的情况,我们可以认为只有一个IP stack能用。当然如果是localstack为ELocalIPStackDual,还需要用getdnssvraddrs的函数获取当前的dns服务器列表,通过dns服务器的地址确认当前可用的IP stack。必须说明下,这个不是一个准确的判断,如果网络是ELocalIPStack_Dual,但是dns服务只设置了IPv6的地址(如果是dhcp配置的情况,很少出现这样,一般情况都是手工设置才会出现),会判断当前网络为ELocalIPStackIPv6。这样ELocalIPStackDual的网络可能不支持NAT64,这样会导致程序无法访问网络。 这个方案是本地操作,成本低,没有网络流量消耗和耗时问题,暂时是最好的可用IP stack检测方案。(当然NAT64检测不了) 新的实现代码如下:

TLocalIPStack local_ipstack_detect() {
    in6_addr addr6_gateway = {0};
    if (0 != getdefaultgateway6(&addr6_gateway)){ return ELocalIPStack_IPv4;}
    if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway)) { return ELocalIPStack_IPv4;}

    in_addr addr_gateway = {0};
    if (0 != getdefaultgateway(&addr_gateway)) { return ELocalIPStack_IPv6;}
    if (INADDR_NONE == addr_gateway.s_addr || INADDR_ANY == addr_gateway.s_addr ) { return ELocalIPStack_IPv6;}

    int have_ipv4 = _have_ipv4();
    int have_ipv6 = _have_ipv6();
    int local_stack = 0;
    if (have_ipv4) { local_stack |= ELocalIPStack_IPv4; }
    if (have_ipv6) { local_stack |= ELocalIPStack_IPv6; }
    if (ELocalIPStack_Dual != local_stack) { return (TLocalIPStack)local_stack; }

    int dns_ip_stack = 0;
    std::vector<socket_address> dnssvraddrs;
    getdnssvraddrs(dnssvraddrs);

    for (int i = 0; i < dnssvraddrs.size(); ++i) {
    if (AF_INET == dnssvraddrs[i].address().sa_family) { dns_ip_stack |= ELocalIPStack_IPv4; }
    if (AF_INET6 == dnssvraddrs[i].address().sa_family) { dns_ip_stack |= ELocalIPStack_IPv6; }
    }

    return (TLocalIPStack)(ELocalIPStack_None==dns_ip_stack? local_stack:dns_ip_stack);
}

其他编程问题

建议大家认真看apple的文档Supporting IPv6 DNS64/NAT64 NetworksRFC 4038 - Application Aspects of IPv6 Transition,里面很多事情都说清楚了,这里说下其他需要关注的地方。

sockaddr的存储sockaddr_storage

这里千万不要犯傻用sockaddr存储sockaddr_in6数据,IOS上sockaddr的大小是16,和sockaddrin一致的,但是sockaddrin6大小是28(不要问我为什么会知道,都是泪)。通用的sockaddr的存储的结构体是sockaddr_storage,它是能存储任何sockaddr的结构。 你可能会问,如果socket用AF_INET6的时候,用sockaddr_in6结构体不就好了。不是说不可以,就是代码会变成IPv6专用的了,如果用到其他地方可能会出错。但是如果用AF_INET呢,虽然强转成sockaddrin没有任何问题,但是程序逻辑上蛋疼,如果大家要写v4/v6通用的逻辑的话,最好还是用sockaddr_storage存储,然后通过ss_family进行判断,最后做不同分支的处理。

//sockaddr_storage sample
socket_address socket_address::getsockname(SOCKET _sock)
{
    struct sockaddr_storage addr = {0};
    socklen_t addr_len = sizeof(addr);
    ::getsockname(_sock, (sockaddr*)&addr, &addr_len);

    if (AF_INET == addr.ss_family)
    {
         return socket_address((const sockaddr_in&)addr);
    }
    else if (AF_INET6 == addr.ss_family)
    {
        return socket_address((const sockaddr_in6&)addr);
    }

    return socket_address("", 0);
}

更加节省空间的方案

sockaddr_storage是能够保存所有sockaddr下属的类型,但是128字节的大小有时候有点不可接受,而且每次使用都需要做类型转换。下面提供一个更加优雅的方案,大小是28字节,节省了很多。

union sockaddr_union {
    struct sockaddr     sa;
    struct sockaddr_in  in;
    struct sockaddr_in6 in6;
} m_addr;

if (AF_INET == m_addr.sa.sa_family) {
    return ntohs(m_addr.in.sin_port);
} else if (AF_INET6 == m_addr.sa.sa_family) {
    return ntohs(m_addr.in6.sin6_port);
}

NSURLConnection

apple要求大家不要直接用IP访问,不过,中国的DNS环境这么恶劣,没有其他更好的办法。 那NSURLConnection怎么样能够在IPv6访问正常的访问呢?我们应该构建怎么样的URL呢? 我们先看看wikipedia的说法

Literal IPv6 addresses in network resource identifiers [^4] Colon (:) characters in IPv6 addresses may conflict with the established syntax of resource identifiers, such as URIs and URLs. The colon has traditionally been used to terminate the host path before a port number.[6] To alleviate this conflict, literal IPv6 addresses are enclosed in square brackets in such resource identifiers, for example: http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/ When the URL also contains a port number the notation is: https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/

我们可以看到除了IP形式不一样外,还多了中括号。当然,上面我们说到IPv4-mapped IPv6 addressNAT64 mapped address同样也是适用的,例如:http://[::ffff:14.17.32.21]:80走IPv4协议栈或http://[64:ff9b::14.17.32.21]走NAT64。关键点还是判断当前 客户端可用的IP stack

IOS下CoreFoudation或者更高级的API

引用手Q同事的原话:

如果使用CoreFoudation或者更高级的API,即使在纯IPv6环境下使用IPv4的ip进行网络通信,iOS9会自动把IPv4地址转换成IPv6地址。换句话说,因为手q里面大部分api都是满足要求的,基本上不用改动。(注意iOS9.0或以上)

例如CFStreamCreatePairWithSocketToCFHost ..待续

DNS API

apple的文档说了,gethostbyname这些已经不能用了(只支持IPv4),只能用getaddrinfo。

struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
getaddrinfo("www.qq.com", "http", &hints, &res0);

这里sample比较简单,其实getaddrinfo的重点在hints.ai_familyhints.ai_flags的设置上,apple已经给出了一个很好sample。我们分析下这两个变量不同设置下的效果,看看有什么区别。

  • hints.ai_family = PF_UNSPEC的意思是v4地址和v6地址都返回,不过呢,这里可是会触发两个UDP的请求,当年微信就给运营商吐槽过,你没有v6地址,就不要做v6请求拉(微信量大)。不过apple爸爸要求用v6地址,怎么办?
  • hints.ai_family = PF_INET的意思是只返回v4地址
  • hints.ai_family = PF_INET6的意思是只返回v6地址
  • hints.ai_flags |= AI_V4MAPPED 且 hints.ai_family = PF_INET6的情况下,如果需要dns的host没有v6地址的情况下,getaddinfo会把v4地址转换成v4-mapped ipv6 address,如果有v6地址返回就不会做任何动作。
  • hints.ai_flags |= AI_ADDRCONFIG这个是一个很有用的特性,这个flags表示getaddrinfo会根据本地网络情况,去掉不支持的IP协议地址。
  • hints.ai_flags = AI_DEFAULT其实就是AI_V4MAPPED|AI_ADDRCONFIG_CFG,也是apple推荐的flags设置方式。

域名 对应着如下 IP 地址: 173.194.127.180 173.194.127.176 2404:6800:4005:802::1010 若本地主机仅配置了 IPV4 地址,则返回的查询结果中不包含 IPV6 地址,即此时只有: 173.194.127.180 173.194.127.176 同样若本地主机仅配置了 IPV6 地址,则返回的查询结果中仅包含IPV6地址. 2404:6800:4005:802::1010

用这个API的时候,建议大家还是按照apple的sample来做hints.ai_family暂时先PF_INET,免得运营商投诉,当然最好是能后台进行控制。

下面一段话是apple文档内对getaddrinfo对NAT64支持的描述。

The current implementation supports synthesis of NAT64 mapped IPv6 addresses. If hostname is a numeric string defining an IPv4 address (for example, '192.0.2.1' ) and aifamily is set to PFUNSPEC or PFINET6, getaddrinfo() will synthesize the appropriate IPv6 address(es) (for example, '64:ff9b::192.0.2.1' ) if the current interface supports IPv6, NAT64 and DNS64 and does not support IPv4. If the AIADDRCONFIG flag is set, the IPv4 address will be suppressed on those interfaces. On non-qualifying interfaces, getaddrinfo() is guaranteed to return immediately without attempting any resolution, and will return the IPv4 address if aifamily is PFUNSPEC or PFINET. NAT64 address synthesis can be disabled by setting the AINUMERICHOST flag. To best support NAT64 networks, it is recommended to resolve all IP address literals with aifamily set to PFUNSPEC and aiflags set to AI_DEFAULT.

可以看到apple最推荐的getaddrinfo用法就是sample那样。

iOS SCNetworkReachabilityCreateWithAddress API问题

在iOS下,一般判断网络连通性和WIFI/Mobile网络的判断是使用SCNetworkReachabilityCreateWithAddress API,一般的sample里面只会测试IPv4的IP地址,这样有可能导致在纯IPv6网络下判断出当前是没有网络,这样明显是不对的。

struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;

SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)zeroAddress);

针对IPv6可以使用下面的方式来判断

struct sockaddr_in6 zeroAddress6;
bzero(&zeroAddress6, sizeof(zeroAddress6));
zeroAddress6.sin6_len = sizeof(zeroAddress6);
zeroAddress6.sin6_family = AF_INET6;

SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)zeroAddress6);

当然大家写通用代码的时候,可以IPv6和IPv4都判断。 最后苹果建议的方式是SCNetworkReachabilityCreateWithName这个API,个人暂时不确定这个API是不是会进行DNS解析。

文档的推荐和说明

Beej's Guide to Network Programming 简体中文 这本书不错的,介绍了很多API的使用,当然IPv6部分也有 Beej's Guide to Network Programming 繁体中文 排版比简体好 unix network programming 不用说了,不过没有v6的部分 Dual-Stack Sockets for IPv6 Winsock Applications(Windows XP SP1后都支持)

IPv6下不能使用的API列表

  • gethostbyname()
  • gethostbyaddr()
  • getservbyname()
  • getservbyport()
  • gethostbyname2()
  • inet_addr()
  • inet_aton()
  • inet_lnaof()
  • inet_makeaddr()
  • inet_netof()
  • inet_network()
  • inet_ntoa()
  • inetntoar()
  • bindresvport()
  • getipv4sourcefilter()
  • setipv4sourcefilter()

下面类型或者结构需要注意使用的正确性

IPv4|IPv6 ---|--- AFINET|AFINET6 PFINET|PFINET6 struct inaddr|struct inaddr6 struct sockaddrin|struct sockaddrin6 kDNSServiceProtocolIPv4|kDNSServiceProtocolIPv6

[^1]: wikipedia Transition from IPv4

[^2]: wikipedia NAT64

[^3]: wikipedia DNS64

[^4]: wikipedia IPv6 address

原文发布于微信公众号 - WeMobileDev(WeMobileDev)

原文发表时间:2016-04-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jerry的SAP技术分享

SAP产品的Field Extensibility

SAP开发人员的工作职责,除了实现软件的功能性需求外,还会花费相当的精力实现一些非功能性需求,来满足所谓的SAP Product Standard(产品标准)。...

13010
来自专栏拂晓风起

Java 连接access 使用access文件 不用配置

15170
来自专栏FreeBuf

树莓派随身工具箱:中间人劫持获取控制权

上文讲解了树莓派随身工具箱的环境搭建,这段时间又对其进行了一些优化,主要是从便携美观上面改进。同时,在实际使用中发现了一些问题,并做了小小的改动。

33030
来自专栏游戏杂谈

使用p3p跨域设置Cookie

有些时候不能将url上的参数传来传去,比如与调用某开放平台上的接口,这时候可能需要借助Cookie来进行处理了,但这里可能又涉及到跨域的问题。

25040
来自专栏大数据架构师专家

运维技能武器库

Bootstrapping: Kickstart、Cobbler、rpmbuild/xen、kvm、lxc、Openstack、 Cloudstack、Open...

19020
来自专栏SDNLAB

IPv6还未完成,IPv10已来!P4带你进入IPv10的世界

P4全称Programming Protocol-Independent Packet Processors,是Nick McKeown和他的团队在2014年提...

511130
来自专栏杨建荣的学习笔记

使用strace诊断奇怪的sqlplus登录问题(r5笔记第29天)

今天刚到公司,印度同事就开始急忙找我,说客户有一个环境sqlplus连不上了。我第一反应是数据库是不是停了,连接资源满了等等,赶紧查收邮件,看到报错信息还是比较...

38330
来自专栏Python小屋

Python版课堂管理系统中使用UDP广播远程关闭客户端程序思路与源码

本文代码来自于我自己使用开发的一套课堂管理系统,界面是用tkinter编写的,教师端界面如图所示: ? 为了防止学生关闭客户端而接收不到屏幕广播,大概3个月前为...

31250
来自专栏我和未来有约会

Silverlight Cairngorm

Cairngorm这个词做过flex开发的朋友应该不会陌生,Cairngorm是Flex开发中的一个MVC框架,由Adobe官方提供支持。现在Silverlig...

28250
来自专栏数据和云

Oracle 12.2中那些温暖人心的特性

在OOW 2015大会上,Oracle已经发布了12.2的Beta版本,其中的很多亮点新特性引人瞩目,包括在IMO和Multitenant方面,以及在Shard...

41260

扫码关注云+社区

领取腾讯云代金券