姐的美无可挑剔 發表於 2026-1-21 20:04:00

【C++】网络编程

<h2 id="前言">前言</h2>
<p>围绕Socket 的基础概念、I/O 模型,逐步实现阻塞 I/O 客户端 - 服务器、多进程 / 多线程服务端处理,以及基于 select、poll、epoll 的 I/O 多路复用服务端,侧重与如何实现。</p>
<h2 id="什么是socket">什么是socket</h2>
<p>在C++中,Socket编程是一种用于在网络上进行通信的技术,它允许不同主机上的应用程序通过TCP/IP协议族进行数据交换。Socket作为应用层与TCP/IP协议族通信的中间软件抽象层,提供了一组接口,隐藏了协议的复杂性,使得开发者只需要关注简单的接口即可实现网络通信。</p>
<h3 id="io">I/O</h3>
<p>I/O (Input/Output) - 输入输出:泛指计算机与外部世界的数据交换,socket是网络I/O;</p>
<p>进行I/O时分为两步:一是检测是否符合条件,二是进行操作。阻塞条件下不满足条件就会一直检测直到符合条件;非阻塞检测一次就会返回。</p>
<p>I/O多路复用就是只检测,找到符合条件的文件描述符,然后只处理这些文件描述符。现在常见的I/O多路复用有三种select、poll、epoll,在检测时select、poll是通过轮询进行检测,epoll是通过回调进行实现。还有就是select需要每次把关注的fd集合拷贝到内核,而epoll只需要第一次把fd拷贝到红黑树中;</p>
<p>阻塞 I/O(Blocking I/O):满足发送/接收条件才输入输出</p>
<ul>
<li>当进程执行I/O操作时,如果条件不满足,进程会被操作系统挂起(睡眠),直到条件满足才被唤醒继续执行。</li>
</ul>
<pre><code class="language-bat">应用程序             内核                网络                  
   |                |                   |                     
   | send()         |                   |                     
   |---------------&gt;|                   |                     
   |                | wait buffer space |                     
   |                |&lt;----------------&gt; |            
   |                | copy data         |                     
   |                | start send      |            
   |                |------------------&gt;|                     
   | return size    |                   |   
   |&lt;---------------|                   |                        
                                                   
                                                   
                                                   
应用程序            内核               网络
   |                |               |
   | recv()         |               |
   |---------------&gt;|               |
   |                |check recv buffer|
   |                | if null-&gt;wait   |
   |                |&lt;---------------&gt;|
   |                | else recv       |
   |                |&lt;----------------|
   |                | copy data to    |
   | return size    |user space   |
   |&lt;---------------|               |                                                
                                                   
</code></pre>
<p>非阻塞 I/O(Non-blocking I/O):</p>
<ul>
<li>当进程执行I/O操作时,如果条件不满足,系统调用立即返回一个错误,而不是让进程进入睡眠状态。进程可以继续执行其他任务,稍后再重试。</li>
</ul>
<ul>
<li>非阻塞 I/O 的关键特征
<ul>
<li>立即返回:无论I/O是否完成,立即返回控制权</li>
<li>错误码指示:用特殊错误码表示"需要等待"</li>
<li>主动轮询:需要程序主动检查I/O状态</li>
<li>并行处理:可以在等待I/O时做其他事情</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: left">模型</th>
<th style="text-align: center">特点</th>
<th style="text-align: center">适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><strong>阻塞 I/O</strong></td>
<td style="text-align: center">条件不满足时进程挂起</td>
<td style="text-align: center">简单应用,连接数少</td>
</tr>
<tr>
<td style="text-align: left"><strong>非阻塞 I/O</strong></td>
<td style="text-align: center">立即返回,需要轮询</td>
<td style="text-align: center">需要实时响应的应用</td>
</tr>
<tr>
<td style="text-align: left"><strong>I/O 多路复用</strong></td>
<td style="text-align: center">一个线程管理多个 I/O</td>
<td style="text-align: center">高并发服务器</td>
</tr>
</tbody>
</table>
<h3 id="tcp-udp">TCP-UDP</h3>
<table>
<thead>
<tr>
<th style="text-align: center">特性</th>
<th style="text-align: center">TCP</th>
<th style="text-align: center">UDP</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><strong>连接</strong></td>
<td style="text-align: center">面向连接</td>
<td style="text-align: center">无连接</td>
</tr>
<tr>
<td style="text-align: center"><strong>可靠性</strong></td>
<td style="text-align: center">可靠,自动重传</td>
<td style="text-align: center">不可靠,可能丢包</td>
</tr>
<tr>
<td style="text-align: center"><strong>顺序性</strong></td>
<td style="text-align: center">保证数据顺序</td>
<td style="text-align: center">不保证顺序</td>
</tr>
<tr>
<td style="text-align: center"><strong>流量控制</strong></td>
<td style="text-align: center">有滑动窗口</td>
<td style="text-align: center">无</td>
</tr>
<tr>
<td style="text-align: center"><strong>头部开销</strong></td>
<td style="text-align: center">20-60字节</td>
<td style="text-align: center">8字节</td>
</tr>
</tbody>
</table>
<p>连接:tcp三次握手,四次挥手</p>
<ul>
<li><strong>SYN</strong> (Synchronize):同步序号,用于建立连接</li>
<li><strong>ACK</strong> (Acknowledgment):确认标志</li>
<li><strong>FIN</strong> (Finish):结束标志,用于关闭连接</li>
<li><strong>RST</strong> (Reset):重置连接</li>
<li><strong>PSH</strong> (Push):推送数据</li>
<li><strong>URG</strong> (Urgent):紧急指针有效</li>
</ul>
<pre><code class="language-bat">连接-三次握手
Client                                    Server
|                                        |
| 1. SYN (seq=x)                         |
|---------------------------------------&gt;|
|                                        |
| 2. SYN-ACK (seq=y, ack=x+1)            |
|&lt;---------------------------------------|
|                                        |
| 3. ACK (ack=y+1)                     |
|---------------------------------------&gt;|
|                                        |
|          连接建立,开始数据传输            |
|&lt;======================================&gt;|


断开连接-四次挥手

Client                                    Server
|                                        |
| 1. FIN (seq=u)                         |
|---------------------------------------&gt;|
|                                        |
| 2. ACK (ack=u+1)                     |
|&lt;---------------------------------------|
|                                        |
|          服务器处理剩余数据               |
|&lt;======================================&gt;|
|                                        |
| 3. FIN (seq=v, ack=u+1)                |
|&lt;---------------------------------------|
|                                        |
| 4. ACK (ack=v+1)                     |
|---------------------------------------&gt;|
|          双方关闭连接                  |
</code></pre>
<h3 id="send-recv">send-recv</h3>
<p>tcp使用send-recv收发消息;</p>
<ul>
<li>发送消息-send</li>
</ul>
<pre><code class="language-C++">ssize_t send(int sockfd,            // 目标socket文件描述符
             const void *buf,       // 要发送的数据缓冲区
             size_t len,         // 要发送的数据长度
             int flags);         // 发送标志(通常为0)

// 0 - 默认行为,阻塞发送
send(sockfd, buf, len, 0);

// MSG_DONTWAIT - 非阻塞发送
send(sockfd, buf, len, MSG_DONTWAIT);

// MSG_NOSIGNAL - 不生成SIGPIPE信号
send(sockfd, buf, len, MSG_NOSIGNAL);

// MSG_OOB - 发送带外数据(紧急数据)
send(sockfd, buf, len, MSG_OOB);

