以下是基于UDP协议的完整客户端和服务器代码。UDP与TCP的核心区别在于无连接特性,因此代码结构会更简单(无需监听和接受连接)。
UDP服务器代码(udp_server.cpp)
cpp
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
int main() {
// 1. 创建UDP套接字(SOCK_DGRAM表示UDP)
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
std::cerr << "Failed to create socket" << std::endl;
return 1;
}
// 2. 绑定IP和端口
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有IP
server_addr.sin_port = htons(9888); // UDP端口
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
std::cerr << "Failed to bind socket" << std::endl;
close(sockfd);
return 1;
}
std::cout << "UDP Server started, listening on port 9888..." << std::endl;
// 3. 接收客户端数据并回复
char buffer[1024];
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
while (true) { // 循环接收多个客户端消息
// 接收客户端数据(自动获取客户端地址)
memset(buffer, 0, sizeof(buffer));
int recv_len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client_addr, &client_addr_len);
if (recv_len == -1) {
std::cerr << "Failed to receive data" << std::endl;
continue; // 继续接收其他客户端消息
}
// 打印客户端信息和消息
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
std::cout << "Received from " << client_ip << ":"
<< ntohs(client_addr.sin_port) << ": " << buffer << std::endl;
// 回复客户端
std::string response = "Hello, client! I got your message: " + std::string(buffer);
sendto(sockfd, response.c_str(), response.size(), 0,
(struct sockaddr*)&client_addr, client_addr_len);
}
// 4. 关闭套接字(实际不会执行到这里,需按Ctrl+C终止)
close(sockfd);
return 0;
}
UDP客户端代码(udp_client.cpp)
cpp
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
using namespace std;
int main() {
// 1. 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
std::cerr << "Failed to create socket" << std::endl;
return 1;
}
// 2. 设置服务器地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9888); // 服务器端口
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 服务器IP
// 3. 发送消息并接收回复
string message = "Hello, UDP server!";
sendto(sockfd, message.c_str(), message.size(), 0,
(struct sockaddr*)&server_addr, sizeof(server_addr));
// 接收服务器回复
char buffer[1024];
struct sockaddr_in server_response_addr;
socklen_t server_addr_len = sizeof(server_response_addr);
memset(buffer, 0, sizeof(buffer));
int recv_len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&server_response_addr, &server_addr_len);
if (recv_len == -1) {
std::cerr << "Failed to receive response" << std::endl;
close(sockfd);
return 1;
}
cout << "Received from server: " << buffer << endl;
// 4. 关闭套接字
close(sockfd);
return 0;
}
核心差异对比(TCP vs UDP)
特性 | TCP | UDP |
---|---|---|
连接方式 | 面向连接(三次握手/四次挥手) | 无连接(直接发送) |
套接字类型 | SOCK_STREAM |
SOCK_DGRAM |
核心API | bind→listen→accept→connect |
bind (仅服务器需要) |
数据传输函数 | read/write 或 send/recv |
sendto/recvfrom |
可靠性 | 可靠(自动重传、按序到达) | 不可靠(可能丢包、乱序) |
通信流程 | 先建立连接,再固定双方通信 | 每次发送需指定目标地址 |
UDP编程关键点说明
-
无连接特性:
- 服务器无需
listen()
和accept()
,直接接收数据; - 客户端无需
connect()
,直接向服务器地址发送数据。
- 服务器无需
-
核心函数
sendto()
和recvfrom()
:cpp// 发送数据到指定地址 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // 从指定地址接收数据(自动获取发送方地址) ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- 每次发送/接收都需指定对方地址(
struct sockaddr*
),因此UDP可与多个不同目标通信(无固定连接)。
- 每次发送/接收都需指定对方地址(
-
UDP服务器的多客户端处理:
- 单个套接字可处理多个客户端(通过
recvfrom()
获取客户端地址,sendto()
回复特定客户端); - 无需为每个客户端创建新套接字(与TCP不同)。
- 单个套接字可处理多个客户端(通过
-
数据边界:
- UDP是"数据报"协议,发送的数据有明确边界(发送几次就接收几次,不会粘包);
- 接收缓冲区需足够大,否则会导致数据截断(如发送1000字节,但缓冲区只有512字节,则仅接收前512字节)。
编译和运行步骤
-
编译程序:
bashg++ udp_server.cpp -o udp_server g++ udp_client.cpp -o udp_client
-
启动服务器:
bash./udp_server # 输出:UDP Server started, listening on port 9888...
-
启动客户端(新终端):
bash./udp_client # 输出:Received from server: Hello, client! I got your message: Hello, UDP server!
-
服务器端会显示:
Received from 127.0.0.1:xxxx: Hello, UDP server!
(
xxxx
是客户端随机分配的端口号)
适用场景
- UDP:适合实时性要求高、允许少量丢包的场景(如视频/语音通话、游戏、实时监控)。
- TCP:适合可靠性要求高、数据完整性重要的场景(如文件传输、网页浏览、邮件)。