<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>John Doe</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>http://example.com/</id>
  <link href="http://example.com/" rel="alternate"/>
  <link href="http://example.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, John Doe</rights>
  <subtitle>这个是记录我学习过程的博客</subtitle>
  <title>GrowlingcAt's Blog</title>
  <updated>2026-05-15T10:07:04.608Z</updated>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <content>
      <![CDATA[<h3 id="reactor，及其百万并发的实现"><a href="#reactor，及其百万并发的实现" class="headerlink" title="reactor，及其百万并发的实现"></a>reactor，及其百万并发的实现</h3><h2 id="什么是reactor"><a href="#什么是reactor" class="headerlink" title="什么是reactor"></a>什么是reactor</h2><p>由select，poll，epoll对io的管理转变为对事件的管理<br>核心是不同的io事件对应不同的回调函数<br>需要的要素：<br>event<br>callback<br>rbuffer<br>rlength<br>wbuffer<br>wlength</p><p>关键在于<br>1.event与callback的匹配<br>2.每一个io与之对应的参数，每一个io是独立的</p>]]>
    </content>
    <id>http://example.com/2026/05/15/2-1-3/</id>
    <link href="http://example.com/2026/05/15/2-1-3/"/>
    <published>2026-05-15T04:55:45.000Z</published>
    <summary>
      <![CDATA[<h3 id="reactor，及其百万并发的实现"><a href="#reactor，及其百万并发的实现" class="headerlink" title="reactor，及其百万并发的实现"></a>reactor，及其百万并发的实现</h3><h2 id="什么是re]]>
    </summary>
    <title>2-1-3</title>
    <updated>2026-05-15T10:07:04.608Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <content>
      <![CDATA[<h2 id="上文拾遗"><a href="#上文拾遗" class="headerlink" title="上文拾遗"></a>上文拾遗</h2><p>1.对于断开连接的处理<br>当tcp客户端断开时我们的recv返回的值为0，因此我们的输出会不断出现<br>recv：0<br>由此我们学要对我们的client_thread方法进行修改<br>‘’’c/c++<br>    if(count==0)//disconnect<br>        {<br>            printf(“disconnected\n”);<br>            close(clientfd);<br>            break;<br>        }<br>‘’’</p><p>2.网络io<br>io就是fd（linux下面一切皆文件）<br>fd则是一个不断递增的int<br>其中0，1，2是被系统固定为stdin，stdout，stderr<br>每一个进程的io数量是有限制的<br>当一个fd被close时会被回收此时下一次分配fd时会自动分配最小的fd（close后需要等待一段时间，回收时间可以通过系统修改）</p><h2 id="io多路复用"><a href="#io多路复用" class="headerlink" title="io多路复用"></a>io多路复用</h2><p>我们的代码目前是一请求一线程<br>优点：<br>代码逻辑简单<br>缺点：<br>不利于并发（线程越多会导致内核调度的负担越重）c1k</p><h1 id="select"><a href="#select" class="headerlink" title="select"></a>select</h1><p>作用：让程序能同时监控多个文件描述符（如网络套接字），并在它们中的任何一个（或几个）准备好进行 I/O 操作时被唤醒并返回。<br>当没有io可以使用时则会阻塞在select函数</p><p>使用步骤<br>1.定义集合 fd_set read_fds;定义一个 fd_set 类型的变量<br>2.清空集合 FD_ZERO(&amp;read_fds); 必须先清空集合，移除所有残留的描述符。<br>3.添加描述符 FD_SET(fd, &amp;read_fds); 把需要监控的套接字（如 listen_fd）添加进集合。<br>4.复制集合 tmp_fds = read_fds; select() 注意！！！会修改原集合，所以必须复制一份副本传入。<br>5.调用 select select(max+1, &amp;tmp_fds, …) 程序在这里阻塞，等待事件发生。<br>6.判断就绪 FD_ISSET(fd, &amp;tmp_fds) 遍历所有关心的 fd，检查哪一个在集合中仍被置位，即代表它已就绪。<br>7.处理事件 accept(), read(), write() 调用对应的 I/O 函数处理就绪的套接字，这些函数现在会立即返回。</p><p>‘’’c/c++<br>fd_set rfds,rset;//fd的集合，本质是一个位图<br>    FD_SET(socketfd,&amp;rfds);//相当于把对应的fd的bit位置为1<br>    int maxfd=socketfd;//相当为fd集合遍历的最大值<br>    while(1)<br>    {<br>        rset=rfds;<br>        int nready=select(maxfd+1,&amp;rset,NULL,NULL,NULL);//max+1，比如说此时maxfd为9但是要遍历10次，其他参数依次为可读集合，可写集合，erro，timeout返回<br>        if(FD_ISSET(socketfd,&amp;rset))//监听的fd有连接<br>        {<br>            struct sockaddr_in clientaddr;<br>            socklen_t len=sizeof(clientaddr);<br>            int clientfd=accept(socketfd,(struct sockaddr*)&amp;clientaddr,&amp;len);<br>            printf(“accept fd:%d\n”,clientfd);<br>            FD_SET(clientfd,&amp;rfds);<br>            if(clientfd&gt;maxfd)maxfd=clientfd;//防止fd被回收后再被分配导致clientfd不是最大值<br>        }<br>        for(int clientfd=socketfd+1;clientfd&lt;=maxfd;clientfd++)//处理其他有fd，由于socketfd一定是最小的fd故可以从sockefd开始<br>        {<br>            if(FD_ISSET(clientfd,&amp;rset))<br>            {<br>                char buffer [1024]={0};<br>                int count = recv(clientfd,buffer,1024,0);<br>                if(count==0)//disconnect<br>                {<br>                    printf(“disconnected\n”);<br>                    close(clientfd);<br>                    FD_CLR(clientfd,&amp;rfds);<br>                    break;<br>                }<br>                printf(“recv %d %s\n”,count,buffer);<br>                send(clientfd,buffer,count,0);<br>                printf(“send %d %s\n”,count,buffer);<br>            }<br>        }<br>    }<br>‘’’</p><p>是fd_set?<br>本质是一个bit位集合</p><p>注意事项<br>1.每次调用需要把fd_set集合从用户空间复制到内核空间<br>2.maxfd，遍历到最大的fd</p><h1 id="poll"><a href="#poll" class="headerlink" title="poll"></a>poll</h1><p>select<br>优点：<br>实现了io多路复用<br>缺点：<br>参数多且麻烦</p><p>pollfd的参数<br>fd：要监控的fd<br>event；我们所关心要发生的事件（可以组合）<br>revent：实际发生的事件</p><p>使用步骤几乎和select一致<br>‘’’c/c++<br>struct pollfd fds[1024]={0};<br>    fds[socketfd].fd=socketfd;<br>    fds[socketfd].events=POLLIN;<br>    int maxfd=socketfd;<br>    while(1)<br>    {<br>        int nready = poll(fds,maxfd+1,-1);<br>        if(fds[socketfd].revents&amp; POLLIN)//注意是计算与&amp;不是逻辑与&amp;&amp;，因为poll返回值可以理解为掩码<br>        {<br>            struct sockaddr_in clientaddr;<br>            int len = sizeof(clientaddr);<br>            int clientfd=accept(socketfd,(struct sockaddr*)&amp;clientaddr,&amp;len);<br>            fds[clientfd].fd=clientfd;<br>            fds[clientfd].events = POLLIN;<br>            if(maxfd&lt;clientfd)maxfd=clientfd;<br>        }<br>        for(int clientfd =socketfd+1;clientfd&lt;maxfd;clientfd++)<br>        {<br>            if(fds[clientfd].revents &amp; POLLIN)<br>            {<br>                char buffer [1024]={0};<br>                int count = recv(clientfd,buffer,1024,0);<br>                if(count==0)//disconnect<br>                {<br>                    printf(“disconnected\n”);<br>                    close(clientfd);<br>                    fds[clientfd].events=-1;<br>                    fds[clientfd].revents=-1;<br>                    continue;<br>                }<br>                printf(“recv %d %s\n”,count,buffer);<br>                send(clientfd,buffer,count,0);<br>                printf(“send %d %s\n”,count,buffer);<br>            }<br>        }<br>    }</p><p>‘’’</p><p>每次使用poll函数时，依旧要把fds复制到内核空间里<br>poll的底层使用的是select，参数要比select少<br>poll的使用场景</p><h1 id="epoll（很重要！！！）"><a href="#epoll（很重要！！！）" class="headerlink" title="epoll（很重要！！！）"></a>epoll（很重要！！！）</h1><p>epoll_create()<br>epoll_ctl()<br>epoll_wait()</p><p>epoll机制<br>                        应用程序<br>                           |<br>                           | 1. epoll_create()<br>                           v<br>                    +———————+<br>                    |  epoll 实例   |<br>                    | +—————+ |<br>                    | |  红黑树   | |&lt;——-+<br>                    | +—————+ |      |<br>                    | +—————+ |      | 2. epoll_ctl(ADD)<br>                    | | 就绪队列  | |      |   添加 fd 到红黑树<br>                    | +—————+ |      |<br>                    +———————+      |<br>                           |               |<br>                           | 3. epoll_wait()|<br>                           |   阻塞等待    |<br>                           |               |<br>                    +———————+      |<br>                    |   内核回调    |      |<br>                    +———————+      |<br>                           |               |<br>     数据到达 socket1 ——+               |<br>     触发回调机制 ————————————-+<br>                           |<br>                           | 4. 返回就绪事件<br>                           v<br>                    +———————+<br>                    |  就绪事件数组 |<br>                    | [socket1,     |<br>                    |  socket3, …]|<br>                    +———————+<br>                           |<br>                           | 5. 遍历处理<br>                           v<br>                    accept() / recv() / send()</p><p>epoll所有fd的存储依靠红黑树<br>就绪的fd依靠队列</p><p>‘’’c/c++<br>    int epollfd=epoll_create(1);//这个参数只要不小于0即可，这个是为了兼容以前的版本<br>    struct epoll_event ev;<br>    ev.events =EPOLLIN;<br>    ev.data.fd=socketfd;<br>    epoll_ctl(epollfd,EPOLL_CTL_ADD,socketfd,&amp;ev);<br>    while(1)<br>    {<br>        struct epoll_event events [1024];<br>        int nready = epoll_wait(epollfd,events,1024,-1);<br>        for(int i= 0;i&lt;nready;i++)<br>        {<br>            int connfd =events[i].data.fd;<br>            if(connfd==socketfd)<br>            {<br>                struct sockaddr_in clientaddr;<br>                int len = sizeof(clientaddr);<br>                int clientfd=accept(socketfd,(struct sockaddr*)&amp;clientaddr,&amp;len);<br>                ev.events=EPOLLIN;<br>                ev.data.fd=clientfd;<br>                epoll_ctl(epollfd,EPOLL_CTL_ADD,clientfd,&amp;ev);<br>            }<br>            else if(events[i].events&amp;EPOLLIN)<br>            {<br>                char buffer [1024]={0};<br>                int count = recv(connfd,buffer,1024,0);<br>                if(count==0)//disconnect<br>                {<br>                    printf(“disconnected\n”);<br>                    close(connfd);<br>                    epoll_ctl(epollfd,EPOLL_CTL_DEL,connfd,&amp;ev);<br>                    continue;<br>                }<br>                printf(“recv %d %s\n”,count,buffer);<br>                send(connfd,buffer,count,0);<br>                printf(“send %d %s\n”,count,buffer);<br>            }<br>        }<br>    }<br>‘’’</p><h1 id="epoll的边缘触发与水平触发"><a href="#epoll的边缘触发与水平触发" class="headerlink" title="epoll的边缘触发与水平触发"></a>epoll的边缘触发与水平触发</h1><p>边缘触发：来数据的时候触发<br>水平触发：有数据的时候触发</p><p>边缘触发：适合非阻塞io，且需要搭配while让数据读完，适合数据大小不确定的情况<br>水平触发：适合阻塞io，不需要while，适合数据大小一致的情况</p><h1 id="epoll相比于select的优势"><a href="#epoll相比于select的优势" class="headerlink" title="epoll相比于select的优势"></a>epoll相比于select的优势</h1><p>1.监控数量无上限 vs 有硬性限制</p><p>select：默认最多只能监控 1024 个文件描述符（FD_SETSIZE），虽然有办法修改，但需要重新编译内核，很不方便。<br>epoll：理论上无上限，可监控的文件描述符数量只受操作系统最大打开文件数（ulimit -n）的限制</p><p>2.效率差异巨大，复杂度 O(1) vs O(n)<br>这是最核心的性能区别。<br>select：每次调用都需要线性扫描全部 fd 集合，时间复杂度为 O(n)<br>epoll：通过内核回调机制，只返回真正就绪的 fd。程序直接处理这些就绪的 fd，无需扫描全集。时间复杂度为 O(1)，海量连接下依然高效。</p><p>3.内存拷贝方式不同<br>select：每次调用 select() 都需要将整个 fd_set 从用户态拷贝到内核态，连接很多时，内存拷贝开销巨大。<br>epoll：通过 epoll_ctl() 添加 fd 时只拷贝一次并存入内核红黑树，后续 epoll_wait() 无需再拷贝，大大减少了内存复制的开销。</p><p>4.触发模式灵活<br>select：仅支持水平触发（LT），只要缓冲区有数据，就会一直通知。<br>epoll：支持边缘触发（ET），只在文件描述符状态发生变化时（如无数据→有数据）才通知一次。这迫使程序一次性处理完所有数据，结合非阻塞 I/O，能显著减少 epoll_wait 的调用次数，进一步提升性能。</p><h1 id="poll对select的优势"><a href="#poll对select的优势" class="headerlink" title="poll对select的优势"></a>poll对select的优势</h1><p>1.没有最大文件描述符数量限制 (FD_SETSIZE)<br>select：使用固定大小的 fd_set 位图，默认最多只能监控 1024 个文件描述符。虽然可以修改宏重新编译内核，但非常麻烦且容易出错。<br>poll：基于 数组 (struct pollfd *fds) 管理，理论上只受系统最大打开文件数 (ulimit -n) 的限制。</p><p>2.事件描述更清晰，解决了“输入输出混杂”问题<br>select：使用 fd_set 位图时，传入的集合会被内核修改，只保留就绪的 fd。这意味着每次调用 select 前都必须重新添加所有关心的 fd，既繁琐又容易出错。<br>poll：将事件分为 events（输入，表示程序关心的事件）和 revents（输出，表示实际发生的事件）。内核不会修改 events，每次调用前只需设置一次，调用后检查 revents 即可。代码逻辑更清晰。</p><p>3.事件类型更丰富<br>select：只提供相对有限的几类事件：read、write、except。<br>poll：支持更精细的事件，比如：</p><p>POLLRDHUP：对端关闭连接，对于检测客户端断开非常有用。<br>POLLPRI：紧急数据带外数据。</p><p>POLLERR、POLLHUP、POLLNVAL 等错误状态，可以直接通过 revents 获取，无需额外调用。</p><p>4.管理大量描述符时，性能略优<br>虽然两者时间复杂度都是 O(n)，但 poll 在内核中的实现通常比 select 略有效率。select 需要复制三个位图并扫描整个 fd_set 范围（从 0 到 max_fd），而 poll 只需复制用户态的 pollfd 数组，只遍历数组中有意义的部分，减少了遍历无效描述符的开销。</p><h1 id="poll，select对epoll的优势"><a href="#poll，select对epoll的优势" class="headerlink" title="poll，select对epoll的优势"></a>poll，select对epoll的优势</h1><p>1.无与伦比的可移植性 </p><p>2.epoll 并非在所有场景下都是最优解。当满足以下条件时，select/poll 的性能与 epoll 几乎无差别，甚至因为实现简单而略占优势：</p><p>监控的文件描述符数量很少：<br>例如只监控 几十个 以内的 fd。此时，select/poll 线性扫描的开销（O(n)）极小，epoll 建立红黑树和回调机制的复杂开销反而显得“杀鸡用牛刀”。</p><p>所有监控的 fd 都非常活跃：<br>例如一个 FTP 服务器，所有连接都在疯狂传输数据。此时，select/poll 每次调用扫描后，发现几乎所有 fd 都就绪了。epoll 的优势在于“只返回活跃的”，但如果所有 fd 都活跃，epoll 就失去了这个优势，反而还要负担红黑树管理和回调的额外开销。在这种场景下，select/poll 的性能和 epoll 几乎持平，甚至可能更好。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>select，poll，epoll解决的都是io事件触发<br>一个io的生命周期由无数个事件组成</p><p>server端，事件—&gt;返回不同的回调函数</p><p>什么是reactor：<br>由以前的io管理，改为了事件管理</p>]]>
    </content>
    <id>http://example.com/2026/05/14/2-1-2/</id>
    <link href="http://example.com/2026/05/14/2-1-2/"/>
    <published>2026-05-14T12:10:58.000Z</published>
    <summary>
      <![CDATA[<h2 id="上文拾遗"><a href="#上文拾遗" class="headerlink" title="上文拾遗"></a>上文拾遗</h2><p>1.对于断开连接的处理<br>当tcp客户端断开时我们的recv返回的值为0，因此我们的输出会不断出现<br>recv：0<]]>
    </summary>
    <title>事件驱动reactor的原理与实现</title>
    <updated>2026-05-15T11:13:35.565Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <content>
      <![CDATA[<p>实验环境：<br>ubuntu 24.04<br>vscode<br>gcc</p><h1 id="1-socket"><a href="#1-socket" class="headerlink" title="1.socket"></a>1.socket</h1><p>什么是socket？socket 本质上是：“操作系统提供的网络通信接口”，程序通过socket完成网络io复杂操作</p><p>—程序 &lt;-&gt; socket &lt;-&gt; 内核TCP/IP协议栈 &lt;-&gt; 网卡 &lt;-&gt; 网络</p><p>网络通信很复杂，因此需要设计出一套API供开发者使用。</p><h1 id="2-利用socket通信的流程"><a href="#2-利用socket通信的流程" class="headerlink" title="2.利用socket通信的流程"></a>2.利用socket通信的流程</h1><h2 id="建立连接"><a href="#建立连接" class="headerlink" title="建立连接"></a>建立连接</h2><p>-服务端<br>1.创建socket<br>‘’’c/c++</p><p>‘’’<br>2.绑定端口和ip<br>‘’’c/c++</p><p>‘’’<br>3.监听<br>‘’’c/c++</p><p>‘’’<br>可以通过<br>‘’’bash<br>netstat -anop | grep {你监听的端口}<br>‘’’<br>来查看端口是否被监听<br>4.accept（等待连接，tcp在给个函数内完成三次握手）<br>注意!!!此时服务端会创建一个socket专门用于给连接的客户端通信，此前创建的socket是用于监听连接的<br>-客户端<br>1.创建socket<br>2.connect（连接服务端）</p><h2 id="数据通信"><a href="#数据通信" class="headerlink" title="数据通信"></a>数据通信</h2><p>1.发送数据send（）<br>2.接收数据recv（）<br>3.关闭连接close（）此时发生四次挥手<br>注意！！！只有双方都close了才会彻底关闭tcp连接，一方close只代表不发送数据了但还是可以接收数据</p><h2 id="改进"><a href="#改进" class="headerlink" title="改进"></a>改进</h2><h1 id="只能面对单个"><a href="#只能面对单个" class="headerlink" title="只能面对单个"></a>只能面对单个</h1><h2 id="总结："><a href="#总结：" class="headerlink" title="总结："></a>总结：</h2><p>1.端口被绑定不能被再次绑定<br>2.执行listen之后可以被连接（数据可以被发送但是无法被处理），并且会产生新的连接状态<br>3.此时虽然连接已经被建立的但仍然需要再创建一个socket进行通信<br>4.fd才是io，与tcp连接其实不是一回事</p>]]>
    </content>
    <id>http://example.com/2026/05/13/2-1-1/</id>
    <link href="http://example.com/2026/05/13/2-1-1/"/>
    <published>2026-05-13T13:19:52.000Z</published>
    <summary>
      <![CDATA[<p>实验环境：<br>ubuntu 24.04<br>vscode<br>gcc</p>
<h1 id="1-socket"><a href="#1-socket" class="headerlink" title="1.socket"></a>1.socket</h1><p>]]>
    </summary>
    <title>简单socket编程和网路io</title>
    <updated>2026-05-15T04:55:01.623Z</updated>
  </entry>
</feed>