// MSG_MORE - 提示有更多数据要发送(用于UDP)
send(sockfd, buf, len, MSG_MORE);

// 可以组合使用
send(sockfd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL);
</code></pre>
<ul>
<li>接收消息- recv</li>
</ul>
<pre><code class="language-C++">ssize_t recv(int sockfd,   // socket文件描述符
             void *buf,      // 接收数据缓冲区
             size_t len,   // 缓冲区长度
             int flags);   // 接收标志(通常为0)
            
// 常用的flags值:
// 0 - 默认行为,阻塞接收
recv(sockfd, buf, len, 0);

// MSG_DONTWAIT - 非阻塞接收
recv(sockfd, buf, len, MSG_DONTWAIT);

// MSG_PEEK - 查看数据但不从缓冲区移除
recv(sockfd, buf, len, MSG_PEEK);

// MSG_OOB - 接收带外数据(紧急数据)
recv(sockfd, buf, len, MSG_OOB);

// MSG_WAITALL - 等待接收所有请求的字节
recv(sockfd, buf, len, MSG_WAITALL);

// MSG_TRUNC - 即使数据被截断也返回数据包长度(原始长度)
recv(sockfd, buf, len, MSG_TRUNC);

// 可以组合使用(某些组合可能无效)
recv(sockfd, buf, len, MSG_DONTWAIT | MSG_PEEK);
</code></pre>
<h2 id="tcp实例">TCP实例</h2>
<h3 id="简单实例">简单实例</h3>
<p>实现一个简单的单线程、单个连接的阻塞I/O的客户端-服务器程序</p>
<h4 id="server">server</h4>
<pre><code class="language-c++">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer;
   
    // 创建socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
      perror("socket failed");
      return 1;
    }
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt)) &lt; 0) {
      perror("setsockopt failed");
      close(server_fd);
      return 1;
    }
    // 设置地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
   
    // 绑定
    if (bind(server_fd, (struct sockaddr*)&amp;address, sizeof(address)) &lt; 0) {
      perror("bind failed");
      return 1;
    }
   
    // 监听
    if (listen(server_fd, 3) &lt; 0) {
      perror("listen");
      return 1;
    }
   
    printf("Server started on port %d\n", PORT);
    // 接受连接
    client_fd = accept(server_fd, (struct sockaddr*)&amp;client_addr, &amp;client_len);
    if (client_fd &lt; 0) {
      perror("accept");
      return 1;
    }
    printf("Client connected\n");
    while (1) {
      // 读取数据
      int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);
      if (bytes_read &gt; 0) {
            buffer = '\0';
            printf("Received: %s\n", buffer);
            
            // 发送响应
            const char* response = "Hello from server!\n";
            write(client_fd, response, strlen(response));
      }
         else if (bytes_read == 0) {
            printf("Client disconnected\n");
            break;
      }
      else {
            perror("Read failed");
            break;
      }
      
    }
   
    // 关闭连接
    close(client_fd);
   
    return 0;
}
</code></pre>
<ul>
<li>CMakeLists.txt</li>
</ul>
<pre><code class="language-bash">cmake_minimum_required(VERSION 3.0)

project(socketServer)

# include_directories(${CMAKE_SOURCE_DIR}/CMAKE_SOURCE_DIRinclude)
include_directories(include)

add_compile_options(-g -std=c++11 -o2 -Wall)

set(CMAKE_BUILD_TYPE Debug)
# 设置可执行文件输出目录(在 build/bin/)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

# 设置库文件输出目录(在 build/lib/)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)


add_executable(server./src/server.cpp)

target_link_libraries(server PRIVATE pthread)

</code></pre>
<h4 id="client">client</h4>
<pre><code> #include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;cstring&gt;
#include &lt;cerrno&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;

#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
   int sock = 0;
   struct sockaddr_in serv_addr;
   char buffer = {0};

   // 创建socket
   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) &lt; 0) {
         std::cerr &lt;&lt; "Socket creation error: " &lt;&lt; strerror(errno) &lt;&lt; std::endl;
         return EXIT_FAILURE;
   }

   serv_addr.sin_family = AF_INET;
   serv_addr.sin_port = htons(PORT);

   // 转换IP地址
   if (inet_pton(AF_INET, SERVER_IP, &amp;serv_addr.sin_addr) &lt;= 0) {
         std::cerr &lt;&lt; "Invalid address/Address not supported: " &lt;&lt; strerror(errno) &lt;&lt; std::endl;
         return EXIT_FAILURE;
   }

   // 连接服务器
   if (connect(sock, (struct sockaddr *)&amp;serv_addr, sizeof(serv_addr)) &lt; 0) {
         std::cerr &lt;&lt; "Connection failed: " &lt;&lt; strerror(errno) &lt;&lt; std::endl;
         return EXIT_FAILURE;
   }

   std::cout &lt;&lt; "Connected to server at " &lt;&lt; SERVER_IP &lt;&lt; ":" &lt;&lt; PORT &lt;&lt; std::endl;
   std::cout &lt;&lt; "Type your messages (type 'exit' to quit):" &lt;&lt; std::endl;

   while (true) {
         // 获取用户输入
         std::string input;
                //不使用do-while 如果直接按enter,程序会向下走,send发送为空,但是服务端接收不到,就会在read中等待
                //客户端也会在read中等待
                do{
                 std::cout &lt;&lt; "&gt; ";
                std::getline(std::cin, input);
         }while(input.empty());
         
         // 检查退出命令
         if (input == "exit") {
             break;
         }

         // 发送消息到服务器
         if (send(sock, input.c_str(), input.length(), 0) &lt; 0) {
             std::cerr &lt;&lt; "Send failed: " &lt;&lt; strerror(errno) &lt;&lt; std::endl;
             break;
         }

         std::cout &lt;&lt; "Message sent to server" &lt;&lt; std::endl;

         // 接收服务器响应
         memset(buffer, 0, BUFFER_SIZE);
         int valread = read(sock, buffer, BUFFER_SIZE - 1);

         if (valread &lt; 0) {
             std::cerr &lt;&lt; "Read error: " &lt;&lt; strerror(errno) &lt;&lt; std::endl;
             break;
         } else if (valread == 0) {
             std::cout &lt;&lt; "Server closed the connection" &lt;&lt; std::endl;
             break;
         } else {
             buffer = '\0';
             std::cout &lt;&lt; "Server response:\n" &lt;&lt; buffer &lt;&lt; std::endl;
         }
   }

   close(sock);
   std::cout &lt;&lt; "Connection closed" &lt;&lt; std::endl;
   return 1;
}

</code></pre>
<h4 id="非阻塞方式">非阻塞方式</h4>
<pre><code class="language-C++">         // 获取当前文件状态标志
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags &lt; 0) {
      perror("fcntl F_GETFL");
      return -1;
    }
   
    // 添加非阻塞标志
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) &lt; 0) {
      perror("fcntl F_SETFL");
      return -1;
    }
</code></pre>
<pre><code class="language-C++">        int on = 1;// 1表示启用非阻塞
   
    if (ioctl(fd, FIONBIO, &amp;on) &lt; 0) {
      perror("ioctl FIONBIO");
      return -1;
    }
