当前位置: 首页 > 编程日记 > 正文

epoll使用详解

epoll的工作原理是,你如果想进行IO操作时,先向epoll查询是否可读或可写,如果处于可读或可写状态后,epoll会通过epoll_wait函数通知你,此时你再进行进一步的recv或send操作。
epoll仅仅是一个异步事件的通知机制,其本身并不作任何的IO读写操作,它只负责告诉你是不是可以读或可以写了,而具体的读写操作,还要应用层自己来作。epoll仅提供这种机制也是非常好的,它保持了事件通知与IO操作之间彼此的独立性,使得epoll的使用更加灵活。
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数据结构和函数:

所用到的数据结构
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,
epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据,例如一个client连接到服务器,服务器通过调用accept函数可以得到与这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行
a、*ptr: 通过指针ptr携带应用层数据, 当事件的通知到来时,它不仅告诉你发生了什么样的事件,还同时告诉这次事件所操作的数据是哪些
epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
所用到的函数:
1、epoll_create函数
函数声明:int epoll_create(int size)
该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围
2、epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数: epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD
改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功返回0,不成功返回-1
3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;
返回发生事件数。

在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE    1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

epoll的接口非常简单,一共就三个函数:
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。


2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。


4、关于ET、LT两种工作模式:
可以得出这样的结论:
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.


那么究竟如何来使用epoll呢?其实非常简单。
通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。

首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

epoll_wait范围之后应该是一个循环,遍利所有的事件。

几乎所有的epoll程序都使用下面的框架:

    for( ; ; )
    {
        nfds = epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd) //有新的连接
            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
            }
            else if( events[i].events&EPOLLIN ) //接收到数据,读socket
            {
                n = read(sockfd, line, MAXLINE)) < 0    //读
                ev.data.ptr = md;     //md为自定义类型,添加数据
                ev.events=EPOLLOUT|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
            }
            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
            {
                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据
                sockfd = md->fd;
                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据
                ev.data.fd=sockfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
            }
            else
            {
                //其他的处理
            }
        }
    }



下面给出一个完整的服务器端例子:


#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

using namespace std;

#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000

void setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(sock,GETFL)");
        exit(1);
    }
    opts = opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

int main(int argc, char* argv[])
{
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
    ssize_t n;
    char line[MAXLINE];
    socklen_t clilen;


    if ( 2 == argc )
    {
        if( (portnumber = atoi(argv[1])) < 0 )
        {
            fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
            return 1;
        }
    }
    else
    {
        fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
        return 1;
    }



    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件

    struct epoll_event ev,events[20];
    //生成用于处理accept的epoll专用的文件描述符

    epfd=epoll_create(256);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    //把socket设置为非阻塞方式

    //setnonblocking(listenfd);

    //设置与要处理的事件相关的文件描述符

    ev.data.fd=listenfd;
    //设置要处理的事件类型

    ev.events=EPOLLIN|EPOLLET;
    //ev.events=EPOLLIN;

    //注册epoll事件

    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    char *local_addr="127.0.0.1";
    inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);

    serveraddr.sin_port=htons(portnumber);
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    maxi = 0;
    for ( ; ; ) {
        //等待epoll事件的发生

        nfds=epoll_wait(epfd,events,20,500);
        //处理所发生的所有事件

        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。

            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                //setnonblocking(connfd);

                char *str = inet_ntoa(clientaddr.sin_addr);
                cout << "accapt a connection from " << str << endl;
                //设置用于读操作的文件描述符

                ev.data.fd=connfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //ev.events=EPOLLIN;

                //注册ev

                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。

            {
                cout << "EPOLLIN" << endl;
                if ( (sockfd = events[i].data.fd) < 0)
                    continue;
                if ( (n = read(sockfd, line, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                        close(sockfd);
                        events[i].data.fd = -1;
                    } else
                        std::cout<<"readline error"<<std::endl;
                } else if (n == 0) {
                    close(sockfd);
                    events[i].data.fd = -1;
                }
                line[n] = '/0';
                cout << "read " << line << endl;
                //设置用于写操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的写操作事件

                ev.events=EPOLLOUT|EPOLLET;
                //修改sockfd上要处理的事件为EPOLLOUT

                //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);

            }
            else if(events[i].events&EPOLLOUT) // 如果有数据发送

            {
                sockfd = events[i].data.fd;
                write(sockfd, line, n);
                //设置用于读操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //修改sockfd上要处理的事件为EPOLIN

                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
        }
    }
    return 0;
}


客户端直接连接到这个服务器就好了。。

相关文章:

软件测试(一)

最近的时间内&#xff0c;我印象最深刻的Bug是在上学期的javaweb的大作业中。 其中的要求是在工作人员的每一条记录后面添加一个修改按钮&#xff0c;要求把前一个页面的内容带入到下一个页面中&#xff0c;由于密码采用的是MD5的加密&#xff0c;所以带入到后面的页面中的内容…

网络分流器-网络分流器IP网络路由交换测试技术探讨

网络分流器1 . 与流量相关的L2-3层高级测试技术探讨戎腾网络分流器: 对于一个L2-3层网络设备&#xff0c;最基本、最重要的测试是流量转发性能测试。作为一个网络转发设备&#xff0c;首先要保证可以高速、低时延、稳定地转发流量。相关的性能测试通常是通过流量生成器&#xf…

浅谈https\ssl\数字证书

在互联网安全通信方式上&#xff0c;目前用的最多的就是https配合ssl和数字证书来保证传输和认证安全了。本文追本溯源围绕这个模式谈一谈。 名词解释 首先解释一下上面的几个名词&#xff1a; https&#xff1a;在http(超文本传输协议)基础上提出的一种安全的http协议&#xf…

input不管用 vue_Vue自定义指令实现快速读取Excel

前几天因为业务需求&#xff0c;所维护的而后台中出现了大量关于上传下载Excel的操作。因为我们的后台是基于Vue&#xff0c;并且是在 vue-element-admin 的基础上结合实际需求开发而来。vue-element-admin 中也有一些相关操作 Excel 的示例&#xff0c;都十分清晰明了&#xf…

数据结构——算法之(010)( 字符串的左旋转操作)

【申明&#xff1a;本文仅限于自我归纳总结和相互交流&#xff0c;有纰漏还望各位指出。 联系邮箱&#xff1a;Mr_chenping163.com】 题目&#xff1a;定义字符串的左旋转操作&#xff1a;把字符串前面的若干个字符移动到字符串的尾部。如把字符串abcdef左旋转2位得到字符串cde…

value_counts()

Serise类型&#xff1a; Series.value_counts(normalizeFalse, sortTrue, ascendingFalse, binsNone, dropnaTrue) 功能&#xff1a;返回包含唯一值计数的对象。结果对象将按降序排列&#xff0c;以便第一个元素是最常出现的元素。 不包括默认的NA值。 参数&#xff1a;normali…

DFA和NFA

1.历史&#xff1a;引用正则表达式萌芽于1940年代的神经生理学研究&#xff0c;由著名数学家Stephen Kleene第一个正式描述。具体地说&#xff0c;Kleene归纳了前述的神经生理学研究&#xff0c;在一篇题为《正则集代数》的论文中定义了“正则集”&#xff0c;并在其上定义了一…

adc采样的值跳动_嵌入式er必知:模数采样知多少(最全总结)

[导读] 生活环境周围信号万万千&#xff0c;对于一个嵌入式er。我们利用技术去了解世界、改变世界。而一个产品要与外界物理环境打交道&#xff0c;一个至关重要的触角就是采样真实模拟世界的信号&#xff0c;翻译成芯片可理解的数字信号&#xff0c;进而实现很多为人服务的应…

Swift泛型

泛型是为Swift编程灵活性的一种语法&#xff0c;在函数、枚举、结构体、类中都得到充分的应用&#xff0c;它的引入可以起到占位符的作用&#xff0c;当类型暂时不确定的&#xff0c;只有等到调用函数时才能确定具体类型的时候可以引入泛型。 泛型函数 定义 fun 函数名<T,S&…

