广播,组播和UNIX域套接字

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);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271