</code></pre>
<h3 id="服务端接收多个客户端">服务端接收多个客户端</h3>
<ul>
<li>多进程、多线程、select、poll、epoll
<ul>
<li><strong>少量连接</strong>:多进程/多线程</li>
<li><strong>中等并发</strong>:select/poll</li>
<li><strong>高并发</strong>:epoll</li>
</ul>
</li>
</ul>
<h4 id="多进程-fork">多进程 fork()</h4>
<pre><code class="language-C++">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;signal.h&gt;

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer;
   
    // 创建socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
      perror("socket failed");
      return 1;
    }
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt)) &lt; 0) {
      perror("setsockopt failed");
      close(server_fd);
      return 1;
    }
    // 设置地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
   
    // 绑定
    if (bind(server_fd, (struct sockaddr*)&amp;address, sizeof(address)) &lt; 0) {
      perror("bind failed");
      return 1;
    }
   
    // 监听
    if (listen(server_fd, 3) &lt; 0) {
      perror("listen");
      return 1;
    }
    signal(SIGCHLD, SIG_IGN);
    printf("Server started on port %d\n", PORT);
    // 只需要修改主循环部分
    while (true) {
      // 接受连接
      client_fd = accept(server_fd, (struct sockaddr*)&amp;client_addr, &amp;client_len);
      if (client_fd &lt; 0) {
            perror("accept faild!");
            continue;// 继续等待下一个连接
      }

      printf("Client connected\n");

      // 创建子进程处理客户端
      pid_t pid = fork();
      if (pid &lt; 0) {
            perror("fork failed");
            close(client_fd);
            continue;
      }

      if (pid == 0) {// 子进程
            close(server_fd);// 子进程不需要监听socket
            // 处理客户端连接(使用你现有的while循环)
            while (true) {
                int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);
                if (bytes_read &gt; 0) {
                  buffer = '\0';
                  printf("PID %d Received: %s\n", getpid(), buffer);
                  const char* response = "server received!\n";
                  write(client_fd, response, strlen(response));
                }
                else if (bytes_read == 0) {
                  printf("PID %d Client disconnected\n", getpid());
                  break;
                }
                else {
                  perror("Read failed");
                  break;
                }
            }
            close(client_fd);
            exit(0);// 子进程退出
      }
      else {// 父进程
            close(client_fd);// 父进程关闭客户端socket,继续监听
      }
    }
   
    // 关闭连接
    close(client_fd);
   
    return 0;
}
</code></pre>
<h4 id="多线程-stdthread">多线程 std::thread</h4>
<pre><code class="language-C++">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;thread&gt;
#include &lt;sys/socket.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;signal.h&gt;
#include &lt;iostream&gt;
#define PORT 8080
#define BUFFER_SIZE 1024

// 客户端处理线程函数
void handle_client(int client_fd) {

    char buffer;
    std::cout &lt;&lt; "Thread " &lt;&lt; std::this_thread::get_id() &lt;&lt; ": Client connected" &lt;&lt; std::endl;
    while (1) {
      memset(buffer, 0, BUFFER_SIZE);
      int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);
      if (bytes_read &gt; 0) {
            buffer = '\0';
            const char * response = "receive over!";
            std::cout&lt;&lt; "received:" &lt;&lt; buffer &lt;&lt; std::endl;
            write(client_fd, response, strlen(response));
      }
      else if (bytes_read == 0) {
            printf("Thread %lu: Client disconnected\n", pthread_self());
            break;
      }
      else {
            perror("Read failed");
            break;
      }
    }

    close(client_fd);
    return;
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_addr;
    socklen_t client_len = sizeof(client_addr);

    // 创建socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
      perror("socket failed");
      return 1;
    }
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt)) &lt; 0) {
      perror("setsockopt failed");
      close(server_fd);
      return 1;
    }
    // 设置地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定
    if (bind(server_fd, (struct sockaddr*)&amp;address, sizeof(address)) &lt; 0) {
      perror("bind failed");
      return 1;
    }

    // 监听
    if (listen(server_fd, 3) &lt; 0) {
      perror("listen");
      return 1;
    }

    signal(SIGCHLD, SIG_IGN);
    printf("Server started on port %d\n", PORT);

    while (true) {
      // 接受连接
      client_fd = accept(server_fd, (struct sockaddr*)&amp;client_addr, &amp;client_len);
      if (client_fd &lt; 0) {
            perror("accept faild!");
            continue;// 继续等待下一个连接
      }
      char client_ip;
      inet_ntop(AF_INET, &amp;client_addr.sin_addr, client_ip, sizeof(client_ip));
      int client_port = ntohs(client_addr.sin_port);

      std::cout &lt;&lt; "New client connected from " &lt;&lt; client_ip &lt;&lt; ":" &lt;&lt; client_port &lt;&lt; std::endl;

      try {
            std::thread client_thread(handle_client, client_fd);
            client_thread.detach();// 分离线程,让它在后台运行
      }
      catch (const std::exception&amp; e) {
            std::cerr &lt;&lt; "Failed to create thread: " &lt;&lt; e.what() &lt;&lt; std::endl;
            close(client_fd);
      }

    }
    close(client_fd);
    std::cout &lt;&lt; "Server stopped" &lt;&lt; std::endl;
    return 0;
}
</code></pre>
<h4 id="select多路复用">select多路复用</h4>
<p>使用select 实现,并做简单的封装。</p>
<p>虽然使用多线程和多进程都可以实现对多个客户端监听,每一个进程/线程都会在send/recv处阻塞,等待发送/接收;</p>
<p>select、poll、epoll可以对文件描述符进行监控,当有事件触发时才会去调用。</p>
<p>select实现I/O多路复用就是对fd_set(位图)的操作,如果要监控可读操作,就声明一个read的位图,把已经连接到服务器的clientfd记录下来(不如clientfd=10,就是把位图中第十个值置为1);通过select对位图进行监控,当有满足read的文件描述符时值不变,不满足的清空,所以要把连接的客户端存起来避免丢失。所以返回的fd_set就是满足可读的已连接客户端,对这些客户端进行操作就行了。</p>
<p>这里面存在的问题就是系统默认fd_set大小为1024,所以最多连接1020个客户端(0-标准输入流、1-标准输出流、2-标准错误流、3-服务端文件描述符),当客户端连接过多时会丢失或者出现错误。</p>
<p>还有就是fd_set会从用户态拷贝到内核,再拷贝出来,性能上不是很好。</p>
<hr>
<p><code>select</code> 是一种 I/O 多路复用技术,允许程序同时监视多个文件描述符(file descriptors),等待一个或多个描述符变为"就绪"状态(可读、可写或发生异常)</p>
<p>I/O多路复用就是使用一个线程管理多个io是否就绪。</p>
<hr>
<ul>
<li>
<p>先了解一下几个概念</p>
</li>
<li>
<p>fd_set是什么</p>
</li>
</ul>
<p>fd_set是一个存放文件描述符的<strong>数组</strong>,大小为1024位; 结构体实际上就是定义一个 长整型的数组 long int fds_bits;</p>
<p>1024 = int * 数组个数 * 8 位,能够储存1024个文件描述符;也就是实际能够储存1024个socket的文件描述符。</p>
<p>每个进程默认打开3个文件描述符,0-标准输入流、1-标准输出流、2-标准错误流</p>
<pre><code class="language-C++">/* The fd_set member is required to be an array of longs.*/
typedef long int __fd_mask;

/* Some versions of &lt;linux/posix_types.h&gt; define this macros.*/
#undef        __NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.*/
#define __NFDBITS        (8 * (int) sizeof (__fd_mask))
#define        __FD_ELT(d)        ((d) / __NFDBITS)
#define        __FD_MASK(d)        ((__fd_mask) (1UL &lt;&lt; ((d) % __NFDBITS)))

