先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
正文
前言
- 亲爱的各位友友们, 小杰从今天开始就自己网络服务器开发方向所学的东西,边学边写随笔,这个系列从epoll 封装 reactor 作为开始, 从0 到 1,小杰也是一样的从0 到 1,小杰之前学习网络高级IO的时候,学会了select poll 和 epoll 等支持IO多路复用的系统调用,但是都是处在很浅显的部分. 做过一部分练习,也是根据接口来封装出最简单的服务器,但是这些都没有借鉴过源码的精华
- 所写的东西几乎都是根据自己的理解来写,但是小杰发现封装性不强,而且感觉写的东西很散,没有框架性,然后小杰为了想要走服务器开发方向,于是在网上找了一家机构进行系统的学习。 之后小杰会将所学尽数写成博文随笔,跟各位博友们相互分享讨论技术。 如果您看完小杰的博文觉得有所问题,请在评论区中给出您宝贵的意见,小杰会万分的感谢, 如果您觉得系列对自己有所帮助,麻烦关注下小杰,让我们共同学习进步
reactor是什么,如何理解?
- reactor是一种设计模式, 是服务器的重要模型, 框架: 是一种事件驱动的反应堆模式, 高效的事件处理模型
- reactor 反应堆: 事件来了,执行,事件类型可能不尽相同,所以我们需要提前注册好不同的事件处理函数. 事件到来就由 epoll_wait 获取同时到来的多个事件,并且根据数据的不同类型将事件分发给事件处理机制 (事件处理器), 也就是我们提前注册的哪些接口函数
- 思考reactor模型的设计思想和思维方式: 它需要的是事件驱动,相应的事件发生,我们需要根据事件自动的调用相应的函数,所以我们需要****提前注册好处理函数的接口到reactor中, 函数是由reactor去调用的,而不是再主函数中直接进行调用的, 所以我需要使用回调函数. -------- 本质:函数指针
- reactor中的 IO 使用的是select poll epoll 这种多路复用IO, 以便提高 IO 事件的处理能力,提高IO事件处理效率,支持更高的并发
reactor所需组件流程分析
Reactor 模式是处理并发 I/O 比较常见的一种模式,用于同步 I/O,中心思想是将所有要处理的 I/O 事件注册到一个 I/O 多路复用器上,同时主线程/进程阻塞在多路复用器上; 一旦有 I/O 事件到来或是准备就绪(文件描述符或 socket 可读、写),多路复用器返回并将事先注册的相应 I/O 事件分发到对应的处理器中。
组件
- **多路复用器 :**由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用
- **事件分发器 :**将多路复用器中返回的就绪事件分到对应的处理函数中,分发给事件处理器
- 事件处理器 :处理对应的IO事件
流程
- 注册事件 和 对应的事件处理器
- 多路复用器等待事件到来
- 事件到来,激发事件分发器分发事件到对应的处理器
- 事件处理器处理事件,然后注册新的事件, (比如处理读事件,处理完成之后需要将其设置为写事件再注册,因为读取之后我们需要针对业务需求进行数据处理,之后将其send 回去响应客户端结果,所以自然需要改成写事件,也就需要从新注册)
如何将epoll的IO驱动封装成reactor事件反应堆驱动
- 其实现在流程还有运作方式已经清楚了,然后关键在于这个封装上了,IO事件fd应该如何封装,reactor又应该如何封装
- 首先事件我们需要接口API, 为了后序可以使用reactor进行调用api函数, 然后 fd 肯定也是需要的,然后为了便于数据的临时存储我们需要用户态的recvbuffer 和 sendbuffer, 然后用户态的两个缓冲区中数据所占的大小我们也需要封装进去, why? 因为 我们 send 和 recv的时候都需要传入这两个参数. 于是这样一分析大体框架出来了
- 这个回调函数我们应该如何设置? 才能符合我们后序的需求?
首先我们肯定需要传入的是 fd 作为参数, 然后我们需要传入事件类型events 还有我们需要传入sockitem 结构体指针, 因为如果是读IO事件我们需要将从客户端读取的数据写入到sockitem的处在用户空间的recvbuffer中去, 以及如果是写IO事件我们需要将sockitem的处在用户空间的sendbuffer中的数据写回客户端
然后针对返回值我们设置为int类型即可, 所以接口设计为了如下结果,
- 然后就是针对reactor的封装了
首先我们肯定需要一个epoll句柄,所以epfd肯定需要封装进去,其次我们需要一个容器存储触发的IO事件,至此我们应该设置一个 sruct epoll_event events[512];在其中存储触发的IO事件,也就是将所有需要的全局数据封装成reactor
reactor分块分析实现
accept_cb : 新连接到来事件处理器
recv_cb : 处理读事件的处理器
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5p2wMzEy,size_20,color_FFFFFF,t_70,g_se,x_16
send_cb 写事件处理器watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5p2wMzEy,size_20,color_FFFFFF,t_70,g_se,x_16
reactor整体代码以及测试结果
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>typedef struct sockaddr SA;
#define BUFFSIZE 1024
struct sockitem {
int sockfd;
//事件处理器,处理函数回调接口
int (callback)(int fd, int events, void arg);//读写函数
char recvbuffer[BUFFSIZE];
char sendbuffer[BUFFSIZE];
//读写字节数
int rlen;
int slen;
};struct reactor {
int epfd;
struct epoll_event events[512];
};//定义全局的eventloop --> 事件循环
struct reactor* eventloop = NULL;//申明这些事件处理器函数
int recv_cb(int fd, int events, void arg);
int accept_cb(int fd, int events, void arg);
int send_cb(int fd, int evnts, void* arg);int recv_cb(int fd, int events, void arg) {
struct sockitem si = (struct sockitem*)arg;
struct epoll_event ev;//后面需要//处理IO读事件
int ret = recv(fd, si->recvbuffer, BUFFSIZE, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { //
return -1;
} else {} //出错了,从监视IO事件红黑树中移除结点,避免僵尸结点 ev.events = EPOLLIN; epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev); close(fd); free(si);
} else if (ret == 0) {
//对端断开连接
printf(“fd %d disconnect\n”, fd);ev.events = EPOLLIN; epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev); //close同一断开连接,避免客户端大量的close_wait状态 close(fd); free(si);
} else {
//打印接收到的数据
printf(“recv: %s, %d Bytes\n”, si->recvbuffer, ret);
//设置sendbuffer
si->rlen = ret;
memcpy(si->sendbuffer, si->recvbuffer, si->rlen);
si->slen = si->rlen;
//注册写事件处理器
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;si->sockfd = fd; si->callback = send_cb; ev.data.ptr = si; epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
int accept_cb(int fd, int events, void* arg) {
//处理新的连接。 连接IO事件处理流程
struct sockaddr_in cli_addr;
memset(&cli_addr, 0, sizeof(cli_addr));
socklen_t cli_len = sizeof(cli_addr);int cli_fd = accept(fd, (SA*)&cli_addr, &cli_len);
if (cli_fd <= 0) return -1;char cli_ip[INET_ADDRSTRLEN] = {0}; //存储cli_ip
printf(“Recv from ip %s at port %d\n”, inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip)),
ntohs(cli_addr.sin_port));
//注册接下来的读事件处理器
struct epoll_event ev;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
//注册接下来的读事件处理器
struct epoll_event ev;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-QOSET82X-1713301342467)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!