自学内容网 自学内容网

UDP协议和Socket编程

引言

看本文章时,先看上一个文章TCP Socket编程。


UDP

UDP(UserDatagramProtocol,用户数据报协议)是互联网协议套件中的一种传输层协议,与广泛使用的TCP(TransmissionControlProtocol,传输控制协议)相比,它是一种无连接、不可靠的协议。UDP被用于对传输速度要求较高、但对可靠性要求较低的场景。

UDP的特点

  • 无连接
    • UDP不需要在发送数据之前建立连接,也不维护连接的状态。数据可以在没有任何事先协商的情况下直接从一台主机发送到另一台主机。
    • 发送数据时,发送方只需要知道接收方的IP地址和端口号,而无需等待接收方的准备响应。
  • 不可靠传输
    • UDP没有像TCP那样的确认、重传、流量控制和拥塞控制机制。数据包可能丢失、重复、乱序到达,发送方不会知道数据是否成功到达接收方。
    • 这使UDP非常轻量和快速,但也意味着需要应用层自己处理可靠性问题(如重传机制、顺序控制等)。
  • 面向数据报
    • UDP以数据报(datagram)为单位传输数据。每个数据报是一个独立的单元,包含完整的源和目标信息。
    • 每个UDP数据报独立处理,且数据报的大小受到网络最大传输单元(MTU)的限制,超过一定大小的数据需要进行分片。
  • 快速,低延迟
    • 由于UDP不需要建立连接,也不需要等待确认,因此它的传输速度通常比TCP 快。特别是在需要实时传输的场景(如语音、视频、游戏)中,UDP是首选协议。
    • UDP常用于实时应用中,如视频流、语音通话(VoIP)、在线游戏等,对实时性要求高,而对偶尔的数据丢失不敏感的场景。
  • 支持广播和多播
    • UDP支持广播(Broadcast)和多播(Multicast),这使得它可以将数据同时发送到多个主机,特别适用于局域网内的数据传输。
  • 无拥塞控制
    • TCP具有内置的拥塞控制机制,但UDP没有。这意味着UDP不会主动减少传速率,可能会在网络拥塞的情况下丢包或产生网络冲突。
  • UDP的典型使用场景
    • 实时应用:如语音通话(VoIP)、视频会议、在线游戏等,这些应用通常要求低延迟,即使偶尔的数据丢失也不会严重影响用户体验。
    • 广播或多播应用:如局域网中的发现协议、局域网的广播消息传输。UDP能够很方便地将数据包发送给多个接收者。
    • 简单的查询-响应协议:如DNS(Domain Name System,域名系统)协议,客户端向服务器发送查询请求并等待快速响应。
    • 物联网(IoT)设备:一些物联网设备由于资源受限,使用UDP协议来减少通信的开销。
  • 优点:
    • 轻量级:UDP没有复杂的握手过程和状态管理,使用的资源非常少。
    • 低延迟:由于没有连接建立和确认机制,UDP传输速度很快,非常适合实时性要求高的场景。
    • 支持广播和多播:UDP能将数据包发送给多个接收者,而TCP无法做到这一点。
  • 缺点:
    • 不可靠:UDP不保证数据的到达、顺序或完整性,数据包可能会丢失、乱序或重复。
    • 无拥塞控制:UDP不会自动适应网络负载,可能在拥塞情况下导致数据丢失。
    • 安全性较低:由于无连接、无状态的特性,UDP更容易受到攻击(如DDoS攻击),应用层需要自己处理安全性问题。