/* fd_set for select and pselect.*/
typedef struct
{
    /* XPG4.2 requires this member name.Otherwise avoid the name
       from the global namespace.*/
#ifdef __USE_XOPEN
    __fd_mask fds_bits;
# define __FDS_BITS(set) ((set)-&gt;fds_bits)
#else
    __fd_mask __fds_bits;
# define __FDS_BITS(set) ((set)-&gt;__fds_bits)
#endif
} fd_set;
</code></pre>
<ul>
<li>文件描述符的分配规则</li>
</ul>
<p>寻找最小的未使用的文件描述符,所以连接数较少时可以使用select进行实现。</p>
<ul>
<li>文件描述符如何映射到socket</li>
</ul>
<p>好像是:进程控制块-进程描述表-&gt;文件描述符表-&gt;文件对象;具体怎么映射的没了解</p>
<pre><code class="language-C++">int select(int maxfdp,                                                 //最大文件描述符值加1
         fd_set *readfds,                                 //指向可读文件描述符集合的指针可读时接收
         fd_set *writefds,                                 //指向可写文件描述符集合的指针可写时发送
         fd_set *exceptfds,                                 //指向异常文件描述符集合的指针   常见:带外数据到达(TCP紧急数据)
         struct timeval *timeout);                //超时时间结构体指针
</code></pre>
<hr>
<ul>
<li>实现</li>
</ul>
<pre><code class="language-C++"> #include &lt;iostream&gt;
#include &lt;sys/select.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;
#include &lt;cstring&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;

#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 10

class TCPServer {
private:
   int server_socket;
   fd_set readfds;
   int client_socket;
   int max_sd;
   
public:
   TCPServer() {
         server_socket = -1;
         // 初始化客户端socket数组
         for (int i = 0; i &lt; MAX_CLIENTS; i++) {
             client_socket = 0;
         }
   }

   void init() {
         int opt = 1;
         struct sockaddr_in address;
         
         // 创建主socket
         if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
             perror("socket failed");
             exit(EXIT_FAILURE);
         }
         
         // 设置socket选项,允许地址重用
         if (setsockopt(server_socket, SOL_
             perror("setsockopt");
             exit(EXIT_FAILURE);
         }

         // 配置地址
         address.sin_family = AF_INET;
         address.sin_addr.s_addr = INADDR_ANY;
         address.sin_port = htons(PORT);

         // 绑定socket
         if (bind(server_socket, (struct sockaddr *)&amp;address, sizeof(address)) &lt; 0) {
             perror("bind failed");
             exit(EXIT_FAILURE);
         }

         std::cout &lt;&lt; "Listener on port " &lt;&lt; PORT &lt;&lt; std::endl;

         // 开始监听
         if (listen(server_socket, MAX_CLIENTS) &lt; 0) {
             perror("listen");
             exit(EXIT_FAILURE);
         }
   }

   void run() {
         int activity, new_socket, sd;
         int max_sd;
         struct sockaddr_in address;
         socklen_t addrlen = sizeof(address);
         char buffer;

         std::cout &lt;&lt; "Waiting for connections..." &lt;&lt; std::endl;

         while (true) {
             // 清空文件描述符集合
             FD_ZERO(&amp;readfds);

             // 添加主socket到集合
             FD_SET(server_socket, &amp;readfds);
             max_sd = server_socket;

             // 添加客户端socket到集合
             for (int i = 0; i &lt; MAX_CLIENTS; ++i) {
               sd = client_socket;
               if (sd &gt; 0) {
                     FD_SET(sd, &amp;readfds);
               }
               max_sd = max_sd &gt; sd ? max_sd : sd;
             }

             // 等待活动
             struct timeval timeout;
             timeout.tv_sec = 0;// 秒
             timeout.tv_usec = 500;

             activity = select(max_sd + 1, &amp;readfds, NULL, NULL, &amp;timeout);

             if (activity &lt; 0 &amp;&amp; errno != EINTR) {
               std::cerr &lt;&lt; "select error" &lt;&lt; std::endl;
               continue;
             }else if (activity == 0) {
               // 超时处理
               continue;
             }

             // 如果有新连接
             if (FD_ISSET(server_socket, &amp;readfds)) {
               if ((new_socket = accept(server_socket,
                                          (struct sockaddr *)&amp;address,
                                          &amp;addrlen)) &lt; 0) {
                     perror("accept");
                     exit(EXIT_FAILURE);
               }

               std::cout &lt;&lt; "New connection, socket fd: " &lt;&lt; new_socket
                           &lt;&lt; ", IP: " &lt;&lt; inet_ntoa(address.sin_addr)
                           &lt;&lt; ", Port: " &lt;&lt; ntohs(address.sin_port) &lt;&lt; std::endl;

               // 添加到客户端数组
               for (int i = 0; i &lt; MAX_CLIENTS; i++) {
                     if (client_socket == 0) {
                         client_socket = new_socket;
                         std::cout &lt;&lt; "Adding to list of sockets as " &lt;&lt; i &lt;&lt;" client id: " &lt;&lt; new_sock    et &lt;&lt; std::endl;
                         break;
                     }
               }
             }

             // 检查客户端socket的IO操作
             for (int i = 0; i &lt; MAX_CLIENTS; i++) {
               sd = client_socket;
               memset(buffer,0,BUFFER_SIZE);
               if (FD_ISSET(sd, &amp;readfds)) {
                     // 检查是否断开连接
                     int valread = read(sd, buffer, BUFFER_SIZE);

                     if (valread == 0) {
                         // 客户端断开连接
                         getpeername(sd, (struct sockaddr*)&amp;address, &amp;addrlen);
                         std::cout &lt;&lt; "Host disconnected, IP: " &lt;&lt; inet_ntoa(address.sin_addr)
                                 &lt;&lt; ", Port: " &lt;&lt; ntohs(address.sin_port) &lt;&lt; std::endl;
                         close(sd);
                         client_socket = 0;
                     } else {
                         // 处理接收到的数据
                         buffer = '\0';
                         std::cout &lt;&lt; "Received: " &lt;&lt; buffer &lt;&lt; std::endl;

                         // 回声给客户端
                         send(sd, buffer, strlen(buffer), 0);
                     }
               }
             }
         }
   }

   ~TCPServer() {
         close(server_socket);
         for (int i = 0; i &lt; MAX_CLIENTS; ++i) {
             if (client_socket &gt; 0) {
               std::cout &lt;&lt; "close client :"&lt;&lt; client_socket &lt;&lt; std::endl;
               close(client_socket);
             }
         }
   }
};

int main() {
   TCPServer server;
   server.init();
   server.run();
   return 0;
}
                           
