思维导图
UDP基础编程(单播)
1.流程图
服务器:短信的接收方
- 创建套接字 (socket)-----------------------------------------》有手机
- 指定网络信息-----------------------------------------------》有号码
- 绑定套接字 (bind)------------------------------------------》插卡
- 接收发送消息(recvfrom sendto)---------------------》收短信
- 关闭套接字(close)------------------------------------------》接收完毕
客户端:短信的发送方
- 创建套接字 (socket)------------------------------------------》有手机
- 指定网络(服务器)信息---------------------------------》有对方号码
- 接收发送消息(recvfrom sendto)----------------------》发短信
- 关闭套接字(close)----------------------------------------》发送完毕
2.函数接口
2.1.recvfrom
recvfrom的最后连两个参数与accept最后两个参数起到了相同的作用,就可以获取到发消息的是谁或者说是接收到的是谁的消息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
头文件:#include <sys/types.h>
#include <sys/socket.h>功能:接收数据
参数:sockfd:套接字描述符
buf:接收缓存区的首地址
len:接收缓存区的大小
flags:0
src_addr:发送端的网络信息结构体的指针
addrlen:发送端的网络信息结构体的大小的指针
返回值:成功:接收的字节个数
失败:-1
客户端退出:0
2.2.sendto
sendto的最后连两个参数与connect最后两个参数传参一样,要确定将消息发送给谁,或者说是接收者信息(一般是服务器)。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
头文件:#include <sys/types.h>
#include <sys/socket.h>功能:发送数据
参数:sockfd:套接字描述符
buf:发送缓存区的首地址
len:发送缓存区的大小
flags:0
src_addr:接收端的网络信息结构体的指针
addrlen:接收端的网络信息结构体的大小
返回值: 成功:发送的字节个数
失败:-1
3.代码实现:
服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int rd = 0;
char buf[32] = "";
int sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字
if(sockfd < 0){
perror("socket err");
return -1;
}
struct sockaddr_in server_addr,clientaddr; //服务器配置
server_addr.sin_family = AF_INET; //地址族选择ipv4
server_addr.sin_port = htons(atoi(argv[1])); //通过终端命令行参数获取端口号(终端输入的都是字符类型,要把其转换为int类型)
server_addr.sin_addr.s_addr = INADDR_ANY; //C语言内自带的宏定义,代表0.0.0.0地址
int len = sizeof(clientaddr);
//绑定套接字
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0){
perror("bind err");
close(sockfd);
return -1;
}
//循环实现:当客户端退出连接,会继续等待连接新的客户端来连接服务器通信
while(1){
rd = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&clientaddr,&len);
if(rd < 0){
perror("recvfrom err");
continue;
}
else
printf("ip:%s port:%d buf:%s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buf);
memset(buf,0,sizeof(buf));
}
close(sockfd); //关闭socket文件描述符号
return 0;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char buf[32] = "";
int sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字
if(sockfd < 0){
perror("socket err");
return -1;
}
struct sockaddr_in server_addr; //服务器配置
server_addr.sin_family = AF_INET; //地址族选择ipv4
server_addr.sin_port = htons(atoi(argv[2])); //配置连接目标的服务器的端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]); //通过终端命令行参数获取ip地址作为服务器地址
//循环输入数据
while(1){
fgets(buf,sizeof(buf),stdin); //从终端输入流获取字符串更安全,超出字符串长度不会段错误
if(buf[strlen(buf)-1] = '\n') //去除字符串尾部添加的\n
buf[strlen(buf)-1] = '\0';
if( strcmp(buf,"quit") == 0 ) //当输入quit,退出客户端
break;
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
memset(buf,0,sizeof(buf));
}
close(sockfd);
return 0;
}
4.TCP与UDP区别:
特性 | TCP (传输控制协议) | UDP (用户数据报协议) |
---|---|---|
连接性 | 面向连接 (需先建立连接) | 无连接 (直接发送) |
可靠性 | 高可靠 (确认、重传、排序) | 不可靠 (不保证送达、顺序、不重传) |
数据传输 | 字节流 (无明确边界,保证顺序) 使用流式套接字(SOCK_STREAM) | 数据报文 (有明确边界,可能乱序、丢失) 使用数据报套接字(SOCK_DGRAM) |
速度 | 相对较慢 (建立连接、确认等开销) | 非常快 (几乎无额外开销) |
开销 | 较大 (头部20-60字节,维护连接状态) | 极小 (固定8字节头部) |
拥塞控制 | 有 (复杂算法动态调整发送速率) | 无 (应用需自行处理) |
流量控制 | 有 (接收方窗口控制发送方速率) | 无 |
应用场景 | 要求可靠、完整传输:Web(HTTP)、邮件(SMTP/POP/IMAP)、文件传输(FTP)、远程登录(SSH) | 要求实时性、速度:音视频流、在线游戏、DNS查询、广播、实时监控 |
广播
1.基础复习
广播地址:
1.受限广播地址:
255.255.255.255
。数据包会被发送到发送主机所在的整个本地网络段的所有主机。路由器通常不会转发这种广播包(限制在本地网段)。2.定向广播地址: 形如
网络前缀.255
(对于传统的/24
子网)或网络前缀.全1主机部分
(对于其他子网掩码)。例如,在192.168.1.0/24
网段中,定向广播地址是192.168.1.255
。这种广播理论上可以被路由器转发到目标网络,但出于安全考虑,现代路由器默认通常也会阻止转发此类广播。(主机号最大的地址是该网段的广播地址 如:192.168.50.222的广播地址是192.168.50.255)
2.函数接口:
int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)
头文件:#include <sys/types.h>
#include <sys/socket.h>功能:获得/设置套接字属性
参数:sockfd:套接字描述符
level:协议层
optname:选项名
optval:选项值
optlen:选项值大小
返回值: 成功 0 失败-1
3.分析:
发送广播:将socket转换后,改变要发送的目标ip地址为当前网络的广播就可以
接收广播: 接收方不需要特殊设置(不像多播需要加入组),只需绑定到
INADDR_ANY
(0.0.0.0) 和发送方使用的端口号,并在该端口上监听 UDP 数据报即可接收到广播消息。接收者(服务器)
1.创建套接字(socket)
2.指定网络信息
3.绑定套接字(bind)
4.接收消息(recvfrom)
5.关闭套接字(close)
发送者
1.创建套接字(socket)
2.由于原本的套接字不支持广播,所以要给套接字设置广播属性
int optval = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&optval,sizeof(optval));3.指定网络(服务器)信息
4.发送消息(sendto)
5.关闭套接字(close)
4.代码:
客户端(服务器端不用改):
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char buf[32] = "";
int sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字
if(sockfd < 0){
perror("socket err");
return -1;
}
int optval = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&optval,sizeof(optval)); //改变套接字为广播
struct sockaddr_in server_addr; //服务器配置
server_addr.sin_family = AF_INET; //地址族选择ipv4
server_addr.sin_port = htons(atoi(argv[2])); //配置连接目标的服务器的端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]); //通过终端命令行参数获取ip地址作为服务器地址(广播地址)
//循环输入数据
while(1){
fgets(buf,sizeof(buf),stdin); //从终端输入流获取字符串更安全,超出字符串长度不会段错误
if(buf[strlen(buf)-1] = '\n') //去除字符串尾部添加的\n
buf[strlen(buf)-1] = '\0';
if( strcmp(buf,"quit") == 0 ) //当输入quit,退出客户端
break;
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
memset(buf,0,sizeof(buf));
}
close(sockfd);
return 0;
}
5.缺点:
广播方式会把信息发给连接当前网络的所有的主机,过多的广播会大量的占用网络带宽,造成广播风暴,影响正常的通信
广播风暴: 网络长时间被大量的广播数据包所占用,使正常的点对点通信无法正常进行,其外在表现为网络速度奇慢无比,甚至导致网络瘫痪
组播(多播)
1.什么是组播
1.多播是一个人发送,加入到多播组的人接收数据。
2.多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
2.多播地址
D类:224.0.0.1-239.255.255.254
3.流程
接收者(服务器)
1.创建套接字(socket)
2.设置多播属性,将自己的IP加入到多播组中
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(argv[2]); // 组播IP
mreq.imr_interface.s_addr = INADDR_ANY; // 自己IP
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq));
3.指定网络信息
4.绑定套接字(bind)
5.接收消息(recvfrom)
6.关闭套接字(close)
发送者
1.创建套接字(socket)
2.指定网络(服务器)信息
3.发送消息(sendto)
4.关闭套接字(close)
4.代码实现:
服务器端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int rd = 0;
char buf[32] = "";
int sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字
if(sockfd < 0){
perror("socket err");
return -1;
}
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(argv[2]); // 组播IP
mreq.imr_interface.s_addr = INADDR_ANY; // 自己IP
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
struct sockaddr_in server_addr,clientaddr; //服务器配置
server_addr.sin_family = AF_INET; //地址族选择ipv4
server_addr.sin_port = htons(atoi(argv[1])); //通过终端命令行参数获取端口号(终端输入的都是字符类型,要把其转换为int类型)
server_addr.sin_addr.s_addr = INADDR_ANY; //C语言内自带的宏定义,代表0.0.0.0地址
int len = sizeof(clientaddr);
//绑定套接字
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0){
perror("bind err");
close(sockfd);
return -1;
}
//循环实现:当客户端退出连接,会继续等待连接新的客户端来连接服务器通信
while(1){
rd = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&clientaddr,&len);
if(rd < 0){
perror("recvfrom err");
continue;
}
else
printf("ip:%s port:%d buf:%s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buf);
memset(buf,0,sizeof(buf));
}
close(sockfd); //关闭socket文件描述符号
return 0;
}
实现效果:
UDP广播三种实现形式比较
特性 | 单播 (Unicast) | 组播 (Multicast) | 广播 (Broadcast) |
---|---|---|---|
通信模式 | 一对一 (1:1) | 一对多 (1:N) | 一对所有 (1:All) |
目标地址 | 单个 特定主机地址 (192.168.1.100 ) | 组播组地址 (Class D, 224.0.0.1~ 239.255.255.254 ) | 广播地址 (255.255.255.255 或 192.168.1.255 ) |
接收者 | 唯一 指定的目标主机 | 加入该组播组 的所有主机 | 同一网络段 的所有主机 |
效率 | 低效 (N份数据,N次传输) | 高效 (1份数据,网络复制) | 低效 (1份数据,所有主机处理) |
网络范围 | 可跨越 任意网络 (路由可达即可) | 可跨越 多个网络 (需路由器支持 IGMP/PIM) | 通常限制在 单个本地网络段 (LAN) |
协议支持 | TCP & UDP (主要方式) | UDP (主要方式,TCP 不原生支持) | UDP (TCP 不支持) |
资源消耗 | 发送端数量与网络带宽消耗:随接收者数量线性增长 | 发送端与骨干网络带宽消耗:恒定 (与接收者数无关) | 发送端带宽消耗:一次,但所有主机都会接收处理 |
应用场景 | Web浏览 (HTTP)、文件传输 (FTP/SCP)、邮件、SSH、数据库访问 | 视频会议、IPTV直播、股票行情、软件分发、网络发现 | DHCP、ARP、局域网简单服务发现、旧式网络游戏 |
路由器处理 | 正常路由 (基于目标IP路由表) | 特殊路由 (需支持 IGMP/PIM,复制数据到下游有接收者的网段) | 通常不转发 (限制在本地网段) |
实现关键 | 目标主机IP地址 | setsockopt(IP_ADD_MEMBERSHIP) 加入组播组 | setsockopt(SO_BROADCAST) 启用广播权限 |
优点 | 可靠(TCP)、点对点控制、简单 | 高效节省带宽、支持大规模接收者、跨网段 | 实现简单、本地网段全覆盖 |
缺点 | 接收者多时效率低、消耗大量发送端和网络资源 | 实现较复杂、需网络设备支持、可靠性需应用层保障 | 浪费资源(非目标主机也处理)、安全隐患、无法跨路由 |