5.2. TCP 与 UDP
5.2.1. TCP vs UDP
特性 |
TCP |
UDP |
|---|---|---|
连接 |
面向连接 |
无连接 |
可靠性 |
可靠,有序 |
不可靠 |
速度 |
较慢 |
较快 |
头部开销 |
20+ 字节 |
8 字节 |
用途 |
HTTP, FTP, SSH |
DNS, VoIP, 游戏 |
5.2.2. UDP 服务器
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
class UdpServer {
int sock_;
public:
UdpServer(int port) {
sock_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_ < 0) {
throw std::runtime_error("socket() failed");
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(sock_, (sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock_);
throw std::runtime_error("bind() failed");
}
}
~UdpServer() {
close(sock_);
}
void run() {
char buffer[1024];
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
while (true) {
// 接收数据
ssize_t n = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0,
(sockaddr*)&client_addr, &client_len);
if (n > 0) {
buffer[n] = '\0';
// 发送响应
sendto(sock_, buffer, n, 0,
(sockaddr*)&client_addr, client_len);
}
}
}
};
5.2.3. UDP 客户端
class UdpClient {
int sock_;
sockaddr_in server_addr_;
public:
UdpClient(const char* host, int port) {
sock_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_ < 0) {
throw std::runtime_error("socket() failed");
}
server_addr_.sin_family = AF_INET;
server_addr_.sin_port = htons(port);
inet_pton(AF_INET, host, &server_addr_.sin_addr);
}
~UdpClient() {
close(sock_);
}
void send(const std::string& msg) {
sendto(sock_, msg.c_str(), msg.size(), 0,
(sockaddr*)&server_addr_, sizeof(server_addr_));
}
std::string receive() {
char buffer[1024];
ssize_t n = recv(sock_, buffer, sizeof(buffer) - 1, 0);
if (n > 0) {
buffer[n] = '\0';
return std::string(buffer);
}
return "";
}
};
5.2.4. 多播 (Multicast)
// 加入多播组
struct ip_mreq mreq{};
inet_pton(AF_INET, "239.0.0.1", &mreq.imr_multiaddr);
mreq.imr_interface.s_addr = INADDR_ANY;
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
// 离开多播组
setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
// 设置 TTL
int ttl = 2;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
// 禁用回环
int loop = 0;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
5.2.5. 广播 (Broadcast)
// 启用广播
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
// 发送广播
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_BROADCAST; // 255.255.255.255
sendto(sock, data, len, 0, (sockaddr*)&addr, sizeof(addr));
5.2.6. TCP 粘包问题
TCP 是流协议,没有消息边界,需要自己处理分包。
5.2.6.1. 定长消息
struct Message {
uint32_t type;
char data[256];
};
// 发送
Message msg;
// 填充 msg...
send(sock, &msg, sizeof(msg), 0);
// 接收
Message msg;
size_t total = 0;
while (total < sizeof(msg)) {
ssize_t n = recv(sock, (char*)&msg + total, sizeof(msg) - total, 0);
if (n <= 0) break;
total += n;
}
5.2.6.2. 长度前缀
// 发送
void send_message(int sock, const std::string& data) {
uint32_t len = htonl(data.size());
send(sock, &len, sizeof(len), 0);
send(sock, data.c_str(), data.size(), 0);
}
// 接收
std::string recv_message(int sock) {
uint32_t len;
recv(sock, &len, sizeof(len), MSG_WAITALL);
len = ntohl(len);
std::string data(len, '\0');
recv(sock, data.data(), len, MSG_WAITALL);
return data;
}
5.2.6.3. 分隔符
// 接收直到遇到分隔符(如 \r\n)
std::string recv_line(int sock) {
std::string line;
char c;
while (recv(sock, &c, 1, 0) == 1) {
if (c == '\n') break;
if (c != '\r') line += c;
}
return line;
}
5.2.7. 高效数据传输
5.2.7.1. scatter/gather I/O
#include <sys/uio.h>
// 分散读取
char header[10];
char body[100];
iovec iov[2] = {
{header, sizeof(header)},
{body, sizeof(body)}
};
ssize_t n = readv(sock, iov, 2);
// 聚集写入
ssize_t n = writev(sock, iov, 2);
5.2.7.2. sendfile (零拷贝)
#include <sys/sendfile.h>
// 直接从文件发送到 socket
int file_fd = open("data.bin", O_RDONLY);
off_t offset = 0;
ssize_t n = sendfile(sock, file_fd, &offset, file_size);
close(file_fd);
5.2.8. 错误处理
#include <cerrno>
#include <cstring>
void handle_error(const char* msg) {
std::cerr << msg << ": " << strerror(errno) << "\n";
}
// 常见错误码
// EAGAIN / EWOULDBLOCK - 非阻塞操作无数据
// EINTR - 被信号中断
// EPIPE - 对方关闭连接
// ECONNRESET - 连接被重置
// ECONNREFUSED - 连接被拒绝
// ETIMEDOUT - 连接超时
// 忽略 SIGPIPE
signal(SIGPIPE, SIG_IGN);
// 或使用 MSG_NOSIGNAL
send(sock, data, len, MSG_NOSIGNAL);
小技巧
TCP/UDP 编程要点:
TCP 需要处理粘包
UDP 需要处理丢包和乱序
使用非阻塞 I/O + I/O 多路复用处理高并发
考虑使用现有库(Boost.Asio, libuv)