</code></pre>
<h4 id="poll多路复用">poll多路复用</h4>
<p>poll不再使用位图对文件描述符存储,而是通过pollfd进行控制,可以声明pollfd的数组(所以就没有了1024的限制)。</p>
<ul>
<li>poll改进</li>
</ul>
<p><strong>没有最大文件描述符限制</strong>:<code>poll</code> 使用数组,可以处理任意数量的文件描述符</p>
<p><strong>更高效</strong>:不需要每次调用都重新设置文件描述符集合</p>
<p><strong>更清晰的事件分离</strong>:每个文件描述符都有独立的事件输入和输出字段</p>
<hr>
<ul>
<li>poll事件常量</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">事件常量</th>
<th style="text-align: center">值(通常)</th>
<th style="text-align: center">说明</th>
<th style="text-align: center">对象</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><strong><code>POLLIN</code></strong></td>
<td style="text-align: center">0x001</td>
<td style="text-align: center">数据可读(普通数据)</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLPRI</code></strong></td>
<td style="text-align: center">0x002</td>
<td style="text-align: center">紧急数据可读(带外数据)</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLOUT</code></strong></td>
<td style="text-align: center">0x004</td>
<td style="text-align: center">数据可写,不阻塞</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLRDNORM</code></strong></td>
<td style="text-align: center">0x040</td>
<td style="text-align: center">普通数据可读</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLRDBAND</code></strong></td>
<td style="text-align: center">0x080</td>
<td style="text-align: center">优先级带数据可读</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLWRNORM</code></strong></td>
<td style="text-align: center">0x100</td>
<td style="text-align: center">普通数据可写</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLWRBAND</code></strong></td>
<td style="text-align: center">0x200</td>
<td style="text-align: center">优先级带数据可写</td>
<td style="text-align: center">events/revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLERR </code></strong></td>
<td style="text-align: center">0x008</td>
<td style="text-align: center">错误情况</td>
<td style="text-align: center">revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLHUP </code></strong></td>
<td style="text-align: center">0x010</td>
<td style="text-align: center">已挂起</td>
<td style="text-align: center">revents</td>
</tr>
<tr>
<td style="text-align: center"><strong><code>POLLNVAL</code></strong></td>
<td style="text-align: center">0x020</td>
<td style="text-align: center">无效轮询请求</td>
<td style="text-align: center">revents</td>
</tr>
</tbody>
</table>
<hr>
<pre><code class="language-C++">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;iostream&gt;
#include &lt;errno.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;poll.h&gt;

#define PORT 8080
#define MAX_CLIENTS 1000
#define BUFFER_SIZE 1024
#define TIMEOUT -1// 无限等待

class TCPServer{
private:
    int server_fd;
    char buffer;
    struct pollfd fds;// +1 给服务器套接字
    int nfds;

public:
    TCPServer(){
      server_fd = -1;
      nfds = 0;
      // 初始化所有 pollfd 结构
         for (int i = 0; i &lt; MAX_CLIENTS + 1; i++) {
             fds.fd = -1;// 表示未使用
             fds.events = 0;
             fds.revents = 0;
         }
    };

    void init(){
         // 创建监听套接字
         if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
             perror("socket failed");
             exit(EXIT_FAILURE);
         }

         // 设置 SO_REUSEADDR 选项
         int opt = 1;
         if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt))) {
             perror("setsockopt failed");
             exit(EXIT_FAILURE);
         }
         struct sockaddr_in address;
         address.sin_family = AF_INET;
         address.sin_addr.s_addr = INADDR_ANY;
         address.sin_port = htons(PORT);

         // 绑定地址
         if (bind(server_fd, (struct sockaddr *)&amp;address, sizeof(address)) &lt; 0) {
             perror("bind failed");
             exit(EXIT_FAILURE);
         }

         // 开始监听listen(server_fd, 10) 等待连接的最大个数
         if (listen(server_fd, 10) &lt; 0) {
             perror("listen failed");
             exit(EXIT_FAILURE);
         }

         printf("Server listening on port %d\n", PORT);

         // 添加服务器套接字到 pollfd 数组
         fds.fd = server_fd;
         fds.events = POLLIN;// 监视可读事件(新连接)
         nfds = 1;// 当前监视的文件描述符数量
    };

    void run(){
      int current_size = 0;
      while (1) {
         // 调用 poll,等待事件发生
         int ret = poll(fds, nfds, TIMEOUT);

            if (ret &lt; 0) {
                perror("poll failed");
                break;
            } else if (ret == 0) {
                // 超时(本例中不会发生,因为TIMEOUT=-1)
                continue;
            }
            current_size = nfds;// 保存当前大小,因为 nfds 可能在循环中改变

            // 检查所有文件描述符
            for (int i = 0; i &lt; current_size; i++) {
               if (fds.fd &lt; 0) continue;// 跳过未使用的文件描述符

               // 检查是否有事件发生
               if (fds.revents == 0) continue;

               // 检查是否是错误事件
               if (fds.revents &amp; (POLLERR | POLLHUP | POLLNVAL)) {
                         printf("Error on fd %d, closing connection\n", fds.fd);
                         close(fds.fd);
                         fds.fd = -1;
                         fds.revents = 0;
                         continue;
               }

                  // 如果是服务器套接字,处理新连接
                  if (fds.fd == server_fd) {
                         if (fds.revents &amp; POLLIN)
                              handle_client_connect();
                  }
                  // 处理客户端套接字
                  else {
                     handle_client_event(i);
                  }
         }
      }
    };


    void handle_client_connect(){
            // 接受新连接
            struct sockaddr_in address;
            socklen_t addrlen = sizeof(address);
            int new_socket = accept(server_fd, (struct sockaddr *)&amp;address, &amp;addrlen);
            if (new_socket &lt; 0) {
                  perror("accept failed");
                  return ;
            }

            printf("New connection, socket fd is %d, IP: %s, port: %d\n",new_socket,
                              inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            // 查找空位添加新客户端
            int added = 0 ;
            for (int j = 1; j &lt; MAX_CLIENTS + 1; ++j) {
                if (fds.fd == -1) {
                  fds.fd = new_socket;
                  fds.events = POLLIN | POLLRDHUP;// 监视可读和连接关闭
                  fds.revents = 0;
                  added = 1;

                  nfds = j &gt;= nfds ? j+1 : nfds;
                  break;
                  }
                }

                  if (!added) {
                      printf("Too many clients, rejecting connection\n");
                      close(new_socket);
                  }
    };


    void handle_client_event(int fd_index){
      // 检查连接是否关闭

      if (fds.revents &amp; POLLRDHUP) {
            printf("Client %d disconnected\n", fds.fd);
            close(fds.fd);
            fds.fd = -1;
            fds.revents = 0;
            return;
      }

      // 检查是否有数据可读
      if (fds.revents &amp; POLLIN) {
            memset(buffer,0,BUFFER_SIZE);
            int valread = read(fds.fd, buffer, BUFFER_SIZE);

            if (valread == 0) {
                // 客户端正常关闭连接
                printf("Client %d closed connection\n", fds.fd);
                close(fds.fd);
                fds.fd = -1;

            } else if (valread &lt; 0) {
                // 读取错误
                perror("read failed");
                close(fds.fd);
                fds.fd = -1;
            } else {
                // 回显数据
                buffer = '\0';
                std::cout &lt;&lt; "Received from client : " &lt;&lt; fds.fd &lt;&lt;" " &lt;&lt; buffer &lt;&lt; std:    :endl;
                send(fds.fd,buffer,strlen(buffer),0);
            }
          }

    }

    ~TCPServer(){
      // 关闭所有连接
      for (int i = 0; i &lt; nfds; i++) {
            if (fds.fd &gt;= 0) {
                close(fds.fd);
            }
      }
                if (server_fd &gt;= 0) {
                close(server_fd);
      }

      printf("Server shutdown complete\n");
    };
};


int main() {
    TCPServer server;
    server.init();
    server.run();

    return 0;
}
</code></pre>
<ul>
<li>事件</li>
</ul>
<pre><code class="language-C++">#define POLLIN                0x001                /* There is data to read.*/
#define POLLPRI                0x002                /* There is urgent data to read.*/
#define POLLOUT                0x004                /* Writing now will not block.*/

