
在写代码开始之前,我们先介绍以下UDP的概念,如果对socket套接字有疑惑的话,可以去TCP协议那篇文章看看。
概念
UDP(User Datagram Protocol 用户数据报协议),是一种无连接、不可靠、基于数据报的传输层协议。它不建立连接,直接把数据包发给对方,不管对方有没有收到。以下是几个核心特点:
- 无连接:不用 connect、listen、accept,想发就发。
- 不可靠:不保证数据一定到达、不保证顺序、不保证不丢失。
- 基于数据报:有消息边界,发几次收几次,不会粘包。
- 开销小、速度快:没有握手、没有确认、没有重传。
- 支持一对一、一对多、多对多:广播、组播都靠 UDP。
从这些特点可以看出,TCP协议与UDP协议存在着一些明显的区别,UDP相比于TCP传输更快,但丢包率高,数据发送过去如果没收到就再也无法找回了(就像直播一样,要求传输速率要快,但是如果网络卡了,我们就无法看到前面发生了什么)。
| 对比项 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠(不丢、不乱、重传) | 不可靠(可能丢包) |
| 传输方式 | 字节流 | 数据报 |
| 粘包 | 会粘包 | 不会粘包 |
| 效率 | 低(有握手、确认、重传) | 高(直接发) |
| 头部开销 | 大(20 字节) | 小(8 字节) |
| 适用场景 | 文件传输、聊天、网页 | 视频、直播、语音、DNS |
编程流程
我们先了解一下UDP需要使用的两个关键函数,一个是接收数据recvfrom(),一个是发送数据sendto()。
recvfrom(sockfd,buf,len,0,(struct sockaddr *)&src_addr,&addr_len);
//(struct sockaddr *)&src_addr为发送方的地址
sendto(sockfd,buf,len,0,(struct sockaddr *)&dest_addr,addr_len);
//(struct sockaddr *)&dest_addr为目标地址
服务端(server)
1. 定义相关变量并填写信息
struct sockaddr_in server_addr, client_addr;
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVERPORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
2. socket():创建UDP套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
需要注意的是,这里第二个参数填写的是SOCK_DGRAM,表明使用UDP协议(SOCK_STREAM为TCP协议)。
3. bind():绑定自己的IP+端口
socklen_t server_addr_len = sizeof(server_addr);
socklen_t client_addr_len = sizeof(client_addr);
int ret = bind(sockfd,(struct sockaddr *)&server_addr,server_addr_len);
4. recvfrom():阻塞等待接受客户端数据
这里是重点!由于UDP使用的是无连接方式,绑定后服务端并没有客户端的地址信息,客户端必须先发送一条信息给服务端,服务端才能获取到客户端的地址,否则无法进行网络通信!
char buffer[1024];
//必须先接收客户端消息,再发送消息给客户端
memset(buffer,0,sizeof(buffer));
ssize_t bytes_recv = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr *)&client_addr,&client_addr_len);
handle_error("recvfrom",bytes_recv);
printf("收到来自%s:%d客户端消息: %s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buffer);
if(strcmp(buffer,"exit") == 0)
{
printf("收到退出请求,服务器即将关闭...\n");
break;
}
5. sendto():给客户端回复消息
获取到了客户端的地址信息存储到client_addr后,我们可以给客户端发信息了。
printf("请输入发送的消息:");
fflush(stdout);
fgets(buffer,sizeof(buffer),stdin);
buffer[strcspn(buffer,"\n")] = 0; //去掉换行符
ssize_t bytes_sent = sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)&client_addr,client_addr_len);
handle_error("sendto",bytes_sent);
if(strcmp(buffer,"exit") == 0)
{
printf("发送退出请求,服务器即将关闭...\n");
break;
}
6. close():关闭套接字
至此,我们就完成了服务端代码的编写。
客户端(client)
1. 定义相关变量并填写目标服务端信息
struct sockaddr_in server_addr, client_addr;
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVERPORT);
server_addr.sin_addr.s_addr = inet_addr(SERVERIP);
2. socket():创建UDP套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
3. sendto():发送数据给服务端
将数据发给服务端后,服务端才可以获取到客户端的地址。
socklen_t server_addr_len = sizeof(server_addr);
socklen_t client_addr_len = sizeof(client_addr);
char buffer[1024];
//必须先发送消息给服务端,再接收服务端消息
printf("请输入发送的消息:");
fflush(stdout);
fgets(buffer,sizeof(buffer),stdin);
buffer[strcspn(buffer,"\n")] = 0; //去掉换行符
ssize_t bytes_sent = sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)&server_addr,server_addr_len);
handle_error("sendto",bytes_sent);
if(strcmp(buffer,"exit") == 0)
{
printf("发送退出请求,客户端即将关闭...\n");
break;
}
4. recvfrom():接收服务端回复
memset(buffer,0,sizeof(buffer));
ssize_t bytes_recv = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr *)&server_addr,&server_addr_len);
handle_error("recvfrom",bytes_recv);
if(strcmp(buffer,"exit") == 0)
{
printf("收到退出请求,客户端关闭...\n");
break;
}
printf("收到来自%s:%d服务端消息: %s\n",inet_ntoa(server_addr.sin_addr),ntohs(server_addr.sin_port),buffer);
5. close():关闭套接字
运行结果
先启动server程序,再启动client程序,客户端发送一条消息,服务端回复一条消息。

客户端或服务端发送一条exit信息,关闭服务端和客户端。

感谢阅读!如有侵权,请联系作者




hello
#include