一、TCP 服务器与客户端基础
网络编程的核心之一就是建立可靠的通信通道,而 TCP 协议因其稳定性和可靠性成为首选。在 C++ 中,我们可以使用系统提供的套接字(socket)接口来实现 TCP 服务器和客户端。
1.1 TCP 服务器示例
下面是一个简单的 TCP 服务器实现,使用标准 C++ 和 POSIX socket API(技术栈:Linux C++ Socket API)。
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
int main() {
// 1. 创建 socket
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
std::cerr << "Socket creation failed" << std::endl;
return -1;
}
// 2. 绑定 IP 和端口
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
address.sin_port = htons(8080); // 监听 8080 端口
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "Bind failed" << std::endl;
return -1;
}
// 3. 监听连接
if (listen(server_fd, 5) < 0) { // 最大等待队列 5
std::cerr << "Listen failed" << std::endl;
return -1;
}
std::cout << "Server listening on port 8080..." << std::endl;
// 4. 接受客户端连接
int client_socket;
socklen_t addr_len = sizeof(address);
client_socket = accept(server_fd, (struct sockaddr*)&address, &addr_len);
if (client_socket < 0) {
std::cerr << "Accept failed" << std::endl;
return -1;
}
// 5. 读取客户端数据
char buffer[1024] = {0};
read(client_socket, buffer, 1024);
std::cout << "Received: " << buffer << std::endl;
// 6. 发送响应
const char* response = "Hello from server!";
send(client_socket, response, strlen(response), 0);
// 7. 关闭连接
close(client_socket);
close(server_fd);
return 0;
}
1.2 TCP 客户端示例
对应的 TCP 客户端代码如下:
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
int main() {
// 1. 创建 socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
std::cerr << "Socket creation failed" << std::endl;
return -1;
}
// 2. 连接服务器
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); // 连接本地服务器
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "Connection failed" << std::endl;
return -1;
}
// 3. 发送数据
const char* message = "Hello from client!";
send(sock, message, strlen(message), 0);
// 4. 接收服务器响应
char buffer[1024] = {0};
read(sock, buffer, 1024);
std::cout << "Server response: " << buffer << std::endl;
// 5. 关闭连接
close(sock);
return 0;
}
1.3 应用场景
- 即时通讯:如聊天服务器。
- 文件传输:可靠的数据传输。
- 远程控制:如 SSH、远程桌面。
1.4 优缺点
- 优点:可靠、有序、面向连接。
- 缺点:三次握手开销较大,不适合高频短连接场景。
二、IO 多路复用:select/poll/epoll
单线程处理多个客户端连接时,IO 多路复用技术可以大幅提升效率。
2.1 select 示例
#include <iostream>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <vector>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 5);
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
while (true) {
fd_set tmp_fds = read_fds;
int max_fd = server_fd;
// 等待事件
select(max_fd + 1, &tmp_fds, nullptr, nullptr, nullptr);
// 检查是否有新连接
if (FD_ISSET(server_fd, &tmp_fds)) {
int client_socket = accept(server_fd, nullptr, nullptr);
FD_SET(client_socket, &read_fds);
max_fd = std::max(max_fd, client_socket);
}
// 检查客户端数据
for (int fd = 0; fd <= max_fd; fd++) {
if (fd != server_fd && FD_ISSET(fd, &tmp_fds)) {
char buffer[1024] = {0};
int bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read <= 0) {
close(fd);
FD_CLR(fd, &read_fds);
} else {
std::cout << "Received: " << buffer << std::endl;
send(fd, "ACK", 3, 0);
}
}
}
}
close(server_fd);
return 0;
}
2.2 epoll 示例(更高效)
#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 5);
int epoll_fd = epoll_create1(0);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);
while (true) {
struct epoll_event events[10];
int num_events = epoll_wait(epoll_fd, events, 10, -1);
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == server_fd) {
int client_socket = accept(server_fd, nullptr, nullptr);
event.events = EPOLLIN;
event.data.fd = client_socket;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
} else {
char buffer[1024] = {0};
int bytes_read = read(events[i].data.fd, buffer, sizeof(buffer));
if (bytes_read <= 0) {
close(events[i].data.fd);
} else {
std::cout << "Received: " << buffer << std::endl;
send(events[i].data.fd, "ACK", 3, 0);
}
}
}
}
close(server_fd);
return 0;
}
2.3 应用场景
- 高并发服务器:如 Web 服务器(Nginx)。
- 实时数据处理:如股票行情推送。
2.4 优缺点
- select:跨平台,但效率低。
- epoll:Linux 专属,高性能。
三、异步 IO 模型
异步 IO(如 io_uring)进一步提升性能,适用于超高并发场景。
3.1 io_uring 示例
// 示例代码较复杂,通常结合 liburing 库使用,此处仅作概念介绍。
3.2 应用场景
- 高性能代理:如反向代理服务器。
- 大规模微服务:如 Kubernetes 服务网格。
3.3 优缺点
- 优点:零拷贝、极高吞吐量。
- 缺点:实现复杂,兼容性较差。
四、总结
- TCP 基础:适合可靠通信,但握手开销大。
- IO 多路复用:提升单线程并发能力,
epoll优于select。 - 异步 IO:性能极致,但实现复杂。
选择合适的模型取决于具体场景,普通应用 epoll 足够,超高并发可考虑 io_uring。
评论