#if defined __USE_XOPEN || defined __USE_XOPEN2K8
/* These values are defined in XPG4.2.*/
# define POLLRDNORM        0x040                /* Normal data may be read.*/
# define POLLRDBAND        0x080                /* Priority data may be read.*/
# define POLLWRNORM        0x100                /* Writing now will not block.*/
# define POLLWRBAND        0x200                /* Priority data may be written.*/
#endif

#ifdef __USE_GNU
/* These are extensions for Linux.*/
# define POLLMSG        0x400
# define POLLREMOVE        0x1000
# define POLLRDHUP        0x2000
#endif

/* Event types always implicitly polled for.These bits need not be set in
   `events', but they will appear in `revents' to indicate the status of
   the file descriptor.*/
#define POLLERR                0x008                /* Error condition.*/
#define POLLHUP                0x010                /* Hung up.*/
#define POLLNVAL        0x020                /* Invalid polling request.*/
</code></pre>
<h5 id="selectpoll-缺点">select/poll 缺点</h5>
<ol>
<li>每次调用时要重复地从用户态读入参数。</li>
<li>每次调用时要重复地扫描文件描述符。</li>
<li>每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。</li>
</ol>
<p>poll核心时间可分为可读、可写、异常事件。</p>
<h4 id="epoll多路复用">epoll()多路复用</h4>
<ul>
<li>
<p>水平触发 :fd事件没有被处理或者没有处理全部,下次还会报告这个fd</p>
</li>
<li>
<p>边缘触发:程序在处理文件描述符的就绪事件时,必须确保将其处理完毕,否则 epoll_wait 将不会重复通知该文件描述符的就绪状态。</p>
</li>
<li>
<p>基于红黑树+链表+回调函数</p>
</li>
</ul>
<pre><code class="language-bat">用户空间                           内核空间
   │                                 │
   │ epoll_create()                  │
   ├───────────────────────────────&gt; │ 创建eventpoll结构
   │                                 │ • 初始化红黑树(rbr)
   │                                 │ • 初始化就绪链表(rdllist)
   │                                 │
   │ epoll_ctl(EPOLL_CTL_ADD, fd)    │
   ├───────────────────────────────&gt; │ • 分配epitem结构
   │                                 │ • 插入红黑树(O(log n))
   │                                 │ • 设置文件回调函数
   │                                 │
   │ epoll_wait()                  │
   ├───────────────────────────────&gt; │ • 检查rdllist是否为空
   │                                 │ • 空则阻塞进程
   │                                 │
   │ 数据到达                         │ • 网卡中断/定时器触发
   │                                 │ • 调用ep_poll_callback()
   │                                 │ • 将epitem加入rdllist
   │                                 │ • 唤醒等待进程
   │                                 │
   │ &lt;───────────────────────────────┤ • 复制events到用户空间
   │                                 │ • 清空rdllist(LT模式)
   │                                 │
   │ 处理事件                         │
   │───────────────────────────────&gt; │
   │                                 │
</code></pre>
<hr>
<pre><code class="language-C++">#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;sys/epoll.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;vector&gt;
#include &lt;cerrno&gt;
#include &lt;arpa/inet.h&gt;

#define MAX_EVENTS 64
#define BUFFER_SIZE 1024

class EpollServer {
private:
    int server_fd;
    int epoll_fd;
    struct sockaddr_in server_addr;

public:
    EpollServer(int port) {
      // 创建socketSOCK_STREAM (TCP)SOCK_DERAM (UDP)
      server_fd = socket(AF_INET, SOCK_STREAM, 0);
      if (server_fd &lt; 0) {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
      }
      
      // 设置socket选项
      int opt = 1;
      if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
                     &amp;opt, sizeof(opt))) {
            perror("setsockopt failed");
            exit(EXIT_FAILURE);
      }
      
      // 绑定地址
      memset(&amp;server_addr, 0, sizeof(server_addr));
      server_addr.sin_family = AF_INET;
      server_addr.sin_addr.s_addr = INADDR_ANY;
      server_addr.sin_port = htons(port);
      
      if (bind(server_fd, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) &lt; 0) {
            perror("bind failed");
            exit(EXIT_FAILURE);
      }
      
      // 创建epoll实例
      epoll_fd = epoll_create1(0);
      if (epoll_fd &lt; 0) {
            perror("epoll_create1 failed");
            exit(EXIT_FAILURE);
      }
      
      // 添加server_fd到epoll
      struct epoll_event event;
      event.data.fd = server_fd;
      event.events = EPOLLIN | EPOLLET;// 边缘触发模式
      epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &amp;event);
      
      // 监听socket
      if (listen(server_fd, SOMAXCONN) &lt; 0) {
            perror("listen failed");
            return;
      }
      
      std::cout &lt;&lt; "Server listening on port "
                  &lt;&lt; ntohs(server_addr.sin_port) &lt;&lt; std::endl;
      
    }
   
    ~EpollServer() {
      close(server_fd);
      close(epoll_fd);
    }
   
    // 设置非阻塞模式
    void set_nonblocking(int fd) {
      int flags = fcntl(fd, F_GETFL, 0);
      fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    }
   
    void run() {
      // 事件循环
      struct epoll_event events;
      
      while (true) {
            int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
            if (nfds &lt; 0) {
                perror("epoll_wait failed");
                break;
            }
            
            for (int i = 0; i &lt; nfds; i++) {
                if (events.data.fd == server_fd) {
                  // 处理新连接
                  handle_new_connection();
                } else {
                  // 处理客户端数据
                  if (events.events &amp; EPOLLIN) {
                        handle_client_data(events.data.fd);
                  }
                  
                  if (events.events &amp; (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                        // 处理连接关闭或错误
                        close_client(events.data.fd);
                  }
                }
            }
      }
    }
   
private:
    void handle_new_connection() {
      struct sockaddr_in client_addr;
      socklen_t addr_len = sizeof(client_addr);
      
      // 接受所有待处理的连接
      do {
            int client_fd = accept(server_fd, (struct sockaddr*)&amp;client_addr, &amp;addr_len);
            if (client_fd &lt; 0) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                  // 没有更多连接
                  break;
                } else {
                  perror("accept failed");
                  break;
                }
            }
            else{
                std::cout &lt;&lt; "New connection from "
                      &lt;&lt; inet_ntoa(client_addr.sin_addr)
                      &lt;&lt; ":" &lt;&lt; ntohs(client_addr.sin_port)
                      &lt;&lt; std::endl;
            
                // 设置非阻塞
                set_nonblocking(client_fd);
                // 添加到epoll
                struct epoll_event event;
                event.data.fd = client_fd;
                event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &amp;event) &lt; 0) {
                  perror("epoll_ctl add client failed");
                  close(client_fd);
                }else{
                     std::cout &lt;&lt; "Successfully added client fd " &lt;&lt; client_fd &lt;&lt; " to epoll" &lt;&lt; std::endl;
                }
                break;
            }
      }while (true);
    }
   
    void handle_client_data(int client_fd) {
      char buffer;
      
      // 边缘触发模式需要读取所有可用数据
      while (true) {
            memset(buffer,0,BUFFER_SIZE);
            ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);
            
            if (bytes_read &gt; 0) {
                buffer = '\0';
                std::cout &lt;&lt; "Received from client " &lt;&lt; client_fd &lt;&lt; ": " &lt;&lt; buffer;
                // 回显数据
                send(client_fd, buffer, bytes_read, 0);
               
                // 如果读取的数据少于缓冲区大小,说明数据已读完
                if (bytes_read &lt; BUFFER_SIZE - 1) {
                  break;
                }
            } else if (bytes_read == 0) {
                // 客户端关闭连接
                close_client(client_fd);
                break;
            } else {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                  // 数据已读完
                  break;
                } else {
                  perror("read failed");
                  close_client(client_fd);
                  break;
                }
            }
      }
    }
   
    void close_client(int client_fd) {
      std::cout &lt;&lt; "Client " &lt;&lt; client_fd &lt;&lt; " disconnected" &lt;&lt; std::endl;
      epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
      close(client_fd);
    }
};

