目录
一、UDP单播介绍
1.UDP介绍
2.单播简介
3.单播的优点:
4.单播的缺点:
二、UDP单播编程
1.创建套接字-socket
2.发送数据-sendto
3.绑定-bind
4.接受数据-recvfrom
三、UDP单播服务端代码实现
1.代码分析
2.运行结果
3.UDP单播服务器注意点
四、UDP单播服务端代码实现
1.代码分析
2.运行结果
3.UDP单播客户端注意点
UDP协议进行信息的传输之前不需要建议连接。换句话说就是客户端向服务器发送信息,客户端只需要给出服务器的ip地址和端口号,然后将信息封装到一个待发送的报文中并且发送出去。至于服务器端是否存在,或者能否收到该报文,客户端根本不用管。更多UDP详细介绍可以查看:Linux网络编程-UDP协议详解
主机之间一对一的通讯模式,网络中的交换机和路由器对数据只进行转发不进行复制。如果10个客户机需要相同的数据,则服务器需要逐一传送,重复10次相同的工作。但由于其能够针对每个客户的及时响应,所以现在的网页浏览全部都是采用单播模式,具体的说就是IP单播协议。网络中的路由器和交换机根据其目标地址选择传输路径,将IP单播数据传送到其指定的目的地。
1)服务器及时响应客户机的请求
2)服务器针对每个客户不通的请求发送不通的数据,容易实现个性化服务。
1)服务器针对每个客户机发送数据流,服务器流量=客户机数量×客户机流量;在客户数量大、每个客户机流量大的流媒体应用中服务器不堪重负。
2)现有的网络带宽是金字塔结构,城际省际主干带宽仅仅相当于其所有用户带宽之和的5%。如果全部使用单播协议,将造成网络主干不堪重负。现在的P2P应用就已经使主干经常阻塞。而将主干扩展20倍几乎是不可能。
int socket(int family,int type,int protocol);
功能:
创建一个用于网络通信的socket套接字(描述符)
参数:
family:协议族(AF_INET、AF_INET6、PF_PACKET等)
type:套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等)
protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等
返回值:套接字
特点:
创建套接字时,系统不会分配端口
创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器时,往往需要修改为被动的
头文件:#include
ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to,
socklen_t addrlen);
功能:
向to结构体指针中指定的ip,发送UDP数据
参数:
sockfd:套接字
buf: 发送数据缓冲区
nbytes: 发送数据缓冲区的大小
flags:一般为0
to: 指向目的主机地址结构体的指针
addrlen:to所指向内容的长度
注意
通过to和addrlen确定目的地址
可以发送0长度的UDP数据包
返回值
成功:发送数据的字符数
失败: -1
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:将本地协议地址与sockfd绑定
参数
sockfd: socket套接字
myaddr: 指向特定协议的地址结构指针
addrlen:该地址结构的长度
返回值
成功:返回0
失败:其他
ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *from,
socklen_t *addrlen);
功能
接收UDP数据,并将源地址信息保存在from指向的结构中
参数
sockfd:套接字
buf: 接收数据缓冲区
nbytes:接收数据缓冲区的大小
flags: 套接字标志(常为0)
from: 源地址结构体指针,用来保存数据的来源
addrlen: from所指内容的长度
注意:
通过from和addrlen参数存放数据来源信息
from和addrlen可以为NULL, 表示不保存数据来源
返回值:
成功:接收到的字符数
失败: -1
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 88
#define PORT 55555
int main(int argc, char *argv[])
{
//建立套接字
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); //IPV4,数据报套接字类型,不指定协议
//说明本服务器地址
const struct sockaddr_in srvaddr = {
.sin_family = AF_INET, //协议类型IPV4
.sin_port = htons(PORT), //端口号-网络字节序
.sin_addr.s_addr = htonl(INADDR_ANY) //IP地址-任意地址
};
//绑定地址
bind(sock_fd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
//接收数据
char buf[BUF_SIZE] = { 0 };
struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(cliaddr);
while (1)
{
bzero(buf, BUF_SIZE);
bzero(&cliaddr, cliaddrlen);
recvfrom(sock_fd, buf, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &cliaddrlen);
printf("udp server from [%s - %hu]:%s\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port,buf);
}
//关闭套接字
close(sock_fd);
return 0;
}
服务器之所以要bind是因为它的本地port需要是固定,而不是随机的,服务器也可以主动地给客户端发送数据。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 88
#define PROT 55555
#define ADDR "192.168.111.213"
int main(int argc, char *argv[])
{
//建立套接字
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); //IPV4,数据报套接字类型,不指定协议
//声明将连接地址
const struct sockaddr_in srvaddr = {
.sin_family = AF_INET, //协议类型IPV4
.sin_port = htons(PROT), //端口号-网络字节序
.sin_addr.s_addr = inet_addr(ADDR) //IP地址-字符串转IP
};
//发送数据
char buf[BUF_SIZE] = { 0 };
int count = 0;
while (1)
{
memset(buf,0, BUF_SIZE);
sprintf(buf, "%d",count);
printf("udp client send:[%s - %d]:%s\n",ADDR,PROT,buf);
sendto(sock_fd, buf, strlen(buf), 0, (const struct sockaddr*)&srvaddr, sizeof(srvaddr));
count++;
sleep(1);
}
//关闭套接字
close(sock_fd);
return 0;
}
在客户端的代码中,我们只设置了目的IP、目的端口客户端的本地ip、本地port是我们调用sendto的时候linux系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的port不一样。