前言

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 籃球。

標簽: