32 | 自己动手写高性能HTTP服务器(一):设计和思路
32 | 自己动手写高性能HTTP服务器(一):设计和思路
讲述:冯永吉
时长10:21大小9.48M
设计需求
主要设计思路
反应堆模式设计
I/O 模型和多线程模型设计
Buffer 和数据读写
反应堆模式设计
概述
event_loop 分析
event_dispacher 分析
channel 对象分析
channel_map 对象分析
增加、删除、修改 channel event
总结
思考题
赞 7
提建议
精选留言(27)
- LDxy2019-10-21event_loop_handle_pending_add函数中, map->entries[fd] = calloc(1, sizeof(struct channel *)); map->entries[fd] = channel; 这两行都给map->entries[fd] 赋值,后一行不是覆盖上一行的赋值了么?有何用意?
作者回复: 这块是我的疏忽,应该直接赋值的,可能是开始我撰写的时候channel对象的初始化放到了event_loop_handle_pending_add函数中,后来把channel对象的初始化重构到外面,这里忘记删掉了。 已经更新文稿(待周一编辑更新)和github代码,感谢指正。
7 - 吴小智2019-10-22map_make_space() 函数里 realloc() 和 memset() 两个函数用的很巧妙啊,realloc() 用来扩容,且把旧的内容搬过去,memset() 用来给新申请的内存赋 0 值。赞,C 语言太强大了。
作者回复: 显然是看进去了。
共 2 条评论7 - 传说中的成大大2019-10-21第二道题 就是一个扩容啊 类似std的vector自动扩容 而且每次成倍的增长3
- 酸葡萄2019-12-01老师你好,问个基础的问题: epoll_dispatcher和poll_dispatcher都有,在添加,删除,更新事件时都有如下的逻辑,其中if条件中的判断怎么理解啊? if (channel1->events & EVENT_READ) { events = events | POLLRDNORM; } if (channel1->events & EVENT_WRITE) { events = events | POLLWRNORM; }展开
作者回复: 这里是位与操作,举个例子,EVENT_READ可能为二进制的00000010,如果有可读事件发生,那么在这个位上的bit值一定位1,这样位与的结果就说明这个事件发生了。之所以采用位与,而不是位或,是因为只需要关心这一种类型的事件。
共 2 条评论2 - 凌空飞起的剪刀腿2021-06-07int map_make_space(struct channel_map *map, int slot, int msize) { if (map->nentries <= slot) { int nentries = map->nentries ? map->nentries : 32; void **tmp; while (nentries <= slot) nentries <<= 1; tmp = (void **) realloc(map->entries, nentries * msize); if (tmp == NULL) return (-1); memset(&tmp[map->nentries], 0, (nentries - map->nentries) * msize); map->nentries = nentries; map->entries = tmp; } return (0); } 老师,fd不一定是连续的吧,这样会浪费内存存储空间吧?展开
作者回复: 很好的问题。 第一,你可以看看实际分配的fd,大概会是什么样子; 第二,除了这个方法,你有别的更高效的方法吗?因为从fd来查找数据,需要非常的快; 我个人的判断,这点内存不算什么,因为我在设计这个结构时,大部分数据都是指针类型的。
1 - 谁家内存泄露了2021-04-12老师好,请问您的代码中关于锁的使用,我想知道您关于每个loop都设计了一个锁,可是这几个mutex都是局部变量吧?他们的作用范围是什么样的呢?这里想不清楚,请指点一下!
作者回复: 所有的作用范围是全局的,而mutex锁看情况,有些是线程级别的,比如这里: pthread_mutex_lock(&eventLoopThread->mutex);
1 - Steiner2021-02-18如果Channel是一个管道,他连接着哪两个对象?
作者回复: 连接着client端的套接字和server端的套接字。
1 - 沉淀的梦想2019-10-22看到map_make_space里面的realloc函数,突然有个疑问,既然操作系统底层支持直接在原数组上扩充内存,为什么Java不支持直接在原数组上扩容呢,ArrayList每次扩容都要重新拷贝一份原来的数据。
作者回复: 好问题,我试着解读一下: 第一,Java有JVM实现,在Java的世界里,它的对象是统一被JVM管理的,包括GC,对象管理,基于这一层考虑,它不可能使用系统级别的对象内存管理,这两个没有办法调和,就像你举的例子,如果我们创建一个类似ReallocList对象,那么这个对象的内存管理完全不是JVM那套了; 第二,JVM是一个跨OS的实现,我不知道是否Windows也有类似realloc函数,这样就需要JVM做到跨OS的直接内存接管,这和Java的思想是不一致的。
共 3 条评论1 - 功夫熊猫2022-09-27 来自江苏老师,套接字不是用于进城通信的嘛,线程也能用?
- 菜鸡互啄2022-07-26再来看看 重温重温。感谢老师 这个教程 是我入门网络编程的领路人。我是做iOS开发的 因为一些原因转到网络这边 一开始一头懵逼 学习了老师的教程就清晰了很多。后面接触到的知识 老师的教程都能引申到 真的很赞。
- 漠博嵩2022-05-24感觉就是仿照netty框架做的
作者回复: 还真不是。
- 菜鸡2022-05-08第二个问题有点疑问。channel_map中元素的空间大小是与fd的值正相关的,而不是跟当前在线的连接数量正相关,这样做是不是有点浪费内存?比如经历了很多次连接、断开之后,fd返回的值比较大,而此时只有几个未断开的连接,那么channel_map有必要申请那么大的内存空间嘛?
作者回复: 还好吧,channel_map中的元素没几个字节,比起复杂的压缩算法,这点实在是微不足道。而且,你也没办法预测后面的连接情况,准备好一个上限比较大的fd存储空间,其实是效率比较高的。
- 群书2021-11-12用sock对通知 唤醒会不会增加逻辑线程或主线程的系统调用次数 限制了吞吐量呢
作者回复: 不会。因为唤醒是kernel干的,只不过是多了一路I/O而已。
- Steiner2021-02-18我看了下定义,channel_element就像是个链表节点,为什么不用C++来做这块呢?
作者回复: 因为是C的代码,通篇都是C语言,不需要学习C++知识。
- YUAN2020-11-05老师请问这个channel就相当于libevent中的event结构体吧?
作者回复: 嗯,差不多的意思。
- spark2020-09-25盛老师好: 为什么要在下面这个函数中lock和unlock? 不是每个线程都对应一个自己的event_loop吗? 这样的话event_loop就不是shared resource。 int event_loop_handle_pending_channel(struct event_loop *eventLoop) { //get the lock pthread_mutex_lock(&eventLoop->mutex); eventLoop->is_handle_pending = 1; struct channel_element *channelElement = eventLoop->pending_head; while (channelElement != NULL) { //save into event_map struct channel *channel = channelElement->channel; int fd = channel->fd; if (channelElement->type == 1) { event_loop_handle_pending_add(eventLoop, fd, channel); } else if (channelElement->type == 2) { event_loop_handle_pending_remove(eventLoop, fd, channel); } else if (channelElement->type == 3) { event_loop_handle_pending_update(eventLoop, fd, channel); } channelElement = channelElement->next; } eventLoop->pending_head = eventLoop->pending_tail = NULL; eventLoop->is_handle_pending = 0; //release the lock pthread_mutex_unlock(&eventLoop->mutex); return 0; }展开
作者回复: 是的呀,因为每个线程对应自己的event_loop对象,而发起event_loop_handle_pending_channel操作的线程可能是不同的线程,所以使用的是线程级别的lock,也就是event_loop里面的lock。
共 2 条评论 - 衬衫的价格是19美元2020-07-23channel_map这里map->entries是一个数组,数组的下标是fd,数组的元素是channel的地址,如果新增的fd跳变很大的话比如从3变成了100,会不会浪费了很多的空间
作者回复: 是一个问题。不过,第一,跳变不是非常巨大,这个可以从实际的程序运行可以看到fd的增长,一个合理的解释是OS也是在"智能"的分配文件描述字;第二,即使跳变,一个channel地址也就是8个字节(64bit OS),占用的内存也不是特别多。 好处fd到channel的查询,是非常非常快的。
共 2 条评论 - 胤2020-05-04问个c语言的问题,比如event_loop_handle_pending_channel这个函数,返回值是int类型,但是除了函数最后是个return 0,其他地方没有错误处理,为什么要返回0?还是就是一种习惯?
作者回复: 好问题。 按照道理说,返回值非0表示有错误信息,只是这里我没有返回而已。个人习惯哈,你可以改成无返回值。
- chs2019-12-24老师,您为了支持poll和epoll,抽象出了struct event_dispatcher结构体,然后在epoll_dispatcher.c 和poll_dispatcher.c中分别实现struct event_dispatcher中定义的接口。请问epoll_dispatcher.c中的 const struct event_dispatcher epoll_dispatcher变量 和poll_dispatcher.c中的const struct event_dispatcher poll_dispatcher变量怎样让其他文件知道其定义的。我自己写的提示上边两个变量未定义。
作者回复: 这个是通过下面的方式在头文件中声明: extern const struct event_dispatcher poll_dispatcher; extern const struct event_dispatcher epoll_dispatcher;
- Steiner2019-10-22请问channel里的fd也需要设置为非阻塞吗
作者回复: channel里的fd是在有连接建立时创建好的,当然,也是被设置为非阻塞的,这里channle对象不需要关系fd的属性。