02、在层级未知情况下通过递归查找子物体

1、在在层级未知情况下通过递归查找子物体 &#xff0c;这个主要是用于UI的的层级查找中 2、代码&#xff1a; 1 using System.Collections;2 using System.Collections.Generic;3 using UnityEngine;4 5 public class EnemyManager : MonoBehaviour6 {7 8 private GameOb…

CentOS装机必备-基本设置以及缺失文件

SecureCRT中注意不要使用以Ascii方式上传文件&#xff0c;只有在需要的地方才使用。主要是虚拟机中安装CentOS每次总会做一些设置&#xff0c;记录下来方便以后。 纯粹基本设置&#xff0c;比如本地SecureCRT可以连接虚拟机中的CentOS。 复杂的非基本设置见&#xff1a;Linux …

unity替换mesh测试

直接替换SkinnedMeshRender的Mesh&#xff0c;实现所谓断肢效果(不过最近发现&#xff0c;绑定多mesh似乎更好实现这样的效果。有时间准备写一篇)&#xff1a; 只要不改变两个Mesh原始文件的层级&#xff0c;就不会出现权重的错乱问题。 权重映射的测试&#xff1a;http://www.…

matlab中patch命令_matlab 放大平移图形是超出边界问题的处理

matlab提供的图形放大和平移函数zoom和pan可以通过鼠标来控制图形&#xff0c;非常方便&#xff0c;在工具条toolbar上也有对应的按钮。但是在放大或平移自己画的数据图是&#xff0c;有时会出现部分图形超出了坐标系的边界的问题&#xff0c;非常奇怪。经分析和试验&#xff0…

关于虚拟化技术软硬件兼容问题的探讨

VMware十几年前就已经出现&#xff0c;个人最早使用VMware的时间似乎是2001年或者2002年&#xff0c;当时可以在个人电脑上通过VMware虚拟多套系统&#xff0c;用于学习研究&#xff08;做实验往往会破坏系统&#xff0c;当时VMware在一些场景下还是比较流行的&#xff09;。由…

自己开发操作系统

算是《30天自制操作系统》的读书笔记吧&#xff0c;但是我觉得原书不少地方啰嗦&#xff0c;某些做法值得商榷 http://product.china-pub.com/36828381.二进制编译器 首先下载Bzl1621.lzh&#xff0c;这个可以把二进制数编辑的软件。 BZ启动画面打开img文件2.使用虚拟机加载IMG…

广东科技学院专插本c语言考卷_广东科技学院第二届红色文化节之红色影视经典配音大赛决赛...

红色经典影视配音大赛追忆革命岁月&#xff0c;传承红色文化&#xff0c;激扬青春生命&#xff0c;传承红色精神&#xff0c;为了让广大师生感受到红色影视经典的魅力和配音的乐趣&#xff0c;加深对红色文化的理解&#xff0c;提高师生们的爱国情怀。2020年12月16日19&#xf…

Social regularizations

trust-aware &#xff1a;如何从隐式信任中导出显示信任。链接预测就是搞这一方面的么&#xff1f; 和类似谱聚类的拉普拉斯矩阵结合在一起&#xff0c;没怎么看。

阿里P7架构师的成长之路

前言 系统架构师是近几年来在国内外迅速成长并发展良好的一个职位&#xff0c;它的重要性及给互联网行业所带来的影响是不言而喻的。很多程序员把成为一名优秀的架构师作为自己职业生涯奋斗的目标&#xff0c;但很多人努力却用不对地方&#xff0c;前段时间我与在阿里的P7架构师…

cad的文字嵌入线条_带你玩转CAD!

CAD画图已经成为化工人的必备技能。什么&#xff0c;这么多CAD必备技巧你居然还不知道&#xff1f;我该拿什么拯救你&#xff0c;我最最最最最最亲爱的旁友&#xff01;&#xff01;&#xff01;下面给大家整理了50个相见恨晚的CAD技巧&#xff0c;带你玩转CAD&#xff01;&…

BZOJ1315 : Ural1557Network Attack

找到一棵dfs搜索树&#xff0c;给每条非树边一个随机非0权值&#xff0c;每条树边为所有经过它的树边的权值的异或。 那么有2种情况是合法的&#xff1a; 1.一条边权值为0&#xff0c;一条边权值非0。 2.两条边异或和为0。 排序后统计即可&#xff0c;时间复杂度$O(m\log m)$。…

android原生跳转到外网

2019独角兽企业重金招聘Python工程师标准>>> super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent intent new Intent(); intent.setAction("android.intent.action.VIEW"); Uri uri Uri.parse("…

linux上使用strace查看C语言级别的php源码【一种方法】

如果你希望看到C语言级别的php代码就需要使用strace 这个默认是安装了的&#xff0c;如果没有安装可以 #yum install strace查看httpd进程 #ps auxw | grep httpd有多个&#xff0c;必须停止apache [rootlocalhost usr]# /usr/local/webserver/apache2/bin/apachectl stop启动…

iphone8p百度云认证_探秘百度数据工厂Pingo的多存储后端数据联合查询技术

作者介绍&#xff1a;张志宏&#xff0c;2013年加入百度大数据部&#xff0c;曾作为核心成员参与百度大数据平台的搭建。目前是百度数据工厂Pingo核心团队的技术负责人。Pingo是来自百度的离线大数据集成开发平台&#xff0c;使用Spark作为计算引擎&#xff0c;深度整合了资源调…

JavaScript文件中调用AngularJS内部方法或改变$scope变量

需要在其他JavaScript文件中调用AngularJS内部方法或改变$scope变量&#xff0c;同时还要保持双向数据绑定&#xff1b; 首先获取AngularJS application&#xff1a; 方法一&#xff1a;通过controller来获取app var appElement document.querySelector([ng-controllermainCon…

web类协议脚本-飞机订票系统示例

以下是LR自带的飞机订票系统的Demo&#xff0c;希望能帮助大家。 Action() {int iRand;int iTmp;char *strTmpA;char *strTmpB;char *strTmpC;char *position;if ((strTmpA (char *)malloc(100 * sizeof(char))) NULL) { lr_output_message ("Insufficient memory avail…

ACCEPT()和ACCEPT4()

ACCEPT章节&#xff1a;Linux 程序员手册 (2) 更新&#xff1a;2010-09-10到 易美翻译 翻译名字accept - 通过套接口接受一个连接 概要 #include Esys/types.h> /* 参看 “注意小节” */ #include Esys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen…

没有提示_华为手机发出莫名的提示音,打开什么也没有?原来是它们在作怪

不知道你们有没有遇到过这样的情况&#xff0c;在使用手机的过程中会出现一个非常奇怪的现象&#xff1a;当你听到手机发出声音&#xff0c;打开手机却发现什么通知也没有&#xff1f;这一度让我感到很困扰&#xff0c;本着“打破砂锅问到底”的精神&#xff0c;终于让我找到了…

近段时间佛我就偶尔无

jo建瓯市金佛玩手机欧力紧凑度 我株型紧凑我阿九倨傲四局李嘉诚转载于:https://juejin.im/post/5b8e5263e51d4538e2278c9b

php内核探索方法与资源

PHP内核探索 TIPI深入理解PHP内核 风雪之隅PHP源码分析 《php扩展开发及内核应用》 百度XLQ Gods blog codinglabsPHP内核探索&#xff1a;从SAPI接口开始PHP内核探索&#xff1a;一次请求的开始与结束PHP内核探索&#xff1a;一次请求生命周期PHP内核探索&#xff1a;单进程SA…

feign调用走不走网关全局拦截_feign服务端出异常客户端处理的方法

在使用feign进行远程方法调用时&#xff0c;如果远程服务端方法出现异常&#xff0c;客户端有时需要捕获&#xff0c;并且把异常信息返回给前端&#xff0c;而如果在开启熔断之后&#xff0c;这个异常会被消化&#xff0c;所以说&#xff0c;如果希望拿到服务端异常&#xff0c…