目录
入门
引入
- 与多进程/线程相比 I/O多路复用的优势是系统开销小
关于更多I/O多路复用 可以参考NIO服务
介绍
- epoll是Linux下多路复用I/O接口select/poll的增强版本
优点
-
支持一个进程打开大数目的socket描述符 - 更高并发
-
I/O效率不随socket描述符增加而线性下降 - 更好性能
Client
vim client.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUFFER_SIZE 40
int main(int argc, char *argv[])
{
int client_sockfd; // 客户端套接字
struct sockaddr_in client_addr; // 服务器端网络地址结构体
// Socket Step1: 创建客户端套接字
memset(&client_addr, 0, sizeof(client_addr)); // 数据初始化
client_addr.sin_family = AF_INET; // 设置为IP通信
client_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址
client_addr.sin_port = htons(8000); // 服务器端口号
if ((client_sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("client socket creation failed");
exit(EXIT_FAILURE);
}
// Socket Step2: 将套接字绑定到服务器的网络地址上
if (connect(client_sockfd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr)) < 0)
{
perror("connect to server failed");
exit(EXIT_FAILURE);
}
while (1)
{
char buf[BUFFER_SIZE];
printf("Please input the message:");
scanf("%s", buf);
// 处理"exit"消息
if (strcmp(buf, "exit") == 0)
{
break;
}
send(client_sockfd, buf, BUFFER_SIZE, 0);
// 接收服务器端信息
int len = recv(client_sockfd, buf, BUFFER_SIZE, 0);
printf("receive from server:%s/n", buf);
if (len < 0)
{
perror("receive from server failed");
exit(EXIT_FAILURE);
}
}
close(client_sockfd);
return 0;
}
关于socket更多介绍 可以参考linux socket编程总结
Server
vim server.c
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUFFER_SIZE 40
#define MAX_EVENTS 10
int main(int argc, char *argv[])
{
int server_sockfd; // 服务器端套接字
int client_sockfd; // 客户端套接字
struct sockaddr_in server_addr; // 服务器网络地址结构体
struct sockaddr_in client_addr; // 客户端网络地址结构体
// Socket Step1: 创建服务器端套接字
memset(&server_addr, 0, sizeof(server_addr)); // 数据初始化
server_addr.sin_family = AF_INET; // 设置为IP通信
server_addr.sin_addr.s_addr = INADDR_ANY; // 服务器IP地址 - 允许连接到所有本地地址上
server_addr.sin_port = htons(8000); // 服务器端口号
if ((server_sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
return 1;
}
// Socket Step2: 将套接字绑定到服务器的网络地址上
if (bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
{
perror("bind");
return 1;
}
// Socket Step3: 监听连接请求
listen(server_sockfd, 5);
// Epoll Step1: 创建一个epoll句柄
int epoll_fd;
epoll_fd = epoll_create(MAX_EVENTS);
if (epoll_fd == -1)
{
perror("epoll_create failed");
exit(EXIT_FAILURE);
}
// Epoll Step2: 向epoll注册server_sockfd监听事件
struct epoll_event ev; // epoll事件结构体
struct epoll_event events[MAX_EVENTS]; // 事件监听队列
ev.events = EPOLLIN;
ev.data.fd = server_sockfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_sockfd, &ev) == -1)
{
perror("epll_ctl:server_sockfd register failed");
exit(EXIT_FAILURE);
}
while (1)
{
// Epoll Step3: 等待事件发生
int nfds;
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1)
{
perror("start epoll_wait failed");
exit(EXIT_FAILURE);
}
int i;
for (i = 0; i < nfds; i++)
{
// 客户端有新的连接请求
if (events[i].data.fd == server_sockfd)
{
// 等待客户端连接请求到达
int sin_size;
sin_size = sizeof(struct sockaddr_in);
if ((client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &sin_size)) < 0)
{
perror("accept client_sockfd failed");
exit(EXIT_FAILURE);
}
// 向epoll注册client_sockfd监听事件
ev.events = EPOLLIN;
ev.data.fd = client_sockfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_sockfd, &ev) == -1)
{
perror("epoll_ctl:client_sockfd register failed");
exit(EXIT_FAILURE);
}
printf("accept client: %s\n", inet_ntoa(client_addr.sin_addr));
}
// 客户端有数据发送过来
else
{
char buf[BUFFER_SIZE];
int len = recv(client_sockfd, buf, BUFFER_SIZE, 0);
if (len < 0)
{
perror("receive from client failed");
exit(EXIT_FAILURE);
}
printf("receive from client: %s\n", buf);
send(client_sockfd, buf, len, 0);
}
}
}
return 0;
}
关于epoll接口定义 可以参考epoll.h
Test
docker run -it --name epoll-demo ubuntu:16.04 /bin/sh
apt update && apt install -y build-essential
docker cp server.c epoll-demo:/root && docker cp client.c epoll-demo:/root
docker exec -it epoll-demo /bin/sh
cd ~
gcc -o server server.c && gcc -o client client.c
关于gcc更多介绍 可以参考gcc命令
./server
accept client: 127.0.0.1
receive from client: 1
accept client: 127.0.0.1
receive from client: 2
./client
# Please input the message:1
# receive from server:1/nPlease input the message:
./client
# Please input the message:2
# receive from server:2/nPlease input the message:
ps -ax | grep server | awk '{print $1}' # 100
cat /proc/100/status | grep Threads # Threads: 1
网友评论