写在前面:本文分两部分,先说UDP的实现,再讲下组播的实现。
再后面补充一下UDP广播的实现。
正文:
一、
1、TCP和UDP通信优缺点
TCP:面向连接的,可靠数据包传输。对于不稳定的网络层,采取完全弥补的通信方式,丢包重传机制。
优点:稳定,数据流量稳定,速度稳定,顺序稳定。
缺点:传输速度慢,传输效率低,资源开销大。
使用场景:数据的完整性要求较高、不追求效率。大数据传输、文件传输。
UDP:无连接的,不可靠的数据报传输。对于不稳定的网络层,采取完全不弥补的通信方式,默认还原网络状况。
优点:传输速度快、效率高、资源开销小。
缺点:不稳定。数据流量不稳定、速度不稳定、顺序不稳定。
使用场景:时效性要求较高,稳定性其次,游戏、视频会议、视频电话。
2、UDP相关的几个API:recvfrom和sendto函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd: 套接字
buf:缓冲区地址
flags:0
src_addr: 传出参数,对端地址结构。
addrlen:传入传出
返回值:成功:返回接受的字节数。 -1,失败。0,对端关闭。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
socket:套接字
buf:缓冲区
len:数据长度
flags:0
dest_addr:传入参数,目标地址结构
addrlen:地址结构长度
返回值: 成功:发送的字节数; -1,失败;
二、UDP Server和UDP Client的实现
基于TCP的CS模型,其实UDP的CS模型只不过就是砍掉了三次握手,下面贴代码:
UDP Server:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#define SER_PORT (8888)
int main(void)
{
int sockfd = -1;
struct sockaddr_in ser_ip,cli_ip;
ssize_t rev_size = 0;
unsigned char rd_buf[1024] = {0};
unsigned char *pStr = "Server recv OK";
socklen_t cli_addr_len = sizeof(struct sockaddr_in) ;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&ser_ip,0,sizeof(ser_ip));
ser_ip.sin_family = AF_INET;
ser_ip.sin_port = htons(SER_PORT);
ser_ip.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (const struct sockaddr *)&ser_ip,sizeof(ser_ip));
printf("server prepared OK\n");
while(1)
{
memset(rd_buf,0,1024);
rev_size = recvfrom(sockfd, rd_buf, 1024, 0,
(struct sockaddr *)&cli_ip, &cli_addr_len);
printf("server role : rev_size = %d, cilent port = %d ,recv context: %s\n ",rev_size,ntohs(cli_ip.sin_port),rd_buf);
sendto(sockfd, pStr, strlen(pStr), 0,(const struct sockaddr *)&cli_ip, cli_addr_len);
sleep(5);
}
return 0;
}
UDP Client:
#include "stdio.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#define SER_PORT (8888)
int main(void)
{
int sockfd = 0;
const unsigned char * pSendBuf = "This is client";
struct sockaddr_in ser_ip;
const char * ser_ip_addr = "127.0.0.1";
unsigned int dst_ip_addr;
unsigned char rd_buf[1024] = {0};
int rev_size = 0;
ser_ip.sin_family = AF_INET;
ser_ip.sin_port = htons(SER_PORT);
inet_pton(AF_INET, ser_ip_addr, &dst_ip_addr);
ser_ip.sin_addr.s_addr = dst_ip_addr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
printf("client prepared OK\n");
while(1)
{
sendto(sockfd, pSendBuf, strlen(pSendBuf), 0,
(const struct sockaddr *)&ser_ip, sizeof(ser_ip));
memset(rd_buf,0,1024);
rev_size = recvfrom(sockfd, rd_buf, 1024, 0,
NULL, NULL);
printf("client role : rev_size = %d, recv context: %s\n ",rev_size,rd_buf);
sleep(5);
}
return 0;
}
三、 UDP组播
1、组播是Server端向局域网中的一个子网内加入了某个组播组的Client批量发送数据,组播的IP地址配置如下:
224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址 224.0.0.0 保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255 是公用组播地址,可以用于 Internet; 欲使用需申请。
224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。
2、组播的实现,代码中也是尽可能的做了注释:
组播Server的实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#define SER_PORT (8000)
#define CLI_PORT (9000)
#define GROUP_IP ("239.0.0.2")
#define LOCAL_IP ("0.0.0.0")
int main(void)
{
int sockfd = -1;
struct sockaddr_in ser_addr,cli_addr;
struct ip_mreq group_addr;
char muticast_buf[1024] = {"muti caset data"};
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
/*第一步,绑定Server端的IP地址和端口号*/
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(SER_PORT);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (const struct sockaddr *)&ser_addr,sizeof(ser_addr));
/*第二步,设置组播地址并指定发送组播数据*/
memset(&group_addr,0,sizeof(group_addr));
inet_pton(AF_INET,GROUP_IP,&group_addr.imr_multiaddr); //设置组播地址
inet_pton(AF_INET,LOCAL_IP,&group_addr.imr_interface); //设置本地IP地址
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group_addr, sizeof(group_addr)); // 制定发送组播数据包
//IP_MULTICAST_IF:指定发送组播数据的命令
/*第三步,构造Client端的IP地址和端口号*/
memset(&cli_addr,0,sizeof(cli_addr));
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(CLI_PORT);
inet_pton(AF_INET,GROUP_IP,&cli_addr.sin_addr.s_addr); //注意:这里的组播客户端的地址就是组播地址
printf("mutiast config finished ,then send muticast datas\n");
/*第四步,向制定的组播地址发送数据*/
while(1)
{
//调用sendto来向组播地址发送组播包
sendto(sockfd, muticast_buf, strlen(muticast_buf), 0,(const struct sockaddr *)&cli_addr, sizeof(cli_addr));
printf("muticast packet send ok\n");
sleep(2);//2s发送一次
}
close(sockfd);
return 0;
}
组播Client的实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#define SER_PORT (8000)
#define CLI_PORT (9000)
#define GROUP_IP ("239.0.0.2")
#define LOCAL_IP ("0.0.0.0")
int main(void)
{
int sockfd = -1;
struct sockaddr_in cli_addr;
struct ip_mreq group_addr;
char recv_buf[1024] = {0};
int recv_len = -1;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
/*第一步,绑定本地IP地址结构*/
memset(&cli_addr,0,sizeof(cli_addr));
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(CLI_PORT);
inet_pton(AF_INET,LOCAL_IP,&cli_addr.sin_addr.s_addr);
bind(sockfd, (const struct sockaddr *)&cli_addr,sizeof(cli_addr));
/*第二步,设置组播地址并将client加入组播组*/
memset(&group_addr,0,sizeof(group_addr));
inet_pton(AF_INET,GROUP_IP,&group_addr.imr_multiaddr); //设置组播地址
inet_pton(AF_INET,LOCAL_IP,&group_addr.imr_interface); //设置本地IP地址
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group_addr, sizeof(group_addr)); // 加入组包组
//IP_ADD_MEMBERSHIP:加入组播组的命令
/*第三步,接受组播数据*/
while(1)
{
memset(recv_buf,0,1024);
recv_len = recvfrom(sockfd, recv_buf, 1024, 0,NULL, NULL);
if(recv_len > 0)
{
printf("recv_len = %d, recv_buf:%s\n",recv_len,recv_buf);
}
}
close(sockfd);
return 0;
}
三、UDP广播
1、先阐述一下IP的东西,IP 地址中 32 位包含两部分,分别为:网络地址和主机地址,网络地址用来表示子网,主机地址用来表示哪一台主机。
子网掩码用来说明网络地址和主机地址各占多少位,子网掩码(转成二进制) 为 1 的位表网络地址,子网掩码(转成二进制)为 0 的位表主机地址,例如, 255.255.255.0 表前 24 位为网络地址,后 8 位为主机地址,子网掩码为 255.255.0.0 表前 16 位为网络地址,后 16 位为主机地址。子网掩码为255.255.255.0 中一共可以有 2^24 个网络,而主机地址决定每个这种网络可以有多少个主机,这个子网掩码可以有 2^8 个主机。
判断两个 IP 是否在一个子网内:
网络标识=IP 地址&子网掩码, 2 个 IP 地址的网路标识一样就处于同一个网络。
2、基于上述,来说明广播的IP地址,将IP地址的主机地址部分全部置1,则此IP就是该子网内的广播地址。
例如:
IP: 192.168.0.2. MASK: 255.255.255.0 则广播地址:192.168.0.255
IP: 172.16.0.2 MASK: 255.255.0.0 广播地址:172.16.255.255
3、广播的实现,代码中也是尽可能的做了注释:
代码之前,先ifconfig看一下本地ip地址信息,
注意这两个IP,inet addr:172.22.199.11 是对外的IP地址,inet addr:127.0.0.1是内部测试的IP地址。
udp 广播server部分:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#define SER_PORT (6666)
#define CLI_PORT (9000)
#define BROADCAST_IP "172.22.199.255 "
int main(void)
{
struct sockaddr_in ser_addr,brdcast_sddr;
char *p_brdcast_buf = "this is a broadcast test";
/*第一步,设置server IP地址结构*/
int sockfd = socket(AF_INET, SOCK_DGRAM,0);
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(SER_PORT);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (const struct sockaddr *)&ser_addr,sizeof(ser_addr));
/*第二步,setsockopt()设定为发送广播包*/
int so_broadcast = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &so_broadcast, sizeof(so_broadcast));//SO_BROADCAST:为配置广播选项,
// so_broadcast :0,关闭广播;1:开启广播
/*第三步:设置广播地址*/
memset(&brdcast_sddr,0,sizeof(brdcast_sddr));
brdcast_sddr.sin_family = AF_INET;
brdcast_sddr.sin_port = htons(CLI_PORT); //注意这里的端口号一定跟接受广播客户端的端口号一致
inet_pton(AF_INET, BROADCAST_IP, &brdcast_sddr.sin_addr.s_addr);
/*第四步,发送广播包*/
while(1)
{
sendto(sockfd, p_brdcast_buf, strlen(p_brdcast_buf), 0,
(const struct sockaddr *)&brdcast_sddr, sizeof(brdcast_sddr));
printf("broadcast packet send ok\n");
sleep(2);
}
return 0;
}
udp组播client部分:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#define CLI_PORT (9000)
int main(void)
{
int sockfd = -1;
struct sockaddr_in cli_addr, ser_sddr;
unsigned char recv_buf[1024] = {0};
socklen_t recv_len = 0;
char ip_addr[16];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
/*第一步,绑定端口号和IP*/
memset(&cli_addr,0,sizeof(cli_addr));
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(CLI_PORT);注意这里的端口号一定跟发送广播的服务器的端口号一致
cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (const struct sockaddr *)&cli_addr,sizeof(cli_addr));
/*第二步,然后就可以接听广播数据了*/
while(1)
{
memset(&ser_sddr,0,sizeof(ser_sddr));
recvfrom(sockfd, recv_buf, 1024, 0,(struct sockaddr *)&ser_sddr, &recv_len);
inet_ntop(AF_INET, &ser_sddr.sin_addr.s_addr, ip_addr, 16);
printf("receive from IP:%s\n",ip_addr);
printf("receive buffer:%s\n",recv_buf);
}
return 0;
}
测试结果一:抓包数据
测试结果二:log信息
测试结果中的这两个IP,一个是wireshark抓包抓到的,所以相当于抓到的对外IP地址,一个是内部测试的,则log中打印出的是对内IP地址。