一,定义:
1,单播用于两个主机之间的端对端通信,广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端,单播对一个主机进行通信,广播对整个局域网上的主机进行通信。
多播,也称为“组播”,将局域网中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。
2,IP地址 和 子网掩码的概念
(1)IP地址的概念和分类
IP地址本质就是在互联网中的唯一地址标识,其数据类型为32位二进制组成的整数(ipv4),当然也有128位二进制组成的整数(ipv6)。日常生活中描述IP地址的主要形式为: 点分十进制表示法,也就是将每8位二进制转换为一个十进制整数,不同的十进制整数之间通过小数点分隔;
如:0x01020304 => 1.2.3.4
查看IP地址的方式:
windows系统中: ipconfig ipconfig/all
linux系统中: ifconfig /sbin/ifconfig
IP地址主要分为两部分: 网络地址 + 主机地址,根据网络地址和主机地址的划分,将IP地址分为以下4类:
A类:0 + 7位网络地址 + 24位主机/本地地址
B类:10 + 14位网络地址 + 16位主机/本地地址
C类:110 + 21位网络地址 + 8位主机/本地地址
D类:1110 + 28位多播地址
(2)子网掩码的概念
子网掩码本质就是用于帮助IP划分具体的网络地址和主机地址,也可以用于判断两个IP地址是否在同一个局域网中,具体的划分方法为:按位&运算进行
如:
IP地址:172.30.100.41
子网掩码:255.255.255.0
3,端口号和字节序的概念
(1)端口号
IP地址 - 互联网中的地址标识,通过该地址可以定位主机
端口号 - 主要用于定位某一台主机上的具体进程
端口号的数据类型是:unsigned short类型,范围是:0 ~ 65535,其中0 ~ 1024之间的端口由系统占用,因此编程指定端口号时,建议从1025开始使用
网络编程中需要提供:IP地址 + 端口号
(2)字节序的概念
小端系统:低位内存地址存放低位数据的系统
大端系统:低位内存地址存放高位数据的系统
如:对于十六进制的数据 0x12345678来说
小端系统中按照地址从小到大依次:0x78 0x56 0x34 0x12
大端系统中按照地址从小到大依次:0x12 0x34 0x56 0x78
二,单播(分为tcp和udp一对一)
1,基于Socket的一对一通信模型
基本概念: Socket - 本意为插座的意思,表示逻辑上的通信载体
基本模型:
服务器:
(1)创建socket,使用socket函数
(2)准备通信地址,使用结构体变量
(3)绑定socket和 通信地址,使用bind函数
(4)进行通信,使用read/write函数
(5)关闭socket,使用close函数
客户端:
(1)创建socket,使用socket函数
(2)准备通信地址,是服务器的地址
(3)连接socket和通信地址,使用connect函数
(4)进行通信,使用read/write函数
(5)关闭socket,使用close函数
2,.基于tcp协议的网络通信模型
服务器:
(1)创建socket,使用socket函数
(2)准备通信地址,使用结构体类型
(3)绑定socket和通信地址,使用bind函数
(4)监听,使用listen函数
(5)响应客户端的连接请求,使用accept函数
(6)进行通信,使用read/write函数
(7)关闭socket,使用close函数
客户端:
(1)创建socket,使用socket函数
(2)准备通信地址,使用服务器的地址
(3)连接socket和通信地址,使用connect函数
(4)进行通信,使用read/write函数
(5)关闭socket,使用close函数
3,基于udp协议的通信模型
服务器:
(1)创建socket,使用socket函数
(2)准备通信地址,使用结构体类型
(3)绑定socket和通信地址,使用bind函数
(4)进行通信,使用sendto/recvfrom函数
(5)关闭socket,使用close函数
客户端:
(1)创建socket,使用socket函数
(2)准备通信地址,使用服务器的地址
(3)进行通信,使用sendto/recvfrom函数
(4)关闭socket,使用close函数
三,组播
1,实际情况下经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是组播的用途。组播的地址是特定的,D类IP地址用于组播地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接组播地址、预留组播地址和管理权限组播地址3类:
局部组播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
预留组播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
管理权限组播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制组播范围。
- 属于永久组的地址:
- 224.0.0.1 所有组播主机
- 224.0.0.2 所有组播路由器
- 224.0.0.4 DRMRP路由器
- 224.0.0.5 所有OSPF的路由器
- 224.0.0.6 OSPF指派路由器
- 224.0.0.9 RPIv2路由器
- 224.0.0.10 EIGRP路由器
- 224.0.0.13 PIM路由器
- 224.0.0.22 IGMPv3
- 224.0.0.25 RGMP
- 224.0.1.1 NTP网络时间协议
2,组播编程流程
(1)建立一个socket;
(2)设置组播的参数,例如超时时间TTL,本地回环许可LOOP等
(3)加入组播组
(4)发送和接收数据
(5)从组播组离开
3,组播主机的三个级别:
0级:主机不能发送或接收I P组播。
这种主机应该自动丢弃它收到的具有D类目的地址的分组。
1级:主机能发送但不能接收I P组播。
在向某个I P组播组发送数据报之前,并不要求主机加入该组。组播数据报的发送方式与单播一样,除了组播数据报的目的地址是 I P组播组之外。网络驱动器必须能 够识别出这个地址,把在本地网络上组播数据报。
2级:主机能发送和接收I P组播。
为了接收I P组播,主机必须能够加入或离开组播组,而且必须支持IGMP,能够在至少一个接口上交换组成员信息。多接口主机必须支持在它的接口的一个子网上的组播Net/3符合2级主机要求,可以完成组播路由器的工作。与单播IP选路一样,我们假定所描述的系统是一个组播路由器,并加上了Net/3组播选路的程序。
4,组播程序设计
使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。
**getsockopt()/setsockopt()**的选项含义
- IP_MULTICAST_TTL
-
- 设置组播组数据的TTL值
-
- IP_ADD_MEMBERSHIP
-
- 在指定接口上加入组播组
-
- IP_DROP_MEMBERSHIP
-
- 退出组播组
-
- IP_MULTICAST_IF
-
- 获取默认接口或设置接口
-
- IP_MULTICAST_LOOP
-
- 禁止组播数据回送
(1)选项IP_MULTICASE_TTL
选项IP_MULTICAST_TTL允许设置超时TTL,范围为0~255之间的任何值,
例如:unsigned char ttl=255; setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
(2)选项IP_MULTICAST_IF
选项IP_MULTICAST_IF用于设置组播的默认默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据。例如:
struct in_addr addr;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
参数addr是希望多播输出接口的IP地址,使用INADDR_ANY地址回送到默认接口。
默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。例如:
unsigned char loop;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));参数loop设置为0禁止回送,设置为1允许回送。
(3)选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP
加入或者退出一个多播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBER- SHIP,对一个结构struct ip_mreq类型的变量进行控制,struct ip_mreq原型如下:
- struct ip_mreq
- {
- struct in_addr imn_multiaddr;/*加入或者退出的广播组IP地址*/
- struct in_addr imr_interface;/*加入或者退出的网络接口IP地址*/
- };
选项IP_ADD_MEMBERSHIP用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据。此选项的值为mreq结构,成员imn_multiaddr是需要加入的多播组IP地址,成员imr_interface是本机需要加入广播组的网络接口IP地址。例如:
struct ip_mreq mreq;
setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
5,linux下组播服务器和客户端编程一:
(1)服务器
持续向多播IP地址"224.0.0.22"的0x8006端口发送数据"BROADCAST TEST DATA",发送间隔5s。
- /*
- *broadcast_server.c -组播服务程序
- */
-
- #define MCAST_PORT 0x8006;
- #define MCAST_ADDR “224.0.0.22”//*一个局部连接多播地址,路由器不进行转发*/
- #define MCAST_DATA “BROADCAST TEST DATA”/*组播发送的数据*
- #define MCAST_INTERVAL 5/*发送间隔时间*/
- int main(int argc, char*argv)
- {
- int s;
- struct sockaddr_in mcast_addr;
- s = socket(AF_INET, SOCK_DGRAM, 0);/*建立套接字*/
- if (s == -1)
- {
- perror(“socket()”);
- return -1;
- }
- memset(&mcast_addr, 0, sizeof(mcast_addr));/*初始化IP组播地址为0*/
- mcast_addr.sin_family = AF_INET;/*设置协议族类行为AF*/
- mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);/*设置组播IP地址*/
- mcast_addr.sin_port = htons(MCAST_PORT);/*设置组播端口*/
- /*向组播地址发送数据*/
- while(1)
- {
- int n = sendto(s,/*套接字描述符*/
- MCAST_DATA,/*数据*/
- sizeof(MCAST_DATA),/*长度*/
- 0,
- (struct sockaddr*)&mcast_addr,
- sizeof(mcast_addr)) ;
- if( n < 0)
- {
- perror(“sendto()”);
- return -2;
- }
- sleep(MCAST_INTERVAL);/*等待一段时间*/
- }
- return 0;
- }
(2)客户端
组播组的IP地址为224.0.0.22,端口为0x8006,当客户端接收到组播的数据后将打印出来。
客户端只有在加入组播组后才能接受组播组的数据,因此组播客户端在接收组播组的数据之前需要先加入组播组,当接收完毕后要退出组播组。
- /**broadcast_client.c -组播的客户端*/
- #define MCAST_PORT 0x8006;
- #define MCAST_ADDR “224.0.0.22”/*一个局部连接多播地址,路由器不进行转发*/
- #define MCAST_INTERVAL 5/*发送间隔时间*/
- #define BUFF_SIZE 256/*接收缓冲区大小*/
- int main(int argc, char*argv[])
- {
- int s;/*套接字文件描述符*/
- struct sockaddr_in local_addr;/*本地地址*/
- int err = -1;
- s = socket(AF_INET, SOCK_DGRAM, 0);/*建立套接字*/
- if (s == -1)
- {
- perror(“socket()”);
- return -1;
- }
- /*初始化地址*/
- memset(&local_addr, 0, sizeof(local_addr));
- local_addr.sin_family = AF_INET;
- local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- local_addr.sin_port = htons(MCAST_PORT);
- /*绑定socket*/
- err = bind(s,(struct sockaddr*)&local_addr, sizeof(local_addr)) ;
- if(err < 0)
- {
- perror(“bind()”);
- return -2;
- }
- /*设置回环许可*/
- int loop = 1;
- err = setsockopt(s,IPPROTO_IP, IP_MULTICAST_LOOP,&loop, sizeof(loop));
- if(err < 0)
- {
- perror(“setsockopt():IP_MULTICAST_LOOP”);
- return -3;
- }
- struct ip_mreq mreq;/*加入多播组*/
- mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); /*多播地址*/
- mreq.imr_interface.s_addr = htonl(INADDR_ANY); /*网络接口为默认*/
- /*将本机加入多播组*/
- err = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq));
- if (err < 0)
- {
- perror(“setsockopt():IP_ADD_MEMBERSHIP”);
- return -4;
- }
- int times = 0;
- int addr_len = 0;
- char buff[BUFF_SIZE];
- int n = 0;
- /*循环接收多播组的消息,5次后退出*/
- for(times = 0;times<5;times++)
- {
- addr_len = sizeof(local_addr);
- memset(buff, 0, BUFF_SIZE);/*清空接收缓冲区*/
- /*接收数据*/
- n=recvfrom(s,buff,BUFF_SIZE,0,(struct sockaddr*)&local_addr,&addr_len);
- if( n== -1)
- {
- perror(“recvfrom()”);
- }
- printf("Recv %dst message from server:%s ", times, buff);//打印信息
- sleep(MCAST_INTERVAL);
- }
- /*退出多播组*/
- err=setsockopt(s,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq));
- close(s);
- return 0;
- }
- }
6,linux下组播编程二:
(1)服务器代码
- #include <sys/types.h>
- #include <arpa/inet.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <netdb.h>
- #define BUFLEN 255
- int main(int argc, char **argv)
- {
- struct sockaddr_in peeraddr;
- struct in_addr ia;
- int sockfd;
- char recmsg[BUFLEN + 1];
- unsigned int socklen, n;
- struct hostent *group;
- struct ip_mreq mreq;
-
- /* 创建 socket 用于UDP通讯 */
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd < 0)
- {
- perror("socket\n");
- exit(errno);
- }
- printf("socket created success!!!\n");
- /* 设置要加入组播的地址 */
- bzero(&mreq, sizeof(struct ip_mreq));
- if (argv[1])
- {
- if ((group = gethostbyname(argv[1])) == (struct hostent *)0)
- {
- perror("gethostbyname");
- exit(errno);
- }
- }
- else
- {
- printf("you should give me a group address, 224.0.0.0-239.255.255.255\n");
- exit(errno);
- }
- bcopy((void *)group->h_addr, (void *)&ia, group->h_length);
- printf("组播地址:%s\n", inet_ntoa(ia));
- /* 设置组地址 */
- bcopy(&ia, &mreq.imr_multiaddr.s_addr, sizeof(struct in_addr));
-
- /* 设置发送组播消息的源主机的地址信息 */
- mreq.imr_interface.s_addr = htonl(INADDR_ANY);
-
- /* 把本机加入组播地址,即本机网卡作为组播成员,只有加入组才能收到组播消息 */
- if(setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(struct ip_mreq))==-1)
- {
- perror("setsockopt");
- exit(errno);
- }
- socklen = sizeof(struct sockaddr_in);
- memset(&peeraddr, 0, socklen);
- peeraddr.sin_family = AF_INET;
- if (argv[2])
- {
- peeraddr.sin_port = htons(atoi(argv[2]));
- }
- else
- {
- peeraddr.sin_port = htons(4567);
- }
- if (argv[1])
- {
- if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0)
- {
- printf("Wrong dest IP address!\n");
- exit(0);
- }
- }
- else
- {
- printf("no group address given, 224.0.0.0-239.255.255.255\n");
- exit(errno);
- }
- /* 绑定组播地址的端口和IP信息到socket上 */
- if(bind(sockfd,(struct sockaddr*)&peeraddr,sizeof(struct sockaddr_in))==-1)
- {
- printf("Binded failure\n");
- exit(0);
- }
- else
- {
- printf("binded success!!!\n");
- }
- /* 循环接收网络上来的组播消息 */
- for (;;)
- {
- bzero(recmsg, BUFLEN + 1);
- n=recvfrom(sockfd,recmsg,BUFLEN, 0,(struct sockaddr *)&peeraddr,&socklen);
- if (n < 0)
- {
- printf("recvfrom error in udptalk!\n");
- exit(errno);
- }
- else
- {
- /* 成功接收到数据报 */
- recmsg[n] = 0;
- printf("peer info:%s", recmsg);
- }
- }
- return 0;
- }
(2)客户端代码
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #define BUFLEN 255
- int main(int argc, char **argv)
- {
- struct sockaddr_in peeraddr;
- int sockfd;
- char recmsg[BUFLEN + 1];
- unsigned int socklen;
- /* 创建 socket 用于UDP通讯 */
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd < 0)
- {
- perror("socket\n");
- exit(errno);
- }
- printf("socket created success!!!\n");
- socklen = sizeof(struct sockaddr_in);
- /* 设置对方的端口和IP信息 */
- memset(&peeraddr, 0, socklen);
- peeraddr.sin_family = AF_INET;
- if (argv[2])
- peeraddr.sin_port = htons(atoi(argv[2]));
- else
- peeraddr.sin_port = htons(4567);
- if (argv[1])
- {
- /* 注意这里设置的对方地址是指组播地址,而不是对方的实际IP地址 */
- if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0)
- {
- printf("wrong group address!\n");
- exit(0);
- }
- }
- else
- {
- printf("no group address!\n");
- exit(0);
- }
- /* 循环接受用户输入的消息发送组播消息 */
- for (;;)
- {
- /* 接受用户输入 */
- bzero(recmsg, BUFLEN + 1);
- if (fgets(recmsg, BUFLEN, stdin) == (char *)EOF)
- exit(0);
- /* 发送消息 */
- if(sendto(sockfd, recmsg, strlen(recmsg), 0,(struct sockaddr *)&peeraddr, sizeof(struct sockaddr_in)) < 0)
- {
- printf("sendto error!\n");
- exit(errno);
- }
- printf("client sendto success!\n");
- }
- return 0;
- }
(3)运行
- $ gcc -o client client.c
- $ gcc -o server server.c
-
-
- $ ./client 234.1.2.3 4567
- socket created success!!!
- just to test!
- client sendto success!
-
-
- $ ./server 234.1.2.3 4567
- socket created success!!!
- 组播地址:234.1.2.3
- binded success!!!
- peer info:just to test!
四,广播
1,在windows上接收广播使用setsockopt结合SO_BROADAST。同样的代码不能在linux上获得同样的效果。使用tcpdump监听,可以收到了广播包:

2,服务器端广播循环发送一个文件,客户端持续接收文件。
<服务器端或客户端谁先运行均可,文件名由命令行指定>
服务端运行:./server wj.txt 客户端运行:./client
(1)服务器代码
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <time.h>
- #include <sys/types.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #define LEN 256
- int main(int argc, char **argv)
- {
- struct sockaddr_in server_socket;
- int sockfd = 0;
- int on = 1;
- int num = 0;
- char msg[LEN] = {0};
- if (argc < 2)
- {
- perror("Usage error: need a filename\n");
- exit(1);
- }
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd < 0)
- {
- perror("socket():Create socket fails!\n");
- exit(1);
- }
- //设置套接字为广播模式 SO_BROADCAST
- setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
- memset(&server_socket,0,sizeof(server_socket));
- server_socket.sin_family = AF_INET;
- //inet_pton是一个IP地址转换函数,可以在将点分文本的IP地址转换为二进制网络字节序”的IP地址
- if (inet_pton(AF_INET, "255.255.255.255", &server_socket.sin_addr) <= 0)
- {
- perror("inet_pton():IP address is error!\n");
- exit(1);
- }
- server_socket.sin_port = htons(1234);
- int fd;
- int len;
- fd = open(argv[1],O_RDONLY);
- if(fd == -1)
- {
- perror("open failed\n");
- exit(1);
- }
- char begin_signal[LEN]="new transmission begin!\n\n";
- while(1)
- {
- sendto(sockfd, begin_signal, strlen(begin_signal), 0,
- (struct sockaddr *)&server_socket, sizeof(struct sockaddr_in));
-
- while((len = read(fd,msg,10)) > 0)
- {
- sendto(sockfd, msg, len, 0,
- (struct sockaddr *)&server_socket, sizeof(struct sockaddr_in));
- }
- lseek(fd,0,SEEK_SET); //移动读指针至文件起始位置
- sleep(3);
- }
- close(fd);
- close(sockfd);
- exit(0);
- }
(2)客户端代码
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <time.h>
- #include <sys/types.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #define LEN 256
- int main(int argc, char **argv)
- {
- struct sockaddr_in client_socket;
- int sockfd = 0;
- int num = 0;
- char msg[LEN] = {0};
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd < 0)
- {
- perror("socket():Create socket fails!\n");
- exit(1);
- }
- memset(&client_socket, 0, sizeof(client_socket));
- client_socket.sin_family = AF_INET;
- client_socket.sin_port = htons(1234);
- client_socket.sin_addr.s_addr = htonl(INADDR_ANY);
- int opt = SO_REUSEADDR;
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
- if(bind(sockfd,(struct sockaddr *)&client_socket,sizeof(struct sockaddr_in))<0)
- {
- perror("bind():bind fails!\n");
- exit(1);
- }
- while(1) //这里不使用while(1)依然可以持续接收
- {
- while((num = read(sockfd, msg, LEN)) > 0)
- {
- write(1,msg,num);
- }
- }
- close(sockfd);
- exit(0);
- }