UDP协议和Socket编程

打印 上一主题 下一主题

主题 1044|帖子 1044|积分 3132

每天开心!!!
  
  

一、UDP的特点

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

二、TCP和UDP的区别

UDP(用胡数据报协议)和TCP(传输控制协议)都是传输层协议,负责在网络中传输数据,但它们的设计目的和实现方式有很大的区别。
以下是UDP和TCP的主要区别:

三、UDP的包头格式



  • UDP伪首部(Pseudo Header)
    伪首部适用于计算校验和的虚拟头部信息,它不包罗在实际传输的数据中,但用于包管数据完整性。伪首部包罗以下字段


  • 32位源IP地址:表示数据包的源IP地址,即发送方的IP地址
  • 32位目的IP地址:表示数据包的目的IP地址,即吸收方的IP地址
  • 0:固定填充的8位0字段,不使用
  • 8位协议(17):标识传输协议范例。对于UDP协议来说,这个字段的值是17
  • 16位UDP长度:表示整个UDP数据报的长度,包括UDP首部和数据部分

  • UDP首部
    UDP首部是真正的数据报头部,它包罗了与UDP通讯干系的根本信息。UDP首部固定为8字节,包罗以下字段:


  • 16位源端口号:发送方的端口号,标识发送数据的应用程序。假如不须要,值可以为0
  • 16位目的端口号:吸收方的端口号,表示吸收数据的应用程序
    16位UDP长度:表示UDP报文的总长度(包括UDP首部和数据部分)。由于UDP首部固定为8字节,因此长度至少为8
  • 16位UDP校验和:用于确保数据报在传输过程中没有被粉碎。它由UDP伪首部、UDP首部和数据部分计算而得。假如发送方不计算校验和,则该字段可以为0。

  • 数据部分
    UDP数据报的实际数据内容。数据部分的长度可以根据具体的应用需求而变化,但必须与首部中的UDP长度字段保持一致
  • 填充字段(0)
    数据包须要肯定的字节对齐规则(如32位对齐)填充到合适的长度,以确保数据包的完整性和便于传输
  • 重点


  • UDP伪首部并不实际存在于UDP报文中,它仅在计算校验和时使用,用于提供更多的上下文(如IP地址)来验证数据的完整性
  • UDP首部非常简朴,仅有8字节,包管了UDP的轻量和高效
  • UDP数据:携带的实际传输数据,长度可以根据应用而变化
    这个图展示了UDP协议的简朴性和高效性,由于UDP协议不须要复杂的毗连管理或传输控制机制。
