一.UDP通信
1.概述
(1)介绍
**UDP(用户数据报协议):**是一种无连接的传输协议,它以数据包为单位进行通信,与TCP不同,UDP不提供可靠的数据传输服务,只是将数据报从一台主机发送到另一台主机,但不保证数据报的到达、顺序、或内容是否正确
**应用场景:**UDP服务器通常用于需要快速响应和较低开销的场景,如实时视频流、DNS查询等
注意事项:
- UDP服务器应处理可能出现的各种错误和异常情况,如网络中断、端口冲突等。
- 由于UDP是无连接的,因此服务器需要自行处理数据的完整性和顺序性。如果应用需要可靠的数据传输,可能需要在应用层实现重传机制等策略。
- UDP通常用于需要快速响应和较低开销的场景,但对于实时性要求不高且需要可靠数据传输的应用,建议使用TCP协议。
- 在编写UDP服务器代码时,应注意内存管理和线程安全等问题,以避免潜在的安全漏洞和性能问题。
(2)协议特点
- 无连接:UDP在通信前不需要建立连接,发送方和接收方不需要相互确认,因此通信效率较高。
- 不可靠:UDP不提供数据传输的可靠性保证,数据可能会因为网络丢包等原因而丢失。
- 快速:UDP不需要等待确认和重传机制,因此可以更快地传输数据并降低网络延迟。
(3)通信流程
UDP服务端代码和客户端类似,仅仅只是数据收发的顺序不同而已
-
① 创建UDP套接字:使用socket()函数创建一个UDP套接字,并指定通信域(如IPv4)和套接字类型(SOCK_DGRAM表示数据报式)
-
**② 绑定套接字:**使用bind()函数将套接字绑定到一个特定的IP地址和端口号上
其他主机就可以通过该IP地址和端口号与服务器进行通信
-
**③ 接收数据:**使用recvfrom()函数从客户端接收数据
该函数会阻塞当前线程,直到有数据到达或发生错误
-
**④ 处理数据:**根据接收到的数据执行相应的操作
由于UDP是无连接的,因此服务器需要自行处理数据的完整性和顺序性
-
**⑤ 发送响应:**使用sendto()函数向客户端发送响应数据
该函数允许你指定目标IP地址和端口号
-
**⑥ 关闭套接字:**当服务器不再需要监听或发送数据时,使用close()函数关闭套接字
2.相关API
(1)recvfrom
**功能:**该函数是Unix系统中用于网络套接字函数,用于从指定的套接字接收数据,并可以通过其获取发送方的地址信息(这对于UDP通信而言是特别有用的,因为其是无连接的)
函数定义:
#include
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数说明:
- sockfd:用于接收数据的套接字标识符
- buf:用于存储接收到数据的缓冲区指针
- len:buf缓冲区的大小,以字节为单位
- flags:控制接收操作的标志
- 大多数程序直接使用设置为
0
,使用默认值即可 - 设置为
1
表示紧急数据 - 设置为
MSG_PEEK
,表示只查看不删除数据
- 大多数程序直接使用设置为
返回值:
- 成功:返回收到的字节数
- 失败:返回
-1
,并设置全局变量errno
以指示错误
(2)sendto
功能:用以向指定的目的地址的发送数据报的函数,主要用于UDP通信。当使用UDP通信时,因为其是面向无连接的,所以需要使用该函数以指定发送数据报的地址,有发送数据包
函数原型:
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数说明:
- sockfd:用于发送数据的套接字
- buf:用以存储待发送数据的缓冲区指针
- len:buf缓冲区的大小,以字节为单位
- flags:发送选项
- 通常设置为0即可
-
MSG_DONTWAIT
:非阻塞发送
- dest_addr:发送数据报地址信息的指针
- addrlen:目的地地址结构体的大小
返回值:
- 成功:返回实际发送的数据报的长度的
- 失败:返回
-1
,并设置全局变量errno
以指示错误
3.实例
(1)UDP服务器
代码:
#include
#include
#include
#include
#include
#include
#include
#include
/*
UDP协议面向无连接,所以只管接收数据处理后响应即可,无需监听。
*/
int main(int argc, char const *argv[])
{
// 1.创建套接字,需要注意的是,UDP协议的套接字类型需要使用SOCK_DGRAM数据报类型
int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
// 2.绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int lfd = bind(socketFd, (struct sockaddr *)&addr, sizeof(addr));
if (lfd < 0) // 绑定失败,直接退出
{
perror("bind");
return 0;
}
// 3.接收数据、响应数据
char reBuf[1024] = ""; // 用以存储接收到的数据
struct sockaddr_in reAddr; // 用于记录请求的主机地址
socklen_t len = sizeof(reAddr);
while (1)
{
memset(reBuf, 0, sizeof(reBuf));
int n = recvfrom(socketFd, reBuf, sizeof(reBuf), 0, (struct sockaddr *)&reAddr, &len); // 接收数据
if (n < 0) // 错误,直接退出
{
perror("recvfrom");
break;
}
else
{
printf("接收到数据:%s\n", reBuf);
sendto(socketFd, reBuf, sizeof(reBuf), 0, (struct sockaddr *)&reAddr, sizeof(reAddr));
}
}
// 4.关闭套接字
close(socketFd);
return 0;
}
效果:
(2)UDP客户端
代码:
#include
#include
#include
#include
#include
#include
#include
#include
/*
UDP客户端和服务端代码整体区别不大,不过是读写的顺序存在差异罢了
*/
int main(int argc, char const *argv[])
{
// 1.创建套接字,需要注意的是,UDP协议的套接字类型需要使用SOCK_DGRAM数据报类型
int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
// 2.绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5148); // 客户端的端口切记不能和服务端重复
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int lfd = bind(socketFd, (struct sockaddr *)&addr, sizeof(addr));
if (lfd < 0) // 绑定失败,直接退出
{
perror("bind");
return 0;
}
// 3.发送数据、接收数据
char sendBuf[1024] = ""; // 用以存储待发送的数据
char reBuf[1024] = ""; // 用以存储接收到的数据
/* 定义服务器的socketAddr */
struct sockaddr_in sendAddr;
sendAddr.sin_family = AF_INET;
sendAddr.sin_port = htons(8080); // 设置为服务器的端口
sendAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置为服务器的地址
socklen_t len = sizeof(sendAddr);
int n = 0;
while (1)
{
memset(sendBuf, 0, sizeof(sendBuf));
n = read(STDIN_FILENO, sendBuf, sizeof(sendBuf)); // 从标准输入读取要发送的数据
if (n < 0)
{
perror("read");
break;
}
sendto(socketFd, sendBuf, strlen(sendBuf), 0, (struct sockaddr *)&sendAddr, sizeof(sendAddr));
/* Tip:这里因为知道发送数据的就是服务器了,所以直接写NULL,表不存储数据发送方信息 */
n = recvfrom(socketFd, reBuf, sizeof(reBuf), 0, NULL, NULL); // 接收数据
if (n < 0) // 错误,直接退出
{
perror("recvfrom");
break;
}
printf("ServerReturn:%s\n", reBuf); // 将服务器发送回来的数据打印到屏幕上
}
// 4.关闭套接字
close(socketFd);
return 0;
}
**效果:**服务器采用的就是上面的服务器代码