code学习

Android网络框架学习——DNS解析与Socket通信原则

作者:Android秃老师

一、什么是DNS?

它所提供的服务是用来将主机名和域名转换为IP地址的工作。
我们知道域名和IP地址是一一对应的关系,但是多个域名可以对应同一个IP地址。           
Android网络框架学习——DNS解析与Socket通信原则

二、DNS查询过程

递归:DNS服务器可使用其自身的资源记录信息缓存来应答查询,
也可代表请求客户机来查询或联系其他DNS服务器,以完全解析该名称,并随后将应答返回至客户机。

迭代:客户机自己也可尝试l联系其他的DNS服务器来解析名称。
如果客户机这么做,它会使用基于服务器应答的独立和附加的查询。

1、在浏览器中输入域名,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系。

2、如果hosts里没有映射关系,查找本地DNS解析器缓存。

3、如果还是没有,首先会找TCP/IP参数中设置的首选DNS服务器。           
Android网络框架学习——DNS解析与Socket通信原则

三、Socket介绍

Socket 的中文翻译过来就是“套接字”。套接字是什么,我们先来看看它的英文含义:插座。

Socket 就像一个电话插座,负责连通两端的电话,进行点对点通信,让电话可以进行通信,端口就像插座上的孔,端口不能同时被其他进程占用。而我们建立连接就像把插头插在这个插座上,创建一个 Socket 实例开始监听后,这个电话插座就时刻监听着消息的传入,谁拨通我这个“IP 地址和端口”,我就接通谁。

实际上,Socket 是在应用层和传输层之间的一个抽象层,它把 TCP/IP 层复杂的操作抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。Socket 起源于 UNIX,在 UNIX 一切皆文件的思想下,进程间通信就被冠名为文件描述符(file descriptor),Socket 是一种“打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

另外我们经常说到的Socket 所在位置如下图:

Android网络框架学习——DNS解析与Socket通信原则

四、Socket 通信过程

Socket 保证了不同计算机之间的通信,也就是网络通信。对于网站,通信模型是服务器与客户端之间的通信。两端都建立了一个 Socket 对象,然后通过 Socket 对象对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。

一图胜千言,下面是面向连接的 TCP 时序图:

Android网络框架学习——DNS解析与Socket通信原则

五、使用socket进行DNS协议解析

在进行域名解析的时候,解析程序向域名服务器发起请求,域名服务器也就是在操作系统网络配置的时候写进去的那个DNS服务器地址,或者也有可能是由 ISP提供的自动获取的,原理都一样,域名服务器收到请求后进行处理,首先在本地缓存中查找对应的域名,找到后将IP地址直接返回,找不到就向其它的授权 服务器请求数据,又可以分为著名的递归查询和非递归查询。

  • 递归查询就是说自始至终都由一台域名服务器进行查询,它在自己这里找不到的时候会向其它的域名服务器请求并且获取数据,然后返回给请求方。
  • 非递归查询是指域名服务器收到请求后,如果自己有这个域名的信息就返回,如果没有就返回其它域名服务器的指针,请求方再根据这些域名服务器再发起查询。

DNS域名有时候会是一个主域名的别名,比如www.baidu.com,它就是 www.a.shifen.com这个域名的别名,在DNS请求发送过去之后,response里面会有一个类型为CNAME的Answers项,里面包 含了主域名的相关信息(其实也就是主域名的名称和TTL),在这个应答消息里面可能会出现多个域名消息,比如每个Answers的第一个字段就是一个域 名,当然为了减少数据包的容量,DNS系统对域名进行了压缩,同一个域名只会出现一次,其它的时候再出现的话就会用一个DNS指针表示。 比如域名:www.baidu.com在数据包中的表示是 03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00

粗体的是长度,将域名中的点去掉,用长度来分隔域名,以0结束。DNS允许的长度为0-63个字节,所以一个8位的长度最高两位都为0。

而如果此处域名重复出现,信令中便会用DNS指针代替长度,指针为两个字节,16位的最位都为1,剩下的14位表示在在整个数据包中的偏移量,当程 序读取到c00c的时候很容易判断它是一个指针而不是一个长度字段,于是根据c00c指向的领移量,即从数据包开始后的第12个字节,跳转过去读取出域名 信息。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
 
#define DNS_SVR "211.68.71.4"
 
#define DNS_HOST  0x01
#define DNS_CNAME 0x05
 
int socketfd;
struct sockaddr_in dest;
 
static void 
send_dns_request(const char *dns_name);
 
static void
parse_dns_response();
 
/**
 * Generate DNS question chunk
 */
static void 
generate_question(const char *dns_name
        , unsigned char *buf , int *len);
 
/**
 * Check whether the current byte is 
 * a dns pointer or a length
 */
static int
is_pointer(int in);
 
/**
 * Parse data chunk into dns name
 * @param chunk The complete response chunk
 * @param ptr The pointer points to data
 * @param out This will be filled with dns name
 * @param len This will be filled with the length of dns name
 */
static void
parse_dns_name(unsigned char *chunk , unsigned char *ptr
        , char *out , int *len);
 
