前言
udp是一种不可靠的通信,但是有些时候还是会有使用。今天分享一个示例:主体逻辑,一个端口广播地址,接收到ip地址数据后,其他端口基于这个ip进行bind绑定,最后通信,这样可以保证我们后续继续增加端口交互时候不需要关注ip地址绑定的问题。
作者:良知犹存
转载授权以及围观:欢迎关注微信公众号:羽林君
或者添加作者个人微信:become_me
主要原理介绍
低通信频率端口进行服务端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));
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 PORT 6000 #define DATA_PORT 9001 // 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; }
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 PORT 7000 #define DATA_PORT 9001 // 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; }
一个终端捕获数据,sudo tcpdump -i lo portrange 5000-8000 -vv -XX -nn
,另外两个终端进行数据交互
结语
这就是我自己的一些udp设计思路的分享。如果大家有更好的想法和需求,也欢迎大家加我好友交流分享哈。