网络编程-广播、组播和Unix套接字

网络属性设置函数

getsockopt() setsockopt()

#include  
#include
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
参数:
sockfd :  获取哪个文件描述的属性;
level : 层次和下面 optname 对应
        SOL_SOCKET : 通用套接字选项
        IPPROTO_IP : IP 选项
        IPPROTO_TCP : TCP 选项
optname :选项
        SOL_SOCKET :=> level
                SO_BROADCAST允许发送广播数据 int
                SO_RCVTIMEO接收超时 struct timeval
                SO_REUSEADDR允许重用本地地址和端口 int
        IPPROTO_IP :=> level
                IP_ADD_MEMBERSHIP 将 ip 添加到组 struct ip_mreq
        IPPROTO_TCP :=> level
                TCP_NODELAY 不使用 Nagle 算法 int
optval :选项待设置的值
optlen : 选项值的空间大小

举例

设置允许重用本地地址和端口

int on=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
level = SOL_SOCKET 层次
对应选项是:
        optname = SO_REUSEADDR 允许重用本地地址和端口
        optval = &on
        optlen = sizeof(on)

设置允许发送广播数据

int on=1
setsockopt(fd,SOL_SOCKET, SO_BROADCAST,&on,sizeof(on))
设置套接字的超时时间 ( 阻塞在 recv, 超时返回 )。
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};
struct timeval tv={5,0};
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,&tv,sizeof(tv));

ip 添加到多播组

ip 添加到组 struct ip_mreq
struct ip_mreq iq;
iq.imr_multiaddr.s_addr=inet_addr("234.234.234.234"); //224-239
iq.imr_interface.s_addr=inet_addr("192.168.10.251"); //自己 ip 加入组播 ip
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&iq,sizeof(iq));

struct ip_mreq {
    struct in_addr imr_multiaddr; /* IP multicast address of group 多播组的地址 */
    struct in_addr imr_interface; /* local IP address of interface 本地 ip 地址 */
};

查找
zhongl@ubuntu:/usr/include$ grep -nir "struct ip_mreq" *
linux/in.h:166:struct ip_mreq {
..
zhongl@ubuntu:/usr/include$ vi linux/in.h +166
zhongl@ubuntu:/usr/include$

getsockopt()用法

#include 
#include 
#include  
#include 
#include 

#include 
#include 
#include 
#include 

void *send_mesg(void *arg)
{
    int newfd=*((int *)arg);
    char buf[50]={};
    while(1){
        bzero(buf,50);
        printf("pls input your message:\n");
        scanf("%s",buf);
        send(newfd,buf,strlen(buf),0);
    } 
}

int main(void)
{
    int fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd==-1){
        perror("socket");
    }
    
    int on;
    int len=sizeof(on);
    
    getsockopt(fd, SOL_SOCKET,SO_REUSEADDR,&on,&len);// on=0 刚开始没有
    printf("on=%d\n",on);
    printf("len=%d\n",len);
    
    on=1;
    setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    getsockopt(fd, SOL_SOCKET,SO_REUSEADDR,&on,&len); //on=1 现在有了
    
    printf("on=%d\n",on);
    printf("len=%d\n",len);
    
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(10001);
    //server.sin_addr.s_addr=inet_addr("192.168.7.3");
    inet_aton("192.168.0.251", &server.sin_addr);
    int ret=bind(fd,(struct sockaddr *)(&server),sizeof(server));
    if(ret==-1){
        perror("bind");
    }

    listen(fd,5);

    struct sockaddr_in client;
    len=sizeof(client);
    int newfd=accept(fd,(struct sockaddr *)(&client),&len); 
    //运行服务器代码,程序阻塞在这个位置
    printf("ip=%s\n",inet_ntoa(client.sin_addr));
    printf("port=%d\n",ntohs(client.sin_port));
    
    //创建线程
    pthread_t tid;
    pthread_create(&tid,NULL,send_mesg,&newfd);//传入文件描述符
    char buf[50]={};
    while(1){
        bzero(buf,50);
        ret=recv(newfd,buf,sizeof(buf),0);
        if(ret==0){
            break;
        }
        printf("server recv mess:%s\n",buf);
    }

    close(newfd);
    close(fd);

    return 0;
}

结果

zhongl@ubuntu:~/net/3day$ gcc getsockopt_server.c -o getsockopt_server -lpthread
zhongl@ubuntu:~/net/3day$ ./getsockopt_server
on=0
len=4
on=1
len=4

网络超时检查的几种方法

select()
struct timeval tv={5,0}; //5
select(sofd+1,&rdfs,NULL,NULL,&tv);
setsockopt()
struct timeval tv={5,0};
setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
设置定时器捕捉 SIGALRM 信号
void handler(int signo){return;}// 信号处理函数
struct sigaction act;
sigaction(SIGALRM,NULL,&act);// 注册闹钟信号
act.sa_handler=handler;// 设置信号处理函数
act.sa_flag&=~SA_RESTART;// 设置自重启属性为假
sigaction(SIGALRM,&act,NULL);// 信号处理函数
alarm(5);// 定时器设置 5 秒钟
if(recv()<0)
...

广播简介

