目录
TCP_Socket通信4-poll/epoll/reator/threadpoll
poll
相对于select的优点是没有最大文件描述符限制,请求和返回时分离的。
一个进程最大能打开的socket文件描述符大小
cat /proc/sys/fs/file-max
haitu@ubuntu:~$ cat /proc/sys/fs/file-max
194530
# 可以通过修改配置文件的方式来修改该上限值
sudo vi /etc/security/limits.conf
# 在文件尾部写入以下配置,soft软限制,hard硬限制
* soft nofile 65536
* hard nofile 100000
pollAPI详解
#include <poll.h>
/*在linux系统的/usr/include/features.h文件中*/
/*打开_GNU_SOURCE这个宏可以打开一些功能,比如为了在Linux系统上编译使用带有检测文件type的宏*/
#define _GNU_SOURCE /* See feature_test_macros(7) */
/*监听多个文件描述符的属性变化*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *tmo_p, const sigset_t *sigmask);
struct pollfd *fds:监听的数组的地址
nfds_t nfds:监控数组中有多少个文件描述符需要被监控,一般填写nfds数组有效元素最大下标+1
int timeout:超时时间,毫秒级等待,-1:永久监听
struct pollfd {
int fd; /* file descriptor 文件描述符*/
short events; /* requested events 监听的事件*/
short revents; /* returned events 监听事件中满足条件返回的事件*/
};
short events: // 主要使用的就是POLLIN和POLLOUT,也就是读事件和写事件
*POLLIN 普通或带外优先数据可读,即 POLLRDNORM | POLLRDBAND
POLLRDNORM 数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级可读数据
*POLLOUT 普通或带外数据可写
POLLWRNORM 数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
poll_server代码
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct pollfd client[OPEN_MAX]; // poll监听数组
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
client[0].fd = listenfd;
client[0].events = POLLRDNORM; /* listenfd监听普通读事件 */
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* 用-1初始化client[]里剩下元素 */
maxi = 0; /* client[]数组有效元素中最大元素下标 */
for (;;) {
nready = poll(client, maxi+1, -1); /* 阻塞 返回被监听到的数据个数*/
/*监听lfd,查看是否有客户端连接进来*/
if (client[0].revents & POLLRDNORM) { /* 有客户端链接请求 */
/*这里不写为client[0].revents == POLLRDNORM, 因为可能返回监听了多个
events = POLLWRNORM | POLLRDNORM,比如监听了可读可写,那么就可能会出现错误。
这里需要将events和revents理解为位图,只需要判断你需要的那一位发生变化
*/
clilen = sizeof(cliaddr);
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));
for (i = 1; i < OPEN_MAX; i++) {
if (client[i].fd < 0) {
client[i].fd = connfd;
/* 找到client[]中空闲的位置,存放accept返回的connfd */
break;
}
}
if (i == OPEN_MAX)
perr_exit("too many clients");
client[i].events = POLLRDNORM; /* 设置刚刚返回的connfd,监控读事件 */
if (i > maxi)
maxi = i; /* 更新client[]中最大元素下标 */
if (--nready <= 0)
continue; /* 没有更多就绪事件时,继续回到poll阻塞 */
}
/*对连接进来的客户端文件描述符进行监听*/
for (i = 1; i <= maxi; i++) { /* 检测client[] */
if ((sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) { // 被监听到
if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) { /* 当收到 RST标志时 */
/* connection reset by client */
printf("client[%d] aborted connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
perr_exit("read error");
}
} else if (n == 0) {
/* connection closed by client */
printf("client[%d] closed connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
return 0;
}
poll_client代码
#include "wrap.h"
#include <poll.h>
#define SERV_PORT 6666
int main()
{
struct sockaddr_in servaddr;
char buf[1024];
int n;
int sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr.s_addr);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, 1024, stdin) != NULL) {
// scanf("%s", buf);
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, 1024);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
poll的优缺点
相对于select函数来说,没有文件描述符的1024的限制。
请求和返回都是分离的。
缺点和select是一样的,每次都是需要将监听的文件描述符都从应用层拷贝到内核。
每次都需要将数组中的元素遍历一遍,才知道哪一个发生了变化。
大量并发,少量活跃的效率低下。
epoll
- 第一步:将创建一颗红黑树
- 第二步:将需要监听的文件描述符上树
- 第三步:监听
- 特点:没有文件描述符的限制,不需要每次将文件描述符拷贝到内核。不需要进行遍历,
返回的是变化的文件描述符,我们使用数组接收,不需要在进行遍历。
epollPAI详解
// a>创建红黑树
#include <sys/epoll.h>
int epoll_create(int size); // int epfd = epoll_create(1);
int epoll_create1(int flags); // int epfd = epoll_createl(0);
// 参数size:需要监听的文件描述符的上限,2.6版本以后,填1即可
// 返回值:指向新创建的红黑树的根节点的fd(红黑树的句柄) 失败返回-1 errno
// b>上树、下数、修改节点
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 参数epfd:红黑树的句柄
// 参数op:EPOLL_CTL_ADD(上树)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DE(下树)
// 参数fd:上树、下树、修改的文件描述符
// 参数event:上树的监听事件、
// 返回值:成功返回0 失败返回-1
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 需要监听的文件描述符*/
};
// 将cfd上树
int epfd = epoll_create(1);
struct epoll_event ev;
ev.data.fd = cfd;
ev.eventd = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
// c>监听树上的文件描述符的变化
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
// 参数epfd:树的句柄
// 参数events:返回的结构体数组节点,传出参数
// 参数maxevents:数组元素的个数
// 参数timeout:-1 阻塞监听,永久监听 大于等于0 限时等待
// 返回值:满足监听的文件描述符个数,失败返回-1 errno
struct epoll_event resevent[10];
res = epoll_wait(epfd, resevent, 10, -1);
epoll特点
没有文件描述符1024的限制,以后每次监听都不需要再次将监听的文件描述符拷贝到内核中。不需要在遍历树,返回的是已经变化的文件描述符。
父子进程管道通信epoll监听
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#define MAXLINE 10
int main(int argc, char *argv[])
{
int efd, i;
int pfd[2];
pid_t pid;
char buf[MAXLINE], ch = 'a';
pipe(pfd); // 创建管道
pid = fork();
if (pid == 0) { //子 写
close(pfd[0]); // close read
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) { //父 读
struct epoll_event event;
struct epoll_event resevent[10]; //epoll_wait就绪返回event
int res, len;
close(pfd[1]); // close write
efd = epoll_create(1);
event.events = EPOLLIN | EPOLLET; // ET 边沿触发
// event.events = EPOLLIN; // LT 水平触发 (默认)
event.data.fd = pfd[0];
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", res);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len); // 写到屏幕上
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork");
exit(-1);
}
return 0;
}
epoll_sever
#include <stdio.h>
#include "./wrap/wrap.h"
#include <sys/epoll.h>
#include <sys/socket.h>
#define PORT 8000
#define IP "127.0.0.1"
int main(int argc, char *argv[])
{
struct epoll_event ev;// 用于创建红黑树
struct epoll_event reev[1024];// 用于接收epoll_wait返回的信息
memset(reev,0,sizeof(reev));
struct sockaddr_in cliaddr;// 用作Accept的时候传出客户端信息
socklen_t clilen;
int res,i,ret,n,j;
int lfd,epfd,sockfd; // 分别为监听套接字、红黑树节点套接字、客户端套接字
char buf[1024];
// 创建套接字
lfd = tcp4bind(PORT, IP);
// 监听
listen(lfd, 128);
// 创建树
epfd = epoll_create(1);
// 上树
ev.data.fd = lfd;
ev.events = EPOLLIN;// 写事件
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
// 循环监听
for(;;)
{
// 监听
res = epoll_wait(epfd, reev, lfd, -1);
if(res == -1)
{
perror("epoll_wait");
break;
}else if(res == 0)
{
continue;
}
for(i = 0;i < res; i++)
{
if(!(reev[i].events & EPOLLIN))
{
continue; // 如果不是读事件,那么就直接继续
}
if(reev[i].data.fd == lfd) // 监控到服务端fd发生变化
{
printf("accept\n");
int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &clilen);
if(cfd == -1)
{
perror("accept");
break;
}
print_clientInfo(cliaddr); // 打印出来ip地址
// 上树
ev.data.fd = cfd;
ev.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if(ret == -1)
{
perror("epoll_ctl");
break;
}
}
else { // 监控到客户端fd变化
sockfd = reev[i].data.fd;
n = Read(sockfd,buf,1024);
if(n < 0)
{
perror("read err");
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); // 从红黑树中删除
// epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,&reev[i]);
Close(sockfd);
}
else if(n == 0)
{
printf("----%d----\n",sockfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); // 从红黑树中删除
Close(sockfd);
}
else
{
for (j = 0;j<n;j++)
{
buf[j] = toupper(buf[j]); // 转为大写的
}
Write(STDOUT_FILENO, buf, n); // 写到屏幕上
Write(sockfd,buf,n); // 写回去客户端
}
}
}
}
Close(lfd);
Close(epfd);
return 0;
}
epoll的两种工作方式
-
水平触发(LT):持续的高电平或者持续的低电平。
当监听读的时候,只要缓冲区有数据的话,那么就会触发epoll_wait()
。
缓冲区中还有未读尽的数据会导致epoll_wait的返回。监听写缓冲区的时候,只要可以写,就会触发,所以只能使用边缘触发。
event.events = EPOLLIN; // LT 水平触发 (默认)
event.data.fd = pfd;
epoll_ctl(efd, EPOLL_CTL_ADD, pfd, &event);
-
边沿触发(ET):电平有高到低或者低到高的变化。
当监听读的时候,边沿触发只有数据到来才触发epoll_wait()
,不管缓存区中是否还有数据。
缓冲区剩余未读尽的数据不会导致epoll_wait
返回。 新的事件满足,才会触发。所以我们要求一次性的将数据全部读取完毕,那么就要求监听的客户端文件描述符是非阻塞的。当监听写缓冲区的时候,数据从有到无就会触发,也就是数据发送完毕以后触发
epoll_wait
。
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 ,默认是水平触发 */
event.data.fd = pfd;
epoll_ctl(efd, EPOLL_CTL_ADD, pfd, &event);
/*设置cfd为非阻塞的模式*/
int flags = fcntl(cfd, F_GETFL); // 获取cfd的标志位
flags |= O_NONBLOCK; // 设置为非阻塞模式
fcntl(cfd, F_SETFL, flags);
高速模式=边沿触发+非阻塞
要解决数据读不尽的问题,可以使用循环读取的方式,此时就要设置sockfd为非阻塞模式
#include <asm-generic/errno-base.h>
#include <stdio.h>
#include "./wrap/wrap.h"
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#define PORT 8000
#define IP "127.0.0.1"
int main(int argc, char *argv[])
{
struct epoll_event ev;
struct epoll_event reev[1024];
memset(reev,0,sizeof(reev));
struct sockaddr_in cliaddr;
socklen_t clilen;
int res,i,ret,n,j;
int lfd,epfd,sockfd;
char buf[1024];
// 创建套接字 socket bind 设置端口复用
lfd = tcp4bind(PORT, IP);
// 监听
listen(lfd, 128);
// 创建树
epfd = epoll_create(1);
// 上树
ev.data.fd = lfd;
ev.events = EPOLLIN; /*lfd一般不用设置为边沿触发*/
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
// 循环监听
for(;;)
{
// 监听
res = epoll_wait(epfd, reev, 1024, -1);
printf("------ epoll_wait begin -------");
if(res == -1)
{
perror("epoll_wait");
break;
}
for(i = 0;i<res;i++)
{
if(!(reev[i].events & EPOLLIN))
{
continue; // 如果不是读事件,那么就直接继续
}
if(reev[i].data.fd == lfd) // 监控到服务端fd发生变化
{
printf("accept\n");
int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &clilen);
if(cfd == -1)
{
perror("accept");
break;
}
print_clientInfo(cliaddr); // 打印出来ip地址
/*设置cfd为非阻塞的模式*/
int flags = fcntl(cfd,F_GETFL); // 获取cfd的标志位
flags |= O_NONBLOCK; // 设置为非阻塞模式
fcntl(cfd,F_SETFL,flags);
// 上树
ev.data.fd = cfd;
ev.events = EPOLLIN | EPOLLET; // 设置为边缘触发
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if(ret == -1)
{
perror("epoll_ctl");
break;
}
}
else { // 监控到客户端fd变化
sockfd = reev[i].data.fd;
while(1)
{
// 如果读取一个缓冲区,缓冲区没有数据,如果是带阻塞就阻塞等待
// 如果是非阻塞,返回值为-1 并且将errno的值设置为 EAGAIN
n = Read(sockfd,buf,1024);
if(n < 0)
{
if(errno == EAGAIN) // 缓冲区没有数据的时候
{
printf("read finish"); // 缓冲区读取干净
break; // 结束去监听
}
perror("read err");
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); // 从中删除
Close(sockfd);
break;
}
else if(n == 0)
{
printf("----%d----\n",sockfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
Close(sockfd);
break;
}
else
{
for (j = 0;j<n;j++)
{
buf[j] = toupper(buf[j]); // 转为大写的
}
Write(STDOUT_FILENO, buf, n); // 写到屏幕上
Write(sockfd,buf,n); // 写回去客户端
}
}
}
}
}
Close(lfd);
Close(epfd);
return 0;
}
三种io复用对比
select 跨平台, poll 没有1024文件描述符的限制,缺点也很明显,每次监听都需要将监听到的信息从应用层拷贝到内存,返回变化的文件描述符的个数,具体那个文件描述符需要遍历,大量并发、少量活跃效率比较低
epoll 优点:没有1024文件描述符的限制,下次监听不需要将需要监听的文件描述符从应用层再次拷贝到内核,返回的是已经变化的文件描述符,不需要遍历,大量并发,少量活跃效率高。
epoll反应堆reator
当文件描述符发生事件去进行回调函数处理数据,将他们封装到一起。原理如下:
- 初始化函数
initlistensocket
中将回调函数设置为acceptconn
,添加服务器描述符lfd
上树。- 当
poll_wait
监控到到lfd
后调用函数acceptconn
,产生的客户端cfd
,将他的回调函数设置为recvdata
,- 当监控到客户端
cfd
变化,那么就会调用recvdata
,在recvdata
函数中将回调函数修改为senddata
函数,并将事件修改为EPOLLOUT
。- 也就是接收到数据,然后客户端
cfd
的读缓冲区中接收到了数据,会触发epoll_wait
,进行处理数据,将数据处理完成后,又将回调函数更改为recvdata
,重写进行接收数据,修改事件为EPOLLIN
。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define MAX_EVENTS 1024 /*监听上限*/
#define BUFLEN 4096 /*缓存区大小*/
#define SERV_PORT 6666 /*端口号*/
void recvdata(int fd,int events,void *arg);
void senddata(int fd,int events,void *arg);
/*描述就绪文件描述符的相关信息*/
struct myevent_s
{
int fd; //要监听的文件描述符
int events; //对应的监听事件,EPOLLIN和EPLLOUT
void *arg; //指向自己结构体指针
void (*call_back)(int fd,int events,void *arg); //回调函数
int status; //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
char buf[BUFLEN];
int len;
long last_active; //记录每次加入红黑树 g_efd 的时间值
};
int g_efd; //全局变量,作为红黑树根
struct myevent_s g_events[MAX_EVENTS+1]; //自定义结构体类型数组. +1-->listen fd
/*
* 封装一个自定义事件,包括fd,这个fd的回调函数,还有一个额外的参数项
* 注意:在封装这个事件的时候,为这个事件指明了回调函数,一般来说,一个fd只对一个特定的事件
* 感兴趣,当这个事件发生的时候,就调用这个回调函数
*/
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int fd,int events,void *arg), void *arg)
{
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
if(ev->len <= 0)
{
memset(ev->buf, 0, sizeof(ev->buf));
ev->len = 0;
}
ev->last_active = time(NULL); //调用eventset函数的时间
return;
}
/* 向 epoll监听的红黑树 添加一个文件描述符 */
void eventadd(int efd, int events, struct myevent_s *ev)
{
struct epoll_event epv={0, {0}};
int op = 0;
epv.data.ptr = ev; // ptr指向一个结构体(之前的epoll模型红黑树上挂载的是文件描述符cfd和lfd,现在是ptr指针)
epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT
if(ev->status == 0) //status 说明文件描述符是否在红黑树上 0不在,1 在
{
op = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1
ev->status = 1;
}
if(epoll_ctl(efd, op, ev->fd, &epv) < 0) // 添加一个节点
printf("event add failed [fd=%d],events[%d]\n", ev->fd, events);
else
printf("event add OK [fd=%d],events[%0X]\n", ev->fd, events);
return;
}
/* 从epoll 监听的 红黑树中删除一个文件描述符*/
void eventdel(int efd,struct myevent_s *ev)
{
struct epoll_event epv = {0, {0}};
if(ev->status != 1) //如果fd没有添加到监听树上,就不用删除,直接返回
return;
epv.data.ptr = NULL;
ev->status = 0;
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
return;
}
/* 当有文件描述符就绪, epoll返回, 调用该函数与客户端建立链接 */
void acceptconn(int lfd,int events,void *arg)
{
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cfd, i;
if((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1)
{
if(errno != EAGAIN && errno != EINTR)
{
sleep(1);
}
printf("%s:accept,%s\n",__func__, strerror(errno));
return;
}
do
{
for(i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素,类似于select中找值为-1的元素
{
if(g_events[i].status ==0)
break;
}
if(i == MAX_EVENTS) // 超出连接数上限
{
printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
break;
}
int flag = 0;
if((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd也设置为非阻塞
{
printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
break;
}
eventset(&g_events[i], cfd, recvdata, &g_events[i]); //找到合适的节点之后,将其添加到监听树中,并监听读事件
eventadd(g_efd, EPOLLIN, &g_events[i]);
}while(0);
printf("new connect[%s:%d],[time:%ld],pos[%d]",inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
return;
}
/*读取客户端发过来的数据的函数*/
void recvdata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = recv(fd, ev->buf, sizeof(ev->buf), 0); //读取客户端发过来的数据
eventdel(g_efd, ev); //将该节点从红黑树上摘除
if (len > 0)
{
ev->len = len;
ev->buf[len] = '\0'; //手动添加字符串结束标记
printf("C[%d]:%s\n", fd, ev->buf);
eventset(ev, fd, senddata, ev); //设置该fd对应的回调函数为senddata
eventadd(g_efd, EPOLLOUT, ev); //将fd加入红黑树g_efd中,监听其写事件
}
else if (len == 0)
{
close(ev->fd);
/* ev-g_events 地址相减得到偏移元素位置 */
printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
}
else
{
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return;
}
/*发送给客户端数据*/
void senddata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = send(fd, ev->buf, ev->len, 0); //直接将数据回射给客户端
eventdel(g_efd, ev); //从红黑树g_efd中移除
if (len > 0)
{
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
eventset(ev, fd, recvdata, ev); //将该fd的回调函数改为recvdata
eventadd(g_efd, EPOLLIN, ev); //重新添加到红黑树上,设为监听读事件
}
else
{
close(ev->fd); //关闭链接
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return ;
}
/*创建 socket, 初始化lfd */
void initlistensocket(int efd, short port)
{
struct sockaddr_in sin;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK); //将socket设为非阻塞
memset(&sin, 0, sizeof(sin)); //bzero(&sin, sizeof(sin))
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(lfd, (struct sockaddr *)&sin, sizeof(sin));
listen(lfd, 20);
/* void eventset(struct myevent_s *ev, int fd,
void (*call_back)(int, int, void *), void *arg); */
eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
/* void eventadd(int efd, int events, struct myevent_s *ev) */
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); //将lfd添加到监听树上,监听读事件
return;
}
int main()
{
int port=SERV_PORT;
g_efd = epoll_create(MAX_EVENTS + 1); //创建红黑树,返回给全局 g_efd
if(g_efd <= 0)
printf("create efd in %s err %s\n", __func__, strerror(errno));
initlistensocket(g_efd, port); //初始化监听socket
struct epoll_event events[MAX_EVENTS + 1];
//定义这个结构体数组,用来接收epoll_wait传出的满足监听事件的fd结构体
printf("server running:port[%d]\n", port);
int checkpos = 0;
int i;
while(1)
{
long now = time(NULL);
for(i=0; i < 100; i++, checkpos++)
{
if(checkpos == MAX_EVENTS);
checkpos = 0;
if(g_events[checkpos].status != 1)
continue;
long duration = now -g_events[checkpos].last_active;
if(duration >= 60)
{
close(g_events[checkpos].fd);
printf("[fd=%d] timeout\n", g_events[checkpos].fd);
eventdel(g_efd, &g_events[checkpos]);
}
}
//调用eppoll_wait等待接入的客户端事件,
//epoll_wait传出的是满足监听条件的那些fd的struct epoll_event类型
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
if (nfd < 0)
{
printf("epoll_wait error, exit\n");
exit(-1);
}
for(i = 0; i < nfd; i++)
{
//evtAdd()函数中,添加到监听树中监听事件的时候将myevents_t结构体类型给了ptr指针
//这里epoll_wait返回的时候,同样会返回对应fd的myevents_t类型的指针
struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
//如果监听的是读事件,并返回的是读事件
if((events[i].events & EPOLLIN) &&(ev->events & EPOLLIN))
{
ev->call_back(ev->fd, events[i].events, ev->arg);
}
//如果监听的是写事件,并返回的是写事件
if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
{
ev->call_back(ev->fd, events[i].events, ev->arg);
}
}
}
return 0;
}
线程池
线程池概念
把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。线程不断的去取任务来执行,如果没有任务休眠。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。
创建一定数量的线程,和一个任务队列,线程池中的线程不停的去任务队列中取任务,又任务到来直接往任务队列中添加。其实也就是生产者消费者模型。线程池处理的任务所需要的事件必须要很短,线程池中线程也不宜太多。
- 任务队列中需要线程锁,队列一次只能由单个线程访问。
- 条件变量1,限制任务队列为空,阻塞消费者消费(线程抢到锁,当任务队列中没有任务的时候,线程中调用
pthread_cond_wait
等待任务,在pthread_cond_wait
函数中的执行步骤为解锁、等待条件、给线程发送信号(pthread_cond_signal
或者pthread_cond_broadcast
),然后线程抢锁,其中前面的两个步骤是原子操作,不可分割的)。 - 条件变量2,限制任务队列为满,阻塞生产者添加任务。
- 队列需要考虑使用循环队列
threadpoll.c
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "threadpool.h"
#define DEFAULT_TIME 10 /*10s检测一次*/
#define MIN_WAIT_TASK_NUM 10 /*如果queue_size > MIN_WAIT_TASK_NUM 添加新的线程到线程池*/
#define DEFAULT_THREAD_VARY 10 /*每次创建和销毁线程的个数*/
#define true 1
#define false 0
typedef struct {
void *(*function)(void *); /* 函数指针,回调函数 */
void *arg; /* 上面函数的参数 */
} threadpool_task_t; /* 各子线程任务结构体 */
/* 描述线程池相关信息 */
struct threadpool_t {
pthread_mutex_t lock; /* 用于锁住本结构体 */
pthread_mutex_t thread_counter; /* 记录忙状态线程个数de琐 -- busy_thr_num */
pthread_cond_t queue_not_full; /* 当任务队列满时,添加任务的线程阻塞,等待此条件变量 */
pthread_cond_t queue_not_empty; /* 任务队列里不为空时,通知等待任务的线程 */
pthread_t *threads; /* 存放线程池中每个线程的tid。数组 */
pthread_t adjust_tid; /* 存管理线程tid */
threadpool_task_t *task_queue; /* 任务队列(数组首地址) */
int min_thr_num; /* 线程池最小线程数 */
int max_thr_num; /* 线程池最大线程数 */
int live_thr_num; /* 当前存活线程个数 */
int busy_thr_num; /* 忙状态线程个数 */
int wait_exit_thr_num; /* 要销毁的线程个数 */
int queue_front; /* task_queue队头下标 */
int queue_rear; /* task_queue队尾下标 */
int queue_size; /* task_queue队中实际任务数 */
int queue_max_size; /* task_queue队列可容纳任务数上限 */
int shutdown; /* 标志位,线程池是否销毁,true或false */
};
void *threadpool_thread(void *threadpool);
void *adjust_thread(void *threadpool);
int is_thread_alive(pthread_t tid);
int threadpool_free(threadpool_t *pool);
//threadpool_create(3,100,100);
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
int i;
threadpool_t *pool = NULL; /* 线程池 结构体 */
do {
if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) {
printf("malloc threadpool fail");
break; /*跳出do while*/
}
pool->min_thr_num = min_thr_num;
pool->max_thr_num = max_thr_num;
pool->busy_thr_num = 0;
pool->live_thr_num = min_thr_num; /* 活着的线程数 初值=最小线程数 */
pool->wait_exit_thr_num = 0;
pool->queue_size = 0; /* 有0个产品 */
pool->queue_max_size = queue_max_size; /* 最大任务队列数 */
pool->queue_front = 0;
pool->queue_rear = 0;
pool->shutdown = false; /* 不关闭线程池 */
/* 根据最大线程上限数, 给工作线程数组开辟空间, 并清零 */
pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*max_thr_num);
if (pool->threads == NULL) {
printf("malloc threads fail");
break;
}
memset(pool->threads, 0, sizeof(pthread_t)*max_thr_num);
/* 给 任务队列 开辟空间 */
pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t)*queue_max_size);
if (pool->task_queue == NULL) {
printf("malloc task_queue fail");
break;
}
/* 初始化互斥琐、条件变量 */
if (pthread_mutex_init(&(pool->lock), NULL) != 0
|| pthread_mutex_init(&(pool->thread_counter), NULL) != 0
|| pthread_cond_init(&(pool->queue_not_empty), NULL) != 0
|| pthread_cond_init(&(pool->queue_not_full), NULL) != 0)
{
printf("init the lock or cond fail");
break;
}
/* 启动 min_thr_num 个 work thread */
for (i = 0; i < min_thr_num; i++) {
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool); /*pool指向当前线程池*/
printf("start thread 0x%x...\n", (unsigned int)pool->threads[i]);
}
pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void *)pool); /* 创建管理者线程 */
return pool;
} while (0);
threadpool_free(pool); /* 前面代码调用失败时,释放poll存储空间 */
return NULL;
}
/* 向线程池中 添加一个任务 */
//threadpool_add(thp, process, (void*)&num[i]); /* 向线程池中添加任务 process: 小写---->大写*/
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg)
{
pthread_mutex_lock(&(pool->lock));
/* ==为真,队列已经满, 调wait阻塞 */
while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown)) {
pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
}
if (pool->shutdown) {
pthread_cond_broadcast(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
/* 清空 工作线程 调用的回调函数 的参数arg */
if (pool->task_queue[pool->queue_rear].arg != NULL) {
pool->task_queue[pool->queue_rear].arg = NULL;
}
/*添加任务到任务队列里*/
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size; /* 队尾指针移动, 模拟环形 */
pool->queue_size++;
/*添加完任务后,队列不为空,唤醒线程池中 等待处理任务的线程*/
pthread_cond_signal(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
/* 线程池中各个工作线程 */
void *threadpool_thread(void *threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
threadpool_task_t task; // 用于拷贝得到任务队列中的任务
while (true) {
/* Lock must be taken to wait on conditional variable */
/*刚创建出线程,等待任务队列里有任务,否则阻塞等待任务队列里有任务后再唤醒接收任务*/
pthread_mutex_lock(&(pool->lock));
/*queue_size == 0 说明没有任务,调 wait 阻塞在条件变量上, 若有任务,跳过该while*/
while ((pool->queue_size == 0) && (!pool->shutdown)) {
printf("thread 0x%x is waiting\n", (unsigned int)pthread_self());
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
/*清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程*/
if (pool->wait_exit_thr_num > 0) {
pool->wait_exit_thr_num--;
/*如果线程池里线程个数大于最小值时可以结束当前线程*/
if (pool->live_thr_num > pool->min_thr_num) {
printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
/*如果指定了true,要关闭线程池里的每个线程,自行退出处理---销毁线程池*/
if (pool->shutdown) {
pthread_mutex_unlock(&(pool->lock));
printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
pthread_detach(pthread_self());
pthread_exit(NULL); /* 线程自行结束 */
}
/*从任务队列里获取任务, 是一个出队操作*/
task.function = pool->task_queue[pool->queue_front].function;
task.arg = pool->task_queue[pool->queue_front].arg;
pool->queue_front = (pool->queue_front + 1) % pool->queue_max_size; /* 出队,模拟环形队列 */
pool->queue_size--;
/*通知可以有新的任务添加进来*/
pthread_cond_broadcast(&(pool->queue_not_full));
/*任务取出后,立即将 线程池琐 释放*/
pthread_mutex_unlock(&(pool->lock));
/*执行任务*/
printf("thread 0x%x start working\n", (unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter)); /*忙状态线程数变量琐*/
pool->busy_thr_num++; /*忙状态线程数+1*/
pthread_mutex_unlock(&(pool->thread_counter));
(*(task.function))(task.arg); /*执行回调函数任务*/
//task.function(task.arg); /*执行回调函数任务*/
/*任务结束处理*/
printf("thread 0x%x end working\n", (unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--; /*处理掉一个任务,忙状态数线程数-1*/
pthread_mutex_unlock(&(pool->thread_counter));
}
pthread_exit(NULL);
}
/* 管理线程 */
void *adjust_thread(void *threadpool)
{
int i;
threadpool_t *pool = (threadpool_t *)threadpool;
while (!pool->shutdown) {
sleep(DEFAULT_TIME); /*定时 对线程池管理*/
pthread_mutex_lock(&(pool->lock));
int queue_size = pool->queue_size; /* 关注 任务数 */
int live_thr_num = pool->live_thr_num; /* 存活 线程数 */
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num = pool->busy_thr_num; /* 忙着的线程数 */
pthread_mutex_unlock(&(pool->thread_counter));
/* 创建新线程 算法: 任务数大于最小线程池个数, 且存活的线程数少于最大线程个数时 如:30>=10 && 40<100*/
if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num) {
pthread_mutex_lock(&(pool->lock));
int add = 0;
/*一次增加 DEFAULT_THREAD 个线程*/
for (i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
&& pool->live_thr_num < pool->max_thr_num; i++) {
if (pool->threads[i] == 0 || !is_thread_alive(pool->threads[i])) {
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
add++;
pool->live_thr_num++;
}
}
pthread_mutex_unlock(&(pool->lock));
}
/* 销毁多余的空闲线程 算法:忙线程X2 小于 存活的线程数 且
存活的线程数 大于 最小线程数时*/
if ((busy_thr_num * 2) < live_thr_num && live_thr_num > pool->min_thr_num) {
/* 一次销毁DEFAULT_THREAD个线程, 隨機10個即可 */
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num = DEFAULT_THREAD_VARY; /* 要销毁的线程数 设置为10 */
pthread_mutex_unlock(&(pool->lock));
for (i = 0; i < DEFAULT_THREAD_VARY; i++) {
/* 通知处在空闲状态的线程, 他们会自行终止*/
pthread_cond_signal(&(pool->queue_not_empty));
}
}
}
return NULL;
}
int threadpool_destroy(threadpool_t *pool)
{
int i;
if (pool == NULL) {
return -1;
}
pool->shutdown = true;
/*先销毁管理线程*/
pthread_join(pool->adjust_tid, NULL);
for (i = 0; i < pool->live_thr_num; i++) {
/*通知所有的空闲线程*/
pthread_cond_broadcast(&(pool->queue_not_empty));
}
for (i = 0; i < pool->live_thr_num; i++) {
pthread_join(pool->threads[i], NULL);
}
threadpool_free(pool);
return 0;
}
int threadpool_free(threadpool_t *pool)
{
if (pool == NULL) {
return -1;
}
if (pool->task_queue) {
free(pool->task_queue);
}
if (pool->threads) {
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destroy(&(pool->thread_counter));
pthread_cond_destroy(&(pool->queue_not_empty));
pthread_cond_destroy(&(pool->queue_not_full));
}
free(pool);
pool = NULL;
return 0;
}
int threadpool_all_threadnum(threadpool_t *pool)
{
int all_threadnum = -1; // 总线程数
pthread_mutex_lock(&(pool->lock));
all_threadnum = pool->live_thr_num; // 存活线程数
pthread_mutex_unlock(&(pool->lock));
return all_threadnum;
}
int threadpool_busy_threadnum(threadpool_t *pool)
{
int busy_threadnum = -1; // 忙线程数
pthread_mutex_lock(&(pool->thread_counter));
busy_threadnum = pool->busy_thr_num;
pthread_mutex_unlock(&(pool->thread_counter));
return busy_threadnum;
}
int is_thread_alive(pthread_t tid)
{
int kill_rc = pthread_kill(tid, 0); //发0号信号,测试线程是否存活
if (kill_rc == ESRCH) {
return false;
}
return true;
}
/*测试*/
#if 1
/* 线程池中的线程,模拟处理业务 */
void *process(void *arg)
{
printf("thread 0x%x working on task %d\n ",(unsigned int)pthread_self(),(int)arg);
sleep(1); //模拟 小---大写
printf("task %d is end\n",(int)arg);
return NULL;
}
int main(void)
{
/*threadpool_t *threadpool_create(int min_thr_num,
int max_thr_num, int queue_max_size);*/
/*创建线程池,池里最小3个线程,最大100,队列最大100*/
threadpool_t *thp = threadpool_create(3,100,100);
printf("pool inited");
//int *num = (int *)malloc(sizeof(int)*20);
int num[20], i;
for (i = 0; i < 20; i++) {
num[i] = i;
printf("add task %d\n",i);
/*int threadpool_add(threadpool_t *pool,
void*(*function)(void *arg), void *arg) */
threadpool_add(thp, process, (void*)&num[i]); /* 向线程池中添加任务 */
}
sleep(10); /* 等子线程完成任务 */
threadpool_destroy(thp);
return 0;
}
#endif
threadpoll.h
#ifndef __THREADPOOL_H_
#define __THREADPOOL_H_
typedef struct threadpool_t threadpool_t;
/**
* @function threadpool_create
* @descCreates a threadpool_t object.
* @param thr_num thread num
* @param max_thr_num max thread size
* @param queue_max_size size of the queue.
* @return a newly created thread pool or NULL
*/
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);
/**
* @function threadpool_add
* @desc add a new task in the queue of a thread pool
* @param pool Thread pool to which add the task.
* @param function Pointer to the function that will perform the task.
* @param argument Argument to be passed to the function.
* @return 0 if all goes well,else -1
*/
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg);
/**
* @function threadpool_destroy
* @desc Stops and destroys a thread pool.
* @param pool Thread pool to destroy.
* @return 0 if destory success else -1
*/
int threadpool_destroy(threadpool_t *pool);
/**
* @desc get the thread num
* @pool pool threadpool
* @return # of the thread
*/
int threadpool_all_threadnum(threadpool_t *pool);
/**
* desc get the busy thread num
* @param pool threadpool
* return # of the busy thread
*/
int threadpool_busy_threadnum(threadpool_t *pool);
#endif