前言

udp是一种不可靠的通信,但是有些时候还是会有使用。今天分享一个示例:主体逻辑,一个端口广播地址,接收到ip地址数据后,其他端口基于这个ip进行bind绑定,最后通信,这样可以保证我们后续继续增加端口交互时候不需要关注ip地址绑定的问题。


(资料图片)

主要原理介绍低通信频率端口进行服务端IP信息udp广播,接收端是不固定IP监听,监听主机任意IP地址的特定端口接收到广播通道的ip地址后,与特定IP、port建立tcp或者udp双向高频率通信。

下图是基于 UDP 的 Socket 函数调用过程:

只有接收的时候需要bind ip和端口

socket 监听所有ip 特定端口代码:

#define PORT 6000bzero(&adr_inet, sizeof(adr_inet));adr_inet.sin_family = AF_INET;adr_inet.sin_addr.s_addr = htonl(INADDR_ANY);adr_inet.sin_port = htons(port);ret = bind(cfd, (struct sockaddr *)&addr, sizeof(addr));

socket绑定的ip为INADDR_ANY 的说明:

socket INADDR_ANY 监听0.0.0.0地址 socket只绑定端口让路由表决定传到哪个ip

其中INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 如果指定ip地址为通配地址(INADDR_ANY),那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报时才选择一个本地IP地址。 一般情况下,如果你要建立网络服务器,则你要通知服务器操作系统:请在某地址 xxx.xxx.xxx.xxx上的某端口 yyyy上进行侦听,并且把侦听到的数据包发送给我。这个过程,你是通过bind()系统调用完成的。——也就是说,你的程序要绑定服务器的某地址,或者说:把服务器的某地址上的某端口占为已用。服务器操作系统可以给你这个指定的地址,也可以不给你。

如果你的服务器有多个网卡,而你的服务(不管是在udp端口上侦听,还是在tcp端口上侦听),出于某种原因:可能是你的服务器操作系统可能随时增减IP地址,也有可能是为了省去确定服务器上有什么网络端口(网卡)的麻烦 —— 可以要在调用bind()的时候,告诉操作系统:“我需要在 yyyy 端口上侦听,所以发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都是我处理的。”这时候,服务器则在0.0.0.0这个地址上进行侦听。无论连接哪个ip都可以连上的,只要是往这个端口发送的所有ip都能连上。

示例代码:

data_send.c 在端口9001进行ip地址的udp广播以及读取终端数据广播到7000端口

#include #include #include #include #include #include #include #include #include #include #include #define IP "127.0.0.1"#define#define// gcc data_send.c -o data_send -pthreadint cfd = -1;//接收线程函数void *receive(void *pth_arg){    int ret = 0;    char name_data[3] = {0};    struct sockaddr_in addr0 = {0};    int addr0_size = sizeof(addr0);    //从对端ip和端口号中接收消息,指定addr0用于存放消息    while (1)    {        bzero(name_data, sizeof(name_data));        ret = recvfrom(cfd, name_data, sizeof(name_data), 0, (struct sockaddr *)&addr0, &addr0_size);        if (-1 == ret)        {            fprintf(stderr, "%d, %s :%s", __LINE__, "recv failed", strerror(errno));            exit(-1);        }        else if (ret > 0)        {            printf("\nname = %s ", name_data); //打印对方的消息和端口号            printf("ip %s,port %d \n", inet_ntoa(addr0.sin_addr), ntohs(addr0.sin_port));        }    }}void *data_send(void *pth_arg){    int ret = 0;    char data[] = "IP address";    struct sockaddr_in addr0 = {0};    addr0.sin_family = AF_INET;            //设置tcp协议族    addr0.sin_port = htons(DATA_PORT);          //设置端口号    addr0.sin_addr.s_addr = htonl(INADDR_ANY); //设置ip地址    //发送消息    while (1)    {        ret = sendto(cfd, (void *)data, sizeof(data), 0, (struct sockaddr *)&addr0, sizeof(addr0));        sleep(1);        if (-1 == ret)        {            fprintf(stderr, "%d, %s :%s", __LINE__, "sendto failed", strerror(errno));            exit(-1);        }    }}int main(){    int ret = -1;    //创建tcp/ip协议族,指定通信方式为无链接不可靠的通信    cfd = socket(AF_INET, SOCK_DGRAM, 0);    if (-1 == cfd)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "socket failed", strerror(errno));        exit(-1);    }    //进行端口号和ip的绑定    struct sockaddr_in addr;    addr.sin_family = AF_INET;   //设置tcp协议族    addr.sin_port = htons(PORT); //设置端口号    addr.sin_addr.s_addr = inet_addr(IP); //设置ip地址    ret = bind(cfd, (struct sockaddr *)&addr, sizeof(addr));    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "bind failed", strerror(errno));        exit(-1);    }    //创建线程函数,用于处理数据接收    pthread_t id,data_send_id;    ret = pthread_create(&id, NULL, receive, NULL);    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "pthread_create failed", strerror(errno));        exit(-1);    }    // pthread_join(id,NULL);    ret = pthread_create(&data_send_id, NULL, data_send, NULL);    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "pthread_create failed", strerror(errno));        exit(-1);    }    struct sockaddr_in addr0;    addr0.sin_family = AF_INET;            //设置tcp协议族    addr0.sin_port = htons(7000);          //设置端口号    addr0.sin_addr.s_addr = inet_addr(IP); //设置ip地址    char name_send[3] = {0};    //发送消息    while (1)    {        bzero(name_send, sizeof(name_send));        printf("send name:");        scanf("%s", name_send);        //发送消息时需要绑定对方的ip和端口号        ret = sendto(cfd, (void *)name_send, sizeof(name_send), 0, (struct sockaddr *)&addr0, sizeof(addr0));        if (-1 == ret)        {            fprintf(stderr, "%d, %s :%s", __LINE__, "accept failed", strerror(errno));            exit(-1);        }    }    return 0;}

