欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

广播、多播和 UNIX 域套接字

最编程 2024-05-18 21:54:28
...

1.广播

1.特点

  • 一对多
  • 仅能使用UDP

2.概念

  • 发送方只有一个接收方则称单播
  • 如果同时发给局域网中的所有主机,成为广播
  • 只有用户数据包(使用UDP协议)套接字才能广播
  • 广播地址

1.以192.168.1.0(255.255.255.0)网段为例,最大的主机地址192.168.1.255代表该网段的广播地址
2.发送到该地址的数据包将被所有主机接收
3.255.255.255.255在所有网段中都代表广播地址

3.广播发送步骤

  • 创建用户数据报套接字
  • 缺省创建的套接字不允许广播数据包,需要设置属性setsockopt
  • 接收方地址指定为广播地址
  • 指定端口信息
  • 发送数据包

4.广播接收步骤

  • 创建用户数据报套接字
  • 绑定本机IP地址和端口(绑定的端口必须与发送方指定的端口相同)
  • 等待接收数据

5.代码

  • 与UDP大同小异
  • 发送方需要将套接字设定为允许广播
int is_use_brc = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &is_use_brc, sizeof(is_use_brc));
  • 发送方绑定的IP地址为广播地址
    ./sender 192.168.1.255 5002

2.组播

1.概念

  • 单播方式只能有一个接收方
  • 广播方式发送给所有主机.过多的广播会大量占用网络带宽,造成网络风暴,影响正常通信
  • 组播(又称多播)为单播与广播的一种折中方式,只有加入到某个组的主机才能收到数据
  • 多播方式既能发送给多个主机,又能避免像广播那样带来过多负载(即每台主机要到传输层才能判断广播包是否需要处理)

2.组播IP地址段

  • 224.0.0.1~239.255.255.254(中间除去广播IP)

3.组播发送步骤

  • 创建用户数据报套接字
  • 接收方地址设定为组播地址
  • 指定端口信息
  • 发送数据报

4.组播接收步骤

  • 创建用户数据报套接字
  • 加入多播组
  • 绑定本机IP地址与端口(绑定的端口必须与发送方指定的端口相同)
  • 等待数据接收

5.代码

  • 接收方需要加入多播组
