UDP协议和Socket编程
天天开心!!!
文章目录
- 一、UDP的特点
- 二、TCP和UDP的区别
- 三、UDP的包头格式
- 四、UDP Socket编程流程
- 五、UDP代码实现
- 六、函数细节
- 七、UDP的挑战
一、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服务器代码实现
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
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 error"<<endl;
exit(EXIT_FAILURE);
}
//配置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; //IPv4
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 error"<<endl;
clsoe(sockfd);
exit(EXIT_FAILURE);
}
cout<<"Server is listening on port "<<PORT<<endl;
while(true)
{
//接收消息
int n=recvform(sockfd,buffer,BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
buffer[n]='\0';
cout<<"Client:"<<buffer<<endl;
//响应消息
const char* message="Message received!!!";
sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&client_addr, addr_len);
}
close(sockfd);
return 0;
}
- UDP客户端代码实现
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
#define PORT 8080 // 端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
#define IP "127.0.0.1" //定义服务器地址
int main()
{
int sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in server_addr;
//创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd <0)
{
cerr<<"Socket creation faliure"<<endl;
return -1;
}
//配置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
//使用inet_pton将IP地址从字符串转换为网络字节序
if(inet_pton(AF_INET, IP, &server_addr.sin_addr)<=0)
{
cerr<<"Invalid address/Address not supported"<<endl;
close(sockfd);
return -1;
}
while(true)
{
//发送消息到服务器
string message;
cout<<"Enter message: ";
getline(cin, message);//从标准输入读取用户输入
//发送消息
int send_result=sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(send_result<0)
{
cerr<<"Failed to send message"<<endl;
break;
}
//接收服务器的响应
socklen_t addrlen = sizeof(server_addr);//服务器地址长度
int n=recvform(sockfd,buffer,BUFFER_SIZE,0,(struct sockaddr*)&server_addr,&addrlen);
if(n<0)
{
cerr<<"Failed to receive response"<<endl;
break;
}
buffer[n]='\0';
cout<<"Server response: "<<buffer<<endl;//输出服务器的响应
}
close(sockfd);
return 0;
}
- Windows客户端的代码实现
- 头文件和库的引入
-
- 在Windows中需要引入winsock2.h和ws2tcpip.h,并且需要链接Ws2_32.lib库
- Winsock初始化和清理
-
- 在Windows中,使用网络功能之前需要调用WSAStartup()进行初始化,使用完毕后需要调用WSACleanup()释放资源
- Windows和Linux之间的一些差异
-
- close()在Windows上对应的是closesocket()
-
- perroor()函数在Windows上不常用,通常使用std::cerr输出错误
#include <iostream>
#include <string>
#include <winsock2.h>
#include <ws2tcpip.h>
using namespace std;
#pragma comment(lib, "Ws2_32.lib") //链接ws2_32.lib库
#define PORT 8080 // 端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
#define IP "127.0.0.1" //定义服务器地址
int main()
{
WSADATA wsaData; // 用于初始化WinSock
SOCKET sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in server_addr;
//初始化WinSock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "WSAStartup failed." << endl;
return 1;
}
//创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == INVALID_SOCKET)
{
cout << "socket failed." << endl;
WSACleanup();
return 1;
}
//配置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
// inet_pton函数用于将点分十进制的IP地址转换为网络字节序的二进制地址
if (inet_pton(AF_INET, IP, &server_addr.sin_addr) <= 0)
{
cout << "inet_pton failed." << endl;
closesocket(sockfd);
WSACleanup();
return 1;
}
while(true)
{
//发送消息到服务器
string message;
cout<<"Enter message: ";
getline(cin, message);//从标准输入读取用户输入
//发送消息
int send_result=sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(send_result<0)
{
cerr<<"Failed to send message"<<endl;
break;
}
//接收服务器的响应
socklen_t addrlen = sizeof(server_addr);//服务器地址长度
int n=recvfrom(sockfd,buffer,BUFFER_SIZE,0,(struct sockaddr*)&server_addr,&addrlen);
if(n<0)
{
cerr<<"Failed to receive response"<<endl;
break;
}
buffer[n]='\0';
cout<<"Server response: "<<buffer<<endl;//输出服务器的响应
}
closesocket(sockfd);
WSACleanup();
}
六、函数细节
- 关于recvform()函数
recvform()函数是套接字编程中用于从他套接字接收数据的一个函数,特别用于UDP协议下的数据接收。它允许程序从一个未连接的套接字(如UDP套接字)接收数据报。下面是对recvform()函数及其参数int n=recvform(sockfd,buffer,BUFFER_SIZE,0,nullptr,nullptr);的详细解释:
- 函数原型
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)头文件中。
- 函数原型
void * memset(void *ptr,int value,size_t num);
- 参数说明
-
- ptr:指向要设置的内存块的指针
-
- value:要设置的值,这个值会被转换成unsigned char类型,并且将其填充到内存块中
-
- num:要设置的字节数
- 关于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:套接字描述符,对于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>头文件中,具体如下:
typedef struct WSAData{
WORD wVersion; //Winsock实现的版本号
WORD wHighVersion;//支持的最高版本号
char szDescription[WSADESCRIPTION_LEN+1];//描述Winsock的文本字符串
char szSystemStatus[WSASYSSTATUS_LEN+1];//当前的状态或配置
unsigned short iMaxSockets;//系统支持的最大套接字数
unsigned short iMaxUdpDg;//支持的最大UDP数据报大小
char *IpVendorInfo;//供应商特定的信息
}WSADATA,*LPWSADATA;
七、UDP的挑战
原文地址:https://blog.csdn.net/hujiahangdewa/article/details/146258064
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/593487.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/593487.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!