data_process.c 进行端口9001的ip数据的捕获,当接收到ip数据后,绑定广播的ip地址进行数据的收发,这里用的是udp接收大家也可以试试tcp交互。

#include #include #include #include #include #include #include #include #include #include #include #define IP "127.0.0.1"#define#define// typedef uint32_t in_addr_t;// gcc data_process.c -o data_process -pthreadint cfd = -1,data_fd = -1;uint32_t receive_ip = -1;void *receive(void *pth_arg){    int ret = 0;    char name_data[3] = {0};    struct sockaddr_in addr0 = {0};    int addr0_size = sizeof(addr0);    while (1)    {        printf("receive:");        bzero(name_data, sizeof(name_data));        ret = recvfrom(cfd, name_data, sizeof(name_data), 0, (struct sockaddr *)&addr0, &addr0_size);        if (-1 == ret)        {            fprintf(stderr, "%d, %s :%s", __LINE__, "recv failed", strerror(errno));            exit(-1);        }        else if (ret > 0)        {            printf("\nname = %s ", name_data);            printf("ip %s,port %d \n", inet_ntoa(addr0.sin_addr), ntohs(addr0.sin_port));        }    }}void *data_receive(void *pth_arg){    int ret = 0;    char name_data[10] = {0};    struct sockaddr_in addr0 = {0};    int addr0_size = sizeof(addr0);    while (1)    {        bzero(name_data, sizeof(name_data));        ret = recvfrom(data_fd, name_data, sizeof(name_data), 0, (struct sockaddr *)&addr0, &addr0_size);        if (-1 == ret)        {            fprintf(stderr, "%d, %s :%s", __LINE__, "recv failed", strerror(errno));            exit(-1);        }        else if (ret > 0)        {            printf("\nname = %s ", name_data);            printf("ip %s,port %d \n", inet_ntoa(addr0.sin_addr), ntohs(addr0.sin_port));            receive_ip = addr0.sin_addr.s_addr;            char buf[20] = { 0 };            inet_ntop(AF_INET, &receive_ip, buf, sizeof(buf));            printf("receive_ip ip = %s ", buf);            // printf("receive_ip ip = %s ", inet_ntop(receive_ip));            break;        }    }}int main(){    int ret = -1;    data_fd = socket(AF_INET, SOCK_DGRAM, 0);    if (-1 == data_fd)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "socket failed", strerror(errno));        exit(-1);    }    struct sockaddr_in addr;    addr.sin_family = AF_INET;            //设置tcp协议族    addr.sin_port = htons(DATA_PORT);          //设置端口号    addr.sin_addr.s_addr = inet_addr(IP); //设置ip地址    ret = bind(data_fd, (struct sockaddr *)&addr, sizeof(addr));    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "bind failed", strerror(errno));        exit(-1);    }        pthread_t receive_id;    ret = pthread_create(&receive_id, NULL, data_receive, NULL);    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "pthread_create failed", strerror(errno));        exit(-1);    }    pthread_join(receive_id,NULL);    cfd = socket(AF_INET, SOCK_DGRAM, 0);    if (-1 == cfd)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "socket failed", strerror(errno));        exit(-1);    }    struct sockaddr_in addr1;    addr1.sin_family = AF_INET;            //设置tcp协议族    addr1.sin_port = htons(PORT);          //设置端口号    addr1.sin_addr.s_addr = receive_ip; //设置ip地址    char buf[20] = { 0 };    inet_ntop(AF_INET, &receive_ip, buf, sizeof(buf));    printf("ip = %s ", buf);    ret = bind(cfd, (struct sockaddr *)&addr1, sizeof(addr1));    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "bind failed", strerror(errno));        exit(-1);    }    pthread_t id;    ret = pthread_create(&id, NULL, receive, NULL);    if (-1 == ret)    {        fprintf(stderr, "%d, %s :%s", __LINE__, "pthread_create failed", strerror(errno));        exit(-1);    }    pthread_join(id,NULL);    struct sockaddr_in addr0;    addr0.sin_family = AF_INET;            //设置tcp协议族    addr0.sin_port = htons(6000);          //设置端口号    addr0.sin_addr.s_addr = inet_addr(IP); //设置ip地址    char name_send[3] = {0};    while (1)    {        bzero(name_send, sizeof(name_send));        printf("send name:");        scanf("%s", name_send);        ret = sendto(cfd, (void *)name_send, sizeof(name_send), 0, (struct sockaddr *)&addr0, sizeof(addr0));        if (-1 == ret)        {            fprintf(stderr, "%d, %s :%s", __LINE__, "accept failed", strerror(errno));            exit(-1);        }    }    return 0;}

一个终端捕获数据,sudo tcpdump -i lo portrange 5000-8000 -vv -XX -nn,另外两个终端进行数据交互

结语

这就是我自己的一些udp设计思路的分享。如果大家有更好的想法和需求,也欢迎大家加我好友交流分享哈。

作者:良知犹存,白天努力工作,晚上原创公号号主。公众号内容除了技术还有些人生感悟,一个认真输出内容的职场老司机,也是一个技术之外丰富生活的人,摄影、音乐 and 篮球。

标签: