一.UDP通信

1.概述

(1)介绍

**UDP(用户数据报协议):**是一种无连接的传输协议,它以数据包为单位进行通信,与TCP不同,UDP不提供可靠的数据传输服务,只是将数据报从一台主机发送到另一台主机,但不保证数据报的到达、顺序、或内容是否正确

**应用场景:**UDP服务器通常用于需要快速响应和较低开销的场景,如实时视频流、DNS查询等

注意事项:

  1. UDP服务器应处理可能出现的各种错误和异常情况,如网络中断、端口冲突等。
  2. 由于UDP是无连接的,因此服务器需要自行处理数据的完整性和顺序性。如果应用需要可靠的数据传输,可能需要在应用层实现重传机制等策略。
  3. UDP通常用于需要快速响应和较低开销的场景,但对于实时性要求不高且需要可靠数据传输的应用,建议使用TCP协议。
  4. 在编写UDP服务器代码时,应注意内存管理和线程安全等问题,以避免潜在的安全漏洞和性能问题。

(2)协议特点

  1. 无连接:UDP在通信前不需要建立连接,发送方和接收方不需要相互确认,因此通信效率较高。
  2. 不可靠:UDP不提供数据传输的可靠性保证,数据可能会因为网络丢包等原因而丢失。
  3. 快速: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;
}

**效果:**服务器采用的就是上面的服务器代码

二.本地套接字

最后修改:2024 年 05 月 03 日
如果觉得我的文章对你有用,请随意赞赏