本文简单介绍了UDP传输层协议,并在Linux下实现简单的socket通讯
一、UDP
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,它不保证数据包的可靠性和顺序。UDP在IP协议的基础上增加了简单的差错检测功能,但是没有流量控制、拥塞控制等复杂机制。
相对于TCP,UDP具有以下优点:
- 速度快:由于没有建立连接和维护状态等额外开销,在网络带宽较好时UDP可以实现更高的吞吐量和更低的延迟。
- 简单:UDP协议非常简单,只提供了最基本的数据传输功能,因此实现起来比TCP更容易。
- 支持广播和多播:UDP支持向多个主机发送同一份数据报文,可以用于组播或广播应用中。
- 实时性强:由于没有拥塞控制等机制,UDP能够实现较为精准地时间同步、音视频传输等实时应用场景。
总之,在需要快速传输数据且可靠性要求不高的情况下,选择使用UDP会比TCP更合适。
要完成一个完整的 UDP 下的 DNS 网络通信过程,需要使用以下一系列函数:
- socket():创建套接字。
- sendto():向指定服务器发送 DNS 请求报文。
- recvfrom():从服务器接收 DNS 响应报文。
- close():关闭套接字,释放资源。
在具体实现时,还需要考虑处理 UDP 数据包丢失、超时重传、DNS 报文的解析和组装等问题。
int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- sockfd:需要接收数据的套接字描述符。
- buf:存储接收到数据的缓冲区。
- len:buf缓冲区的长度。
- flags:控制recvfrom函数的行为。
- src_addr:发送方地址信息结构体指针,用于保存发送方IP地址和端口号等信息。如果不需要获取此信息,则可设置为NULL。
- addrlen:上述地址信息结构体长度。
二、实现简单的socket通讯
注意本文是在Linux下实现,若要在window下实现socket需要链接库,参考Windows的socket通讯
#include <stdio.h> #include <time.h> #include <string.h> #include <stdlib.h> //window下的头文件 // #include <winSock2.h> // #include <windows.h> // #include <sys/types.h> //Linux下的头文件 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define DNS_SERVER_PORT 53 #define DNS_SERVER_IP "114.114.114.114" #define DNS_HOST 0x01 #define DNS_CNAME 0x05 #define socklen_t int struct dns_header{ unsigned short id; unsigned short flags; unsigned short questions; unsigned short answer; unsigned short authority; unsigned short additional; }; struct dns_question{ int length; unsigned short qtype; unsigned short qclass; unsigned char *name; }; struct dns_item { char *domain; char *ip; }; //创建头部 int dns_create_header(struct dns_header *header){ if (header == NULL) return -1; memset(header,0,sizeof(struct dns_header)); srand(time(NULL)); header->id=rand(); //htons()用于将16位整数由主机字节序转换为网络字节序 header->flags=htons(0x0100); header->questions=htons(1); return 0; } 创建正文 int dns_create_question(struct dns_question *question,const char *hostname){ if (question == NULL || hostname == NULL) return -1; memset(question,0,sizeof(struct dns_question)); //因为hostname末尾还有结束标记 question->name=(char *)malloc(strlen(hostname)+2); if (question->name == NULL){ return -2; } question->length=strlen(hostname)+2; question->qtype=htons(1); question->qclass=htons(1); //若hostname=www.baidu.com,则question->name(查询名)为3www5baidu3com0 const char delim[2]="."; //C语言中字符串以空字符'\0'作为结尾,因此在定义字符数组时需要额外留出一个元素来存储结尾标志。 char *qname=question->name; char *hostname_dup=strdup(hostname);//将字符串复制到新的内存空间,并返回指向该空间的指针 char *token=strtok(hostname_dup,delim); while (token != NULL){ size_t len=strlen(token); //qname第一位放len,如3 *qname=len; qname++; //放入字母,如www\0,len+1代表加上结束符\0 strncpy(qname,token,len+1); qname+=len; token=strtok(NULL,delim);//在后续调用时可直接传入NULL作为第一个参数继续处理上一次未处理完的字符串。 } free(hostname_dup); } //构建查询请求 //将DNS消息头部分和查询问题部分按照规定格式打包到缓冲区中,形成一个完整的DNS查询请求报文。 int dns_bulid_requestion(struct dns_header *header,struct dns_question *question,char *request,int rlen){ if (header == NULL || question == NULL || request == NULL) return -1; memset(request,0,rlen); //复制header到request memcpy(request,header,sizeof(struct dns_header)); int offset=sizeof(struct dns_header); //复制question到request memcpy(request+offset,question->name,sizeof(struct dns_question)); offset+=question->length; memcpy(request+offset,&question->qtype,sizeof(question->qtype)); offset+=sizeof(question->qtype); memcpy(request+offset,&question->qclass,sizeof(question->qclass)); offset+=sizeof(question->qclass); //返回长度 return offset; } //'''''''''''''''''''''''''以下三个函数用于解析''''''''''''''''''''''''''''''''''''''''''''''' //判断一个DNS记录中的域名是否使用了指针(Pointer) static int is_pointer(int in) { return ((in & 0xC0) == 0xC0); } //将DNS查询或响应报文中的域名字段解析成普通字符串。 static void dns_parse_name(unsigned char *chunk, unsigned char *ptr, char *out, int *len) { int flag = 0, n = 0, alen = 0; char *pos = out + (*len); while (1) { flag = (int)ptr[0]; if (flag == 0) break; if (is_pointer(flag)) { n = (int)ptr[1]; ptr = chunk + n; dns_parse_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; } } } } //用于解析从服务器返回的DNS响应报文,并提取出其中包含的信息,如IP地址等。 static int dns_parse_response(char *buffer, struct dns_item **domains) { int i = 0; unsigned char *ptr = buffer; ptr += 4; int querys = ntohs(*(unsigned short*)ptr); ptr += 2; int answers = ntohs(*(unsigned short*)ptr); ptr += 6; for (i = 0;i < querys;i ++) { while (1) { int flag = (int)ptr[0]; ptr += (flag + 1); if (flag == 0) break; } ptr += 4; } char cname[128], aname[128], ip[20], netip[4]; int len, type, ttl, datalen; int cnt = 0; struct dns_item *list = (struct dns_item*)calloc(answers, sizeof(struct dns_item)); if (list == NULL) { return -1; } for (i = 0;i < answers;i ++) { bzero(aname, sizeof(aname)); len = 0; dns_parse_name(buffer, ptr, aname, &len); ptr += 2; type = htons(*(unsigned short*)ptr); ptr += 4; ttl = htons(*(unsigned short*)ptr); ptr += 4; datalen = ntohs(*(unsigned short*)ptr); ptr += 2; if (type == DNS_CNAME) { bzero(cname, sizeof(cname)); len = 0; dns_parse_name(buffer, ptr, cname, &len); ptr += datalen; } else 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); list[cnt].domain = (char *)calloc(strlen(aname) + 1, 1); memcpy(list[cnt].domain, aname, strlen(aname)); list[cnt].ip = (char *)calloc(strlen(ip) + 1, 1); memcpy(list[cnt].ip, ip, strlen(ip)); cnt ++; } ptr += datalen; } } *domains = list; ptr += 2; return cnt; } //使用DNS协议向指定域名domain发送查询请求,并接收并处理响应结果。 int dns_client_commit(const char *domain){ //Socket是一种提供网络通信功能的编程接口或API,它允许不同的计算机之间通过网络进行数据传输。 // 创建socket int sockfd=socket(AF_INET,SOCK_DGRAM,0); if (sockfd <0){ return -1; } // 绑定地址和端口号 struct sockaddr_in servaddr={0}; servaddr.sin_family =AF_INET; servaddr.sin_port=htons(DNS_SERVER_PORT); servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP); //连接服务器 int ret= connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); printf("connect:%d\n",ret); // 打包构建查询请求 struct dns_header header={0}; dns_create_header(&header); struct dns_question question={0}; dns_create_question(&question,domain); char request[1024]={0}; int length=dns_bulid_requestion(&header,&question,request,1024); // 发送消息 sendto(sockfd,request,length,0,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)); //接收回复 char response[1024]={0}; struct sockaddr_in addr; size_t addr_len=sizeof(struct sockaddr_in); int n=recvfrom(sockfd,response,sizeof(response),0,(struct sockaddr *)&addr,(socklen_t*)&addr_len); //解析 struct dns_item *dns_domain = NULL; dns_parse_response(response, &dns_domain); free(dns_domain); return n; } int main(int argc ,char *argv[]){ if (argc<2) return -1; dns_client_commit(argv[1]); }