int main() {
    EpollServer server(8080);
    server.run();
    return 0;
}
</code></pre>
<h2 id="udp实例">UDP实例</h2>
<p>udp使用sendto-recvfrom收发消息;</p>
<ul>
<li>server</li>
</ul>
<pre><code class="language-C++">#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;

#define BUFFER_SIZE 1024

class UDPServer {
private:
    int server_fd;
    struct sockaddr_in server_addr;

public:
    UDPServer(int port) {
      // 创建UDP socket
      server_fd = socket(AF_INET, SOCK_DGRAM, 0);
      if (server_fd &lt; 0) {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
      }
      
      // 设置地址重用
      int opt = 1;
      if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt))) {
            perror("setsockopt failed");
            close(server_fd);
            exit(EXIT_FAILURE);
      }
      
      // 绑定地址
      memset(&amp;server_addr, 0, sizeof(server_addr));
      server_addr.sin_family = AF_INET;
      server_addr.sin_addr.s_addr = INADDR_ANY;// 监听所有接口
      server_addr.sin_port = htons(port);
      
      if (bind(server_fd, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) &lt; 0) {
            perror("bind failed");
            close(server_fd);
            exit(EXIT_FAILURE);
      }
      
      std::cout &lt;&lt; "UDP Server listening on port " &lt;&lt; port &lt;&lt; std::endl;
    }
   
    ~UDPServer() {       
      if (server_fd &gt;= 0) {
            close(server_fd);
      }
    }
   
    void run() {
      char buffer;
      struct sockaddr_in client_addr;
      socklen_t addr_len = sizeof(client_addr);
      
      while (true) {
            // 清空缓冲区
            memset(buffer, 0, BUFFER_SIZE);
            memset(&amp;client_addr, 0, sizeof(client_addr));
            
            // 接收数据
            ssize_t recv_len = recvfrom(server_fd, buffer, BUFFER_SIZE - 1, 0,
                                       (struct sockaddr*)&amp;client_addr, &amp;addr_len);
            
            if (recv_len &lt; 0) {
                perror("recvfrom failed");
                continue;
            }
            
            // 获取客户端信息
            char client_ip;
            inet_ntop(AF_INET, &amp;client_addr.sin_addr,
                     client_ip, INET_ADDRSTRLEN);
            
            std::cout &lt;&lt; "Received from " &lt;&lt; client_ip &lt;&lt; ":"
                      &lt;&lt; ntohs(client_addr.sin_port)
                      &lt;&lt; " (" &lt;&lt; recv_len &lt;&lt; " bytes): "
                      &lt;&lt; buffer &lt;&lt; std::endl;
            
            // 准备回显消息
            std::string echo_msg = "Echo: ";
            echo_msg += buffer;
            
            // 发送回显(发送到刚才接收的地址)
            sendto(server_fd, echo_msg.c_str(), echo_msg.length(), 0,
                  (struct sockaddr*)&amp;client_addr, addr_len);
      }
    }
   
    void run_with_select() {
      fd_set read_fds;
      struct timeval timeout;
      char buffer;
      
      std::cout &lt;&lt; "UDP Server with select() running..." &lt;&lt; std::endl;
      
      while (true) {
            // 设置文件描述符集合
            FD_ZERO(&amp;read_fds);
            FD_SET(server_fd, &amp;read_fds);
            
            // 设置超时(5秒)
            timeout.tv_sec = 5;
            timeout.tv_usec = 0;
            
            // 使用select等待数据
            int ready = select(server_fd + 1, &amp;read_fds, NULL, NULL, &amp;timeout);
            
            if (ready &lt; 0) {
                perror("select failed");
                break;
            } else if (ready == 0) {
                // 超时
                std::cout &lt;&lt; "Timeout, waiting for data..." &lt;&lt; std::endl;
                continue;
            }
            
            // 有数据可读
            if (FD_ISSET(server_fd, &amp;read_fds)) {
                struct sockaddr_in client_addr;
                socklen_t addr_len = sizeof(client_addr);
               
                ssize_t recv_len = recvfrom(server_fd, buffer, BUFFER_SIZE - 1, 0,
                                           (struct sockaddr*)&amp;client_addr, &amp;addr_len);
               
                if (recv_len &gt; 0) {
                  buffer = '\0';
                  std::cout &lt;&lt; "Received: " &lt;&lt; buffer &lt;&lt; std::endl;
                  
                  // 回显
                  sendto(server_fd, buffer, recv_len, 0,
                        (struct sockaddr*)&amp;client_addr, addr_len);
                }
            }
      }
    }
};

int main() {
    UDPServer server(8080);
    server.run();// 或 server.run_with_select();
    return 0;
}
</code></pre>
<ul>
<li>client</li>
</ul>
<pre><code class="language-C++">#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string&gt;

class UDPClient {
private:
    int client_fd;
    struct sockaddr_in server_addr;
   
public:
    UDPClient(const std::string&amp; server_ip, int port) {
      // 创建UDP socket
      client_fd = socket(AF_INET, SOCK_DGRAM, 0);
      if (client_fd &lt; 0) {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
      }
      
      // 设置服务器地址
      memset(&amp;server_addr, 0, sizeof(server_addr));
      server_addr.sin_family = AF_INET;
      server_addr.sin_port = htons(port);
      
      // 转换IP地址
      if (inet_pton(AF_INET, server_ip.c_str(), &amp;server_addr.sin_addr) &lt;= 0) {
            std::cerr &lt;&lt; "Invalid address: " &lt;&lt; server_ip &lt;&lt; std::endl;
            close(client_fd);
            exit(EXIT_FAILURE);
      }
      
      std::cout &lt;&lt; "UDP Client ready to send to "
                  &lt;&lt; server_ip &lt;&lt; ":" &lt;&lt; port &lt;&lt; std::endl;
    }
   
    ~UDPClient() {
      if (client_fd &gt;= 0) {
            close(client_fd);
      }
    }
   
    // 发送单条消息
    bool send_message(const std::string&amp; message) {
      ssize_t sent = sendto(client_fd, message.c_str(), message.length(), 0,
                           (struct sockaddr*)&amp;server_addr, sizeof(server_addr));
      
      if (sent &lt; 0) {
            perror("sendto failed");
            return false;
      }
      
      std::cout &lt;&lt; "Sent " &lt;&lt; sent &lt;&lt; " bytes: " &lt;&lt; message &lt;&lt; std::endl;
      return true;
    }
   
    // 发送并接收回复(阻塞)
    bool send_and_receive(const std::string&amp; message, int timeout_sec = 5) {
      // 发送消息
      if (!send_message(message)) {
            return false;
      }
      
      // 设置接收超时
      struct timeval timeout;
      timeout.tv_sec = timeout_sec;
      timeout.tv_usec = 0;
      
      if (setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO,
                      &amp;timeout, sizeof(timeout)) &lt; 0) {
            perror("setsockopt timeout failed");
      }
      