UDP和TCP的区别

  • 连接模式
    • TCP:
      面向连接(Connection-oriented),在发送数据之前需要先建立连接。通过三次握手(Three-wayhandshake)来确保双方准备好通信,然后在通信结束时通过四次挥手(Four-wayhandshake)来关闭连接。TCP会维护连接的状态和信息,确保可靠的传输。
    • UDP:
      无连接(Connectionless),不需要建立或维护连接。每个数据包独立传输,发送方可以直接将数据发送给接收方,无需事先进行连接建立和连接关闭的过程。
  • 可靠性
    • TCP:
      可靠协议,提供确认机制、序列号、重传机制和数据校验,确保数据包按顺序、无误地传递给接收方。如果数据在传输中丢失或损坏,TCP会自动请求重传。
    • UDP:不
      可靠协议,不提供数据包确认、重传或保证顺序传输的机制。数据包可能会丢失、重复或乱序到达,发送方不知道数据是否成功送达。
  • 传输方式
    • TCP:
      面向字节流(Stream-oriented),TCP将数据作为一个连续的数据流进行传输。这意味着应用程序传输的所有数据都会被TCP拆分为适当大小的段(segment)并顺序传输。
    • UDP:
      面向报文(Message-oriented),UDP以数据报(datagram)的形式发送。每个UDP数据报是独立的,包含完整的头部信息。发送的每个数据报都不会与其他数据报相关联,接收方按数据报为单位处理数据。
  • 数据包顺序
    • TCP:
      保证数据包按发送顺序到达接收方。TCP协议会给每个数据段分配一个序列号,确保接收方按序接收。如果有数据包乱序或丢失,TCP会请求重传以确保正确的顺序。
    • UDP:
      不保证数据包的顺序。由于UDP不追踪数据包的状态,数据包可能乱序到达接收方,接收方需要自行处理顺序问题(如果顺序重要的话)。
  • 拥塞控制和流量控制
    • TCP:
      提供拥塞控制和流量控制机制。当网络拥堵时,TCP会自动降低发送速率来减轻网络负载。此外,TCP还会根据接收方的接收能力动态调整发送速率,防止接收方因无法处理而丢包。
    • UDP:
      没有拥塞控制和流量控制机制。UDP总是尽最大可能发送数据,不会检测网络拥塞或接收方的接收能力,因此有可能导致网络拥塞或数据包丢失。
  • 传输效率
    • TCP:
      由于有连接建立、确认、重传、流量控制等机制,TCP的传输效率较低,特别是在高延迟或高丢包率的网络环境中。TCP的可靠性带来了额外的开销。
    • UDP:
      UDP协议头部较小,无需建立连接和确认机制,因此传输速度快,效率高。
  • 使用场景
    • TCP:适用于需要保证数据准确性和顺序的应用,例如文件传输(如FTP)、电子邮件(SMTP)、网页浏览(HTTP/HTTPS)等。这些应用对数据的完整性有较高要求。
    • UDP:适用于对实时性要求高、但对数据可靠性要求较低的应用,例如视频流、语音通话(VoIP)、在线游戏、DNS查询等。在这些场景中,偶尔的数据丢失或乱序不会显著影响用户体验,而快速传输才是关键。
  • 头部开销
    • TCP:
      TCP报文头部较大,包含20到60字节的控制信息,包括源端口、目的端
      口、序列号、确认号、标志位、性机制,所以头部占用较大。窗口大小等。因为TCP提供复杂的控制和可靠
    • UDP:
      UDP报文头部很小,仅有8字节,包含源端口、目的端口、长度和校验和。这使得UDP更加轻量,适合对网络开销敏感的应用。
  • 广播和多播
    • TCP:
      不支持广播和多播。由于TCP是面向连接的,它只能进行点对点的通信。
    • UDP:
      支持广播和多播,允许将数据发送给多个接收方,这使UDP成为局域网内广播通信或特场景下多播通信的理想选择。
  • 流控制
    • TCP:
      使用滑动窗口机制进行流控制,确保发送方不会超出接收方的处理能力。
    • UDP:
      没有流控制机制,发送方可以持续发送数据,而不考虑接收方是否能处理这些数据。
  • 延迟
    • TCP:由于需要三次握手、确认机制、重传等操作,TCP的延迟可能较高,特别是在网络较差的情况下。
    • UDP:没有连接建立和确认机制,传输延迟较低,适合对实时性要求高的应用。

UDP Socket编程流程

看完TCP Socket编程下图就没什么好说的了。
在这里插入图片描述

recvfrom函数

recvfrom函数是套接字编程中用于从套接字接收数据的一个函数,特别用于UDP协议下的数据接收。它允许程序从一个未连接的套接字(如UDP套接字)接收数据报。

函数原型

ssize_t recvfrom(int sockfd,void  *buf,size_t len,int flags,
struct sockaddr *src_addr,socklen_t *addrlen);

参数详解

  • sockfd:套接字描述符,指定了接收数据的套接字。这个套接字通常是通过socket函数创建的,并且绑定到了一个特定的端口(对于UDP来说)。
  • buf:指向数据缓冲区的指针,这个缓冲区用于存储接收到的数据。接收到的数据会被复制到这个缓冲区中。 len:指定了buf缓冲区的长度,即可以接收的最大数据量(以字节为单位)。
  • flags:标志位,用于修改recvfrom的行为。常用的标志包括MSG_PEEK(查看数据但不从队列中移除)、MSG_WAITALL(请求阻塞操作直到接收到完整的请求数据但这对于UDP来说通常不适用,因为UDP是无连接的、数据报驱动的协议)等。在这个例子中,flags被设置为0,表示使用默认行为。
  • src_addr:指向sockaddr结构体的指针,用于存储发送方的地址信息。如果不需要这个信息,可以传递nullptr。在这个例子中,src_addr被设置为nullptr,表示不关心发送方的地址。
  • addrlen:指向socklen_t变量的指针,该变量在调用前应该被初始化为src_addr所指向的地址结构的大小。在函数调用后,这个变量会被更新为实际存储在src_addr中的地址结构的大小。如果src_addr是nullptr,则addrlen也应该是nullptr。在这个例子中,addrlen被设置为nullptr。