四、UDP Socket编程流程



  • UDP客户端流程


  • socket():创建一个UDP套接字(Socket)。这是启动UDP通讯的第一步,客户端通过调用socket()函数生成一个用于通讯的套接字
  • sendto(():向服务器发送数据。客户端使用sendto()函数来将数据报发送到指定的服务器IP地址和端口。这是一个无毗连的操纵,不须要事先建立毗连
  • 等候相应:客户端待用recvform()函数,进入阻塞状态,等候从服务器返回的数据。recvform()会吸收来自服务器的数据报,函数会在吸收到数据后解除阻塞。
  • recvform():吸收到服务器返回的数据后,继续处理该数据。
  • close():通讯完成后,关闭客户端套接字,释放系统资源。

  • UDP服务器流程


  • socket():与客户端一样,服务器起首创建一个UDP套接字,通过调用socket()函数。
  • bind():将套接字与指定的IP地址和端口绑定。服务器必须绑定到一个特定的端口上。这样才能吸收来自客户端的数据,bind()是服务器端持有的操纵,客户端通常不须要显式的调用bind()
  • recvform():服务器使用recvform()吸收客户端发送的数据报,并进入阻塞状态,直到吸收到数据为止。
  • 处理请求:收到数据后,服务器使用recvform()吸收客户端发送的数据报,并进入阻塞状态,直到吸收到数据为止。
  • sendto():处理完成后,服务器通过sendto()向客户端发送相应数据。
  • 继续等候:服务器可以继续调用recvform()来吸收下一个数据请求

  • 图中的其他元素


  • 阻塞直到收到数据:无论是客户端照旧服务器,调用recvform()后,程序会进入阻塞状态,等候对方发送数据,这是一个UDP通讯中的常见模式
  • 数据请求和数据相应:图中表现了客户端向服务器发送请求数据,服务器处理后会返回相应数据的流程。

  • UDP通讯的特点


  • 无毗连:UDP协议是无毗连的,客户端不须要先与服务器建立毗连,直接发送数据。服务器收到数据后可以立即处理。
  • 阻塞模式:图中表现的recvform()操纵是阻塞的,直到有数据到来才会继续执行
  • 简朴轻量:由于UDP不须要维护毗连状态,它比TCP更加简朴和轻量,适用于对实时性要求高但是对数据可靠性要求较低的场景。
五、UDP代码实现


  • UDP服务器代码实现
  1. #include <iostream>
  2. #include <cstring>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <unistd.h>
  6. using namespace std;
  7. #define PORT 8080
  8. #define BUFFER_SIZE 1024
  9. int main()
  10. {
  11.     int sockfd;
  12.     char buffer[BUFFER_SIZE];
  13.     struct sockaddr_in server_addr, client_addr;
  14.     socklen_t addr_len = sizeof(client_addr);
  15.     //创建UDPsocket
  16.     sockfd=socket(AF_INET, SOCK_DGRAM, 0);
  17.     if(sockfd<0)
  18.     {
  19.         cerr<<"socket error"<<endl;
  20.         exit(EXIT_FAILURE);
  21.     }
  22.     //配置服务器地址
  23.     memset(&server_addr, 0, sizeof(server_addr));
  24.     server_addr.sin_family = AF_INET;   //IPv4
  25.     server_addr.sin_addr.s_addr = INADDR_ANY;  //监听所有地址
  26.     server_addr.sin_port = htons(PORT);      //端口号
  27.     //绑定socket到地址
  28.     if(bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr))<0)
  29.     {
  30.         cerr<<"bind error"<<endl;
  31.         clsoe(sockfd);
  32.         exit(EXIT_FAILURE);
  33.     }
  34.     cout<<"Server is listening on port "<<PORT<<endl;
  35.     while(true)
  36.     {
  37.         //接收消息
  38.         int n=recvform(sockfd,buffer,BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
  39.         buffer[n]='\0';
  40.         cout<<"Client:"<<buffer<<endl;
  41.         //响应消息
  42.         const char* message="Message received!!!";
  43.         sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&client_addr, addr_len);
  44.     }
  45.     close(sockfd);
  46.     return 0;
  47. }
复制代码

  • UDP客户端代码实现
  1. #include <iostream>
  2. #include <string>
  3. #include <cstring>
  4. #include <sys/socket.h>
  5. #include <arpa/inet.h>
  6. #include <unistd.h>
  7. using namespace std;
  8. #define PORT 8080   // 端口号
  9. #define BUFFER_SIZE 1024  // 缓冲区大小
  10. #define IP "127.0.0.1"  //定义服务器地址
  11. int main()
  12. {
  13.     int sockfd;
  14.     char buffer[BUFFER_SIZE];
  15.     struct sockaddr_in server_addr;
  16.     //创建UDP套接字
  17.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  18.     if(sockfd <0)
  19.     {
  20.         cerr<<"Socket creation faliure"<<endl;
  21.         return -1;
  22.     }
  23.     //配置服务器地址
  24.     memset(&server_addr, 0, sizeof(server_addr));
  25.     server_addr.sin_family = AF_INET;
  26.     server_addr.sin_port = htons(PORT);
  27.     //使用inet_pton将IP地址从字符串转换为网络字节序
  28.     if(inet_pton(AF_INET, IP, &server_addr.sin_addr)<=0)
  29.     {
  30.         cerr<<"Invalid address/Address not supported"<<endl;
  31.         close(sockfd);
  32.         return -1;
  33.     }
  34.     while(true)
  35.     {
  36.         //发送消息到服务器
  37.         string message;
  38.         cout<<"Enter message: ";
  39.         getline(cin, message);//从标准输入读取用户输入
  40.         //发送消息
  41.         int send_result=sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
  42.         if(send_result<0)
  43.         {
  44.             cerr<<"Failed to send message"<<endl;
  45.             break;
  46.         }
  47.         //接收服务器的响应
  48.         socklen_t addrlen = sizeof(server_addr);//服务器地址长度
  49.         int n=recvform(sockfd,buffer,BUFFER_SIZE,0,(struct sockaddr*)&server_addr,&addrlen);
  50.         if(n<0)
  51.         {
  52.             cerr<<"Failed to receive response"<<endl;
  53.             break;
  54.         }
  55.         buffer[n]='\0';
  56.         cout<<"Server response: "<<buffer<<endl;//输出服务器的响应
  57.     }
  58.     close(sockfd);
  59.     return 0;
  60. }