      // 接收回复
      char buffer;
      struct sockaddr_in from_addr;
      socklen_t addr_len = sizeof(from_addr);
      
      ssize_t recv_len = recvfrom(client_fd, buffer, sizeof(buffer) - 1, 0,
                                 (struct sockaddr*)&amp;from_addr, &amp;addr_len);
      
      if (recv_len &lt; 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                std::cout &lt;&lt; "Timeout: No response received" &lt;&lt; std::endl;
            } else {
                perror("recvfrom failed");
            }
            return false;
      }
      
      buffer = '\0';
      
      char from_ip;
      inet_ntop(AF_INET, &amp;from_addr.sin_addr, from_ip, INET_ADDRSTRLEN);
      
      std::cout &lt;&lt; "Received from " &lt;&lt; from_ip &lt;&lt; ":"
                  &lt;&lt; ntohs(from_addr.sin_port)
                  &lt;&lt; " (" &lt;&lt; recv_len &lt;&lt; " bytes): "
                  &lt;&lt; buffer &lt;&lt; std::endl;
      
      return true;
    }
   
    // 广播消息
    bool broadcast(const std::string&amp; message, int port) {
      // 开启广播选项
      int broadcast_enable = 1;
      if (setsockopt(client_fd, SOL_SOCKET, SO_BROADCAST,
                      &amp;broadcast_enable, sizeof(broadcast_enable)) &lt; 0) {
            perror("setsockopt broadcast failed");
            return false;
      }
      
      // 设置广播地址
      struct sockaddr_in broadcast_addr;
      memset(&amp;broadcast_addr, 0, sizeof(broadcast_addr));
      broadcast_addr.sin_family = AF_INET;
      broadcast_addr.sin_port = htons(port);
      broadcast_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);// 255.255.255.255
      
      // 发送广播
      ssize_t sent = sendto(client_fd, message.c_str(), message.length(), 0,
                           (struct sockaddr*)&amp;broadcast_addr, sizeof(broadcast_addr));
      
      if (sent &lt; 0) {
            perror("broadcast sendto failed");
            return false;
      }
      
      std::cout &lt;&lt; "Broadcast " &lt;&lt; sent &lt;&lt; " bytes" &lt;&lt; std::endl;
      return true;
    }
};

int main() {
    UDPClient client("127.0.0.1", 8080);
   
    // 发送测试消息
    client.send_and_receive("Hello UDP Server!");
    client.send_and_receive("Another message");
   
    // 发送多条消息
    for (int i = 0; i &lt; 5; i++) {
      std::string msg = "Message " + std::to_string(i);
      client.send_and_receive(msg);
    }
   
    return 0;
}
</code></pre>
<h3 id="组播">组播</h3>
<p><strong>组播地址范围</strong>:224.0.0.0 - 239.255.255.255</p>
<ul>
<li>224.0.0.0 - 224.0.0.255:本地网络控制块</li>
<li>224.0.1.0 - 238.255.255.255:全球范围组播地址</li>
<li>239.0.0.0 - 239.255.255.255:本地管理组播地址</li>
</ul>
<pre><code class="language-C++">#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;

class MulticastServer {
private:
    int sock_fd;
    struct sockaddr_in multicast_addr;
   
public:
    MulticastServer(const std::string&amp; multicast_ip, int port) {
      // 创建UDP socket
      sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
      if (sock_fd &lt; 0) {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
      }
      
      // 设置组播地址
      memset(&amp;multicast_addr, 0, sizeof(multicast_addr));
      multicast_addr.sin_family = AF_INET;
      multicast_addr.sin_port = htons(port);
      
      if (inet_pton(AF_INET, multicast_ip.c_str(),
                     &amp;multicast_addr.sin_addr) &lt;= 0) {
            std::cerr &lt;&lt; "Invalid multicast address" &lt;&lt; std::endl;
            close(sock_fd);
            exit(EXIT_FAILURE);
      }
      
      // 设置TTL(生存时间)
      unsigned char ttl = 1;// 只在本地网络
      if (setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_TTL,
                      &amp;ttl, sizeof(ttl)) &lt; 0) {
            perror("setsockopt TTL failed");
      }
      
      std::cout &lt;&lt; "Multicast Server ready for "
                  &lt;&lt; multicast_ip &lt;&lt; ":" &lt;&lt; port &lt;&lt; std::endl;
    }
   
    void send_message(const std::string&amp; message) {
      ssize_t sent = sendto(sock_fd, message.c_str(), message.length(), 0,
                           (struct sockaddr*)&amp;multicast_addr,
                           sizeof(multicast_addr));
      
      if (sent &lt; 0) {
            perror("multicast sendto failed");
      } else {
            std::cout &lt;&lt; "Multicast sent " &lt;&lt; sent &lt;&lt; " bytes" &lt;&lt; std::endl;
      }
    }
};


</code></pre>
<pre><code class="language-C++">#include &lt;iostream&gt;
#include &lt;cstring&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;

class MulticastClient {
private:
    int sock_fd;
    struct sockaddr_in local_addr;
    struct ip_mreq mreq;
   
public:
    MulticastClient(const std::string&amp; multicast_ip, int port) {
      // 创建UDP socket
      sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
      if (sock_fd &lt; 0) {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
      }
      
      // 允许地址重用
      int reuse = 1;
      if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
                      &amp;reuse, sizeof(reuse)) &lt; 0) {
            perror("setsockopt reuseaddr failed");
      }
      
      // 绑定到任意地址和指定端口
      memset(&amp;local_addr, 0, sizeof(local_addr));
      local_addr.sin_family = AF_INET;
      local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
      local_addr.sin_port = htons(port);
      
      if (bind(sock_fd, (struct sockaddr*)&amp;local_addr,
                sizeof(local_addr)) &lt; 0) {
            perror("bind failed");
            close(sock_fd);
            exit(EXIT_FAILURE);
      }
      
      // 加入多播组
      mreq.imr_multiaddr.s_addr = inet_addr(multicast_ip.c_str());
      mreq.imr_interface.s_addr = htonl(INADDR_ANY);
      
      if (setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                      &amp;mreq, sizeof(mreq)) &lt; 0) {
            perror("setsockopt add membership failed");
            close(sock_fd);
            exit(EXIT_FAILURE);
      }
      
      std::cout &lt;&lt; "Joined multicast group " &lt;&lt; multicast_ip
                  &lt;&lt; " on port " &lt;&lt; port &lt;&lt; std::endl;
    }
   
    void receive_messages() {
      char buffer;
      
      std::cout &lt;&lt; "Listening for multicast messages..." &lt;&lt; std::endl;
      
      while (true) {
            struct sockaddr_in from_addr;
            socklen_t addr_len = sizeof(from_addr);
            
            ssize_t recv_len = recvfrom(sock_fd, buffer, sizeof(buffer) - 1, 0,
                                       (struct sockaddr*)&amp;from_addr, &amp;addr_len);
            
            if (recv_len &gt; 0) {
                buffer = '\0';
               
                char from_ip;
                inet_ntop(AF_INET, &amp;from_addr.sin_addr,
                         from_ip, INET_ADDRSTRLEN);
               
                std::cout &lt;&lt; "Multicast from " &lt;&lt; from_ip &lt;&lt; ": "
                        &lt;&lt; buffer &lt;&lt; std::endl;
            }
      }
    }
};
</code></pre><br><br>
来源:https://www.cnblogs.com/hjk-airl/p/19513934
頁: [1]
查看完整版本: 【C++】网络编程