/*创建epoll句柄*/
int epoll_create(int size); //size参数实际上已经被弃用
/*epoll句柄的控制接口*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*等待 epoll 文件描述符上的 I/O 事件*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd: epoll 专用的文件描述符,epoll_create()的返回值
op: 表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从 epfd 中删除一个 fd;
fd: 需要监听的文件描述符
event: 告诉内核要监听什么事件
epoll_event结构体
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 */
};
EPOLLIN :表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET :将 EPOLL 设为边缘触发(Edge Trigger)模式,这是相对于水平触发(Level Trigger)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);epfd: epoll 专用的文件描述符,epoll_create()的返回值
events: 分配好的 epoll_event 结构体数组,epoll_wait 将会把发生的事件赋值到events 数组中
maxevents: events 数组的元素个数
timeout: 超时时间,单位为毫秒,为 -1 时,函数为阻塞
头文件
#ifndef _NET_H_
#define _NET_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)
void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);
#endif
epoll实现
#include "net.h"
#include
#define MAX_SOCK_FD 1024
int main(int argc, char *argv[])
{
int i, nfds, fd, epfd, newfd;
Addr_in addr;
socklen_t addrlen = sizeof(Addr_in);
struct epoll_event tmp, events[MAX_SOCK_FD] = {};
/*检查参数,小于3个 直接退出进程*/
Argment(argc, argv);
/*创建已设置监听模式的套接字*/
fd = CreateSocket(argv);
if( (epfd = epoll_create(1)) < 0)
ErrExit("epoll_create");
tmp.events = EPOLLIN;
tmp.data.fd = fd;
if( epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &tmp) )
ErrExit("epoll_ctl");
while(1) {
if( (nfds = epoll_wait(epfd, events, MAX_SOCK_FD, -1) ) < 0)
ErrExit("epoll_wait");
printf("nfds = %d\n", nfds);
for(i = 0; i < nfds; i++) {
if(events[i].data.fd == fd){
/*接收客户端连接,并生成新的文件描述符*/
if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)
perror("accept");
printf("[%s:%d] connection.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
tmp.events = EPOLLIN;
tmp.data.fd = newfd;
if( epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &tmp) )
ErrExit("epoll_ctl");
}else{/*处理客户端数据*/
if(DataHandle(events[i].data.fd) <= 0){
if( epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) )
ErrExit("epoll_ctl");
if( getpeername(events[i].data.fd, (Addr *)&addr, &addrlen) )
perror("getpeername");
printf("[%s:%d] exited.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
close(events[i].data.fd);
}
}
}
}
close(epfd);
close(fd);
return 0;
}
封装函数
#include "net.h"
void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s\n", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*创建套接字*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*允许地址快速重用*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
perror("setsockopt");
/*设置通信结构体*/
Addr_in addr;
bzero(&addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*绑定通信结构体*/
if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*设置套接字为监听模式*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
Addr_in peeraddr;
socklen_t peerlen = sizeof(Addr_in);
if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )
perror("getpeername");
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret > 0){
printf("[%s:%d]data: %s\n",
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return ret;
}