/**
* 该文件名为epoll.c
*
*
* 我的测试环境AS4U3
* [gan@localhost ~]$ uname -r
* 2.6.9-34.EL
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <strings.h>
#include <pthread.h>
#define MAXLINE 1024
#define OPEN_MAX 100
#define LISTENQ 20
#define INFTIM 1000
#define LOCAL_IP "192.168.1.101" /* 修改为自己本地机器就可以测试了 */
#define SERV_PORT 5555
/**
* thread task link
*/
struct task
{
int fd; /* file descriptor */
struct task *next; /* next task */
};
struct user_data
{
int fd;
unsigned int n_size;
char line[MAXLINE];
};
/* thread exec function */
void *readtask(void *args);
void *writetask(void *args);
/* declare epoll_event */
struct epoll_event ev, events[20];
int epfd;
pthread_mutex_t mutex; /* 线程安全使用 */
pthread_cond_t cond1; /* 线程条件等待使用 */
struct task *readhead = NULL,
*readtail = NULL,
*writehead = NULL;
void setnonblocking(int sock)
{
int opts;
opts = fcntl(sock, F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1); /* 其实这样做不怎么好, 最好自己做好出错处理的工作, 不光是进程退出就可以了 */
}
if(fcntl(sock, F_SETFL, opts | O_NONBLOCK)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd, nfds;
socklen_t clilen;
pthread_t tid1,tid2;
struct task *new_task=NULL;
struct user_data *rdata=NULL;
/* initialize the thread pool */
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond1, NULL);
/* 创建线程, 最好做好错误处理工作, 自己也比较懒. 真正作东西千万别这样噢! */
pthread_create(&tid1, NULL, readtask, NULL);
pthread_create(&tid2, NULL, readtask, NULL);
/* 生成用于处理accept的epoll专用的文件描述符
* 以前从没用过
*
Create a new epoll file descriptor by requesting the kernel allocate an event backing store dimensioned[n. 尺寸, 尺度, 维(数), 度(数), 元] for size descriptors. The size is not the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures. The returned file descriptor will be used for all the subsequent calls to the epoll interface. The file descriptor returned by epoll_create must be closed by using POSIX::close.
When successful, epoll_create returns a positive integer identifying the descriptor. When an error occurs, epoll_create returns -1 and errno is set appropriately.
*
*/
epfd = epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd = listenfd;
//设置要处理的事件类型
ev.events = EPOLLIN | EPOLLET;
/*注册epoll事件
Control an epoll descriptor, $epfd, by requesting the operation op be performed on the target file descriptor, fd.
$epfd is an epoll descriptor returned from epoll_create.
$op is one of EPOLL_CTL_ADD, EPOLL_CTL_MOD or EPOLL_CTL_DEL.
$fd is the file desciptor to be watched.
$eventmask is a bitmask of events defined by EPOLLIN, EPOLLOUT, etc.
When successful, epoll_ctl returns 0. When an error occurs, epoll_ctl returns -1 and errno is set appropriately.
*/
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr = LOCAL_IP;
inet_aton(local_addr, &(serveraddr.sin_addr));
serveraddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; )
{
/*等待epoll事件的发生
Wait for events on the epoll file descriptor $epfd.
$epfd is an epoll descriptor returned from epoll_create.
$maxevents is an integer specifying the maximum number of events to be returned.
$timeout is a timeout, in milliseconds
When successful, epoll_wait returns a reference to an array of events. Each event is a two element array, the first element being the file descriptor which triggered the event, and the second is the mask of event types triggered. For example, if epoll_wait returned the following data structure:
*/
nfds = epoll_wait(epfd, events, 20, 500);
//处理所发生的所有事件
for(i = 0; i < nfds; ++i)
{
if(events[i].data.fd == listenfd)
{
connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
if(connfd < 0)
{
perror("connfd<0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
printf("connect_from >> %s /n", str);
ev.data.fd = connfd;//设置用于读操作的文件描述符
ev.events = EPOLLIN | EPOLLET;//设置用于注测的读操作事件
//注册ev
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
printf("reading!/n");
if ((sockfd = events[i].data.fd) < 0)
continue;
new_task = (struct task *)malloc(sizeof(struct task));
new_task->fd = sockfd;
new_task->next = NULL;
pthread_mutex_lock(&mutex);//添加新的读任务
if(readhead == NULL)
{
readhead = new_task;
readtail = new_task;
}
else
{
readtail->next = new_task;
readtail = new_task;
}
//唤醒所有等待cond1条件的线程
pthread_cond_broadcast(&cond1);
pthread_mutex_unlock(&mutex);
}
else if (events[i].events & EPOLLOUT)
{
rdata = (struct user_data *)events[i].data.ptr;
sockfd = rdata->fd;
printf("thread.%u Write data fd.%d len.%d data.%s /n"
, (uint32_t)pthread_self(), sockfd, rdata->n_size, rdata->line);
write(sockfd, rdata->line, rdata->n_size);
close(sockfd);
free(rdata);
ev.data.fd = sockfd;//设置用于读操作的文件描述符
ev.events = EPOLLIN | EPOLLET;//设置用于注测的读操作事件
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
}
}
}
/**
* thread exec function
*/
void *readtask(void *args)
{
int fd = -1;
unsigned int n;
//用于把读出来的数据传递出去
struct user_data *data = NULL;
while (1)
{
pthread_mutex_lock(&mutex);
//等待到任务队列不为空
while(readhead == NULL)
{
printf("thread.%u waiting, task is NULL ... /n", (uint32_t)pthread_self());
pthread_cond_wait(&cond1, &mutex);
}
fd = readhead->fd;
//从任务队列取出一个读任务
struct task *tmp = readhead;
readhead = readhead->next;
free(tmp);
pthread_mutex_unlock(&mutex);
data = (struct user_data *)malloc(sizeof(struct user_data));
data->fd = fd;
if ((n = read(fd, data->line, MAXLINE)) < 0)
{
if (errno == ECONNRESET)
{
close(fd);
}
else
printf("readline error /n");
if(data != NULL)
free(data);
}
else if (n == 0)
{
close(fd);
printf("Client close connect!/n");
if(data != NULL)
free(data);
}
else
{
data->n_size = n;
printf("thread.%u read fd.%d len.%d data.%s /n"
, (uint32_t)pthread_self(), fd, n, data->line);
ev.data.ptr = data;//设置需要传递出去的数据
ev.events = EPOLLOUT | EPOLLET;//设置用于注测的写操作事件
//修改sockfd上要处理的事件为EPOLLOUT
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
}
/**
* 一个简单的Makefile
*/
all: epoll.c
gcc -Wall -g -o epoll epoll.c -lpthread
clean:
rm -f epoll