
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。常用于以下情况:
UDP的服务器客户端通信流程:
UDP常用的数据接收和发送函数:recvfrom(),sendto()
#include#include 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#include 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#include #include #include #include #include #include #include #include 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:%dn", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); printf("message:%sn", buf); if(strcmp(buf,"quit")==0)break; memset(buf, 0, 64); //数组清零 } close(sockfd); return 0; }
客户端:
#include#include #include #include #include #include #include #include #include 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:%dn", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port)); while(1) { fgets(buf,1024,stdin); buf[strlen(buf)-1]=' '; 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:%sn", 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代表该网段的广播地址发到该地址的数据包被所有的主机接收
发送广播消息:(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#include 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#include #include #include #include #include #include #include #include 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:%dn", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); printf("message:%sn", buf); if(strcmp(buf,"quit")==0)break; memset(buf, 0, 64); //数组清零 } close(sockfd); return 0; }
客户端:
#include组播#include #include #include #include #include #include #include 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:%dn", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port)); while(1) { fgets(buf,1024,stdin); buf[strlen(buf)-1]=' '; int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len); printf("n=%dn",n); //printf("message:%sn", 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;
struct in_addr imr_address;
int imr_ifindex;
};
服务器:
#include#include #include #include #include #include #include #include #include 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:%dn", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); printf("message:%sn", buf); if(strcmp(buf,"quit")==0)break; memset(buf, 0, 64); //数组清零 } close(sockfd); return 0; }
客户端:
#include#include #include #include #include #include #include #include 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:%dn", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port)); while(1) { fgets(buf,1024,stdin); buf[strlen(buf)-1]=' '; int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len); printf("n=%dn",n); //printf("message:%sn", buf); if(strcmp(buf,"quit")==0)break; memset(buf, 0, 64); //数组清零 } close(sockfd); return 0; }