Jdk8 DNS解析
注:JDK7和JDK8关于DNS解析的实现有差异,该问题在JDK7下可能不存在;
Java中的DNS解析一般是通过调用下面的方法:
- public static InetAddress getByName(String host)
- public static InetAddress[] getAllByName(String host)
getByName先调用getAllByName,然后返回地址列表的第一个地址;
下面主要看看getAllByName的实现;
getAllByName
getAllByName会调用getAllByName0方法:
- InetAddress[] addresses = getCachedAddresses(host);
- /* If no entry in cache, then do the host lookup */
- if (addresses == null) {
- addresses = getAddressesFromNameService(host, reqAddr);
- }
- if (addresses == unknown_array)
- throw new UnknownHostException(host);
- return addresses.clone();
可以看到首先会从缓存中获取,如果缓存找不到则调用getAddressesFromNameService进行解析;
- private static InetAddress[] getCachedAddresses(String hostname) {
- hostname = hostname.toLowerCase();
- // search both positive & negative caches
- synchronized (addressCache) {
- cacheInitIfNeeded();//如果是第一次调用,执行初始化
- CacheEntry entry = addressCache.get(hostname);
- if (entry == null) {
- entry = negativeCache.get(hostname);
- }
- if (entry != null) {
- return entry.addresses;
- }
- }
- // not found
- return null;
- }
既然JDK对IP地址解析有缓存,那么它是如何缓存的呢?缓存策略定义在InetAddressCachePolicy类,摘录其初始化的代码如下:
- static {
- Integer tmp = java.security.AccessController.doPrivileged(
- new PrivilegedAction<Integer>() {
- public Integer run() {
- try {
- //读取JDK目录java.security文件的属性networkaddress.cache.ttl
- String tmpString = Security.getProperty(cachePolicyProp);
- if (tmpString != null) {
- return Integer.valueOf(tmpString);
- }
- } catch (NumberFormatException ignored) {
- // Ignore
- }
- try {
- //读取-D指定的系统属性sun.net.inetaddr.ttl
- String tmpString = System.getProperty(cachePolicyPropFallback);
- if (tmpString != null) {
- return Integer.decode(tmpString);
- }
- } catch (NumberFormatException ignored) {
- // Ignore
- }
- return null;
- }
- });
- if (tmp != null) {
- cachePolicy = tmp.intValue();
- if (cachePolicy < 0) {
- cachePolicy = FOREVER;//如果配置的是负数,表示缓存永不过期
- }
- propertySet = true;
- } else {
- //可以通过-Djava.security.manager-Djava.security.policy=security.policy启动安全管理器
- if (System.getSecurityManager() == null) {
- cachePolicy = DEFAULT_POSITIVE;//默认是不启动SecurityManager的,也就是说默认缓存失效时间为30s
- }
- }
- tmp = java.security.AccessController.doPrivileged (
- new PrivilegedAction<Integer>() {
- public Integer run() {
- try {
- //读取networkaddress.cache.negative.ttl属性,默认是10s
- String tmpString = Security.getProperty(negativeCachePolicyProp);
- if (tmpString != null) {
- return Integer.valueOf(tmpString);
- }
- } catch (NumberFormatException ignored) {
- // Ignore
- }
- try {
- //读取-D指定的系统属性sun.net.inetaddr.negative.ttl
- String tmpString = System.getProperty(negativeCachePolicyPropFallback);
- if (tmpString != null) {
- return Integer.decode(tmpString);
- }
- } catch (NumberFormatException ignored) {
- // Ignore
- }
- return null;
- }
- });
- if (tmp != null) {
- negativeCachePolicy = tmp.intValue();
- if (negativeCachePolicy < 0) {
- negativeCachePolicy = FOREVER;
- }
- propertyNegativeSet = true;
- }
- }
上面介绍了JVM对ip地址解析的缓存策略和相关的配置,接下来看看,如果缓存找不到,JVM该如何解析ip地址;
getAddressesFromNameService
从上面的代码看到,InetAddress会调用getAddressesFromNameService方法,循环调用nameService的lookupAllHostAddr方法,直到找到结果:
NameService的初始化代码如下:
- impl = InetAddressImplFactory.create();
- // get name service if provided and requested
- String provider = null;;
- String propPrefix = "sun.net.spi.nameservice.provider.";
- int n = 1;
- nameServices = new ArrayList<NameService>();
- //可以通过sun.net.spi.nameservice.provider.n指定自己的DNS
- Provider
- provider = AccessController.doPrivileged(
- new GetPropertyAction(propPrefix + n));
- while (provider != null) {
- NameService ns = createNSProvider(provider);
- if (ns != null)
- nameServices.add(ns);
- n++;
- provider = AccessController.doPrivileged(
- new GetPropertyAction(propPrefix + n));
- }
- //如果不单独指定,创建默认的NameService
- if (nameServices.size() == 0) {//
- NameService ns = createNSProvider("default");
- nameServices.add(ns);
- }
在这里要特别提下Java提供的DNSNameService,该类可以通过下述参数启用:
- -Dsun.net.spi.nameservice.provider.1=dns,sun
- -Dsun.net.spi.nameservice.nameservers=192.168.1.188
该类会根据sun.net.spi.nameservice.nameservers指定的name server或/etc/resolv.conf文件中配置的name server进行DNS解析;
创建默认的NameService方法代码如下:
- if (provider.equals("default")) {
- // initialize the default name service
- nameService = new NameService() {
- public InetAddress[] lookupAllHostAddr(String host)
- throws UnknownHostException {
- return impl.lookupAllHostAddr(host);
- }
- public String getHostByAddr(byte[] addr)
- throws UnknownHostException {
- return impl.getHostByAddr(addr);
- }
- };
- }
根据指定的provider创建NameService的方法如下:
- nameService = java.security.AccessController.doPrivileged(
- new java.security.PrivilegedExceptionAction<NameService>() {
- public NameService run() {
- Iterator itr = Service.providers(NameServiceDescriptor.class);
- while (itr.hasNext()) {
- NameServiceDescriptor nsd
- = (NameServiceDescriptor)itr.next();
- if (providerName.
- equalsIgnoreCase(nsd.getType()+","
- +nsd.getProviderName())) {
- try {
- return nsd.createNameService();
- } catch (Exception e) {
- e.printStackTrace();
- System.err.println(
- "Cannot create name service:"
- +providerName+": " + e);
- }
- }
- }
- return null;
- }
- }
- );
对于DNSNameServiceDescriptor,其Type和ProviderName分别为dns,sun;
继续看默认Provider的处理逻辑,可以看到其是通过impl.lookupAllHostAddr(host)方法进行解析的,impl的初始化代码为:
- impl = InetAddressImplFactory.create();
- static InetAddressImpl create() {
- return InetAddress.loadImpl(isIPv6Supported() ?
- "Inet6AddressImpl" : "Inet4AddressImpl");
- }
这里以Inet4AddressImpl为例,说明DNS的解析:
- public native InetAddress[]
- lookupAllHostAddr(String hostname) throws UnknownHostException;
- public native String getHostByAddr(byte[] addr) throws UnknownHostException;
Inet4AddressImp类的方法是native的,是采用本地方法实现的:
- JNIEXPORT jobjectArray JNICALL
- Java_java_net_Inet4AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this,
- jstring host) {
- const char *hostname;
- jobjectArray ret = 0;
- int retLen = 0;
- int error = 0;
- struct addrinfo hints, *res, *resNew = NULL;
- if (!initializeInetClasses(env))
- return NULL;
- if (IS_NULL(host)) {
- JNU_ThrowNullPointerException(env, "host is null");
- return 0;
- }
- hostname = JNU_GetStringPlatformChars(env, host, JNI_FALSE);
- CHECK_NULL_RETURN(hostname, NULL);
- /* Try once, with our static buffer. */
- memset(&hints, 0, sizeof(hints));
- hints.ai_flags = AI_CANONNAME;
- hints.ai_family = AF_INET;
- error = getaddrinfo(hostname, NULL, &hints, &res);
- if (error) {
- /* report error */
- ThrowUnknownHostExceptionWithGaiError(env, hostname, error);
- JNU_ReleaseStringPlatformChars(env, host, hostname);
- return NULL;
- } else {
- int i = 0;
- struct addrinfo *itr, *last = NULL, *iterator = res;
- while (iterator != NULL) {
- // remove the duplicate one
- int skip = 0;
- itr = resNew;
- while (itr != NULL) {
- struct sockaddr_in *addr1, *addr2;
- addr1 = (struct sockaddr_in *)iterator->ai_addr;
- addr2 = (struct sockaddr_in *)itr->ai_addr;
- if (addr1->sin_addr.s_addr ==
- addr2->sin_addr.s_addr) {
- skip = 1;
- break;
- }
- itr = itr->ai_next;
- }
- if (!skip) {
- struct addrinfo *next
- = (struct addrinfo*) malloc(sizeof(struct addrinfo));
- if (!next) {
- JNU_ThrowOutOfMemoryError(env, "Native heap allocation failed");
- ret = NULL;
- goto cleanupAndReturn;
- }
- memcpy(next, iterator, sizeof(struct addrinfo));
- next->ai_next = NULL;
- if (resNew == NULL) {
- resNew = next;
- } else {
- last->ai_next = next;
- }
- last = next;
- i++;
- }
- iterator = iterator->ai_next;
- }
- retLen = i;
- iterator = resNew;
- ret = (*env)->NewObjectArray(env, retLen, ni_iacls, NULL);
- if (IS_NULL(ret)) {
- /* we may have memory to free at the end of this */
- goto cleanupAndReturn;
- }
- i = 0;
- while (iterator != NULL) {
- jobject iaObj = (*env)->NewObject(env, ni_ia4cls, ni_ia4ctrID);
- if (IS_NULL(iaObj)) {
- ret = NULL;
- goto cleanupAndReturn;
- }
- setInetAddress_addr(env, iaObj, ntohl(((struct sockaddr_in*)iterator->ai_addr)->sin_addr.s_addr));
- setInetAddress_hostName(env, iaObj, host);
- (*env)->SetObjectArrayElement(env, ret, i++, iaObj);
- iterator = iterator->ai_next;
- }
- }
- }
上面的代码一大堆,核心是调用getaddrinfo函数,在getaddrinfo的man文档中有这么一句话:
the application should try using the addresses in the order in which they are returned. The sorting function used within getaddrinfo() is defined in RFC 3484; the order can be tweaked for a
particular system by editing /etc/gai.conf (available since glibc 2.5).
getaddrinfo返回的地址列表根据RFC3484规定的排序算法进行了排序,如果这样的话,那么返回的地址列表顺序是规定的,那就达不到负载均衡的目的了;
关于这个排序的话题,网上有很多讨论:
- https://lists.debian.org/debian-glibc/2007/09/msg00347.html
- https://lists.debian.org/debian-ctte/2007/09/msg00067.html
- https://daniel.haxx.se/blog/2012/01/03/getaddrinfo-with-round-robin-dns-and-happy-eyeballs/
getaddrinfo的部分代码如下:
- int getaddrinfo (const char *__restrict name, const char *__restrict service,
- const struct addrinfo *__restrict hints,
- struct addrinfo **__restrict pai)
- {
- int i = 0, j = 0, last_i = 0;
- int nresults = 0;
- struct addrinfo *p = NULL, **end;
- struct gaih *g = gaih, *pg = NULL;
- struct gaih_service gaih_service, *pservice;
- struct addrinfo local_hints;
- while (g->gaih)
- {
- if (hints->ai_family == g->family || hints->ai_family == AF_UNSPEC)
- {
- j++;
- if (pg == NULL || pg->gaih != g->gaih)
- {
- pg = g;
- i = g->gaih (name, pservice, hints, end);
- if (i != 0)
- {
- /* EAI_NODATA is a more specific result as it says that
- we found a result but it is not usable. */
- if (last_i != (GAIH_OKIFUNSPEC | -EAI_NODATA))
- last_i = i;
- if (hints->ai_family == AF_UNSPEC && (i & GAIH_OKIFUNSPEC))
- {
- ++g;
- continue;
- }
- freeaddrinfo (p);
- return -(i & GAIH_EAI);
- }
- if (end)
- while (*end)
- {
- end = &((*end)->ai_next);
- ++nresults;
- }
- }
- }
- ++g;
- }
- if (j == 0)
- return EAI_FAMILY;
- if (nresults > 1)
- {
- /* Sort results according to RFC 3484. */
- struct sort_result results[nresults];
- struct addrinfo *q;
- struct addrinfo *last = NULL;
- char *canonname = NULL;
- for (i = 0, q = p; q != NULL; ++i, last = q, q = q->ai_next)
- {
- results[i].dest_addr = q;
- results[i].got_source_addr = false;
- /* If we just looked up the address for a different
- protocol, reuse the result. */
- if (last != NULL && last->ai_addrlen == q->ai_addrlen
- && memcmp (last->ai_addr, q->ai_addr, q->ai_addrlen) == 0)
- {
- memcpy (&results[i].source_addr, &results[i - 1].source_addr,
- results[i - 1].source_addr_len);
- results[i].source_addr_len = results[i - 1].source_addr_len;
- results[i].got_source_addr = results[i - 1].got_source_addr;
- }
- else
- {
- /* We overwrite the type with SOCK_DGRAM since we do not
- want connect() to connect to the other side. If we
- cannot determine the source address remember this
- fact. */
- int fd = socket (q->ai_family, SOCK_DGRAM, IPPROTO_IP);
- socklen_t sl = sizeof (results[i].source_addr);
- if (fd != -1
- && connect (fd, q->ai_addr, q->ai_addrlen) == 0
- && getsockname (fd,
- (struct sockaddr *) &results[i].source_addr,
- &sl) == 0)
- {
- results[i].source_addr_len = sl;
- results[i].got_source_addr = true;
- }
- else
- /* Just make sure that if we have to process the same
- address again we do not copy any memory. */
- results[i].source_addr_len = 0;
- if (fd != -1)
- close_not_cancel_no_status (fd);
- }
- /* Remember the canonical name. */
- if (q->ai_canonname != NULL)
- {
- assert (canonname == NULL);
- canonname = q->ai_canonname;
- q->ai_canonname = NULL;
- }
- }
- /* We got all the source addresses we can get, now sort using
- the information. */
- qsort (results, nresults, sizeof (results[0]), rfc3484_sort);
- /* Queue the results up as they come out of sorting. */
- q = p = results[0].dest_addr;
- for (i = 1; i < nresults; ++i)
- q = q->ai_next = results[i].dest_addr;
- q->ai_next = NULL;
- /* Fill in the canonical name into the new first entry. */
- p->ai_canonname = canonname;
- }
- if (p)
- {
- *pai = p;
- return 0;
- }
- if (pai == NULL && last_i == 0)
- return 0;
- return last_i ? -(last_i & GAIH_EAI) : EAI_NONAME;
- }
排序是通过rfc3484_sort完成的,后面有时间准备仔细看看其排序规则:
- static int
- rfc3484_sort (const void *p1, const void *p2)
- {
- const struct sort_result *a1 = (const struct sort_result *) p1;
- const struct sort_result *a2 = (const struct sort_result *) p2;
- /* Rule 1: Avoid unusable destinations.
- We have the got_source_addr flag set if the destination is reachable. */
- if (a1->got_source_addr && ! a2->got_source_addr)
- return -1;
- if (! a1->got_source_addr && a2->got_source_addr)
- return 1;
- /* Rule 2: Prefer matching scope. Only interesting if both
- destination addresses are IPv6. */
- int a1_dst_scope
- = get_scope ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
- int a2_dst_scope
- = get_scope ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
- if (a1->got_source_addr)
- {
- int a1_src_scope = get_scope (&a1->source_addr);
- int a2_src_scope = get_scope (&a2->source_addr);
- if (a1_dst_scope == a1_src_scope && a2_dst_scope != a2_src_scope)
- return -1;
- if (a1_dst_scope != a1_src_scope && a2_dst_scope == a2_src_scope)
- return 1;
- }
- /* Rule 3: Avoid deprecated addresses.
- That's something only the kernel could decide. */
- /* Rule 4: Prefer home addresses.
- Another thing only the kernel can decide. */
- /* Rule 5: Prefer matching label. */
- if (a1->got_source_addr)
- {
- int a1_dst_label
- = get_label ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
- int a1_src_label = get_label (&a1->source_addr);
- int a2_dst_label
- = get_label ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
- int a2_src_label = get_label (&a2->source_addr);
- if (a1_dst_label == a1_src_label && a2_dst_label != a2_src_label)
- return -1;
- if (a1_dst_label != a1_src_label && a2_dst_label == a2_src_label)
- return 1;
- }
- /* Rule 6: Prefer higher precedence. */
- int a1_prec
- = get_precedence ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
- int a2_prec
- = get_precedence ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
- if (a1_prec > a2_prec)
- return -1;
- if (a1_prec < a2_prec)
- return 1;
- /* Rule 7: Prefer native transport.
- XXX How to recognize tunnels? */
- /* Rule 8: Prefer smaller scope. */
- if (a1_dst_scope < a2_dst_scope)
- return -1;
- if (a1_dst_scope > a2_dst_scope)
- return 1;
- /* Rule 9: Use longest matching prefix. */
- if (a1->got_source_addr
- && a1->dest_addr->ai_family == a2->dest_addr->ai_family)
- {
- int bit1 = 0;
- int bit2 = 0;
- if (a1->dest_addr->ai_family == PF_INET)
- {
- assert (a1->source_addr.ss_family == PF_INET);
- assert (a2->source_addr.ss_family == PF_INET);
- struct sockaddr_in *in1_dst;
- struct sockaddr_in *in1_src;
- struct sockaddr_in *in2_dst;
- struct sockaddr_in *in2_src;
- in1_dst = (struct sockaddr_in *) a1->dest_addr->ai_addr;
- in1_src = (struct sockaddr_in *) &a1->source_addr;
- in2_dst = (struct sockaddr_in *) a2->dest_addr->ai_addr;
- in2_src = (struct sockaddr_in *) &a2->source_addr;
- bit1 = ffs (in1_dst->sin_addr.s_addr ^ in1_src->sin_addr.s_addr);
- bit2 = ffs (in2_dst->sin_addr.s_addr ^ in2_src->sin_addr.s_addr);
- }
- else if (a1->dest_addr->ai_family == PF_INET6)
- {
- assert (a1->source_addr.ss_family == PF_INET6);
- assert (a2->source_addr.ss_family == PF_INET6);
- struct sockaddr_in6 *in1_dst;
- struct sockaddr_in6 *in1_src;
- struct sockaddr_in6 *in2_dst;
- struct sockaddr_in6 *in2_src;
- in1_dst = (struct sockaddr_in6 *) a1->dest_addr->ai_addr;
- in1_src = (struct sockaddr_in6 *) &a1->source_addr;
- in2_dst = (struct sockaddr_in6 *) a2->dest_addr->ai_addr;
- in2_src = (struct sockaddr_in6 *) &a2->source_addr;
- int i;
- for (i = 0; i < 4; ++i)
- if (in1_dst->sin6_addr.s6_addr32[i]
- != in1_src->sin6_addr.s6_addr32[i]
- || (in2_dst->sin6_addr.s6_addr32[i]
- != in2_src->sin6_addr.s6_addr32[i]))
- break;
- if (i < 4)
- {
- bit1 = ffs (in1_dst->sin6_addr.s6_addr32[i]
- ^ in1_src->sin6_addr.s6_addr32[i]);
- bit2 = ffs (in2_dst->sin6_addr.s6_addr32[i]
- ^ in2_src->sin6_addr.s6_addr32[i]);
- }
- }
- if (bit1 > bit2)
- return -1;
- if (bit1 < bit2)
- return 1;
- }
- /* Rule 10: Otherwise, leave the order unchanged. */
- return 0;
- }
可以看到,首先根据RFC3484的Rule1~Rule9排序,如果上述规则都未触发,则返回原列表;简单的说,返回结果的顺序是不固定的,有可能是DNS Server返回的顺序,也有可能不是;因此最好的办法是在Java层自己进行控制;
转自:https://www.jianshu.com/p/f10808ae4b60
作者:allanYan
Jdk8 DNS解析的更多相关文章
- DNS解析过程详解
先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的网址www.baidu.com在配置当中应该是www.baidu.com.(最后有一点),一般我们在浏览器里输入时会省略后面的点 ...
- C#实现DNS解析服务和智能DNS服务
C#实现DNS解析服务有一个开源项目ARSoft.Tools.Net, ARSoft.Tools.Net是一个非常强大的开源DNS控件库,包含.Net SPF validation, SenderID ...
- 解决域名DNS解析的故障
在实际应用过程中可能会遇到DNS解析错误的问题,就是说当我们访问一个域名时无法完成将其解析到IP地址的工作,而直接输入网站IP却可以正常访问,这就是因为DNS解析出现故障造成的.这个现象发生的机率比较 ...
- LINUX DNS解析的3种修改方法~
1.HOST 本地DNS解析 vi /etc/hosts 添加规则 例如: 223.231.234.33 www.baidu.com 2.网卡配置文件DNS服务地址 vi /etc/sysconfi ...
- DNS解析过程和域名收敛、域名发散、SPDY应用
前段时间项目要做域名收敛,糊里糊涂的完成了,好多原理不清晰,现在整理搜集下知识点. 域名收敛的目的是什么?简单来说就是域名解析慢.那为什么解析慢?且听下文慢慢道来. 什么是DNS? DNS( Doma ...
- 通过统计用户DNS解析记录,实现监控用户上网行为
上次通过扫描抓包分析TTL的方式检测公司网络开放的端口,发现没有开放53端口(DNS),也就是在公司内部的主机只能用服务器自动分配的DNS,并且发现这是台内部服务器.今天发现bing上不去,检测后发现 ...
- DNS解析全过程及原理
DNS解析原理及过程. 当用户访问我们网站一个网页时,他需要经过以下步骤: 1)找到这个网页的存放服务器: 2)服务器将用户的请求信息接入: 3)服务器通过文件路径(URL)查找用户请求网页: 4)用 ...
- 使用dig查询dns解析
原文地址:使用dig查询dns解析 作者:chenwenming 一般来说linux下查询域名解析有两种选择,nslookup或者dig,而在使用上我觉得dig更加方便顺手. 如果是在debian下的 ...
- Dnsmasq安装与配置-搭建本地DNS服务器 更干净更快无广告DNS解析
默认的情况下,我们平时上网用的本地DNS服务器都是使用电信或者联通的,但是这样也导致了不少的问题,首当其冲的就是上网时经常莫名地弹出广告,或者莫名的流量被消耗掉导致网速变慢.其次是部分网站域名不能正常 ...
随机推荐
- Matlab解释器模式
解释器模式(Interperter),给定一个语言,定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,实际开发中EL表达式或者正则表达式的解释器就是采用这种设计模式.其模式结构如下图.本文使 ...
- Java 之 Set 接口
一.Set 概述 java.util.Set 接口继承 collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Co ...
- openssl生成证书及签名
第一步,生成私钥 $ openssl genrsa -out privatekey.pem 2048 查看生成的私钥内容 $ file privatekey.pem privatekey.pem: P ...
- mysql修改密码策略
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/Hello_World_QWP/arti ...
- springboot配置tomcat大全
server.tomcat.accept-count=100 # Maximum queue length for incoming connection requests when all poss ...
- 浅谈Python设计模式 - 抽象工厂模式
声明:本系列文章主要参考<精通Python设计模式>一书,并且参考一些资料,结合自己的一些看法来总结而来. 在上一篇我们对工厂模式中的普通工厂模式有了一定的了解,其实抽象工作就是 表示针对 ...
- crontab 定时任务简单备份数据库
备份数据库/usr/local/mysql5.5/bin/mysqldump -uroot -p1234abcd wordpress >~/wordpress_20151206.sql 59 2 ...
- 多态典型用例之virtual
多态典型用例之virtual 参考:https://www.cnblogs.com/dormant/p/5223215.html 1.虚函数(virtual) (1)在某基类中声明为 virtual ...
- Pat 1003 甲级
#include <cstdlib> #include <cstring> #include <iostream> #include <cstdio> ...
- thrift rpc通信
thrift rpc通信 框架 别人的简历: 负责抓取程序的开发和维护,对抓取内容进行数据提取.整理.1.定向数据抓取程序的维护和开发,了解了Sqlite数据库.Thrift服务和多线程的开发调试.2 ...