/*加入多播组*/
#define MULTICAST_IP "235.10.10.3"
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);/*多播组的IP地址*/
mreq.imr_interface.s_addr = htonl(INADDR_ANY);/*加入的客服端主机IP地址*/
setsockopt(fd, SOL_SOCKET, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
  • 发送方绑定地址为多播地址
    ./sender 235.10.10.3 5002

3.UNIX域套接字

1.特点

  • socket同样可以用于本地通信
  • 创建套接字的时候需使用本地协议AF_UNIX/AF_LOCAL
socket(AF_LOCAL, SOCK_STREAM, 0);
socket(AF_LOCAL, SOCK_DGRAM, 0);
  • 分为流式套接字与用户数据报套接字
  • 与其他进程之间通信方式相比效率更高,使用更方便
  • 常用于前后台进程通信

2.总结

  • 进程之间通信方式有6种:管道,消息队列,共享内存,UNIX域套接字,信号,信号量
  • 按照使用频率排序:消息队列>UNIX域套接字>管道>共享内存(经常需要与信号量一起使用)
  • 按照效率排序:共享内存>UNIX域套接字>管道>消息队列

1.进程之间数据共享:管道,消息队列,共享内存,UNIX域套接字
2.异步通信:信号
3.同步与互斥,做资源保护:信号量

3.代码

  • 大体与网络编程步骤一样,只是在绑定的时候其结构体不同,网络编程为sockaddr_in,域套接字为sockaddr_un
#include <sys/un.h>
#define UNIX_PATH_MAX 108

struct sockaddr_un
{
  sa_family_t sun_family;/*AF_UNIX/AF_LOCAL*/
  char sun_path[UNIX_PATH_MAX ];/*域套接字文件路径名*/
};

注意:域套接字文件路径名必须事先不存在且一般使用绝对路径,该文件存在于内存中

4.图示

  • TCP服务器端
  • TCP客户端
  • UDP服务器端
  • UDP客户端

5.示例

  • 客户端
#include "common.h"

void tips(char *s)
{
    printf("\n%s unix_domain_file\n\n", s);
}
int main(int argc, char **argv)
{
    int fd = -1;
    struct sockaddr_un sin;
    char buff[BUFSIZ];
    int ret = -1/*write函数返回值 */;
    fd_set rset;/*定义读集合 */
    int maxfd = -1;/*保存最大的文件描述符 */
    struct timeval tout;/*超时时间结构体 */
    /*创建socket */
    if(argc != 2)
    {
        tips(argv[0]);
        exit(-1);
    }
    if((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*判断UNIX_DOMAIN_FILE文件是否存在且可写*/
    if(access(UNIX_DOMAIN_FILE, F_OK|W_OK) < 0)
    {
        puts("UNIX_DOMAIN_FILE is unkonwn");
        exit(-1);
    }
    /*结构体成员清零 */
    bzero(&sin, sizeof(sin));
    /*填充结构体 */
    sin.sun_family = AF_LOCAL;
    strncpy(sin.sun_path, UNIX_DOMAIN_FILE, strlen(UNIX_DOMAIN_FILE));
    /*连接服务器 */
    if(connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("connect");
        exit(-1);
    }
    puts("Unix domain client init ok...");
    while (1)
    {
        /*读集合清零 */
        FD_ZERO(&rset);
        /*加入进程默认打开的标准键盘输入fd */
        FD_SET(0, &rset);
        /*加入客户端与服务器通信的fd */
        FD_SET(fd, &rset);
        maxfd = fd;
        tout.tv_sec = 5;/*超时时间为5秒 */
        tout.tv_usec = 0;
        select(maxfd + 1, &rset, NULL, NULL, &tout);
        /*代表标准键盘有输入 */
        if(FD_ISSET(0, &rset))
        {
            bzero(buff, BUFSIZ);
            /*-1防止数组下标越界 */
            do
            {
                ret = read(0, buff, BUFSIZ - 1);
            } while (ret < 0 && EINTR == errno);
            
            if(ret < 0)
            {
                perror("read from keyboard");
                continue;
            }   
            /*如果ret=0表示没有从键盘上读到数据 */
            if(!ret)
            {
                continue;
            }
            if(write(fd, buff, strlen(buff)) < 0)
            {
                perror("write to socket");
                continue;
            }
            if(strcmp(buff, QUIT_STR) == 0)
            {
                printf("Client is exting...\n");
                break;
            }
        }
        /*代表有服务器的数据到达 */
        if(FD_ISSET(fd, &rset))
        {
            bzero(buff, BUFSIZ);
            /*-1防止数组下标越界 */
            do
            {
                ret = read(fd, buff, BUFSIZ - 1);
            } while (ret < 0 && EINTR == errno);
            
            if(ret < 0)
            {
                perror("read from server");
                continue;
            }   
            /*如果ret=0表示服务器关闭 */
            if(!ret)
            {
                break;
            }
            printf("recv from server:%s\n", buff);
            /*此处存在BUG,待修复 */
            if(strlen(buff) > strlen(SERV_RESP_STR))
            {
                if(strcmp(buff + strlen(SERV_RESP_STR), QUIT_STR) == 0)
                {
                    printf("sender is exting...\n");
                    break;
                }
            }
            
        }
    }
    close(fd);
    return 0;
}
  • 服务器
#include "common.h"


void client_fork(void *arg);
/*子进程结束信号处理函数 */
void sig_child_handle(int signo)
{
    /*回收子进程 */
    if(SIGCHLD == signo)
    {
        waitpid(-1, NULL, WNOHANG);
    }
}
int main(void)
{
    pid_t pid;
    
    int fd = -1;
    int newfd = -1;
    
    int b_reuse = 1;
    struct sockaddr_un sun;/*定义本地通信套接字*/
    signal(SIGCHLD, sig_child_handle);
    /*创建本地域套接字 */
    if((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*允许绑定地址快速重用 */
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
    /*结构体成员清零 */
    bzero(&sun, sizeof(sun));
    /*填充结构体 */
    sun.sun_family = AF_LOCAL;
    /*如果UNIX指定文件存在则删除该文件*/
    if(!access(UNIX_DOMAIN_FILE, F_OK))
    {
        unlink(UNIX_DOMAIN_FILE);
    }
    strncpy(sun.sun_path, UNIX_DOMAIN_FILE, strlen(UNIX_DOMAIN_FILE));
    /*绑定socket */
    if(bind(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
    {
        perror("bind");
        exit(-1);
    }
    /*主动套接字转被动套接字 */
    if(listen(fd, BACKLOG) < 0)
    {
        perror("listen");
        exit(-1);
    }
    puts("Server staring ok");
    while(1)
    {
        /*阻塞等待客户端连接请求并保存客户端信息 */
        if((newfd = accept(fd, NULL, NULL)) < 0)
        {
            perror("accept");
            break;
        }
        /*创建子进程,用于处理客户端数据 */
        if((pid = fork()) < 0)
        {
            perror("fork");
            break;
        }
        /*子进程 */
        if(0 == pid)
        {
            close(fd);
            /*客户端IP地址网络字节序转本地字节序 */
            client_fork(&newfd);
            return 0;
        }
        /*父进程 */
        else
        {
            close(newfd);
        }
    }
    close(fd);
    return 0;
}
void client_fork(void * arg)
{
    char buf[BUFSIZ];
    char resp_buf[BUFSIZ + 10];
    int ret = -1;/*read函数返回值 */
    int newfd = *(int *)arg;
    printf("process newfd = %d\n", newfd);
    while(1)
    {
        /*与newfd进行读写 */
        bzero(buf, BUFSIZ);
        /*判断读函数是否出错 */
        do
        {
            ret = read(newfd, buf, BUFSIZ - 1);
        } while (ret < 0 && EINTR == errno);
        if(ret < 0)
        {
            perror("read");
            exit(-1);
        }
        if(0 == ret)
        {
            break;
        }
        printf("Unix domain service receive data: %s\n", buf);
        bzero(resp_buf, BUFSIZ + 10);
        strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));
        strcat(resp_buf, buf);
        do
        {
            ret = write(newfd, resp_buf, strlen(resp_buf));
        } while (ret < 0 && EINTR == errno);
        
        if(strcmp(buf, QUIT_STR) == 0)
        {
            printf("Client is exting...\n");
            break;
        }
    }
    close(newfd);
}