epoll的使用场景及C++回声服务器示例(附源码)

6 篇文章 0 订阅

epoll的使用场景

epoll 是 Linux 下一种高效的 I/O 事件通知机制,相比传统的 select 和 poll 机制,epoll 能够更好地扩展到大数目的描述符。当应用程序需要处理多个文件描述符时,尤其是数量非常大时,使用 epoll 可以显著提高应用程序效率。主要使用场景包括:

  1. 高性能网络服务器:如 HTTP 服务器、数据库服务器等,这些服务器需要处理数以千计甚至更多的并发连接。
  2. 异步 I/O 处理:在一些需要高并发处理的场景,如消息队列的网络通信、实时数据处理。
  3. 事件驱动编程:如使用非阻塞 I/O 操作,通过 epoll 事件通知进行数据读取、写入等操作。

实际案例与代码示例

1、服务端 server.cpp

以下是使用 epoll 实现的简单 TCP 服务器的示例,该服务器能够接受多个客户端连接,并回显客户端发送的消息。

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

#define MAX_EVENTS 10
#define PORT 8888
#define BUFFER_SIZE 1024

int make_socket_non_blocking(int fd) 
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) 
    {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) 
    {
        perror("fcntl");
        return -1;
    }
    return 0;
}

int main() 
{
    int sfd, s;
    struct sockaddr_in addr;
    int efd;
    struct epoll_event event;
    struct epoll_event events[MAX_EVENTS];

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1) 
    {
        perror("socket");
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    s = bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
    if (s == -1) 
    {
        perror("bind");
        return -1;
    }

    s = make_socket_non_blocking(sfd);
    if (s == -1)
    {
        return -1;
	}
	
    s = listen(sfd, SOMAXCONN);
    if (s == -1) 
    {
        perror("listen");
        return -1;
    }

    efd = epoll_create1(0);
    if (efd == -1) 
    {
        perror("epoll_create");
        return -1;
    }

    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLET;
    s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
    if (s == -1) 
    {
        perror("epoll_ctl");
        return -1;
    }

    // Event loop
    while (1) 
    {
        int n = epoll_wait(efd, events, MAX_EVENTS, -1);
        for (int i = 0; i < n; i++) 
        {
            if ((events[i].events & EPOLLERR) || 
                (events[i].events & EPOLLHUP) ||
                (!(events[i].events & EPOLLIN))) 
            {
                fprintf(stderr, "epoll error\n");
                close(events[i].data.fd);
                continue;
            } 
            else if (sfd == events[i].data.fd) 
            {
                // Handle new connection
                struct sockaddr_in in_addr;
                socklen_t in_len = sizeof(in_addr);
                int infd = accept(sfd, (struct sockaddr *)&in_addr, &in_len);
                if (infd == -1) 
                {
                    perror("accept");
                    continue;
                }

                s = make_socket_non_blocking(infd);
                if (s == -1)
                {
                    abort();
				}
				
                event.data.fd = infd;
                event.events = EPOLLIN | EPOLLET;
                s = epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
                if (s == -1) 
                {
                    perror("epoll_ctl");
                    abort();
                }
            } 
            else 
            {
                // Handle data from a client
                int done = 0;
                while (1) 
                {
                    char buf[BUFFER_SIZE];
                    ssize_t count = read(events[i].data.fd, buf, sizeof buf);
                    if (count == -1) 
                    {
                        if (errno != EAGAIN) 
                        {
                            perror("read");
                            done = 1;
                        }
                        break;
                    } 
                    else if (count == 0) 
                    {
                        done = 1;
                        break;
                    }
		
		    		std::cout << "recieve from Client:  " << buf << std::endl;
                    s = write(events[i].data.fd, buf, count);
                   
		    		memset(buf, 0, sizeof(buf));
		    		if (s == -1) 
		    		{
            			perror("write");
                		done = 1;
                		break;
            		}
                }
                if (done) 
                {
                    close(events[i].data.fd);
                }
            }
        }
    }

    close(sfd);
    return 0;
}