int main(int argc , char *argv[]){
 
    if(argc != 2){
        printf("Usage : %s <domain name>\n" , argv[0]);
        exit(-1);
    }
    socketfd = socket(AF_INET , SOCK_DGRAM , 0);
    if(socketfd < 0){
        perror("create socket failed");
        exit(-1);
    }
    bzero(&dest , sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(53);
    dest.sin_addr.s_addr = inet_addr(DNS_SVR);
 
    send_dns_request(argv[1]);
 
    parse_dns_response();
 
    return 0;
}
 
static void parse_dns_response(){
 
    unsigned char buf[1024];
    unsigned char *ptr = buf;
    struct sockaddr_in addr;
    char *src_ip;
    int n , i , flag , querys , answers;
    int type , ttl , datalen , len;
    char cname[128] , aname[128] , ip[20] , *cname_ptr;
    unsigned char netip[4];
    size_t addr_len = sizeof(struct sockaddr_in);
 
    n = recvfrom(socketfd , buf , sizeof(buf) , 0
        , (struct sockaddr*)&addr , &addr_len);
    ptr += 4; /* move ptr to Questions */
    querys = ntohs(*((unsigned short*)ptr));
    ptr += 2; /* move ptr to Answer RRs */
    answers = ntohs(*((unsigned short*)ptr));
    ptr += 6; /* move ptr to Querys */
    /* move over Querys */
    for(i= 0 ; i < querys ; i ++){
        for(;;){
            flag = (int)ptr[0];
            ptr += (flag + 1);
            if(flag == 0)
                break;
        }
        ptr += 4;
    }
    printf("-------------------------------\n");
    /* now ptr points to Answers */
    for(i = 0 ; i < answers ; i ++){
        bzero(aname , sizeof(aname));
        len = 0;
        parse_dns_name(buf , ptr , aname , &len);
        ptr += 2; /* move ptr to Type*/
        type = htons(*((unsigned short*)ptr));
        ptr += 4; /* move ptr to Time to live */
        ttl = htonl(*((unsigned int*)ptr));
        ptr += 4; /* move ptr to Data lenth */
        datalen = ntohs(*((unsigned short*)ptr));
        ptr += 2; /* move ptr to Data*/
        if(type == DNS_CNAME){
            bzero(cname , sizeof(cname));
            len = 0;
            parse_dns_name(buf , ptr , cname , &len);
            printf("%s is an alias for %s\n" , aname , cname);
            ptr += datalen;
        }
        if(type == DNS_HOST){
            bzero(ip , sizeof(ip));
            if(datalen == 4){
                memcpy(netip , ptr , datalen);
                inet_ntop(AF_INET , netip , ip , sizeof(struct sockaddr));
                printf("%s has address %s\n" , aname , ip);
                printf("\tTime to live: %d minutes , %d seconds\n"
                        , ttl / 60 , ttl % 60);
            }
            ptr += datalen;
        }
 
    }
    ptr += 2;
}
 
static void
parse_dns_name(unsigned char *chunk
        , unsigned char *ptr , char *out , int *len){
    int n , alen , flag;
    char *pos = out + (*len);
 
    for(;;){
        flag = (int)ptr[0];
        if(flag == 0)
            break;
        if(is_pointer(flag)){
            n = (int)ptr[1];
            ptr = chunk + n;
            parse_dns_name(chunk , ptr , out , len);
            break;
        }else{
            ptr ++;
            memcpy(pos , ptr , flag);   
            pos += flag;
            ptr += flag;
            *len += flag;
            if((int)ptr[0] != 0){
                memcpy(pos , "." , 1);
                pos += 1;
                (*len) += 1;
            }
        }
    }
 
}
 
static int is_pointer(int in){
    return ((in & 0xc0) == 0xc0);
}
 
static void send_dns_request(const char *dns_name){
 
    unsigned char request[256];
    unsigned char *ptr = request;
    unsigned char question[128];
    int question_len;
 
 
    generate_question(dns_name , question , &question_len);
 
    *((unsigned short*)ptr) = htons(0xff00);
    ptr += 2;
    *((unsigned short*)ptr) = htons(0x0100);
    ptr += 2;
    *((unsigned short*)ptr) = htons(1);
    ptr += 2;
    *((unsigned short*)ptr) = 0;
    ptr += 2;
    *((unsigned short*)ptr) = 0;
    ptr += 2;
    *((unsigned short*)ptr) = 0;
    ptr += 2;
    memcpy(ptr , question , question_len);
    ptr += question_len;
 
    sendto(socketfd , request , question_len + 12 , 0
       , (struct sockaddr*)&dest , sizeof(struct sockaddr));
}
 
static void
generate_question(const char *dns_name , unsigned char *buf , int *len){
    char *pos;
    unsigned char *ptr;
    int n;
 
    *len = 0;
    ptr = buf;  
    pos = (char*)dns_name; 
    for(;;){
        n = strlen(pos) - (strstr(pos , ".") ? strlen(strstr(pos , ".")) : 0);
        *ptr ++ = (unsigned char)n;
        memcpy(ptr , pos , n);
        *len += n + 1;
        ptr += n;
        irstr(pos , ".")){
            *ptr = (unsigned char)0;
            ptr ++;
            *len += 1;
            break;
        }
        pos += n + 1;
    }
    *((unsigned short*)ptr) = htons(1);
    *len += 2;
    ptr += 2;
    *((unsigned short*)ptr) = htons(1);
    *len += 2;
}           
Android网络框架学习——DNS解析与Socket通信原则

以上是对网络框架中的DNS解析与socket通信原则的浅析,这些都属于OKhttp网络框架中的部分内容;如果想深入学习Android的 网络框架;我这里推荐资料《OKhttp手册》文档私信“手册”获取↓↓↓,让你更方便的深入学习网络框架。

简述OKhttp优点

OkHttp是一个优秀的网络请求框架,它有以下默认特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
  • 连接池减少请求延时
  • 透明的GZIP压缩减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

私信“手册”可领取!

继续阅读