多线程服务器在一个线程中处理多个客户端
Multi-threaded Server handling multiple clients in one thread
我想使用C++11和标准的linux C-Librarys创建一个多线程套接字服务器。
最简单的方法是为每个传入连接打开一个新线程,但必须有其他方法,因为Apache没有这样做。据我所知,Apache在一个线程中处理多个连接。如何实现这样一个系统?
我想创建一个线程,始终侦听新客户端,并将这个新客户端分配给一个线程。但是,如果所有线程当前都超过了一个"select()",并且有无限的超时,并且已经分配的客户端都没有做任何事情,那么客户端可能需要一段时间才能可用。
因此,"select()"需要超时。将超时设置为0.5毫秒会很好,但我想工作量可能会增加太多,不是吗?
你们中有人能告诉我,你们将如何实现这样一个系统,为每个线程处理多个客户端吗?附言:希望我的英语足够好,你能理解我的意思;)
将多个请求复用到单个线程上的标准方法是使用Reactor模式。一个中心对象(通常称为SelectServer、SocketServer或IOService)监视运行请求的所有套接字,并在套接字准备好继续读取或写入时发出回调。
正如其他人所说,滚动自己的可能是个坏主意。处理超时、错误和跨平台兼容性(例如,用于linux的epoll、用于bsd的kqueue、用于windows的iocp)是很棘手的。对生产系统使用boost::asio或libevent。
这里有一个框架SelectServer(编译但未测试),可以给你一个想法:
#include <sys/select.h>
#include <functional>
#include <map>
class SelectServer {
public:
enum ReadyType {
READABLE = 0,
WRITABLE = 1
};
void CallWhenReady(ReadyType type, int fd, std::function<void()> closure) {
SocketHolder holder;
holder.fd = fd;
holder.type = type;
holder.closure = closure;
socket_map_[fd] = holder;
}
void Run() {
fd_set read_fds;
fd_set write_fds;
while (1) {
if (socket_map_.empty()) break;
int max_fd = -1;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
for (const auto& pr : socket_map_) {
if (pr.second.type == READABLE) {
FD_SET(pr.second.fd, &read_fds);
} else {
FD_SET(pr.second.fd, &write_fds);
}
if (pr.second.fd > max_fd) max_fd = pr.second.fd;
}
int ret_val = select(max_fd + 1, &read_fds, &write_fds, 0, 0);
if (ret_val <= 0) {
// TODO: Handle error.
break;
} else {
for (auto it = socket_map_.begin(); it != socket_map_.end(); ) {
if (FD_ISSET(it->first, &read_fds) ||
FD_ISSET(it->first, &write_fds)) {
it->second.closure();
socket_map_.erase(it++);
} else {
++it;
}
}
}
}
}
private:
struct SocketHolder {
int fd;
ReadyType type;
std::function<void()> closure;
};
std::map<int, SocketHolder> socket_map_;
};
首先,看看使用poll()
而不是select()
:当从不同线程使用大量文件描述符时,效果会更好。
为了让当前在I/O中等待的线程摆脱等待,我知道两种方法:
- 您可以使用
pthread_kill()
向线程发送合适的信号。对poll()
的调用失败,并且errno
被设置为EINTR
- 一些系统允许从线程控制设备获得文件描述符。当线程控制设备被发信号时,CCD_ 7将相应的文件描述符用于输入成功。请参阅,例如,我们可以获得信号量或条件变量的文件描述符吗
这不是一项琐碎的任务。
为了实现这一点,您需要维护所有打开的套接字(服务器套接字和到当前客户端的套接字)的列表。然后使用select()函数,可以为其提供套接字列表(文件描述符)。如果参数正确,select()将等待,直到其中一个套接字上发生任何事件。
然后,您必须找到导致select()退出并处理事件的套接字。对于服务器套接字,它可以是一个新的客户端。对于客户端套接字,可以是请求、终止通知等。
关于您在问题中所说的内容,我认为您对选择()API的理解不是很好。在不同的线程中同时调用select()是可以的,只要它们不在同一个套接字上等待。然后,如果客户端什么都不做,也不会阻止服务器select()工作并接受新客户端。
如果你想在客户端什么都不做的情况下也能做一些事情,你只需要给select()一个超时。例如,您可能有一个计时器来向客户端发送定期信息。然后,您给select一个与第一个要过期的计时器相对应的超时,并在select()返回时处理过期的计时器(以及任何其他并发事件)。
我建议您长时间阅读选择手册页。
- 将更高的优先级设置为 boost::asio 线程处理进程
- C++ 使用 2 个容器进行线程处理
- 当线程处理不同的类时,应该在哪里声明条件变量、互斥对象
- 多线程处理中的静态成员变量
- Opencv cpp 使用多线程处理同一视频的不同部分
- 对象析构函数在多线程处理时不断被调用,但该对象并未超出范围
- 使用 wxWidgets 进行多线程处理时出现奇怪的行为
- 使用共享变量进行线程处理
- 通过多线程处理确定每个字符在文件中出现的次数
- 使用多线程处理的异步请求
- 多线程处理,同时保持部分序列
- 如何在类中进行 c++ 多线程处理(将线程引用保留为成员 var)
- 用管道在C++中创建调度队列/线程处理程序:FIFO溢出
- 用多个线程处理SIGTERM的正确方法
- 使用C++的递归线程处理会使资源暂时不可用
- SDL 带变量的多线程处理 -- 无法按预期工作
- 使用多线程处理对象数组 - 无效使用 void 表达式错误
- 如何使用"priority"进行多线程处理?
- 使用简单的过程进行慢速多线程处理
- C++11 使用共享对象的多线程处理