一種通過Udp進行無確認Ip的雙向的通信
                    一種通過Udp進行無確認Ip的雙向的通信
前言
udp是一種不可靠的通信,但是有些時候還是會有使用。今天分享一個示例:主體邏輯,一個端口廣播地址,接收到ip地址數據後,其他端口基於這個ip進行bind綁定,最後通信,這樣可以保證我們後續繼續增加端口交互時候不需要關注ip地址綁定的問題。
主要原理介紹
- 低通信頻率端口進行服務端IP信息udp廣播,接收端是不固定IP監聽,監聽主機任意IP地址的特定端口
 - 接收到廣播通道的ip地址後,與特定IP、port建立tcp或者udp雙向高頻率通信。
 
下圖是基於UDP 的Socket 函數調用過程:
只有接收的時候需要bind ip和端口

socket 監聽所有ip 特定端口代碼:
#define PORT 6000
bzero(&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));- 1.
 - 2.
 - 3.
 - 4.
 - 5.
 - 6.
 
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#define IP "127.0.0.1"
#define
#define
// gcc data_send.c -o data_send -pthread
int 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;
}- 1.
 - 2.
 - 3.
 - 4.
 - 5.
 - 6.
 - 7.
 - 8.
 - 9.
 - 10.
 - 11.
 - 12.
 - 13.
 - 14.
 - 15.
 - 16.
 - 17.
 - 18.
 - 19.
 - 20.
 - 21.
 - 22.
 - 23.
 - 24.
 - 25.
 - 26.
 - 27.
 - 28.
 - 29.
 - 30.
 - 31.
 - 32.
 - 33.
 - 34.
 - 35.
 - 36.
 - 37.
 - 38.
 - 39.
 - 40.
 - 41.
 - 42.
 - 43.
 - 44.
 - 45.
 - 46.
 - 47.
 - 48.
 - 49.
 - 50。
 - 51.
 - 52.
 - 53.
 - 54.
 - 55.
 - 56.
 - 57.
 - 58.
 - 59.
 - 60.
 - 61.
 - 62.
 - 63.
 - 64.
 - 65.
 - 66.
 - 67.
 - 68.
 - 69.
 - 70.
 - 71.
 - 72.
 - 73.
 - 74.
 - 75.
 - 76.
 - 77.
 - 78.
 - 79.
 - 80.
 - 81.
 - 82.
 - 83.
 - 84.
 - 85.
 - 86.
 - 87.
 - 88.
 - 89.
 - 90.
 - 91.
 - 92.
 - 93.
 - 94.
 - 95.
 - 96.
 - 97.
 - 98.
 - 99.
 - 100。
 - 101.
 - 102.
 - 103.
 - 104.
 - 105.
 - 106.
 - 107.
 - 108.
 - 109.
 - 110.
 - 111.
 - 112.
 - 113.
 - 114.
 - 115.
 - 116.
 - 117.
 - 118.
 - 119.
 - 120.
 - 121.
 - 122.
 - 123.
 - 124.
 - 125.
 - 126.
 - 127.
 
data_process.c 進行端口9001的ip數據的捕獲,當接收到ip數據後,綁定廣播的ip地址進行數據的收發,這裡用的是udp接收大家也可以試試tcp交互。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#define IP "127.0.0.1"
#define
#define
// typedef uint32_t in_addr_t;
// gcc data_process.c -o data_process -pthread
int 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;
}- 1.
 - 2.
 - 3.
 - 4.
 - 5.
 - 6.
 - 7.
 - 8.
 - 9.
 - 10.
 - 11.
 - 12.
 - 13.
 - 14.
 - 15.
 - 16.
 - 17.
 - 18.
 - 19.
 - 20.
 - 21.
 - 22.
 - 23.
 - 24.
 - 25.
 - 26.
 - 27.
 - 28.
 - 29.
 - 30.
 - 31.
 - 32.
 - 33.
 - 34.
 - 35.
 - 36.
 - 37.
 - 38.
 - 39.
 - 40.
 - 41.
 - 42.
 - 43.
 - 44.
 - 45.
 - 46.
 - 47.
 - 48.
 - 49.
 - 50。
 - 51.
 - 52.
 - 53.
 - 54.
 - 55.
 - 56.
 - 57.
 - 58.
 - 59.
 - 60.
 - 61.
 - 62.
 - 63.
 - 64.
 - 65.
 - 66.
 - 67.
 - 68.
 - 69.
 - 70.
 - 71.
 - 72.
 - 73.
 - 74.
 - 75.
 - 76.
 - 77.
 - 78.
 - 79.
 - 80.
 - 81.
 - 82.
 - 83.
 - 84.
 - 85.
 - 86.
 - 87.
 - 88.
 - 89.
 - 90.
 - 91.
 - 92.
 - 93.
 - 94.
 - 95.
 - 96.
 - 97.
 - 98.
 - 99.
 - 100。
 - 101.
 - 102.
 - 103.
 - 104.
 - 105.
 - 106.
 - 107.
 - 108.
 - 109.
 - 110.
 - 111.
 - 112.
 - 113.
 - 114.
 - 115.
 - 116.
 - 117.
 - 118.
 - 119.
 - 120.
 - 121.
 - 122.
 - 123.
 - 124.
 - 125.
 - 126.
 - 127.
 - 128.
 - 129.
 - 130.
 - 131.
 - 132.
 - 133.
 - 134.
 - 135.
 - 136.
 - 137.
 - 138.
 - 139.
 - 140.
 - 141.
 - 142.
 - 143.
 - 144.
 - 145.
 - 146.
 - 147.
 - 148.
 - 149.
 - 150。
 - 151.
 - 152.
 
一個終端捕獲數據,sudo tcpdump -i lo portrange 5000-8000 -vv -XX -nn,另外兩個終端進行數據交互

結語
這就是我自己的一些udp設計思路的分享。如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。
作者:良知猶存,白天努力工作,晚上原創公號號主。公眾號內容除了技術還有些人生感悟,一個認真輸出內容的職場老司機,也是一個技術之外豐富生活的人,攝影、音樂and 籃球。