1.UDP协议格式
- 16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的大长度。
- 如果校验和出错, 就会直接丢弃。
2.UDP协议的特点
面向数据报,无连接,不可靠的通信协议。
- 无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接。
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层 返回任何错误信息。
- 面向数据报: 不能够灵活的控制读写数据的次数和数量。
3. UDP的使用事项
我们注意到, UDP协议首部中有一个16位的大长度. 也就是说一个UDP能传输的数据大长度是64K(包含UDP首 部). 然而64K在当今的互联网环境下, 是一个非常小的数字. 如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;。
4.基于UDP的应用层协议
- NFS: 网络文件系统
- TFTP: 简单文件传输协议
- DHCP: 动态主机配置协议
- BOOTP: 启动协议(用于无盘设备启动)
- DNS: 域名解析协议
5.基于UDP协议的通信编程
服务端:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
if(argc != 3){
printf("Usage: ./main 192.168.116.129 9000\n");
}
//创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sockfd < 0){
perror("create error");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr("192.168.116.129");
//绑定地址信息
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd,(struct sockaddr*)&addr,len);
if(ret < 0){
perror("bind error");
return -1;
}
//接收信息
while(1){
char buff[1024] = {0};
struct sockaddr_in cliaddr;
socklen_t len = sizeof(struct sockaddr_in);
int ret = recvfrom(sockfd,buff,1023,0,(struct sockaddr*)& cliaddr,&len); //0默认阻塞
if(ret < 0){
perror("recvfrom error");
close(sockfd);
return -1;
}
printf("client say: %s\n",buff);
//发送信息
memset(buff,0x00,1024);
scanf("%s",buff);
ret = sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)& cliaddr,len);
if(ret < 0){
perror("sendto error");
close(sockfd);
return -1;
}
}
return 0;
}
客户端:
include<iostream>
#include<string>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<stdlib.h>
class Udpsocket{
public:
Udpsocket():_sockfd(-1){
}
~Udpsocket(){
Close();
}
bool Socket(){
_sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(_sockfd < 0){
std::cerr << "socket error\n";
return false;
}
return true;
}
bool Bind(const std::string &ip,uint16_t port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd,(struct sockaddr*)& addr,len);
if(ret < 0){
std::cerr << "bind error\n";
return false;
}
return true;
}
bool Recv(std::string& buff,std::string& ip, uint16_t& port){
char tmp[4096];
struct sockaddr_in peeraddr;
socklen_t len = sizeof(struct sockaddr_in);
int ret = recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)& peeraddr,&len);
if(ret < 0){
std::cerr << "recvfrom error\n";
return false;
}
buff.assign(tmp,ret);
port = ntohs(peeraddr.sin_port);
ip = inet_ntoa(peeraddr.sin_addr);
// inet_ntoa 将网络字节序的整数ip地址转换为字符串ip地址
// 返回缓冲区首地址,内部实现使用了静态成员变量
// 非线程安全
return true;
}
bool Send(std::string &data,std::string& ip, uint16_t port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = sendto(_sockfd,&data[0],data.size(),0,(struct sockaddr*)& addr,len);
if(ret < 0){
std::cerr << "sendto error\n";
return false;
}
return true;
}
bool Close(){
if(_sockfd >= 0){
close(_sockfd);
_sockfd = -1;
}
return true;
}
private:
int _sockfd;
};
#define CHECK_RET(q) if((q) == false){return -1;}
int main(int argc, char* argv[])
{
if(argc != 3){
std::cout << "./udp_cli serverip serverport";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = atoi(argv[2]);
Udpsocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind("192.168.116.129",8000));
while(1){
std::string buff;
std::cin >> buff;
CHECK_RET(sock.Send(buff,srv_ip,srv_port));
buff.clear();
CHECK_RET(sock.Recv(buff,srv_ip,srv_port));
std::cout << "server say:\n" << buff << "\n";
}
CHECK_RET(sock.Close());
return 0;
}