Menu Home

linux socket: 使用广播查询服务器地址

流程

  1. 服务器端监听端口9999
  2. 客户端通过发送广播信息向网络查询需要的服务器地址
  3. 服务器端收到客户端的查询后把自己的地址信息发送给客户端

代码

公共头文件common.h

#ifndef __COMMON_H
#define __COMMON_H

#include <stdio.h>
#include <stdlib.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 QUERY_SERV "QUERY_SERV"
/*服务器应答消息*/
#define IAMSERV "IAMSERV"
#define PORT 9999

/*返回值*/
#define ERR 1
#define OK 0

#endif

服务器端serv.c

#include "common.h"

int main(int argc, char *argv[]) {
	int ret = -1;
	int sock = -1;
	struct sockaddr_in serv_addr;
	struct sockaddr_in cli_addr;
	socklen_t cli_len = sizeof(struct sockaddr_in);
	fd_set readfd;
	char buffer[1024] = {0};

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock == -1) {
		perror("sock error");
		return ERR;
	}

	memset(&serv_addr, 0x0, sizeof(struct sockaddr_in));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(PORT);

	ret = bind(sock, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));
	if(ret == -1) {
		perror("bind error");
		return ERR;
	}

	while(1) {
		FD_ZERO(&readfd);
		FD_SET(sock, &readfd);

		ret = select(sock + 1, &readfd, NULL, NULL, NULL);
		switch(ret) {
		case -1:
			perror("select error");
			break;
		case 0:
			fprintf(stderr, "select timeout\n");
			break;
		default:
			if(FD_ISSET(sock, &readfd)) {
				recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&cli_addr, &cli_len);
				if(strstr(buffer, QUERY_SERV)) {
					printf("Client IP: %s  PORT: %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
					sendto(sock, IAMSERV, strlen(IAMSERV), 0, (struct sockaddr*)&cli_addr, cli_len);
				}
			}
			break;
		}
	}
	return OK;
}

客户端cli.c

#include "common.h"

int main(int argc, char *argv[]) {
    int ret = -1;
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(struct sockaddr_in);
    fd_set readfd;
    char buffer[1024] = {0};
	char *netDev = NULL;

    struct timeval timeout;
    timeout.tv_sec = 2;
    timeout.tv_usec = 0;

	/*设置客户端的网口*/
	if(argc == 2) {
		netDev = argv[1];	
	} else {
		/*默认使用eth0*/
		netDev = "eth0";	
	}

    int sock = -1;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock ==  -1) {
		perror("socket error");
        return ERR; 
    }
    
    struct ifreq ifr;
	memset(&ifr, 0x0, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, netDev, strlen(netDev));
	printf("use device: %s\n", ifr.ifr_name);
	/*查询选定网口的广播地址*/
    if(ioctl(sock, SIOCGIFBRDADDR, &ifr) == -1) {
        perror("ioctl error");
        return ERR;
    }

    int so_broadcast = 1;
    struct sockaddr_in broadcast_addr; 
    memcpy(&broadcast_addr, &ifr.ifr_broadaddr, sizeof(struct sockaddr_in));
    printf("broadcast IP is: %s\n", inet_ntoa(broadcast_addr.sin_addr));
    
    broadcast_addr.sin_family = AF_INET;
    broadcast_addr.sin_port = htons(PORT);
    ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &so_broadcast, sizeof(so_broadcast));
	if(ret == -1) {
		perror("setsockopt error");
		return ERR;
	}

	/*广播查询服务器信息,如果没有回应,则重新发送,共重复10次*/
    int times = 10;
	int trys = 0;

    for(trys = 0; trys < times; trys++) {
        timeout.tv_sec = 2;
        timeout.tv_usec = 0;

        ret = sendto(sock, QUERY_SERV, strlen(QUERY_SERV), 0, (struct sockaddr*)&broadcast_addr, sizeof(struct sockaddr));
        if(ret == -1) {
            continue;
        }
        
        FD_ZERO(&readfd);
        FD_SET(sock, &readfd);

        ret = select(sock + 1, &readfd, NULL, NULL, &timeout);
        switch(ret) {
        case -1:
			perror("select error");
            break;
        case 0:
            fprintf(stderr, "select timeout\n");
            break;
        default:
            if(FD_ISSET(sock, &readfd)) {
                recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&serv_addr, &serv_len);
                printf("recvmsg is: %s\n", buffer);

                if(strstr(buffer, IAMSERV)) {
                    printf("Server found, IP: %s PORT: %d \n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
                }
                return OK;
            }
            break;
        }
    }

	if(trys == 10) {
		printf("Sorry, cannot find the server\n");
	}
	
    return OK;
}

测试运行

  1. 打开服务器端: ./serv
  2. 打开客户端: ./cli
    客户端参数: ./cli [ethx]
    参数说明:ethx是你要使用的网络端口,如eth0, eth1等,默认为eth0
    1. 运行输出

      客户端输出:

      2

      服务器端输出:

      1

      github: https://github.com/lnmcc/BroadcastQuery.git

      Categories: C/C++ linux 网络

      Tagged as:

      lnmcc