UDP通信
UDP简介
UDP(User Datagram Protocol)即用户数据报协议,是OSI(Open System Interconnection,即开放式系统互联)参考模型中的一种传输层协议。
该协议提供了一种面向无连接模式的通信。由于这种特性,使用UDP协议进行传输时的开销更小,但同时并不能保证被传输的数据能够到达目的地。
UDP协议特性
总结一下UDP协议的一些特性:
- 需要资源少
- 不保证接收
- 无连接
- 快
UDP协议与TCP协议的主要区别
TCP协议简介
TCP(Transmission Control Protocol)即传输控制协议,与UDP协议一样,是OSI参考模型中的一种传输层协议。
TCP协议特性
为了明晰UDP协议与TCP协议两者之间的区别,有必要对TCP的一些特性进行简要的了解。
- 面向有连接模式
在建立通信的时候需要三次握手,同时在断开通信的时候需要四次挥手 - 顺序传输与累计确认
每个数据包均有一个ID,按照ID顺序发送,同时为了保证不丢包,需要对发送的包进行应答,称为累计确认。 - 确认与重传机制
如果数据包没有收到,会要求发送端重发 - 流量控制
控制发送速度让接收端来得及接收 - 拥塞控制
在不堵塞的情况下尽量发挥带宽
主要区别
UDP | TCP |
---|---|
面向无连接 | 面向有连接 |
支持一对一、一对多、多对一、和多对多的通信 | 只能有两个端点,实现一对一的通信 |
不保证数据传输的可靠性 | 传输数据无差错,不丢失,不重复,且按时序到达 |
占用资源较少 | 占用资源较多 |
UDP协议应用场景
基于以上特性,UDP协议更适合应用于:
- 系统资源较少的嵌入式系统
- 网络条件稳定的内部局域网
- IP与端口号都固定的情况
- 对实时性要求较高的情况
UDP通信代码
本文提供了两个操作系统下,UDP通信的代码。
Linux系统下的UDP通信代码
发送端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
//第一个传参,目的ip地址
//第二个传参,目的端口号
int main(int argc, char *argv[])
{
//输出参数数量校验
if (argc < 3)
{
printf("输入的参数数量错误!\n");
printf(“请检查并重新执行此程序!\n");
return -1;
}
char buf[1000] = { "hello world" }; //声明发送数据缓存区
struct sockaddr_in sockaddr_dest; //声明接收服务器地址
int fd = socket(AF_INET, SOCK_DGRAM, 0); //建立socket
//AF_INET IPV4
//SOCK_DGRAM UDP
//0 传输层通信
//socket建立测试
if (client_fd < 0)
{
printf("socket建立失败!\n");
return -1;
}
//清空接收服务器地址
memset(&client_addr, 0, sizeof(sockaddr_dest));
//配置接收端服务器
sockaddr_dest.sin_family = AF_INET; //ipv4
sockaddr_dest.sin_addr.s_addr = inet_addr(argv[1]); //第一个传参,ip地址
sockaddr_dest.sin_port = htons(atoi(argv[2])); //第二个传参,端口号
//发送数据
sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sockaddr_dest, sizeof(sockaddr_dest));
//关闭socket
close(fd);
}
接收端(阻塞模式)
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
//第一个传参,本机ip地址
//第二个传参,本机端口号
int main(int argc, char *argv[])
{
//输出参数数量校验
if (argc < 3)
{
printf("输入的参数数量错误!\n");
printf(“请检查并重新执行此程序!\n");
return -1;
}
int len = sizeof(struct sockaddr_in);
char buf[100] = { 0 };
struct sockaddr_in sockaddr_recv; //接收端通信地址结构
struct sockaddr_in sockaddr_send; //发送端通信地址结构
int fd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_recv.sin_family = AF_INET;
sockaddr_recv.sin_port = htons(33333);
sockaddr_recv.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定socket
int ret = bind(fd, (struct sockaddr *)&sockaddr_recv, sizeof(sockaddr_recv));
//判断是否绑定成功
if (ret == 0)
{
printf("绑定成功!\n");
}
else
{
printf("绑定失败!\n");
}
//以阻塞端方式接收数据
recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sockaddr_send, &len);
//打印接收到的数据
printf("recv is %s\n", buf);
}
接收端(非阻塞模式)
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <unistd.h>
//第一个传参,本机ip地址
//第二个传参,本机端口号
//第三个船餐,等待时间,单位秒
int main(int argc, char *argv[])
{
//输出参数数量校验
if (argc < 4)
{
printf("输入的参数数量错误!\n");
printf(“请检查并重新执行此程序!\n");
return -1;
}
int len = sizeof(struct sockaddr_in);
char buf[100] = { 0 };
struct sockaddr_in sockaddr_recv; //接收端通信地址结构
struct sockaddr_in sockaddr_send; //发送端通信地址结构
int fd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_recv.sin_family = AF_INET;
sockaddr_recv.sin_port = htons(33333);
sockaddr_recv.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定socket
int ret = bind(fd, (struct sockaddr *)&sockaddr_recv, sizeof(sockaddr_recv));
//判断是否绑定成功
if (ret == 0)
{
printf("绑定成功!\n");
}
else
{
printf("绑定失败!\n");
}
//使用select完成非阻塞
fd_read read; //声明一个fd_set集合来保存被检测的句柄
struct timeval timeout; //声明一个时间结构来保存阻塞的时间
//循环接收
while(1)
{
FD_ZERO(&read); //使用select函数之前先将集合清零
FD_SET(fd, &read); //将所要检测端socket句柄加入到集合中
timeout.tv_sec = atoi(argv[3]); //设置select等待的最大时间
timeout.tv_usec = 0;
int ret_select = select(4, &read, NULL, NULL, &timeout); //检测集合read中的句柄是否有可读信息
//如果返回值小于0,则select函数出错
if (ret_select < 0)
{
printf("select函数出错!\n");
return -1;
}
//如果返回值为0,则在计时周期内socket的状态没有发生改变
else if (ret_select == 0)
{
printf("此周期内无数据接收!\n");
sleep(1); //无数据接收,等待1秒
}
//ret_select记录了发生变化的句柄的个数
else
{
if (FD_ISSET(fd, &read)) //如果这个被监视端句柄真的变为可读了
{
memset(buf, 0, 100); //先将接收缓存区清零
len = sizeof(sockaddr_send); //获取发送端通信地址结构长
//在这一时间周期内以阻塞的方式接收数据
int recv = recvfrom(server_fd, buf, BUF_LEN, 0, (struct sockaddr *)&sockaddr_send, &len);
//判断是否接收到了数据
if (recv == -1)
{
printf("接收数据失败!\n");
return -1;
}
printf("recv is %s\n", buf);
}
}
}
}
Windows系统下的UDP通信代码
配置UDP
int Deploy_UDP(int &skt, char *IP_Address, UINT port)
{
skt = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in svrsockaddr;
svrsockaddr.sin_family = AF_INET;
svrsockaddr.sin_port = htons(port);
svrsockaddr.sin_addr.S_un.S_addr = inet_addr(IP_Address);
int result_bind = bind(skt, (struct sockaddr FAR*)&svrsockaddr, sizeof(struct sockaddr_in));
//设置UDP的模式为非阻塞接收
u_long unblock=100;
int result_unblock = ioctlsocket(skt, FIONBIO, &unblock);
return 0;
}
发送函数
int shareSend(int skt, char *buffer, int len, char *IP_Address, UINT port)
{
sockaddr_in Comsockaddr;
Comsockaddr.sin_family = AF_INET;
Comsockaddr.sin_port = htons(port);
Comsockaddr.sin_addr.S_un.S_addr = inet_addr(IP_Address);
return sendto(skt, buffer, len, 0, (struct sockaddr FAR*)&Comsockaddr, sizeof(sockaddr_in));
}
接收函数
int UDPRecv(int skt, BYTE *buffer, int len, struct sockaddr_in &recvsock)
{
int len_recvsock = sizeof(struct sockaddr_in);
if (skt > 0)
{
return recvfrom(skt, (char *)buffer, len, 0, (struct sockaddr FAR*)&recvsock, &len_recvsock);
}
return -1;
}
关闭函数
int UDPClose(int skt)
{
closesocket(skt);
return 0;
}