数据包发送方式只有一个接受方,称为单播;
如果同时发给局域网中的所有主机,称为广播;只有用户数据报( 使用 UDP 协议 ) 套接字才能广播;
广播地址以 192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址 192.168.1.255 代表该网段的广播地址,发到该地址的数据包被所有的主机接收,255.255.255.255 在所有网段中都代表广播地址。
255.255.255.255 在所有网段中都代表广播地。

广播实现步骤

广播发送端

1> 创建用户数据报套接字;
2> 设置套接字为可以广播;
缺省创建的套接字不允许广播数据包,需要设置属性
setsockopt 可以设置套接字属性
3 >sendto() 到广播地址;
3.1 接收方地址指定为广播地址
3.2 指定端口信息
3.3 发送数据包
伪代码
sockfd = socket(,,);
...
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
...
sendto();
广播接收端
1> 创建用户数据报套接字;
2> 绑定地址;(广播 IP/ INADDR_ANY /0.0.0.0
绑定的端口必须和发送方指定的端口相同
//#define INADDR_ANY ((unsigned long int) 0x00000000)
inet_addr("192.168.0.255");
htonl(INADDR_ANY); // 用宏是长整型
inet_addr("0.0.0.0") // 宏实际转换为 "0.0.0.0"
查找
ron@ubuntu:/usr/include$ grep -nir "INADDR_ANY" *
        linux/in.h:273:#define INADDR_ANY ((unsigned long int) 0x00000000)
接收端可以使用3种:
//recv.sin_addr.s_addr=inet_addr("192.168.0.255");
//recv.sin_addr.s_addr=inet_addr("0.0.0.0");
recv.sin_addr.s_addr=htonl(INADDR_ANY);
3> 等待接收数据 recvfrom();

组播简介

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

组播实现步骤

组播发送端:

1 创建用户数据报套接字;
2 设置为广播;
3 接收方地址指定为组播地址
4 指定端口信息
5sendto 发送数据包到组播地址;

组播接收端:

1 创建用户数据报套接字;
2 加入多播组;
3 绑定 IP 与端口;(加入组的组 IP/INADDR_ANY/0.0.0.0
4 绑定的端口必须和发送方指定的端口相同
5recvfrom(); 等待接收数据

广播,组播小结

1. 广播
发送端 接收端
socket socket
允许发送广播数据 允许端口重复使用
bind() ----------------> 广播 IP
sendto( 对着广播 IP) recvfrom
close close
2. 组播
发送端 接收端
socket socket
允许发送广播数据 允许端口重复使用
bind() ----------------> 组播 IP
自身 IP 加入到组播组
sendto( 对着组播 IP) recvfrom
close close

UNIX 域套接字

什么是 UNIX 域套接字

网络通信: 跨主机间的进程间通信;
进程间通信方式也包括 unix 域套接字;
unix 域套接字: 用于本地通信;
创建套接字时使用本地协议 AF_UNIX 分为流式套接字和用户数据报套接字;
和其他进程间通信方式相比使用方便、效率更高,常用于前后台进程通信。

使用 UNIX 域套接字

调用方法基本上和普通 TCP/IP 的套接字一样,只是有些许差别。
UNIX 域套接字在一台机器上的两个不同进程间通信,还要用到 IP 地址就有点大材小用了。 UNIX 域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。
使用本地协议; AF_UNIX
bind 绑定的结构体为
struct sockaddr_un
{
sa_family_t sun_family; // 协议类型
char sun_path[108]; // 套接字文件路径
}
会在路径下创建一个套接字文件,若已存在,先删除
添加结构体头文件
填充地址结
struct sockaddr_un myaddr;
bzero(&myaddr, sizeof(myaddr));
myaddr.sun_family = AF_UNIX;
strcpy(myaddr.sun_path, "mysocket");

 UNIX 域双向通信,UDP 要两个 socket 文件

UNIX (流式)套接字

实际上,生成了一个叫 socket 的文件,类似于有名管道
1. 除了地址类型不同外,其他和 TCP 套接字的使用方法完全一样
2. 绑定套接字文件时,会自动创建该文件。若文件已存在则无法绑定。可以在调用 bind 前先用 remove 删除
3. 若客户端没有绑定地址 ( 套接字文件 ) ,系统不会自动分配
4. 客户端即使没有绑定地址,依然可以从服务器端收数据 ( 因为套接字是面向连接的 )

UNIX (用户数据报)套接字

1. 除了地址类型不同外,其他和 UDP 套接字的使用方法完全一样
2. 绑定套接字文件时,会自动创建该文件。若文件已存在则无法绑定。可以在调用 bind 前先用 remove 删除
3. 若客户端没有绑定地址 ( 套接字文件 ) ,系统不会自动分配
4. 客户端没有绑定地址,只能发送数据,不能接收数据
5. 由于客户端没有建立连接,所以在双向通信时,需要两个文件

删除文件的函数

remove() 一般用这个
unlink()

常用进程通信

无名管道,有名管道,信号,信号量,共享内存,消息队列,线程信号量, unix ------> 同主机
tcp udp ------> 跨主机

你可能感兴趣的:(网络,udp,tcp/ip)