前面介绍的数据包发送方式只有一个接受方,称为单播如果同时发给局域网中的所有主机,称为广播只有用户数据报(使用UDP协议)套接字才能广播
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址 192.168.1.255 代表该网段的广播地址发到该地址的数据包被所有的主机接收255.255.255.255在所有网段中都代表广播地址
同之前的单播一样,广播也是需要发送方和接收方的,具体流程如下:
fd = socket(AF_INET,SOCK_DGRAM,0); //基于IPv4协议的UDP套接字
int ab_aben = 1; setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&ab_aben,sizeof(int)); //允许发送广播数据包
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define DEBUG 0 void usage(char* s) { printf("\nThis is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n"); } int main(int argc, char* argv[]) { int fd = -1; int result = 0; int SERV_PORT = -1; char* SERV_IP_ADDR = NULL; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd!!!\n"; char botton[64] = {0}; #if DEBUG SERV_PORT = 5050; SERV_IP_ADDR = "192.168.0.158"; #else if (argc != 3) { usage(argv[0]); return -1; } SERV_PORT = atoi(argv[2]); if (SERV_PORT <= 5000) { usage(argv[0]); return -1; } SERV_IP_ADDR = argv[1]; #endif /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*2.连接服务器 写入数据 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号 #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 读写数据*/ printf("Client staring...OK!\n"); struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; clientaddrlen = sizeof(clientscokaddr); while (1) { do { result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); break; } sleep(1); bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, MSG_DONTWAIT, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); printf("message is:%s\n", botton); } close(fd); return 0; }
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" int fun_process(int fd,char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen); void handler(int signo) { //自动回收子进程 防止僵尸进程 if (signo == SIGCHLD) { printf("Recall SIGINT\n"); waitpid(-1, NULL, WNOHANG); } } int main() { int fd = -1; int result = 0; pid_t pid; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用recvfrom 接收客户端发来的信息 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ clientaddrlen = sizeof(clientscokaddr); signal(SIGCHLD, handler); while (1) { bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); if (-1 == result) { perror("recvfrom"); break; } pid = fork(); if (pid < 0) { perror("fork"); break; } else if (0 == pid) { result = fun_process(fd, botton,clientscokaddr, clientaddrlen); close(fd); return result; } } close(fd); return 0; } int fun_process(int fd, char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen) { char ipv4_addr[16]; int result; char botton_re[64] = "recv success!"; printf("23333\n"); if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); printf("message is:%s\n", botton); do { result = sendto(fd, botton_re, strlen(botton_re), 0, (struct sockaddr*)&clientscokaddr, sizeof(clientscokaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); return -1; } return 0; }
主机之间“一对一组”的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据,网络中的交换机和路由器只向有需求者复制并转发其所需数据。主机可以向路由器请求加入或退出某个组,网络中的路由器和交换机有选择的复制并传输数据,即只将组内数据传输给那些加入组的主机。这样既能一次将数据传输给多个有需要(加入组)的主机,又能保证不影响其他不需要(未加入组)的主机的其他通讯。
组播是一个发送方,多个接收方,接收方必须是加入多播组的成员。
组播的IP地址:224.0.0.1 – 239.255.255.254 (中间除掉广播)239.0.0.0—239.255.255.255是私有地址,供各个内部网在内部使用
组播必须基于UDP的编程方法
组播的编程分发送端和接收端两种,具体思路如下:
fd = socket(AF_INET,SOCK_DGRAM,0); //基于IPv4协议的UDP套接字
接收方地址指定为组播地址
239.0.0.0—239.255.255.255是私有地址,供各个内部网在内部使用
指定端口信息
5050
发送数据包
sendto()
fd = socket(AF_INET,SOCK_DGRAM,0); //基于IPv4协议的UDP套接字
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));
绑定本机IP地址和端口 ,绑定的端口必须和发送方指定的端口相同
5050
等待接收数据
recvfrom();
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define DEBUG 0 void usage(char* s) { printf("\nThis is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n"); } int main(int argc, char* argv[]) { int fd = -1; int result = 0; int SERV_PORT = -1; char* SERV_IP_ADDR = NULL; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd!!!\n"; char botton[64] = {0}; #if DEBUG SERV_PORT = 5050; SERV_IP_ADDR = "192.168.0.158"; #else if (argc != 3) { usage(argv[0]); return -1; } SERV_PORT = atoi(argv[2]); if (SERV_PORT <= 5000) { usage(argv[0]); return -1; } SERV_IP_ADDR = argv[1]; #endif /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*2.连接服务器 写入数据 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号 #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 读写数据*/ printf("Client staring...OK!\n"); struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; clientaddrlen = sizeof(clientscokaddr); while (1) { do { result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); break; } sleep(1); bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, MSG_DONTWAIT, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); printf("message is:%s\n", botton); } close(fd); return 0; }
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "239.0.0.10" int fun_process(int fd,char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen); void handler(int signo) { //自动回收子进程 防止僵尸进程 if (signo == SIGCHLD) { printf("Recall SIGINT\n"); waitpid(-1, NULL, WNOHANG); } } int main() { int fd = -1; int result = 0; pid_t pid; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /* 设置组播 */ struct ip_mreq mreq; bzero(&mreq,sizeof(struct ip_mreq)); mreq.imr_multiaddr.s_addr = inet_addr("239.0.0.10"); mreq.imr_interface.s_addr = htonl(INADDR_ANY); setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用recvfrom 接收客户端发来的信息 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ clientaddrlen = sizeof(clientscokaddr); signal(SIGCHLD, handler); while (1) { bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); if (-1 == result) { perror("recvfrom"); break; } pid = fork(); if (pid < 0) { perror("fork"); break; } else if (0 == pid) { result = fun_process(fd, botton,clientscokaddr, clientaddrlen); close(fd); return result; } } close(fd); return 0; } int fun_process(int fd, char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen) { char ipv4_addr[16]; int result; char botton_re[64] = "recv success!"; printf("23333\n"); if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); printf("message is:%s\n", botton); do { result = sendto(fd, botton_re, strlen(botton_re), 0, (struct sockaddr*)&clientscokaddr, sizeof(clientscokaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); return -1; } return 0; }
socket同样可以用于本地通信
进程间通信:
进程间的数据共享
共享内存,管道,消息队列,,unix域套接字
易用性:消息队列 > unix域套接字 > 管道 > 共享内存(经常需要和信号量一起用)
效率: 共享内存 > unix域套接字 > 管道 > 消息队列
常用: 共享内存 unix域套接字
异步通信:
信号
同步和互斥(做资源保护):
信号量
/* 创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。 */ socket(AF_LOCAL, SOCK_STREAM, 0); //AF_LOCAL 可替换成 AF_UNIX socket(AF_LOCAL, SOCK_DGRAM, 0); //同上
UNIX域套接字分为 流式套接字 和 用户数据报套接字, 和其他进程间通信方式相比使用方便、效率更高,常用于前后台进程通信
SYNOPSIS
#include <sys/socket.h> sockfd = socket(int socket_family, int socket_type, int protocol);
socket_family:填AF_LOCAL或者AF_UNIX均可
socket_type:填SOCK_STREAM(流式)SOCK_DGRAM(报文)视情况而定
protocol:填0
本地地址结构
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");
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[108]; /* Pathname */ }; The sun_family field always contains AF_UNIX. On Linux, sun_path is 108 bytes in size; 翻译:sun_family 通常值为 AF_UNIX (或者AF_LOCAL),sun_path的值一般值是96~108之间,linux下是108. 注意:sun_path 是unix域套接的文件路径名,该文件为在内存中的文件(使用ls -a可以查看到其特殊的文件属性), 该文件有两点要求:1.必须事先不存在 2.地址格式一般写作绝对路径。
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/select.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <pthread.h> #include <errno.h> #include <sys/un.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" #define FILE_ADDR "/tmp/mysocket" int func_recall(int arg); char botton[64]; int main() { int fd = -1; int fdbox[1024], fd_count = 0;; int result = 0, i = 0; struct sockaddr_un unaddr; struct sockaddr_un clientscokaddr; /* 1. 创建socket fd */ fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /*2. 绑定 */ /*2.1 填充struct sockaddr_un构体变量 */ bzero((void*)&unaddr, sizeof(unaddr)); unaddr.sun_family = AF_UNIX; strcpy(unaddr.sun_path,FILE_ADDR);//网络字节序的端口号 /*2.2 绑定 */ result = access(FILE_ADDR, F_OK);//测试文件是否存在 if(!result){ result = unlink(FILE_ADDR); //如果存在则删除 if(result){ perror("unlink"); return -1; } } result = bind(fd, (struct sockaddr*)&unaddr, sizeof(unaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用listen()把主动套接字变成被动套接字 */ result = listen(fd, 5); if (result != 0) { perror("listen"); return -1; } /*4. 等待客户端连接请求 */ /* 优化3. 采用多路复用的方式防止阻塞*/ fd_set rset; int fd1; fd1 = fd+1; fd_count = 1; fdbox[0] = fd; struct timeval tm; //非阻塞状态 采用轮询方式 tm.tv_sec = 0; tm.tv_usec = 0; int fd_client; while(1){ //清零rset FD_ZERO(&rset); //置位rset 标识出要监控的文件标识符 for (i = 0; i < fd_count; i++) { FD_SET(fdbox[i], &rset); } //查询是否有标识符被激活 result = select(fd1, &rset, NULL, NULL, &tm); if (result < 0) { perror("select"); return 0; } else if (result > 0) { //socket fd被激活 接收到新的客户端连接请求 if (FD_ISSET(fdbox[0], &rset)) { bzero((void*)&clientscokaddr, sizeof(clientscokaddr)); fd_client = accept(fd, NULL, NULL); //本地文件 不关心从何而来 if (-1 == result) { perror("accpet"); return 0; } printf("unix socket is connected!\n"); //将新建立连接的fd写入数组 以供select调用 fdbox[fd_count] = fd_client; fd_count++; fd1++; } //客户端fd被激活 处理客户端发来信息 for (i = 1; i < fd_count; i++) { if (FD_ISSET(fdbox[i], &rset)){ result = func_recall(fdbox[i]); if (result != 0) { printf("error:func_recall"); return -1; } } } } sleep(1); } close(fd); return 0; } int func_recall(int arg) { int result = 0; int newfd = arg; char Quit[] = "quit"; printf("handler thread: newfd =%d\n", newfd); //..和newfd进行数据读写 char buf[BUFSIZ]; bzero(buf, BUFSIZ); do { result = read(newfd, buf, BUFSIZ - 1); } while (result < 0 && EINTR == errno); if (result < 0) { perror("read"); return -1; } if (!result) return 0; if (!strcmp(Quit, buf)) { printf("client quit...close socket.\n "); close(newfd); } else { printf("message is:%s\n", buf); } return 0; }
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <sys/un.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" #define FILE_ADDR "/tmp/mysocket" int main() { int fd = -1; int result = 0; struct sockaddr_un mysockaddr; char MESSAGE[] = "hELLo wOLd...\n"; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return -1; } bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sun_family = AF_LOCAL; strcpy(mysockaddr.sun_path,FILE_ADDR); /* 确保UNIX_DOMAIN_FILE要先存在,并且可读写 */ result = access(FILE_ADDR,F_OK|R_OK|W_OK); if(result){ perror("access"); return -1; } result = connect(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("connect"); return -1; } printf("Unix client staring...OK!\n"); while (1) { do { result = write(fd, (void*)MESSAGE, sizeof(MESSAGE)); } while (result < 0 && EINTR == errno); sleep(10); } close(fd); return 0; }
组播详解:https://blog.csdn.net/kkfloat/article/details/38252291?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-1-38252291.nonecase&utm_term=%E7%BB%84%E6%92%AD%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE