一、广播和组播
1、广播的概念:
- 前面介绍的数据包发送 方式只有一个接收方,称为 单播;
- 如果同时发送给局域网中的所有主机,称为 广播;
- 只有用户数据报(使用UDP协议)套接字才能广播;
- 广播的地址:
- 以192.168.1.0(255.255.255.0)网段为例 ,最大的主机地址 192.168.1.255代表该网段的广播地址;
- 发到该地址的数据包被所有的主机接收;
- 255.255.255.255在所有网段中都代表广播地址;
- 要点:
- 发送 方要通过 setsockopt设置套接字广播属性;
- 发送方的发送地址 为广播地址:XXX.XXX.XXX.255
- 接收方和发送方的端口号相同;
2、广播发送示例
- 缺省创建 的 套接字文件是不允许 广播
- 客户端 改成 sender方;
int b_br = 1;
setsockopt(fd , SOL_SOCKET , SO_BROADCAST,&b_br , sizeof(int));
- 传入的IP地址 以.255的广播地址;
#include "net_exp.h"
void usage(const char* s){
printf("\n %s serv_ip ser_port \n",s);
printf("\n\t serv_ip:server ip address");
printf("\r\n serv_port: server port(>5000)\n");
}
int main(int argc, const char *argv[])
{
int fd = -1;
int port = 0;
struct sockaddr_in sin;
if((fd = socket(AF_INET , SOCK_DGRAM,0)) < 0 ){
perror("socket");
exit(1);
}
if(argc != 3 ){
usage(argv[0]);
exit(1);
}
port = atoi(argv[2]);
if (port < 5000){
usage(argv[0]);
exit(1);
}
int b_br = -1;
setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&b_br,sizeof(int)); //允许广播
/*2、填充*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
if( inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr) != 1){
perror("inet_ntop");
exit(1);
}
printf("broadcast starting ....... \n");
char buff[BUFSIZ];
while(1){
bzero(buff,BUFSIZ);
if(fgets(buff,BUFSIZ-1,stdin) == NULL){
perror("fgets");
continue;
}
sendto(fd,buff,strlen(buff),0,(struct sockaddr*)&sin,sizeof(sin));
if(!strncasecmp(buff , "quit\n",sizeof("quit\n"))){
printf("Client is exiting !!! \n");
break;
}
}
close(fd);
return 0;
}
3、组播
- 单播方式只能发给一个接收方。
- 广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信;
- 组播(又称为多播)是一种折中的方式。只是加入某个多播组的主机才能收到数据;
- 多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
- 加入组播
struct ip_mreq
{
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“235.10.10.3”);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
#include "net_udp.h"
int main(int argc, const char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
/*1、创建socket fd*/
if((fd = socket(AF_INET , SOCK_DGRAM , 0)) < 0){
perror("socket");
exit(1);
}
/*优化1 : 允许客户端快速重连*/
int b_reuse = -1;
char server_ip[16];
setsockopt(fd , SOL_SOCKET, SO_REUSEADDR , &b_reuse , sizeof(int));
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“235.10.10.3”); //
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
/*2 、绑定*/
/*2.1 填充*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);// INADDR_ANY :为0.0.0.0 泛指本机的意思
/*2.2 绑定*/
if(bind(fd , (struct sockaddr*)&sin, sizeof(sin))){
perror("bind");
exit(1);
}
if(!inet_ntop(AF_INET , (void*)&sin.sin_addr,server_ip,sizeof(sin))){
perror("inet_ntop");
exit(1);
}
printf("Server %s starting......\n",server_ip);
/*3、*/
char buff[BUFSIZ];
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1){
bzero(buff, sizeof(buff));
if(recvfrom(fd , buff , BUFSIZ-1 , 0 ,(struct sockaddr*)&cin ,&addrlen) < 0){
perror("recvform");
continue;
}
char ipv4_addr[16];
if(!inet_ntop(AF_INET , (void*)&cin.sin_addr,ipv4_addr,sizeof(cin))){
perror("inet_ntop");
exit(1);
}
printf("Client (%s:%d) Send : %s \n",ipv4_addr,htons(sin.sin_port) ,buff);
if(!strncasecmp(buff,"quit\n",sizeof("quit\n"))){
printf("Client (%s : %d) is exiting \n",ipv4_addr , htons(sin.sin_port));
}
}
close(fd);
return 0;
}
4、网络地址
- A类地址
第1字节为网络地址,其他3个字节为主机地址。第1字节的最高位固定为0
1.0.0.1 – 126.255.255.255
- B类地址
第1字节和第2字节是网络地址,其他2个字节是主机地址。第1字节的前两位固定为10
128.0.0.1 – 191.255.255.255
- C类地址
前3个字节是网络地址,最后1个字节是主机地址。第1字节的前3位固定为110
192.0.0.1 – 223.255.255.255
- D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
组播地址:224.0.0.1 – 239.255.255.255(除掉广播地址)
二、UNIX域套接字
1、UNIX域套接字基本概念
- socket套接字的 简化版,在进程(本地)间进行通信;
- socket也可以 在进程(本地)通信中使用,但没有UNIX域套接字效率高;
- 创建套接字时使用本地协议PF_UNIX(或PF_LOCAL).
- socket(AF_LOCAL,SOCK_STREAM);
- socket(AF_LOCAL,SOCK_DGRAM);
- 分为流式套接字和用户数据报套接字;
- 和其他进程间通信方式相比使用方便、效率更高;
- 常用于前后台进程通信;
2、地址结构
struct sockaddr_un //<sys /un .h>
{
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 , “/tmp/mysocket”);
3、UNIX域 流式套接字
- 客户端
- 服务器
— 绑定的地址 要是同一个 路径的 UNIX域套接字文件;
4、UNIX域 用户数据报套接字
- 客户端
- 服务器
5、总结
- 编程时:先bind() 在socket_unix结构体变量;
- 要点:
- unix域套接字 的文件路径名(在内存中的文件)
- 必须事先不存在;
- 一般给绝对路径;
- 总结:进程间通信
— 进程间的数据共享:
- 管道、消息队列、共享内存、unix域套接字
易用性:消息队列 > unix域套接字 > 管道 > 共享内存(经常要和信号量一起用)
效率 : 共享内存 > unix域套接字 > 管道 > 消息队列
常用 : 共享内存 、 unix域套接字
— 异步通信
- 信号
— 同步和互斥(做资源保护)
- 信号量
6、示例 — 将 多进程TCP网络通信 改为 多进程UNIX域 流式套接字通信
- 服务器端
- 客户端