我想使用C ++ 11和标准linux C-Librarys创建一个多线程套接字服务器。
最简单的方法是为每个传入连接打开一个新线程,但必须有另一种方法,因为Apache没有这样做。据我所知,Apache在Thread中处理多个连接。如何实现这样的系统?
我想创建一个线程,始终监听新客户端并将此新客户端分配给线程。但是如果所有线程当前正在执行“select()”,具有无限超时并且没有任何已分配的客户端正在执行任何操作,则可能需要一段时间才能使客户端可用。
所以“select()”需要超时。将超时设置为0.5ms会很好,但我猜工作量可能会上升太多,不是吗?
有人可以告诉我你将如何实现这样一个系统,为每个线程处理多个客户端? PS:希望我的英语足以让你理解我的意思;)
答案 0 :(得分:2)
将多个请求复用到单个线程的标准方法是使用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_;
};
答案 1 :(得分:0)
首先,请看一下使用poll()
而不是select()
:当您从不同的线程中使用大量文件描述符时,它会更好。
要使当前在I / O中等待的线程无法等待,我知道有两种方法:
pthread_kill()
向线程发送合适的信号。对poll()
的调用失败,errno
设置为EINTR
。poll()
用于输入的相应文件描述符成功。例如,参见Can we obtain a file descriptor for a semaphore or condition variable?。答案 2 :(得分:0)
这不是一项微不足道的任务。
为了实现这一点,您需要维护所有已打开套接字的列表(服务器套接字和当前客户端的套接字)。然后使用 select()函数,您可以为其提供套接字列表(文件描述符)。使用正确的参数,select()将等待,直到其中一个套接字发生任何事件。
然后您必须找到导致select()退出并处理事件的套接字。对于服务器套接字,它可以是新客户端。对于客户端套接字,它可以是请求,终止通知等。
关于您在问题中所说的内容,我认为您不太了解 select() API。可以在不同的线程中进行并发 select()调用,只要它们不在相同的套接字上等待即可。然后,如果客户端没有做任何事情,它不会阻止服务器select()工作和接受新客户端。
如果您希望能够执行某些操作,即使客户端没有执行任何操作,您也只需要给select()一个超时。例如,您可能有一个计时器来向客户端发送定期信息。然后,您可以选择与您的第一个计时器相对应的超时,并在select()返回时处理过期的计时器(以及任何其他并发事件)。
我建议你仔细阅读 select 联机帮助页。