memched1.0源码阅读(4)——事件的处理流程
来源:互联网 发布:cloud tv mac 编辑:程序博客网 时间:2024/06/07 06:28
在讲解事件处理之前,先讲解套接字会话的创建:
我们可以看到在套接字会话创建的过程中,调用了一个叫event_set/event_add的函数,这是libevent的接口,它就把套接字感兴趣的事件(例如读写之类的事件)以及相应的事件回调函数(event_handler)添加到libevent中,这样每当这些事件发生的时候libevent就会调用这个事件回调函数。memched做的所有工作几乎都在event_handler这个函数中。
// 新建一个套接字会话conn *conn_new(int sfd, int init_state, int event_flags) { conn *c; /* do we have a free conn structure from a previous close? */ // 从空闲的connect数组中查找一个conn if (freecurr > 0) { c = freeconns[--freecurr]; } // 如果在空闲数组中没有找到 else { /* allocate a new one */ // 申请内存 if (!(c = (conn *)malloc(sizeof(conn)))) { perror("malloc()"); return 0; } // 读写缓存的初始化 c->rbuf = c->wbuf = 0; c->ilist = 0; // 创建读写缓存 c->rbuf = (char *) malloc(DATA_BUFFER_SIZE); c->wbuf = (char *) malloc(DATA_BUFFER_SIZE); // 创建该连接对应的item数组,该数组的每一个元素都存放了一个item的指针 c->ilist = (item **) malloc(sizeof(item *)*200); if (c->rbuf == 0 || c->wbuf == 0 || c->ilist == 0) { if (c->rbuf != 0) free(c->rbuf); if (c->wbuf != 0) free(c->wbuf); if (c->ilist !=0) free(c->ilist); free(c); perror("malloc()"); return 0; } c->rsize = c->wsize = DATA_BUFFER_SIZE; c->isize = 200; // 增加系统中连接的计数 stats.conn_structs++; } c->sfd = sfd; c->state = init_state; c->rlbytes = 0; c->rbytes = c->wbytes = 0; c->wcurr = c->wbuf; c->rcurr = c->rbuf; c->icurr = c->ilist; c->ileft = 0; c->iptr = c->ibuf; c->ibytes = 0; // 下一个准备进行的操作? c->write_and_go = conn_read; c->write_and_free = 0; c->item = 0; // 设置对应的事件处理器,事件处理的回调函数是event_handler,这个函数会调用核心函数drive_machine // drive_machine中处理各种事件(accpet,read、write) event_set(&c->event, sfd, event_flags, event_handler, (void *)c); c->ev_flags = event_flags; // 把它的事件处理器添加到libevent中 if (event_add(&c->event, 0) == -1) { free(c); return 0; } stats.curr_conns++; stats.total_conns++; return c;}
事件的处理流程——核心函数drive_machine(在event_handler 中被调用) :进入一个大循环中,根据套接字会话的状态进行不同的处理:
1、如果是conn_listening状态。表示套接字会话要处理客户的连接,那么调用accpet函数,接收一个套接字,然后把套接字设置为非阻塞,然后根据这个套接字创建套接字会话(conn_new函数),并同时把该套接字感兴趣事件以及相应的事件回调函数添加到libevent中。
2、如果是conn_read状态。表示套接字准备处理读事件,那么调用try_read_command读取客户发送过来的命令,然后调用try_read_network读取客户发送过来数据块,接着调用update_event更新套接字感兴趣的事件。
3、如果是conn_nread状态。表示套接字准备接收指定长度的数据,如果还没有接收完成,那么就继续接收。如果接收完成,那么调用complete_nread函数,这个函数主要的作用就是往memched添加或者更新一个对象。
4、如果是conn_swallow状态。在某些情况下,memched已经满了(内存不足),那么对于客户端发送过来的数据进行读取然后忽略掉。
5、如果是conn_write状态。表示套接字准备向客户发送数据。
6、如果是conn_mwrite状态。同上,不过情况稍微复杂点。
7、如果是conn_closing状态。表示客户端关闭了。
// 核心函数void drive_machine(conn *c) { int exit = 0; int sfd, flags = 1; socklen_t addrlen; struct sockaddr addr; conn *newc; int res; while (!exit) { /*printf("state %d\n", c->state); */ switch(c->state) { // 如果是监听事件 case conn_listening: addrlen = sizeof(addr); // accpet一个套接字 if ((sfd = accept(c->sfd, &addr, &addrlen)) == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { perror("accept() shouldn't block"); } else { perror("accept()"); } return; } // 设置套接字为非阻塞 if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 || fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) { perror("setting O_NONBLOCK"); close(sfd); return; } // 根据套接字以及相关信息创建会话对象 newc = conn_new(sfd, conn_read, EV_READ | EV_PERSIST); if (!newc) { if(settings.verbose) fprintf(stderr, "couldn't create new connection\n"); close(sfd); return; } exit = 1; break; // 如果是读事件 case conn_read: // 读取命令 if (try_read_command(c)) { continue; } // 读取数据 if (try_read_network(c)) { continue; } /* we have no command line and no data to read from network */ // 更新事件 if (!update_event(c, EV_READ | EV_PERSIST)) { if(settings.verbose) fprintf(stderr, "Couldn't update event\n"); c->state = conn_closing; break; } exit = 1; break; case conn_nread: /* we are reading rlbytes into rcurr; */ // 读取指定数量的字节,rlbytes存放着还要读取的字节的数量 // 如果rlbytes等于0,那么表示已经读取完毕 if (c->rlbytes == 0) { // 进行处理 complete_nread(c); break; } /* first check if we have leftovers in the conn_read buffer */ // 如果读取的字节数量大于0 // 这里没看懂,为什么要把rbuf指向的数据复制复制到rcurr所指的位置(关键是它们指向同一个缓冲区)? if (c->rbytes > 0) { int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes; // 那么把读取到的数据复制到读缓存中 memcpy(c->rcurr, c->rbuf, tocopy); c->rcurr += tocopy; c->rlbytes -= tocopy; if (c->rbytes > tocopy) { memmove(c->rbuf, c->rbuf+tocopy, c->rbytes - tocopy); } c->rbytes -= tocopy; break; } /* now try reading from the socket */ // 尝试读取数据(非阻塞的读) res = read(c->sfd, c->rcurr, c->rlbytes); // 成功读取到数据 if (res > 0) { stats.bytes_read += res; c->rcurr += res; c->rlbytes -= res; break; } // 返回0,表示对方关闭了连接 if (res == 0) { /* end of stream */ c->state = conn_closing; break; } // 出错,但是出错码是EAGAIN或者EWOULDBLOCK,表示系统正在等待数据的到来 if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (!update_event(c, EV_READ | EV_PERSIST)) { if(settings.verbose) fprintf(stderr, "Couldn't update event\n"); c->state = conn_closing; break; } exit = 1; break; } /* otherwise we have a real error, on which we close the connection */ if(settings.verbose) fprintf(stderr, "Failed to read, and not due to blocking\n"); c->state = conn_closing; break; // conn_swallow状态是当内存不足并且调用add/set/replace命令的时候才会出现, 此操作主要是读取客户端发送过来的数据, 并且忽略掉 case conn_swallow: /* we are reading sbytes and throwing them away */ if (c->sbytes == 0) { c->state = conn_read; break; } /* first check if we have leftovers in the conn_read buffer */ if (c->rbytes > 0) { int tocopy = c->rbytes > c->sbytes ? c->sbytes : c->rbytes; c->sbytes -= tocopy; if (c->rbytes > tocopy) { memmove(c->rbuf, c->rbuf+tocopy, c->rbytes - tocopy); } c->rbytes -= tocopy; break; } /* now try reading from the socket */ res = read(c->sfd, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize); if (res > 0) { stats.bytes_read += res; c->sbytes -= res; break; } if (res == 0) { /* end of stream */ c->state = conn_closing; break; } if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (!update_event(c, EV_READ | EV_PERSIST)) { if(settings.verbose) fprintf(stderr, "Couldn't update event\n"); c->state = conn_closing; break; } exit = 1; break; } /* otherwise we have a real error, on which we close the connection */ if(settings.verbose) fprintf(stderr, "Failed to read, and not due to blocking\n"); c->state = conn_closing; break; // 写 case conn_write: /* we are writing wbytes bytes starting from wcurr */ if (c->wbytes == 0) { if (c->write_and_free) { free(c->write_and_free); c->write_and_free = 0; } c->state = c->write_and_go; break; } // 非阻塞写 res = write(c->sfd, c->wcurr, c->wbytes); if (res > 0) { stats.bytes_written += res; c->wcurr += res; c->wbytes -= res; break; } if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (!update_event(c, EV_WRITE | EV_PERSIST)) { if(settings.verbose) fprintf(stderr, "Couldn't update event\n"); c->state = conn_closing; break; } exit = 1; break; } /* if res==0 or res==-1 and error is not EAGAIN or EWOULDBLOCK, we have a real error, on which we close the connection */ if(settings.verbose) fprintf(stderr, "Failed to write, and not due to blocking\n"); c->state = conn_closing; break; // 发送(写)指定长度的字节 case conn_mwrite: /* * we're writing ibytes bytes from iptr. iptr alternates between * ibuf, where we build a string "VALUE...", and it->data for the * current item. When we finish a chunk, we choose the next one using * ipart, which has the following semantics: 0 - start the loop, 1 - * we finished ibuf, go to current it->data; 2 - we finished it->data, * move to the next item and build its ibuf; 3 - we finished all items, * write "END". */ if (c->ibytes > 0) { res = write(c->sfd, c->iptr, c->ibytes); if (res > 0) { stats.bytes_written += res; c->iptr += res; c->ibytes -= res; break; } if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (!update_event(c, EV_WRITE | EV_PERSIST)) { if(settings.verbose) fprintf(stderr, "Couldn't update event\n"); c->state = conn_closing; break; } exit = 1; break; } /* if res==0 or res==-1 and error is not EAGAIN or EWOULDBLOCK, we have a real error, on which we close the connection */ if(settings.verbose) fprintf(stderr, "Failed to write, and not due to blocking\n"); c->state = conn_closing; break; } else { item *it; /* we finished a chunk, decide what to do next */ switch (c->ipart) { case 1: it = *(c->icurr); c->iptr = it->data; c->ibytes = it->nbytes; c->ipart = 2; break; case 2: it = *(c->icurr); item_remove(it); if (c->ileft <= 1) { c->ipart = 3; break; } else { c->ileft--; c->icurr++; } /* FALL THROUGH */ case 0: it = *(c->icurr); sprintf(c->ibuf, "VALUE %s %u %u\r\n", it->key, it->flags, it->nbytes - 2); c->iptr = c->ibuf; c->ibytes = strlen(c->iptr); c->ipart = 1; break; case 3: out_string(c, "END"); break; } } break; case conn_closing: // 关闭套接字 conn_close(c); exit = 1; break; } } return;}
0 0
- memched1.0源码阅读(4)——事件的处理流程
- memched1.0源码阅读(3)——运行流程
- memched1.0源码阅读(1)——介绍
- memched1.0源码阅读(2)——基础数据结构
- Netty源码分析(九)—IO事件处理流程
- android源码分析——事件输入流程MotionEvent事件处理流程
- Spring源码阅读(十)—SpringMVC的处理逻辑
- Android View系统源码分析(一)——概述&触摸事件总体处理流程
- Android入门 —— activity的启动流程源码阅读
- Activity启动流程,界面绘制到事件处理的整个流程(基于Android6.0源码)(1)
- Activity启动流程,界面绘制到事件处理的整个流程(基于Android6.0源码)(2)
- Activity启动流程,界面绘制到事件处理的整个流程(基于Android6.0源码)(3)
- Nginx源码阅读(worker进程处理http请求流程)
- Volley源码阅读详解(一)---网络任务分发,处理和交付的核心流程
- Spark修炼之道(高级篇)——Spark源码阅读:第十二节 Spark SQL 处理流程分析
- Spark修炼之道(高级篇)——Spark源码阅读:第十二节 Spark SQL 处理流程分析
- Horizon 源码阅读(四)—— 调用Novaclient流程
- Horizon 源码阅读(四)—— 调用Novaclient流程
- MySQL按时间分组
- 改变已知程序练习
- C++包含c头文件&读取程序输入参数
- 孙孙啊i之项目实战(五) 封装日志
- 测试
- memched1.0源码阅读(4)——事件的处理流程
- 分类评测标准
- 自己动手实现主题搜索引擎
- Android五大布局
- 类模版
- 攻略ajax
- ns2运行nam图形界面时,出现错误_X11TransSocketINETConnect:Can't get address for Administrator解决方法
- 练习1-14 编写一个程序,打印输入中各个字符出现频度的直方图
- 计算机内存中的堆与栈