返回值

recvfrom函数返回成功接收到的字节数。
如果返回0,表示连接已正常关闭(但这对UDP来说并不常见,因为 UDP是无连接的)。
如果返回-1,表示发生了错误,错误类型可以通过errno来检查。

sendto函数

sendto函数是套接字编程中用于发送数据的一个函数,特别适用于UDP协议下的数据发送。它允许程序向指定的地址发送数据报。

函数原型

ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,
const struct sockaddr *dest_addr,socklen_t addrlen);

参数详解

  • sockfd:套接字描述符,指定了发送数据的套接字。这个套接字通常是通过socket函数创建的,对于UDP来说,它可能还没有通过connect函数与特定的远程地址关联。
  • buf:指向数据缓冲区的指针,这个缓冲区包含了要发送的数据。
  • len:指定了buf缓冲区的长度,即要发送的数据量(以字节为单位)。在这个例子中,通过strlen(respons e)来获取response字符串的长度。
  • flags:标志位,用于修改sendto的行为。常用的标志包括MSG_cONFIRM(请求确认消息已发送)、MSG_DON TROUTE(绕过路由表直接发送)等。在这个例子中,flags被设置为0,表示使用默认行为。
  • dest_addr:指向sockaddr结构体的指针,用于指定接收方的地址信息。这个结构体包含了目标主机的IP地址和端口号。
  • addrlen:指定了dest_addr所指向的地址结构的大小。这个值通常是通过sizeof操作符获取的。

返回值

sendto函数返回成功发送的字节数。
如果返回-1,表示发生了错误,错误类型可以通过errno来检查。

Linux服务端代码实现

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>

using namespace  std;

#define PORT 8080
#define BUFFER_SIZE 1024

int main()
{
    int sockfd;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_addr,client_addr;
    socklen_t addr_len = sizeof(client_addr);

    //创建UDPsocket
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        cerr << "Socket creation failed!" <<endl;
        exit(1);
    }

    //配置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    //绑定Socket到地址
    if(bind(sockfd,(const struct sockaddr*)&server_addr,sizeof(server_addr))<0)
    {
        cerr<<"bind failed!" <<endl;
        exit(1);
    }

    cout << "UDP server is listening on port : " << PORT << endl;

    while(true)
    {
        //接收信息
        int n = recvfrom(sockfd,buffer,BUFFER_SIZE,0,(struct sockaddr *)&client_addr,&addr_len);
        buffer[n] ='\0';
        cout << "Client: " << buffer << endl;

        //响应消息
        string response;
        sendto(sockfd,response.c_str(),response.size(),0,(const struct sockaddr *)&client_addr,addr_len);
    }

    close(sockfd);
    return 0;
}

Windows客户端代码实现

#include <iostream>
#include <string>
#include <WinSock2.h>
#include <WS2tcpip.h>

using namespace std;

#pragma  comment(lib,"Ws2_32.lib")

#define PORT 8080
#define BUFFER_SIZE 1024
#define IP "192.168.88.130"

int main()
{
WSADATA wsaData;
SOCKET sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in server_addr;

//初始化Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cerr << "WSAStartup failer!" << endl;
return 1;
}

//创建UDPsocket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == INVALID_SOCKET)
{
cerr << "Socket creation failed!" << endl;
WSACleanup();
return 1;
}

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, IP, &server_addr.sin_addr) <= 0)
{
cerr << "无效的IP地址" << endl;
return 1;
}

while (true)
{
string message;
cout << "Enter message: " << endl;
getline(cin, message);


//发送消息
int send_result = sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (send_result == SOCKET_ERROR)
{
cerr << "sendto failed!" << endl;
break;
}

//接收响应
int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, nullptr, nullptr);
if (n == SOCKET_ERROR)
{
cerr << "recvfrom failed!" << endl;
break;
}

buffer[n] = '\0';
cout << "Server : " << buffer << endl;
}

closesocket(sockfd);
WSACleanup();

return 0;
}

原文地址:https://blog.csdn.net/qq_72680384/article/details/145177495

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!