提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
epoll 实现简单聊天程序代码示例
c代码实现,可以运行
提示:以下是本篇文章正文内容,下面案例可供参考
一、epoll是什么?
epoll 是 Linux 内核提供的一种高效的 I/O 事件通知机制,它允许程序注册多个文件描述符,并监控这些文件描述符上的 I/O 事件。epoll 是在 Linux 2.6 内核中引入的,它比早期的 select 和 poll 更加高效,尤其是在处理大量文件描述符时。
epoll 的主要特点
效率高:
epoll 使用事件驱动模型,而不是轮询所有文件描述符的状态。
epoll 只会通知那些状态发生变化的文件描述符,因此在文件描述符数量较多时效率非常高。
灵活:
epoll 支持多种事件类型,包括读、写、错误等。
可以针对每个文件描述符设置不同的事件掩码。
可扩展性:
epoll 不受限于文件描述符的数量限制,理论上可以处理成千上万个文件描述符。
epoll 的性能随着文件描述符数量的增长变化不大,非常适合处理大量并发连接。
低延迟:
epoll 使用高效的内核数据结构来存储文件描述符信息。
内核和用户空间之间的数据交换最小化,从而减少了上下文切换和复制开销。
主要函数:
epoll_create 创建一个新的 epoll 文件描述符
epoll_ctl 用于向 epoll 文件描述符中添加、修改或删除文件描述符
epoll_wait 用来等待文件描述符上的事件。
二、使用步骤
1.ser代码
代码如下(示例):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define SOCKET_PATH "/tmp/chat.sock"
#define BUFFER_SIZE 1024
int main() {
int server_socket, client_socket;
struct sockaddr_un server_addr, client_addr;
socklen_t client_len;
int cli_fd = 0;
// 创建 Unix 域套接字
server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 清除旧的 socket 文件
unlink(SOCKET_PATH);
// 准备地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 绑定套接字
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 开始监听
if (listen(server_socket, 1) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受客户端连接
/*client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
// 设置套接字为非阻塞模式
int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
*/
char buffer[BUFFER_SIZE];
struct epoll_event events[3];
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加标准输入和客户端套接字到 epoll
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl (stdin)");
exit(EXIT_FAILURE);
}
event.events = EPOLLIN | EPOLLET; // 边缘触发模式
event.data.fd = server_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
perror("epoll_ctl (client_socket)");
exit(EXIT_FAILURE);
}
while (1) {
int nready = epoll_wait(epoll_fd, events, 3, -1);
if (nready == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < nready; ++i) {
if (events[i].data.fd == STDIN_FILENO) {
if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
if(cli_fd != 0)
write(cli_fd, buffer, strlen(buffer)); // 发送消息给客户端
}
}else if (events[i].data.fd == server_socket) {
printf("events[i].data.fd == server_socket accept\n");
client_len = sizeof(client_addr);
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1) {
perror("accept");
continue;
}
event.events = EPOLLIN | EPOLLET; // 边缘触发模式
event.data.fd = client_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
perror("epoll_ctl (client_socket)");
continue;
}
cli_fd = client_socket;
}else/* if (events[i].data.fd == client_socket) */{
//printf("enter read, events[j].data.fd = 0x%x\n", events[i].data.fd);
ssize_t nread = read(events[i].data.fd, buffer, BUFFER_SIZE - 1);
if (nread > 0) {
buffer[nread] = '\0';
printf("Received: %s", buffer);
} else if (nread == 0) {
printf("Client disconnected.\n");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
cli_fd = 0;
continue;
} else {
perror("Error reading from client");
break;
}
}
}
}
// 清理
close(client_socket);
close(server_socket);
close(epoll_fd);
unlink(SOCKET_PATH);
return 0;
}
2.cli代码
代码如下(示例):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define SOCKET_PATH "/tmp/chat.sock"
#define BUFFER_SIZE 1024
int main() {
int client_socket;
struct sockaddr_un server_addr;
// 创建 Unix 域套接字
client_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 准备地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 连接到服务器
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
// 设置套接字为非阻塞模式
int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
char buffer[BUFFER_SIZE];
struct epoll_event events[2];
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加标准输入和客户端套接字到 epoll
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl (stdin)");
exit(EXIT_FAILURE);
}
event.events = EPOLLIN | EPOLLET; // 边缘触发模式
event.data.fd = client_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
perror("epoll_ctl (client_socket)");
exit(EXIT_FAILURE);
}
while (1) {
int nready = epoll_wait(epoll_fd, events, 2, -1);
if (nready == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < nready; ++i) {
if (events[i].data.fd == STDIN_FILENO) {
if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
write(client_socket, buffer, strlen(buffer)); // 发送消息给服务器
}
} else if (events[i].data.fd == client_socket) {
ssize_t nread = read(client_socket, buffer, BUFFER_SIZE - 1);
if (nread > 0) {
buffer[nread] = '\0';
printf("Received: %s", buffer);
} else if (nread == 0) {
printf("Server disconnected.\n");
break;
} else {
perror("Error reading from server");
break;
}
}
}
}
// 清理
close(client_socket);
close(epoll_fd);
return 0;
}
总结
epoll 实现简单聊天程序c代码示例,stdin输入发送到cli端,代码可以运行,仅供参考