UDP通信

UDP简介

UDP(User Datagram Protocol)即用户数据报协议,是OSI(Open System Interconnection,即开放式系统互联)参考模型中的一种传输层协议。
OSI参考模型
该协议提供了一种面向无连接模式的通信。由于这种特性,使用UDP协议进行传输时的开销更小,但同时并不能保证被传输的数据能够到达目的地。

UDP协议特性

总结一下UDP协议的一些特性:

  • 需要资源少
  • 不保证接收
  • 无连接

UDP协议与TCP协议的主要区别

TCP协议简介

TCP(Transmission Control Protocol)即传输控制协议,与UDP协议一样,是OSI参考模型中的一种传输层协议。

TCP协议特性

为了明晰UDP协议与TCP协议两者之间的区别,有必要对TCP的一些特性进行简要的了解。

  • 面向有连接模式
    在建立通信的时候需要三次握手,同时在断开通信的时候需要四次挥手
  • 顺序传输与累计确认
    每个数据包均有一个ID,按照ID顺序发送,同时为了保证不丢包,需要对发送的包进行应答,称为累计确认。
  • 确认与重传机制
    如果数据包没有收到,会要求发送端重发
  • 流量控制
    控制发送速度让接收端来得及接收
  • 拥塞控制
    在不堵塞的情况下尽量发挥带宽

主要区别

UDPTCP
面向无连接面向有连接
支持一对一、一对多、多对一、和多对多的通信只能有两个端点,实现一对一的通信
不保证数据传输的可靠性传输数据无差错,不丢失,不重复,且按时序到达
占用资源较少占用资源较多

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;
}

  • 10
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值