UDP
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。常用于以下情况:
- 发送小尺寸数据( 如对DNS服务器进行IP地址查询时)。
- 在接收到数据, 给出应答较困难的网络中使用UDP。(如: 无线网络)
- 适合于广播/组播式通信中。
- MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议。
- 流媒体、 VOD、 VoIP、 IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输。
UDP的服务器客户端通信流程:
UDP常用的数据接收和发送函数:recvfrom(),sendto()
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd :数据报套接字,socket函数返回值
buf :内存地址
len :接收数据的大小
flags :标志位 0
src_addr :发送端的addr结构体地址
addrlen :addr结构体的长度的地址
返回值:成功则返回接收的字节数,出错返回-1
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:接收数据
参数:
sockfd :数据报套接字,socket函数返回值
buf :内存地址
len :接收数据的大小
flags :标志位 0
src_addr :接收端的addr结构体地址
addrlen :addr结构体的长度的地址
返回值:成功则返回发送的字节数,出错返回-1
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//2. 绑定服务器的IP地址和端口号
struct sockaddr_in serveraddr ={0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(7777);
//serveraddr.sin_addr.s_addr = inet_addr("0");
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int len = sizeof(serveraddr);
int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len );
if(ret == -1)
{
perror("bind");
return -1;
}
//3.收发数据
struct sockaddr_in clientaddr={0};
int l=sizeof(clientaddr);
char buf[64] = {0};
while(1)
{
int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&l);
if(n<0)
{
perror("recvfrom");
return -1;
}
printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
printf("message:%s\n", buf);
if(strcmp(buf,"quit")==0)break;
memset(buf, 0, 64); //数组清零
}
close(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
if(argc<3)
{
printf("usage ./可执行文件 服务器IP地址 端口号");
return -1;
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//服务器的IP地址和端口号
struct sockaddr_in serveraddr = {0},clientaddr={0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int len = sizeof(serveraddr);
//2.收发数据
char buf[64] = {0};
int l=sizeof(clientaddr);
printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
while(1)
{
fgets(buf,1024,stdin);
buf[strlen(buf)-1]='\0';
int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);
if(n<0)
{
perror("sendto");
return -1;
}
if(strcmp(buf,"quit")==0)break;
//printf("message:%s\n", buf);
memset(buf, 0, 64); //数组清零
}
close(sockfd);
return 0;
}
补充:
TCP和UDP无好坏之分,只是适用场景不同
TCP:
发送小包裹:可能会产生粘包,可通过让发送和接收字节数一致或者设置协议来解决
发送大包裹:会拆包传输,可能会数据混乱,需要协议来解决
UDP:
发送小包裹:一个一个发,不会产生粘包
发送大包裹:会丢包,不会拆包
广播
-
如果同时发给局域网中的所有主机,称为广播
-
只有用户数据报(使用UDP协议)套接字才能广播
以192.168.1.0 (255.255.255.0) 网段为例, 最大的主机地址192.168.1.255代表该网段的广播地址发到该地址的数据包被所有的主机接收
- 255.255.255.255在所有网段中都代表广播地址,但有权限,普通用户无法发送广播数据
流程
发送广播消息:(client)
1.socket(AF_INET, SOCK_DGRAM, 0);
2.struct sockaddr_in argv[1] //向广播地址发送
//192.168.2.255
3.setsockopt //开广播权限
4.sendto (如果不开权限,会报错)
接收广播消息:(server)
1.socket(AF_INET, SOCK_DGRAM, 0);
2.struct sockaddr_in serveraddr;
3.bind //"0"地址 or 广播地址(只能收广播消息)
4.recvfrom
socket设置函数:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,void *optval, socklen_t
optlen);
参数:
level : 选项级别( 例如SOL_SOCKET)
optname : 选项名( 例如SO_BROADCAST)
optval : 存放选项值的缓冲区的地址
optlen : 缓冲区长度
返回值: 成功返回0 失败返回-1并设置errno
属性设置表:level
SOL_SOCKET
------------------------------------------------
参数optname 宏的作用 对应参数optaval的类型
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSEADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
======================================================
IPPROTO_IP
------------------------------------------------------
IP_ADD_MEMBERSHIP 加入到组播组中 struct ip_mreq
IP_MULTICAST_IF 允许开启组播报文的接口 struct ip_mreq
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//2. 绑定服务器的IP地址和端口号
struct sockaddr_in serveraddr ={0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(7777);
//serveraddr.sin_addr.s_addr = inet_addr("0");
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int len = sizeof(serveraddr);
int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len );
if(ret == -1)
{
perror("bind");
return -1;
}
//3.收发数据
struct sockaddr_in clientaddr={0};
int l=sizeof(clientaddr);
char buf[64] = {0};
while(1)
{
int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&l);
if(n<0)
{
perror("recvfrom");
return -1;
}
printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
printf("message:%s\n", buf);
if(strcmp(buf,"quit")==0)break;
memset(buf, 0, 64); //数组清零
}
close(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
if(argc<3)
{
printf("usage ./可执行文件 服务器IP地址 端口号");
return -1;
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//打开广播权限
int on=1;
int m=setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
if(m<0)
{
perror("setsockopt");
return -1;
}
//服务器的IP地址和端口号
struct sockaddr_in serveraddr = {0},clientaddr={0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int len = sizeof(serveraddr);
//2.收发数据
char buf[64] = {0};
int l=sizeof(clientaddr);
printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
while(1)
{
fgets(buf,1024,stdin);
buf[strlen(buf)-1]='\0';
int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);
printf("n=%d\n",n);
//printf("message:%s\n", buf);
if(strcmp(buf,"quit")==0)break;
memset(buf, 0, 64); //数组清零
}
close(sockfd);
return 0;
}
组播
-
广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
-
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
-
多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
D类地址(组播地址)
不分网络地址和主机地址, 第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
流程
发送组播消息:(client)
1.socket
2.struct sockaddr_in argv[1]//向组播地址发送
3.sendto
接收组播消息:(server) man 7 ip
1.socket
2.struct sockaddr_in serveraddr;
3.struct ip_mreqn mreq; // 组播地址 + "0"地址
4.setsockopt //加入多播组
5.bind
6.recvfrom
struct ip_mreqn {
struct in_addr imr_multiaddr; /* IP multicast group address即多播组IP地址 */
struct in_addr imr_address; /* IP address of local interface 本地接口的IP地址 */
int imr_ifindex; /* interface index接口索引 */
};
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//2. 绑定服务器的IP地址和端口号
struct sockaddr_in serveraddr ={0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(7777);
//serveraddr.sin_addr.s_addr = inet_addr("0");
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int len = sizeof(serveraddr);
int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len );
if(ret == -1)
{
perror("bind");
return -1;
}
//组播
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.10");
mreq.imr_address.s_addr = inet_addr("0");
//加入多播组
ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq) );
if(ret == -1)
{
perror("setsockopt");
return -1;
}
//3.收发数据
struct sockaddr_in clientaddr={0};
int l=sizeof(clientaddr);
char buf[64] = {0};
while(1)
{
int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&l);
if(n<0)
{
perror("recvfrom");
return -1;
}
printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
printf("message:%s\n", buf);
if(strcmp(buf,"quit")==0)break;
memset(buf, 0, 64); //数组清零
}
close(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
if(argc<3)
{
printf("usage ./可执行文件 服务器IP地址 端口号");
return -1;
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//服务器的IP地址和端口号
struct sockaddr_in serveraddr = {0},clientaddr={0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int len = sizeof(serveraddr);
//2.收发数据
char buf[64] = {0};
int l=sizeof(clientaddr);
printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
while(1)
{
fgets(buf,1024,stdin);
buf[strlen(buf)-1]='\0';
int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);
printf("n=%d\n",n);
//printf("message:%s\n", buf);
if(strcmp(buf,"quit")==0)break;
memset(buf, 0, 64); //数组清零
}
close(sockfd);
return 0;
}