Linux網絡編程之廣播
作者:Eric(wongpz@foxmail.com)
時間:2012-09-14
常見的TCP和UDP通信大都使用的是點對點的單播方式,這種方式可以很方便進行交互處理,在多個交互同時進行時,網絡帶寬占的比較多。廣播是由一個主機向一個網絡上所有主機發送消息的方式,需要的網絡帶寬相對單播來說,降低很多。
廣播的地址是將IP地址中主機部分全部置為1,即xxx.xxx.xxx.255。255.255.255.255這一特殊的廣播地址可以向全世界進行廣播,但一般路由器都會屏蔽。
廣播發送的目的MAC地址為FF:FF:FF:FF:FF:FF。
一般發送廣播步驟如下:
(1)確定廣播接口名字,如eth0;
(2)確定廣播地址,通過ioctl函數,請求碼為SIOCGIFBRDADDR,從而得到廣播地址;
(3)使用廣播地址進行廣播;
由於TCP協議是端到端的協議,通信之前必須建立三次握手才能發送數據,而廣播是一種一對多的通信方式,所以TCP不支持廣播。一般在局域網中,廣播用來探測服務器地址或客戶端的發現。
編程實現:
服務器端:
/********************************************************************* * Filename: bcast_server.c * Description: 廣播服務器端代碼 * Author: Eric(wongpz@foxmail.com) * Date: 2012-9-14 ********************************************************************/ #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <fcntl.h> #include <linux/in.h> #include <stdlib.h> #define IP_FOUND "IP_FOUND" #define IP_FOUND_ACK "IP_FOUND_ACK" #define PORT 9999 int main(int argc, char*argv[]) { int ret = -1; int sock; 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; sock = socket(AF_INET, SOCK_DGRAM, 0); //建立數據報套接字 if (sock < 0) { perror("sock error"); return -1; } 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); //將地址結構綁定到套接字上 ret = bind(sock, (struct sockaddr*) &server_addr, sizeof(server_addr)); if (ret < 0) { perror("bind error"); return -1; } /** * 循環等待客戶端 */ while (1) { timeout.tv_sec = 100; timeout.tv_usec = 0; //文件描述符集合清0 FD_ZERO(&readfd); //將套接字描述符加入到文件描述符集合 FD_SET(sock, &readfd); //select偵聽是否有數據到來 ret = select(sock + 1, &readfd, NULL, NULL, &timeout); //偵聽是否可讀 switch (ret) { case -1: //發生錯誤 perror("select error:"); break; case 0: //超時 printf("select timeout\n"); break; default: if (FD_ISSET(sock,&readfd)) { count = recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&from_addr, &from_len); //接收客戶端發送的數據 //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(sock, buffer, strlen(buffer), 0, (struct sockaddr*) &from_addr, from_len); } } break; } } return 0; }
客戶端:
/********************************************************************* * Filename: bcast_client.c * Description:廣播客戶端代碼 * Author: Eric(wongpz@foxmail.com) * Date: 2012-9-14 ********************************************************************/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/types.h> #include<netdb.h> #include <sys/ioctl.h> #include <net/if.h> #define IP_FOUND "IP_FOUND" #define IP_FOUND_ACK "IP_FOUND_ACK" #define IFNAME "eth0" #define MCAST_PORT 9999 int main(int argc, char*argv[]) { int ret = -1; int sock = -1; int j = -1; int so_broadcast = 1; 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; //建立數據報套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("create socket failed:"); return -1; } // 獲取所有套接字接口 ifc.ifc_len = sizeof(buffer); ifc.ifc_buf = buffer; if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { perror("ioctl-conf:"); return -1; } ifr = ifc.ifc_req; for (j = ifc.ifc_len / sizeof(struct ifreq); --j >= 0; ifr++) { if (!strcmp(ifr->ifr_name, "eth0")) { if (ioctl(sock, SIOCGIFFLAGS, (char *) ifr) < 0) { perror("ioctl-get flag failed:"); } break; } } //將使用的網絡接口名字復制到ifr.ifr_name中,由於不同的網卡接口的廣播地址是不一樣的,因此指定網卡接口 //strncpy(ifr.ifr_name, IFNAME, strlen(IFNAME)); //發送命令,獲得網絡接口的廣播地址 if (ioctl(sock, SIOCGIFBRDADDR, ifr) == -1) { perror("ioctl error"); 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(MCAST_PORT); //默認的套接字描述符sock是不支持廣播,必須設置套接字描述符以支持廣播 ret = setsockopt(sock, 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; //超時時間為2秒 timeout.tv_usec = 0; ret = sendto(sock, IP_FOUND, strlen(IP_FOUND), 0, (struct sockaddr*) &broadcast_addr, sizeof(broadcast_addr)); if (ret < 0) { continue; } //文件描述符清0 FD_ZERO(&readfd); //將套接字文件描述符加入到文件描述符集合中 FD_SET(sock, &readfd); //select偵聽是否有數據到來 ret = select(sock + 1, &readfd, NULL, NULL, &timeout); switch (ret) { case -1: break; case 0: perror("select timeout\n"); break; default: //接收到數據 if (FD_ISSET(sock,&readfd)) { count = recvfrom(sock, buffer, 1024, 0, (struct sockaddr*) &from_addr, &from_len); //from_addr為服務器端地址 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; }
Makefile文件:
OBJS_SERVER = bcast_server.o OBJS_CLIENT = bcast_client.o LIBS_SERVER = LIBS_CLIENT = CFLAGS = -c CC = gcc PROS = bcast_client bcast_server all: $(PROS) .c.o: $(CC) $(CFLAGS) $< bcast_client: $(OBJS_CLIENT) $(CC) -o $@ $^ $(LIBS_SERVER) bcast_server: $(OBJS_SERVER) $(CC) -o $@ $^ $(LIBS_CLIENT) clean: rm -rf $(PROS) $(OBJS_CLIENT) $(OBJS_SERVER)
運行截圖:
備注:
以上代碼參考於http://blog.csdn.net/chenjin_zhong/article/details/7270213,獲取網絡接口部分有改動。
具體代碼及文檔可以在如下地址下載:
http://download.csdn.net/detail/ericdev/4570901