复制代码

  • Windows客户端的代码实现


  • 头文件和库的引入


    • 在Windows中须要引入winsock2.h和ws2tcpip.h,而且须要链接Ws2_32.lib库

  • Winsock初始化和清理


    • 在Windows中,使用网络功能之前须要调用WSAStartup()举行初始化,使用完毕后须要调用WSACleanup()释放资源

  • Windows和Linux之间的一些差异


    • close()在Windows上对应的是closesocket()



    • perroor()函数在Windows上不常用,通常使用std::cerr输堕落误

  1. #include <iostream>
  2. #include <string>
  3. #include <winsock2.h>
  4. #include <ws2tcpip.h>
  5. using namespace std;
  6. #pragma comment(lib, "Ws2_32.lib")   //链接ws2_32.lib库
  7. #define PORT 8080   // 端口号
  8. #define BUFFER_SIZE 1024  // 缓冲区大小
  9. #define IP "127.0.0.1"  //定义服务器地址
  10. int main()
  11. {
  12.     WSADATA wsaData;   // 用于初始化WinSock
  13.     SOCKET sockfd;
  14.     char buffer[BUFFER_SIZE];
  15.     struct sockaddr_in server_addr;
  16.     //初始化WinSock
  17.     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
  18.     {
  19.         cout << "WSAStartup failed." << endl;
  20.         return 1;
  21.     }
  22.     //创建UDP套接字
  23.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  24.     if (sockfd == INVALID_SOCKET)
  25.     {
  26.         cout << "socket failed." << endl;
  27.         WSACleanup();
  28.         return 1;
  29.     }
  30.     //配置服务器地址
  31.     memset(&server_addr, 0, sizeof(server_addr));
  32.     server_addr.sin_family = AF_INET;
  33.     server_addr.sin_port = htons(PORT);
  34.     // inet_pton函数用于将点分十进制的IP地址转换为网络字节序的二进制地址
  35.     if (inet_pton(AF_INET, IP, &server_addr.sin_addr) <= 0)
  36.     {
  37.         cout << "inet_pton failed." << endl;
  38.         closesocket(sockfd);
  39.         WSACleanup();
  40.         return 1;
  41.     }
  42.     while(true)
  43.     {
  44.         //发送消息到服务器
  45.         string message;
  46.         cout<<"Enter message: ";
  47.         getline(cin, message);//从标准输入读取用户输入
  48.         //发送消息
  49.         int send_result=sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
  50.         if(send_result<0)
  51.         {
  52.             cerr<<"Failed to send message"<<endl;
  53.             break;
  54.         }
  55.         //接收服务器的响应
  56.         socklen_t addrlen = sizeof(server_addr);//服务器地址长度
  57.         int n=recvfrom(sockfd,buffer,BUFFER_SIZE,0,(struct sockaddr*)&server_addr,&addrlen);
  58.         if(n<0)
  59.         {
  60.             cerr<<"Failed to receive response"<<endl;
  61.             break;
  62.         }
  63.         buffer[n]='\0';
  64.         cout<<"Server response: "<<buffer<<endl;//输出服务器的响应
  65.     }
  66.     closesocket(sockfd);
  67.     WSACleanup();
  68. }
复制代码
六、函数细节


  • 关于recvform()函数
    recvform()函数是套接字编程中用于从他套接字吸收数据的一个函数,特别用于UDP协议下的数据吸收。它允许程序从一个未毗连的套接字(如UDP套接字)吸收数据报。下面是对recvform()函数及其参数int n=recvform(sockfd,buffer,BUFFER_SIZE,0,nullptr,nullptr);的详细解释:


  • 函数原型
  1. ssize_t recvform(int sockfd,void *buf,size_t len,int falgs,struct sockaddr *src_addr,socklen_t *addrlen);
