UDP-组播,广播
UDP是面向非连接的协议,它不与对方建立连接,而是直接把数据报发给对方。UDP无需建立类如三次握手的连接,使得通信效率很高。因此UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景
sendto
int sendto(int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
返回值:
成功则返回实际传送出去的字符数,失败返回-1,错误原因会存于errno 中。
参数说明:
s: socket描述符;
buf: UDP数据报缓存区(包含待发送数据);
len: UDP数据报的长度;
flags:调用方式标志位(一般设置为0);
to: 指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换);
tolen:to所指结构体的长度;
recvfrom()
int recvfrom(int s, void *buf, int len, unsigned int flags,struct sockaddr *from, int *fromlen);
返回值:
成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中。
参数:
s: socket描述符;
buf: UDP数据报缓存区(包含所接收的数据);
len: 缓冲区长度。
flags: 调用操作方式(一般设置为0)。
from: 指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换);
fromlen:指针,指向from结构体长度值
UDP点对点
udp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
const int SERV_PORT=6000;
const int MAXLINE=2048;
void dg_echo(int sockfd,struct sockaddr *pcliaddr,socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
for (;;)
{
len=clilen;
if ((n=recvfrom(sockfd,mesg,MAXLINE,0,pcliaddr,&len))<0)
{
perror("recvfrom error");
exit(1);
}
fputs(mesg, stdout);
if((n=sendto(sockfd,mesg,n,0,pcliaddr,len))<0)
{
perror("sendto error");
exit(1);
}
}
}
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in servaddr,cliaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port =htons(SERV_PORT);
if ((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
printf("SOCKET ERROR");
exit(1);
}
if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))
{
perror("bind error");
exit(1);
}
dg_echo(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
}
udp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
const int SERV_PORT = 6000;
const int MAXLINE = 2048;
void dg_cli(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while (fgets(sendline, MAXLINE, fp) != NULL)
{
if (sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen) < 0)
{
perror("sendto error");
exit(1);
}
if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0)
{
perror("recvfrom error");
exit(1);
}
recvline[n] = '\0';
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
{
perror("usage:tcpcli");
exit(1);
}
//1.创建套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket error");
exit(1);
}
//2.设置链接服务器地址结体
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if ((inet_pton(AF_INET, argv[1], &servaddr.sin_addr)) <= 0)
{
printf("inet_pton error for %s\n", argv[1]);
exit(1);
}
if ((sockfd == socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket error");
}
dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
exit(0);
}
UDP广播数据包
路由器不转发广播数据包
交换机转发广播数据包
广播只能在一个广播域(局域网)中传播,而不能跨网段传播
arp协议
- MAC:FF:FF:FF:FF:FF:FF
- IP:10.0.0.0/8-->10.255.255.255 广播地址
192.168.199.1/24-->192.168.199.255 广播地址
192.168.0.1/30-->192.168.0.3(广播地址)(192.168.0.0回环地址)
UDP广播递送规则
- 如果未设置BLOADCASE选项不递送
- 如果bind端口不匹配不递送该套接口
- 必须bind一个广播地址或绑定INADDR_ANY
- 如果udp调用了connect那么源地址和源端口不匹配也不递送,否则递送
- ifconfig可disable网卡的BROADCAST标志,让其不能接受以太网广播
- ioctl的SIOCSIFFLAGS方法去掉接口的标志IFF_BROADCAST,使之不能接受以太网广播。
recv.c
#include "../config.h"
int main(int argc,char **argv)
{
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
int sock;
socklen_t addr_len;
int len;
char buff[128];
if ((sock=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
perror("socket");
exit(EXIT_FAILURE);
}else
{
printf("create socket.\n\r");
}
bzero(&s_addr,sizeof(s_addr));
s_addr.sin_addr.s_addr=INADDR_ANY;
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(PORT);
if (bind(sock,(struct sockaddr *)&s_addr,sizeof(s_addr)))
{
perror("bind err");
exit(EXIT_FAILURE);
}else
printf("bind address to socket.\n\t");
addr_len=sizeof(c_addr);
while (1)
{
len=recvfrom(sock,buff,sizeof(buff)-1,0,(struct sockaddr *) &c_addr,&addr_len);
if (len<0)
{
perror("recvfrom");
exit(EXIT_FAILURE);
}
buff[len]='\0';
printf("recive come from %s:%d message:%s\n\r",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port),buff);
return 0;
}
}
send.c
#include "../config.h"
int main(int argc,char **argv)
{
struct sockaddr_in s_addr;
int sock;
int addr_len;
int len;
char buff[128];
int yes;
if ((sock=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
perror("socket");
exit(EXIT_FAILURE);
}else
printf("create socket.\n\r");
//开启广播
yes=1;
setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&yes,sizeof(yes));
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(PORT);
if (argv[1])
s_addr.sin_addr.s_addr=inet_addr(argv[1]); //广播地址:xxx.xxx.xxx.255/24
else{
printf("input server ip\n");
exit(0);
}
addr_len=sizeof(s_addr);
strcpy(buff,"hello message");
len=sendto(sock,buff,strlen(buff),0,(struct sockaddr *) &s_addr,addr_len);
if (len<0)
{
printf("\n\rsend error. \n\r");
exit(EXIT_FAILURE);
}
printf("send success\n\r");
return 0;
}
./send 192.168.146.255
create socket.
send success
- 抓包
UDP组播数据包
视频会议项目
1.能够在组播组中进行传播,并且
路由器
可以进行组播数据转发
2.组播(多播)使用D类地址,首位前4位1110,认为是多播地址,其它28位是多播的组编号
3.224.0.0.0~239.255.255.255范围 224.0.0.0~224.0.0.255不需要路由控制
4.所有主机和终端机必须属于224.0.0.1的组
5.所有路由器必须属于224.0.0.2的组
IGMP等协议支持
- struct hostent
记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
struct hostent
{
char *h_name; //主机名,即域名
char **h_aliases; //主机所有别名构成的字符串数组,同一IP可绑定多个域名
int h_addrtype; //主机IP地址的类型,IPv4,IPv6
int h_length; //主机IP地址长度,IPv4地址为4,IPv6地址为16
char **h_addr_list; //主机ip地址,以网络字节序存储
}
- gethostbyname()
gethostbyname
函数可以利用字符串格式的域名
或字符串点分十进制ip,获得IP地址,并且将地址信息装入hostent
域名结构体
#include <netdb.h>
struct hostent * gethostbyname(const char * hostname);
返回值
成功返回hostent
结构体地址
失败返回NULL
指针
- gethostbyaddr()
使用IP地址获取域名
#include <netdb.h>
struct hostent * gethostbyaddr(const char * addr,socklen_t len,int family);
参数
addr:含有IP地址信息的in_addr结构体指针
len:向第一个参数传递的地址信息的字节数。ipv4为4,ipv6为16
family:传递地址族信息,ipv4为AF_INET,ipv6为AF_INET6
返回值
成功返回hostent
结构体地址
失败返回NULL
指针
- struct ip_mreq
struct ip_mreq
{
struct in_addr imr_multiaddr; //多播组的IP地址
struct in_addr imr_interface; //加入的客户端主机IP地址
}
group_brodcast_recv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
const int MAX_LINE = 2048;
const int PORT = 8888;
const int BACKLOG = 10;
const int LISTENQ = 6666;
const int MAX_CONNECT = 20;
#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;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf("socket creating err in udptalk\n");
exit(EXIT_FAILURE);
}
bzero(&mreq, sizeof(struct ip_mreq));
if (argv[1])
{ //传入命令行参数1(主机域名或点分十进制字符串IP)组播地址
if ((group = gethostbyname(argv[1])) == (struct hostent *)0)
// if((group=gethostbyaddr(argv[1],sizeof(argv[1]),AF_INET))==(struct hostent *)0)
{
perror("gethostbyname");
exit(EXIT_FAILURE);
}
}
else
{
printf("you should give me a group address,224.0.0.0-239.255.255.255\n");
exit(EXIT_FAILURE);
}
//组播地址写入组播组
bcopy((void *)group->h_addr, (void *)&ia, group->h_length);
bcopy(&ia, &mreq.imr_multiaddr.s_addr, sizeof(struct in_addr));
//命令行参数2(当前IP地址),写入组播组客户端IP地址列表中
if (argv[2])
{
if (inet_pton(AF_INET, argv[2], &mreq.imr_interface.s_addr) <= 0)
{
printf("Wrong dest IP address!\n");
exit(EXIT_FAILURE);
}
}
//设置组播
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)) == -1)
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
//绑定组播地址和端口,接收组播信息
socklen = sizeof(struct sockaddr_in);
bzero(&peeraddr, socklen);
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = htons(PORT);
if (argv[1])
{
if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0)
{
printf("Wrong dest IP address!\n");
exit(EXIT_FAILURE);
}
}
else
{
printf("you should give me a group address,224.0.0.0-239.255.255.255\n");
exit(EXIT_FAILURE);
}
if (bind(sockfd, (struct sockaddr *)&peeraddr, sizeof(struct sockaddr_in)) == -1)
{
printf("Bind error\n");
exit(EXIT_FAILURE);
}
for (;;)
{
bzero(recmsg, BUFLEN + 1);
n = recvfrom(sockfd, recmsg, BUFLEN, 0, (struct sockaddr *)&peeraddr, &socklen);
if (n < 0)
{
printf("recvfrom err in udptalk\n");
exit(EXIT_FAILURE);
}else{
recmsg[n]=0;
printf("perr:%s",recmsg);
}
}
}
group_brodcast_send.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
const int MAX_LINE = 2048;
const int PORT = 8888;
const int BACKLOG = 10;
const int LISTENQ = 6666;
const int MAX_CONNECT = 20;
#define BUFLEN 255
int main(int argc, char **argv)
{
struct sockaddr_in peeraddr,myaddr;
int sockfd;
char recmsg[BUFLEN + 1];
unsigned int socklen;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf("socket creating err in udptalk\n");
exit(EXIT_FAILURE);
}
//组播
socklen=sizeof(struct sockaddr_in);
bzero(&peeraddr,socklen);
peeraddr.sin_family=AF_INET;
peeraddr.sin_port=htons(PORT);//组播客户端绑定的端口
//命令行参数1为组播地址
if (argv[1])
{
if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0)
{
printf("Wrong dest IP address!\n");
exit(EXIT_FAILURE);
}
}
else
{
printf("you should give me a group address,224.0.0.0-239.255.255.255\n");
exit(EXIT_FAILURE);
}
//绑定本机IP和端口
bzero(&myaddr,socklen);
myaddr.sin_family=AF_INET;
myaddr.sin_port=htons(23456);
//命令行参数2为本机IP
if (argv[2])
{
if (inet_pton(AF_INET,argv[2],&myaddr.sin_addr)<=0)
{
printf("self ip address error\n");
exit(EXIT_FAILURE);
}
}else
myaddr.sin_addr.s_addr=INADDR_ANY;
if (bind(sockfd,(struct sockaddr *)&myaddr,sizeof(struct sockaddr_in))==-1)
{
printf("Bind error\n");
exit(EXIT_FAILURE);
}
for(;;)
{
bzero(recmsg,BUFLEN+1);
printf("input message to send:");
if (fgets(recmsg,BUFLEN,stdin)==(char *)EOF)
{
exit(EXIT_FAILURE);
}
//发送组播消息
if (sendto(sockfd,recmsg,strlen(recmsg),0,(struct sockaddr *)&peeraddr,sizeof(struct sockaddr_in))<0)
{
printf("sendto error\n");
exit(EXIT_FAILURE);
}
printf("send message:%s",recmsg);
}
}
- 接收端
- 发送端
- 抓包