9. Linux下实现简单的UDP请求

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 9. Linux下实现简单的UDP请求

本文简单介绍了UDP传输层协议,并在Linux下实现简单的socket通讯


一、UDP

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,它不保证数据包的可靠性和顺序。UDP在IP协议的基础上增加了简单的差错检测功能,但是没有流量控制、拥塞控制等复杂机制。

相对于TCP,UDP具有以下优点:

  • 速度快:由于没有建立连接和维护状态等额外开销,在网络带宽较好时UDP可以实现更高的吞吐量和更低的延迟。
  • 简单:UDP协议非常简单,只提供了最基本的数据传输功能,因此实现起来比TCP更容易。
  • 支持广播和多播:UDP支持向多个主机发送同一份数据报文,可以用于组播或广播应用中。
  • 实时性强:由于没有拥塞控制等机制,UDP能够实现较为精准地时间同步、音视频传输等实时应用场景。


总之,在需要快速传输数据且可靠性要求不高的情况下,选择使用UDP会比TCP更合适。

要完成一个完整的 UDP 下的 DNS 网络通信过程,需要使用以下一系列函数:

  1. socket():创建套接字。
  2. sendto():向指定服务器发送 DNS 请求报文。
  3. recvfrom():从服务器接收 DNS 响应报文。
  4. 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]);
}


目录
相关文章
|
6月前
|
Linux Shell 数据安全/隐私保护
Linux配置代理请求
Linux配置代理请求
118 0
|
6月前
|
网络协议 Linux C++
Linux C/C++ 开发(学习笔记十一 ):TCP服务器(并发网络网络编程 一请求一线程)
Linux C/C++ 开发(学习笔记十一 ):TCP服务器(并发网络网络编程 一请求一线程)
132 0
|
4月前
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
266 2
|
1月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
36 0
Linux C/C++之TCP / UDP通信
|
1月前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
106 1
|
5月前
|
Linux
【Linux】一条命令,转发所有请求到另一台服务器上 -高级技巧
【Linux】一条命令,转发所有请求到另一台服务器上 -高级技巧
84 0
|
3月前
|
网络协议 Linux 网络安全
在Linux中,如何将本地 80 端口的请求转发到 8080 端口?当前主机 IP 为10.0.0.104。
在Linux中,如何将本地 80 端口的请求转发到 8080 端口?当前主机 IP 为10.0.0.104。
|
3月前
|
网络协议 Linux Shell
【Azure 应用服务】App Service For Linux 中安装paping, 用于验证从App Service向外请求的网络连通性
【Azure 应用服务】App Service For Linux 中安装paping, 用于验证从App Service向外请求的网络连通性
|
3月前
|
域名解析 网络协议 Linux
在Linux中,我们都知道,dns采用了tcp协议,又采用了udp协议,什么时候采用tcp协议?什么 时候采用udp协议?为什么要这么设计?
在Linux中,我们都知道,dns采用了tcp协议,又采用了udp协议,什么时候采用tcp协议?什么 时候采用udp协议?为什么要这么设计?
|
3月前
|
网络协议 Ubuntu Linux
在Linux中,如何将本地80端口的请求转发到8080端口,当前主机IP为192.168.16.1,其中本地网卡eth0。
在Linux中,如何将本地80端口的请求转发到8080端口,当前主机IP为192.168.16.1,其中本地网卡eth0。