复制代码


  • 参数解释:


    • sockfd:套接字形貌符,这个套接字通常是通过socket()函数创建的,而且绑定到了一个特定的端口(对于UDP来说)



    • buf:指定数据缓冲区的指针,这个缓冲区用于存储吸收到的数据。吸收的数据会被复制到这个缓冲区中



    • flags:标志位,用于修改recvform的举动,常用的标志包括MSG_PEEK(检察数据但不从队列中移除),MSG_WAITALL(请求阻塞操纵直到吸收到完整的请求数据,但这对于UDP来说通常不适用,由于UDP是无毗连的、数据报驱动的协议)等。



    • src_addr::指向sockaddr结构体的指针,用于存储发送方的地址信息。假如不须要这个信息,可以通报nullptr。。。



    • addrlen:指向socklen_t变量的指针,该变量在带哦用前应该被初始化位src_addr所指向的地址结构的大小。在函数被调用后,这个变量会被更新为实际存储在src_addr中的地址结构的大小。假如src_addr是nullptr,则addrlen也应该是nullptr。

  • 返回值:recvform()函数
    返回成功吸收到的字节数。假如返回0,表示毗连已正常关闭(但这对UDP来说并不常见,由于UDP是无毗连的),假如返回-1,表示发生了错误,错误范例可以通过errno来检查。

  • 关于memset
    memset是一个C/C++标准库函数,用于将一块内存区域的内容设置为指定的值。它通常用于初始化数组或结构体,以确保在使用这些数据之前内存中的内容是已知的。memset函数界说在(C++)中或《string.h》(C)头文件中。


  • 函数原型
  1. void * memset(void *ptr,int value,size_t num);
复制代码


  • 参数说明


    • ptr:指向要设置的内存块的指针



    • value:要设置的值,这个值会被转换成unsigned char范例,而且将其填充到内存块中



    • num:要设置的字节数


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


  • 函数原型
  1. ssize_t sendto(int sockfd ,const void *buf,size_t len,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
复制代码


  • 参数解释


    • sockfd:套接字形貌符,对于UDP来说,它可能还没有通过connect函数与特定的远程地址关联



    • buf:只想数据缓冲区的指针



    • len:指定了buf缓冲区的长度



    • flags:标志位,用于修改sendto的举动。常用的标志包括MSG_CONFIRM(请求确认消息已发送)、MSG_TROUTE(绕过路由表直接发送)等。



    • dest_addr:指向sockaddr结构体的指针,用于指定吸收方的地址信息。这个结构体包罗了目的主机的IP地址和端口号。

  • addrlen:指定了dest_addr所指向的地址结构的大小。这个值是通过sizeof操纵符获取的
  • 返回值:sendto()函数返回成功发送的字节数。假如返回-1,表示发生了错误,错误范例可以通过errno来检查

  • WASDATA结构体
    WASDATA是Windows Sockets API(Winsock)中的一个结构体,包罗有关Windows Sockets的实现版本和系统的配置信息。在调用WSAStartup()函数时,应用程序必须通报该结构体的指针,以便Winsock初始化并返回干系信息。


  • WSADATA结构体界说在<winsock2.h>头文件中,具体如下:
  1. typedef struct WSAData{
  2.         WORD wVersion;  //Winsock实现的版本号
  3.         WORD wHighVersion;//支持的最高版本号
  4.         char szDescription[WSADESCRIPTION_LEN+1];//描述Winsock的文本字符串
  5.         char szSystemStatus[WSASYSSTATUS_LEN+1];//当前的状态或配置
  6.         unsigned short iMaxSockets;//系统支持的最大套接字数
  7.         unsigned short iMaxUdpDg;//支持的最大UDP数据报大小
  8.         char *IpVendorInfo;//供应商特定的信息       
  9. }WSADATA,*LPWSADATA;
复制代码
七、UDP的挑战








免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

自由的羽毛

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表