解释代码

  • 初始化 socket:创建 socket,并设置为非阻塞模式。
  • 绑定并监听:绑定 socket 到指定端口,并监听连接。
  • 设置 epoll:创建 epoll 实例,并将监听 socket 添加到 epoll 的监视列表中。
  • 事件循环:使用 epoll_wait 等待事件发生,处理新的连接以及处理已连接的客户端发来的数据。

为了完整测试之前编写的基于 epoll 的服务器,需要一个简单的客户端程序。下面的 C++ 代码是一个基本的 TCP 客户端,它连接到服务器,发送字符串,并接收服务器回显的相同字符串。

2、客户端 client.cpp

以下是客户端程序,实现了一个可以持续与服务器通信的客户端,并且能够通过输入 “quit” 来安全地断开连接,在客户端代码中设置一个循环来持续读取用户输入并发送到服务器。该程序保持与服务器的连接直到用户输入 “quit”。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <cstdio>  // For perror()

#define SERVER_PORT 8888
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() 
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) 
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) 
    {
        perror("connect");
        return -1;
    }

    std::cout << "Connected to the server. Type 'quit' to exit." << std::endl;

    char buffer[BUFFER_SIZE];
    std::string input;

    while (true) 
    {
        std::cout << "Enter message: ";
        std::getline(std::cin, input);

        if (input == "quit") 
        {
            break;
        }

        if (send(sock, input.c_str(), input.length(), 0) == -1) 
        {
            perror("send");
            break;
        }

        memset(buffer, 0, sizeof(buffer));
        ssize_t bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);
        if (bytes_received == -1) 
        {
            perror("recv");
            break;
        }

        std::cout << "Received from server: " << buffer << std::endl;
    }

    close(sock);
    std::cout << "Disconnected from server." << std::endl;
    return 0;
}

代码解释

  1. Socket 创建和连接:与之前相同,创建一个 socket 并连接到指定的服务器地址。
  2. 通信循环
    • 使用 std::getline(std::cin, input) 从标准输入读取用户的输入。
    • 检查用户是否输入了 “quit”,如果是,则跳出循环,关闭连接。
    • 发送用户输入到服务器。
    • 接收服务器的回应并显示。
  3. 关闭 Socket:退出循环后,关闭 socket 连接。

3、安装所需的库

上述服务端和客户端代码使用了标准的 POSIX 网络编程接口,这些接口在大多数现代 Linux 发行版中默认可用,因此通常不需要安装任何额外的库来编译和运行这些代码。

确保安装了 GCC

在编译之前,需要确保你的系统中已经安装了 GCC 编译器。可以使用以下命令来安装 GCC:

在 Ubuntu/Debian 系统中:
sudo apt-get update
sudo apt-get install build-essential
在 Fedora 系统中:
sudo dnf install make gcc gcc-c++ kernel-devel
在 CentOS 系统中:
sudo yum install make gcc gcc-c++ kernel-devel

上述命令中的 build-essentialgcc, gcc-c++ 包包含了编译 C++ 程序所需的基本工具和库。

4、编译服务端和客户端代码

在 Linux 系统上,C++ 程序通常使用 g++ 编译器进行编译。g++ 编译器是 GNU Compiler Collection (GCC) 的一部分,它可以处理 C++ 代码。下面是如何编译上述服务端和客户端代码的步骤:

编译服务端代码

假设服务端代码保存在名为 server.cpp 的文件中,可以使用以下命令编译它:

g++ server.cpp -o server -lpthread

这条命令会生成一个名为 server 的可执行文件。选项 -o server 指定输出文件的名称。

编译客户端代码

如果客户端代码保存在名为 client.cpp 的文件中,使用以下命令编译:

g++ client.cpp -o client

这将生成一个名为 client 的可执行文件。

编译后的操作

一旦编译完成,你可以直接运行可执行文件来启动服务器和客户端:

  • 运行服务端:
./server
  • 在另一个终端窗口运行客户端:
./client

客户端应该能够连接到服务器,发送消息,并接收服务器的回显。这验证了网络通信是否正常工作。

这些基础的步骤足以让你开始在大多数 Linux 系统上编译和运行简单的网络程序。不需要任何特别的库,只需要一个正常工作的 C++ 环境和网络 API 支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Warren++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值