写高并发服务器一直是C++程序员最想征服的高地之一。不过别被“高并发”这个词吓到,掌握几个核心概念,搞懂基本原理,就能轻松驾驭。我写了不少服务器程序,今天就和大家分享下经验心得。
说到服务器开发, 网络编程 是绕不开的话题。在Linux系统下,咱们主要用到 socket编程 。socket就像两个程序之间的电话线,建立连接后就能互相传数据了。
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
温馨提示:记得检查socket相关函数的返回值,处理可能的错误情况。这个习惯得养成,不然程序可能莫名其妙就崩溃了。
说到高并发, I/O模型 是个大重点。传统的 阻塞式I/O 就像排队买奶茶,前面的人不完事,后面的就只能干等。
epoll 就厉害了,它像个超级店员,谁的奶茶做好了就通知谁,不用傻等。看代码:
int epfd = epoll_create(1);
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);
while(true) {
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for(int i = 0; i < nfds; i++) {
if(events[i].data.fd == listen_sock) {
// 处理新连接
} else {
// 处理数据
}
}
}
光有epoll还不够, 多线程 才是提升性能的关键。不过开太多线程也不好,整个 线程池 最合适。
class ThreadPool {
private:
vector<thread> workers;
queue<function<void()>> tasks;
mutex queue_mutex;
condition_variable condition;
bool stop;
public:
ThreadPool(size_t threads) : stop(false) {
for(size_t i = 0; i < threads; i++)
workers.emplace_back([this] {
while(true) {
function<void()> task;
{
unique_lock<mutex> lock(queue_mutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
if(stop && tasks.empty()) return;
task = move(tasks.front());
tasks.pop();
}
task();
}
});
}
};
温馨提示:用 智能指针 管理资源,别直接用裸指针。内存泄漏这种低级错误咱可不能犯。
写高并发服务器, 内存管理 特别重要。用 对象池 预分配内存,避免频繁的内存申请和释放:
template<typename T>
class ObjectPool {
queue<T*> pool;
mutex mtx;
public:
T* acquire() {
lock_guard<mutex> lock(mtx);
if (pool.empty()) {
return new T();
}
T* obj = pool.front();
pool.pop();
return obj;
}
void release(T* obj) {
lock_guard<mutex> lock(mtx);
pool.push(obj);
}
};
还有个小妙招,用 无锁队列 减少线程竞争:
template<typename T>
class LockFreeQueue {
struct Node {
T data;
atomic<Node*> next;
};
atomic<Node*> head;
atomic<Node*> tail;
};
服务器程序一跑起来就得有 日志系统 ,不然出了问题真找不着北。我习惯用 异步日志 ,性能好很多:
class AsyncLogger {
queue<string> log_queue;
thread logger_thread;
void background_logging() {
while(true) {
string log;
{
lock_guard<mutex> lock(mtx);
if(!log_queue.empty()) {
log = move(log_queue.front());
log_queue.pop();
}
}
if(!log.empty()) {
write_to_file(log);
}
this_thread::sleep_for(chrono::milliseconds(100));
}
}
};
写服务器最怕的就是 内存泄漏 和 死锁 。用 valgrind 检查内存问题,用 gdb 调试死锁,这俩工具必须熟练掌握。
代码写完了,还得做 压力测试 。ab、wrk这些工具都挺好用,能快速发现性能瓶颈。
高并发服务器说难也难,说简单也简单。掌握这些核心知识点,动手写几个demo,慢慢就上手了。错误和bug是难免的,关键是要及时发现和解决。