UDP 广播编程研究一
(一)介绍
广播是一个主机向一个网络上所有主机发送的操作方式,一对多的,同一个子网内的所有主机都可以收到此广播发送的数据。可见TCP是不支持广播的。
广播IP地址:IP地址一般可分为两部分,右部分是主机ID,左部分是网络ID,广播地址要求主机ID部分为全1。255.255.255.255是一个特殊广播地址,而我的机子:
可以看到广播地址:192.168.1.255,可以认为前三个字节192.168.1是网络ID部分。
(二)举例
服务器地址发现,假设服务器A,客户端B,客户端在某个局域网启动的时候,不知道本局域网是否有合适的服务器存在,它会使用广播在本局域网发送一个特定请求,如果有服务器相应了这个请求,则使用相应请求的IP地址进行链接,这也是一种服务器/客户端自动发现的一种常用办法。
代码如下:
广播客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> /*htonl htons ntohs */
#include <sys/ioctl.h>
#include <net/if.h>
#define IP_FOUND "IP_FOUND"
#define IP_FOUND_ACK "IP_FOUND_ACK"
#define IFNAME "eth0"
#define PORT 8554
int main(int argc, char*argv[])
{
int ret = -1;
int sockfd = -1;
int j = -1;
/* 保存eth0接口信息 */
struct ifreq *ifr;
/* 保存所有接口信息 */
struct ifconf ifc;
/* 广播地址 */
struct sockaddr_in broadcast_addr;
/* 服务端地址 */
struct sockaddr_in from_addr;
int from_len = sizeof(from_addr);
int count = -1;
/* 读文件描述符集合 */
fd_set readfd;
char buffer[1024];
struct timeval timeout;
timeout.tv_sec = 2; //超时时间为2秒
timeout.tv_usec = 0;
/* 建立数据报套接字 */
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0))<0)
{
perror("Socket error!\n");
return -1;
}
printf("socket ok!\n");
/* 获得所有网络接口列表(struct ifconf) */
ifc.ifc_len = sizeof(buffer);
ifc.ifc_buf = buffer;
if (ioctl(sockfd, SIOCGIFCONF, (char *) &ifc) < 0)
{
perror("ioctl struct ifconf error!\n");
return -1;
}
ifr = ifc.ifc_req;
for (j = ifc.ifc_len / sizeof(struct ifreq); --j >= 0; ifr++)
{
if (!strcmp(ifr->ifr_name, IFNAME))
{
if (ioctl(sockfd, SIOCGIFFLAGS, (char *)ifr) < 0)
{
perror("ioctl-get flag error!");
}
break;
}
}
/* 发送命令,获得eth0网络接口的广播地址 */
if (ioctl(sockfd, SIOCGIFBRDADDR, ifr) == -1)
{
perror("ioctl-get broadcast_addr error\n");
return -1;
}
/* 将获得的广播地址复制到broadcast_addr */
memcpy(&broadcast_addr, (char *)&ifr->ifr_broadaddr, sizeof(struct sockaddr_in));
printf("\nBroadcast-IP: %s\n", inet_ntoa(broadcast_addr.sin_addr));
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_port = htons(PORT); /* 设置广播端口号 */
/* 默认的套接字描述符sock是不支持广播,必须设置套接字描述符以支持广播 */
int so_broadcast = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &so_broadcast,sizeof(so_broadcast));
/* 发送多次广播,看网络上是否有服务器存在 */
int times = 10;
int i = 0;
for (i = 0; i < times; i++)
{
/* 一共发送10次广播,每次等待2秒是否有回应 */
timeout.tv_sec = 2;
timeout.tv_usec = 0;
/* 广播发送服务器地址请求 */
ret = sendto(sockfd, IP_FOUND, strlen(IP_FOUND), 0,(struct sockaddr*) &broadcast_addr, sizeof(broadcast_addr));
if (ret < 0)
{
continue;
}
/* 文件描述符清0 */
FD_ZERO(&readfd);
/* 将套接字文件描述符加入到文件描述符集合中 */
FD_SET(sockfd, &readfd);
/* select侦听是否有数据到来 */
ret = select(sockfd + 1, &readfd, NULL, NULL, &timeout);
switch (ret)
{
case -1: break;
case 0:
perror("select timeout!\n");
break;
default:
/* 有数据到来 */
if (FD_ISSET(sockfd,&readfd))
{
// 从服务器端地址接收数据 */
count = recvfrom(sockfd, buffer, 1024, 0,(struct sockaddr*) &from_addr, &from_len);
printf("\trecvmsg is %s\n", buffer);
if (strstr(buffer, IP_FOUND_ACK))
{
printf("\tfound server IP is %s, Port is %d\n",inet_ntoa(from_addr.sin_addr),htons(from_addr.sin_port));
}
return -1;
}
break;
}
}
return 0;
}
广播服务器
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> /*htonl htons ntohs */
#include <stdlib.h>
#define IP_FOUND "IP_FOUND"
#define IP_FOUND_ACK "IP_FOUND_ACK"
#define PORT 8554
int main(int argc, char *argv[])
{
int ret = -1;
int sockfd = -1;
/* 服务器端地址 */
struct sockaddr_in server_addr;
/* 客户端地址 */
struct sockaddr_in from_addr;
int from_len = sizeof(struct sockaddr_in);
int count = -1;
/* 读文件描述符集合 */
fd_set readfd;
char buffer[1024];
struct timeval timeout;
timeout.tv_sec = 2;
timeout.tv_usec = 0;
/* 建立数据报套接字 */
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //建立数据报套接字
if (sockfd < 0)
{
perror("Socket error!\n");
return -1;
}
printf("socket ok!\n");
memset((void*) &server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY );
server_addr.sin_port = htons(PORT);
/* 将地址结构绑定到套接字上 */
if ((ret = bind(sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr)))<0)
{
perror("bind error!\n");
return -1;
}
printf("bind ok!\n");
/* 循环等待客户端 */
while (1)
{
timeout.tv_sec = 100;
timeout.tv_usec = 0;
/* 文件描述符清0 */
FD_ZERO(&readfd);
/* 将套接字文件描述符加入到文件描述符集合中 */
FD_SET(sockfd, &readfd);
/* select侦听是否有数据到来 */
ret = select(sockfd + 1, &readfd, NULL, NULL, &timeout);
switch (ret)
{
case -1:
perror("select error!\n");
break;
case 0:
printf("select timeout!\n");
break;
default:
/* 有数据到来 */
if (FD_ISSET(sockfd,&readfd))
{
/* 接收客户端发送的数据 */
count = recvfrom(sockfd, buffer, 1024, 0,(struct sockaddr*)&from_addr, (socklen_t *)&from_len);
printf("recvfrom %s!\n",buffer);
/* from_addr保存客户端的地址结构 */
if (strstr(buffer, IP_FOUND))
{
/* 响应客户端请求,打印客户端的IP地址和端口号 */
printf("\nClient connection information:\n\t IP: %s, Port: %d\n",(char *)inet_ntoa(from_addr.sin_addr),ntohs(from_addr.sin_port));
/* 将数据发送给客户端 */
memcpy(buffer, IP_FOUND_ACK, strlen(IP_FOUND_ACK) + 1);
count = sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr*) &from_addr, from_len);
}
}
break;
}
}
return 0;
}
代码测试: