概述
本文接着讲述beanstalkd的事件处理机制,这其实也是大多数服务器要实现的步骤。我们且看beanstalkd是如何实现的。
源码分析
接受客户端连接
在主服务函数:void srvserve(Server *s)中初始化时,会把sock.f设置成srvaccept。
该函数主要是用来接受来自客户端的连接请求。
void
srvserve(Server *s)
{
...
s->sock.f = (Handle)srvaccept;
...
}
接受客户端连接请求的服务函数定义如下:
void
srvaccept(Server *s, int ev)
{
h_accept(s->sock.fd, ev, s);
}
h_accept函数的实现
通过对该函数的实现分析,我们可以看出一些可以优化的点。
void
h_accept(const int fd, const short which, Server *s)
{
Conn *c;
int cfd, flags, r;
socklen_t addrlen;
struct sockaddr_in6 addr;
addrlen = sizeof addr;
//接受客户端的连接请求
cfd = accept(fd, (struct sockaddr *)&addr, &addrlen);
if (cfd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) twarn("accept()");
update_conns();
return;
}
if (verbose) {
printf("accept %d\n", cfd);
}
// 完成三次握手,建立了一个新的连接,并得到一个新的fd:cfd
// 需要把该cfd设置成非阻塞
flags = fcntl(cfd, F_GETFL, 0);
if (flags < 0) {
twarn("getting flags");
close(cfd);
if (verbose) {
printf("close %d\n", cfd);
}
update_conns();
return;
}
r = fcntl(cfd, F_SETFL, flags | O_NONBLOCK); // 设置非阻塞
if (r < 0) {
twarn("setting O_NONBLOCK");
close(cfd);
if (verbose) {
printf("close %d\n", cfd);
}
update_conns();
return;
}
// 为已经连接成功的客户端,创建一个新的连接Conn结构实体,并进行初始化
// 注:这里可以优化,做缓存,把没有使用的Conn实体缓存下来,下一次进行复用,但可能浪费一些内存。
c = make_conn(cfd, STATE_WANTCOMMAND, default_tube, default_tube);
if (!c) { // 若创建Conn实体失败,关闭该连接
twarnx("make_conn() failed");
close(cfd);
if (verbose) {
printf("close %d\n", cfd);
}
update_conns();
return;
}
// 为新创建的Conn实体,填写参数
c->srv = s; // 反向指针,指向该连接所在的Server结构实体。
c->sock.x = c; // sock.x指向本Conn实体的指针
c->sock.f = (Handle)prothandle; // 设置回调函数,在该函数中处理客户端的请求
c->sock.fd = cfd; // 设置sock.fd描述符的值为cfd
r = sockwant(&c->sock, 'r'); // 把sock添加到读监控队列,接受客户端发送消息。
if (r == -1) { // 若失败,关闭和客户端的连接
twarn("sockwant");
close(cfd);
if (verbose) {
printf("close %d\n", cfd);
}
update_conns();
return;
}
update_conns();
}
事件驱动的实现
beanstalkd和redis一样使用epoll来做事件驱动框架,其实现如下:
int
sockinit(void)
{
epfd = epoll_create(1); //创建epoll文件系统句柄
if (epfd == -1) {
twarn("epoll_create");
return -1;
}
return 0;
}
int
sockwant(Socket *s, int rw)
{
int op;
struct epoll_event ev = {};
if (!s->added && !rw) {
return 0;
} else if (!s->added && rw) { // 若s->added字段为0,但rw变量为1,则需要添加
s->added = 1;
op = EPOLL_CTL_ADD;
} else if (!rw) {
op = EPOLL_CTL_DEL;
} else {
op = EPOLL_CTL_MOD;
}
switch (rw) {
case 'r': //读事件
ev.events = EPOLLIN;
break;
case 'w': // 写事件
ev.events = EPOLLOUT;
break;
}
ev.events |= EPOLLRDHUP | EPOLLPRI;
ev.data.ptr = s;
return epoll_ctl(epfd, op, s->fd, &ev);
}
int
socknext(Socket **s, int64 timeout)
{
int r;
struct epoll_event ev;
r = epoll_wait(epfd, &ev, 1, (int)(timeout/1000000));
if (r == -1 && errno != EINTR) {
twarn("epoll_wait");
exit(1);
}
if (r > 0) {
*s = ev.data.ptr;
if (ev.events & (EPOLLHUP|EPOLLRDHUP)) { // 发生错误
return 'h';
} else if (ev.events & EPOLLIN) { // 读事件
return 'r';
} else if (ev.events & EPOLLOUT) { // 写事件
return 'w';
}
}
return 0;
}
总结
通过本文的分析,应该对beanstalkd对客户端的请求处理过程有一个比较清楚的认识。基本框架有了,接下来就是对其客户端和服务器端的协议进行实现。