先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
**
**
在前面的博客《linux进程间通信—本地socket套接字(五)—多路IO转接服务器实现一个server对应多个client—poll实现》,我们介绍了多路IO转接服务器之poll方式实现,今天我们使用epoll来实现多路IO转接服务器。
epoll是linux下多路复用IO接口select/poll的增强版本,能显著提高程序在大量并发连接中只有少量是活跃的情况下系统CPU的使用率;epoll和poll都是为了提升服务器性能的一种多路IO接口;
epoll和poll的特点都可以通过命令修改文件描述符数量
1. 用cat命令查看一个进程可以打开的socket描述符上限
cat /proc/sys/fs/file-max
2. 如果由需要,可以通过修改配置温恩建的方式修改该上限值
sudo vi /etc/security/limits.conf
在文件尾部写入以下配置,soft软限制,hard硬限制,如下图所示
soft nofile 65536
hard nofile 100000
当连接的文件描述符比较多,但是监听的文件描述符比较少,那么epoll/poll性能较高;当连接的文件描述符比较多,但是监听的文件描述符比较多,那么epoll/poll和select性能相差不多。
**
**
基础API
(1) 创建
int epoll_create(int size)
size:监听数目
调用创建接口之后,相当于在内核中创建了一个红黑树(平衡二叉树),如下所示
(2) 控制某个epoll监控的文件描述符上的事件 : 注册,修改,删除
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd: epoll_create的句柄(句柄实际上是windows下面的叫法),以现实生活中举例来看
句柄类比于把柄,我有了某人的把柄,基本上可以叫他干嘛就干嘛
op: 表示动作,用3个宏来表示
EPOLL_CTL_ADD : 注册新的fd到epfd
EPOLL_CTL_MOD : 修改已经注册的fd的监听时间
EPOLL_CTL_DEL : 从epfd删除一个fd
fd : 待监听的文件描述符
event: 告诉内核需要监听的事件
调用epoll_ctl接口相当于向epfd指向的红黑树中添加子节点,假设我们分别添加了lfd,cfd1,cfd2到efd中
evt.events = EPOLLIN;
evt.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &evt);
evt.events = EPOLLIN;
evt.data.fd = cfd1;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd1, &evt);
evt.events = EPOLLIN;
evt.data.fd = cfd2;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd2, &evt);
红黑树当前的结构状态如下:
(3) 等待所监控文件描述符上有事件产生,类似select()调用
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
epfd: epoll_create的句柄
events: 用来存内核得到时间的集合,这里是一个数组,它是一个传出值
maxevents: 告知内核这个events有多大,这个值不能大于epoll_create()时的size
timeout: 超时时间
-1,阻塞
0,立即返回,不阻塞
>0, ms
返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
当我们调用epoll_wait函数并且返回之后,evts数组如下(假设此时lfd和cfd1的读事件发生)
epoll_wait返回之后,我们就需要判断当前文件描述符表示的是哪个文件描述符的事件,调用相应的处理,这里就是epoll和poll的一个区别,它能直接知道返回了几个对应的事件,不需要再做判断
for (i = 0; i < rval; i++) {
== lfd ---> accept
== cfd1 ---> recv
}
**
**
(1) 先创建socket
static int listenfd;
static int socket_server_init(void)
{
struct sockaddr_in servaddr;
int opt = 1;
int rval = 0;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("create socket fail");
return -1;
}
rval = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
&opt, sizeof(opt));
if (rval < 0) {
perror("setsockopt fail");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
rval = bind(listenfd, (struct sockaddr*) &servaddr,
sizeof(servaddr));
if (rval < 0 ) {
perror("bind fail");
return -1;
}
rval = listen(listenfd, LISTENLEN);
if (rval < 0 ) {
perror("listen fail");
return -1;
}
}
(2) epoll监听
epoll监听我们放在main函数中实现,没有给提取出来
int main(int argc, char **argv)
{
struct epoll_event evt;
struct epoll_event evts[EPOLL_FD_MAX_NUM];
struct sockaddr_in cliaddr;
socklen_t clilen;
int epfd = -1;
int rval = 0, nready, n;
int i, j, connfd, sockfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
rval = socket_server_init();
if (rval < 0) {
printf("socket_server_init fail\n");
return 0;
}
//创建epfd
epfd = epoll_create(EPOLL_FD_MAX_NUM);
if (epfd == -1) {
perror("epoll_create error");
return 0;
}
//先将listenfd事件添加到epoll中,因为只有listenfd检测到客户端的连接,然后才会
//读取client发过来的数据
evt.events = EPOLLIN;
evt.data.fd = listenfd;
rval = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &evt);
if (rval < 0) {
perror("epoll_ctl error");
return 0;
}
while (1) {
nready = epoll_wait(epfd, evts, EPOLL_FD_MAX_NUM, -1);
if (nready <= 0) {
perror("epoll_wait error");
return 0;
}
for (i = 0; i < nready; i++) {
if (evts[i].events != EPOLLIN) //如果不是读事件,继续循环
continue;
if (evts[i].data.fd == listenfd) { // listenf事件
clilen = sizeof(cliaddr);
/* 因为检测到了listenfd读事件,这个时候调用accept就不会阻塞 */
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
//将检测到的client读事件添加进来,给epoll监听
evt.events = EPOLLIN;
evt.data.fd = connfd;
rval = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &evt);
if (rval < 0) {
perror("epoll_ctl error");
return 0;
}
} else { // cfd事件
memset(buf, 0, sizeof(buf));
sockfd = evts[i].data.fd;
n = read(sockfd, buf, MAXLINE);
if (n < 0) {
perror("read error");
//将当前文件描述符删除,沒有必要继续监听当前客户端读事件
rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
if (rval < 0) {
perror("epoll_ctl delete error");
return 0;
}
close(sockfd);
} else if (n == 0) { //说明客戶端先关闭连接
//将当前文件描述符删除,沒有必要继续监听当前客户端读事件
rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
if (rval < 0) {
perror("epoll_ctl delete error");
return 0;
}
printf("client[%d] close connect\n", sockfd);
close(sockfd);
} else { //收到有效数据
printf("server receive %s\n", buf);
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
write(sockfd, buf, n);
}
}
}
}
}
server完整代码如下
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 1024
#define LISTENLEN 10
#define SERV_PORT 7809
#define EPOLL_FD_MAX_NUM 10
static int listenfd;
static int socket_server_init(void)
{
struct sockaddr_in servaddr;
int opt = 1;
int rval = 0;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("create socket fail");
return -1;
}
rval = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
&opt, sizeof(opt));
if (rval < 0) {
perror("setsockopt fail");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
rval = bind(listenfd, (struct sockaddr*) &servaddr,
sizeof(servaddr));
if (rval < 0 ) {
perror("bind fail");
return -1;
}
rval = listen(listenfd, LISTENLEN);
if (rval < 0 ) {
perror("listen fail");
return -1;
}
}
int main(int argc, char **argv)
{
struct epoll_event evt;
struct epoll_event evts[EPOLL_FD_MAX_NUM];
struct sockaddr_in cliaddr;
socklen_t clilen;
int epfd = -1;
int rval = 0, nready, n;
int i, j, connfd, sockfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
rval = socket_server_init();
if (rval < 0) {
printf("socket_server_init fail\n");
return 0;
}
//创建epfd
epfd = epoll_create(EPOLL_FD_MAX_NUM);
if (epfd == -1) {
perror("epoll_create error");
return 0;
}
//先将listenfd事件添加到epoll中,因为只有listenfd检测到客户端的连接,然后才会
//读取client发过来的数据
evt.events = EPOLLIN;
evt.data.fd = listenfd;
rval = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &evt);
if (rval < 0) {
perror("epoll_ctl error");
return 0;
}
while (1) {
nready = epoll_wait(epfd, evts, EPOLL_FD_MAX_NUM, -1);
if (nready <= 0) {
perror("epoll_wait error");
return 0;
}
for (i = 0; i < nready; i++) {
if (evts[i].events != EPOLLIN) //如果不是读事件,继续循环
continue;
if (evts[i].data.fd == listenfd) { // listenf事件
clilen = sizeof(cliaddr);
/* 因为检测到了listenfd读事件,这个时候调用accept就不会阻塞 */
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
//将检测到的client读事件添加进来,给epoll监听
evt.events = EPOLLIN;
evt.data.fd = connfd;
rval = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &evt);
if (rval < 0) {
perror("epoll_ctl error");
return 0;
}
} else { // cfd事件
memset(buf, 0, sizeof(buf));
sockfd = evts[i].data.fd;
n = read(sockfd, buf, MAXLINE);
if (n < 0) {
perror("read error");
//将当前文件描述符删除,沒有必要继续监听当前客户端读事件
rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
if (rval < 0) {
perror("epoll_ctl delete error");
return 0;
}
close(sockfd);
} else if (n == 0) { //说明客戶端先关闭连接
//将当前文件描述符删除,沒有必要继续监听当前客户端读事件
rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
if (rval < 0) {
perror("epoll_ctl delete error");
return 0;
}
printf("client[%d] close connect\n", sockfd);
close(sockfd);
} else { //收到有效数据
printf("server receive %s\n", buf);
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
write(sockfd, buf, n);
}
}
}
}
}
客户端代码如下
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 7809
#define BUFFER_SIZE 1024
#define LOCAL_LOOP_BACK_IP "127.0.0.1"
int main(int argc, char **argv)
{
struct sockaddr_in servaddr;
char sendbuf[BUFFER_SIZE] = {0};
char recvbuf[BUFFER_SIZE] = {0};
int client_fd;
//定义IPV4的TCP连接的套接字描述符
client_fd = socket(AF_INET,SOCK_STREAM, 0);
//set sockaddr_in
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(LOCAL_LOOP_BACK_IP);
servaddr.sin_port = htons(PORT); //服务器端口
//连接服务器,成功返回0,错误返回-1
if (connect(client_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
printf("connect server(IP:%s).\n",LOCAL_LOOP_BACK_IP);
//客户端将控制台输入的信息发送给服务器端,服务器原样返回信息
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
send(client_fd, sendbuf, strlen(sendbuf),0); ///发送
if(strcmp(sendbuf,"exit\n")==0)
{
printf("client exited.\n");
break;
}
recv(client_fd, recvbuf, sizeof(recvbuf),0); ///接收
printf("client receive: %s\n", recvbuf);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(client_fd);
return 0;
}
**
**
编译
gcc 10_server.c -o 10_server
gcc 10_client.c -o 10_client
运行
./10_server
./10_client