#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#define MAX_EVENT_NUMBER 1024
#define TCP_BUFFER_SIZE 512
#define UDP_BUFFER_SIZE 1024
/*设置文件描述符为非阻塞*/
int setnonblocking(int fd)
{
int old_option = fcntl(fd, F_GETFL);//获取文件描述符状态标志
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);//设置文件描述符状态标志
return old_option;
}
/*向epoll内核事件表中注册文件描述符事件*/
void epoll_addfd(int epollfd, int fd)
{
struct epoll_event event;
event.data.fd = fd;//epoll_event的data字段是一个union,最常用的就是用它保存事件所属的文件描述符
event.events = EPOLLIN | EPOLLET;//注册可读事件和设置epoll工作在边沿触发模式
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);//凡是使用I/O复用技术监听的文件描述符都要非阻塞的
}
/*用ip地址和端口号创建socket地址*/
void create_sockaddr_in(const char* ip, int port, struct sockaddr_in* address)
{
bzero(address, sizeof(*address));
address->sin_family = AF_INET;
inet_pton(AF_INET, ip, &(address->sin_addr));
address->sin_port = htons(port);
}
int main(int argc, char **argv)
{
if (argc <= 2)
{
printf("argv[1]:ip address, argv[2]:port\n");
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
/*创建TCP socket,并将其绑定到端口port上*/
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
create_sockaddr_in(ip, port, &address);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
/*创建UDP socket,并将其绑定到端口port上*/
int udpfd = socket(PF_INET, SOCK_DGRAM, 0);
assert(udpfd >= 0);
create_sockaddr_in(ip, port, &address);
ret = bind(udpfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
//udp不需要listen
struct epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
/*注册TCP socket和UDP socket上的可读事件*/
epoll_addfd(epollfd, listenfd);
epoll_addfd(epollfd, udpfd);
while (1)
{
/*
* 系统会修改events数组,使其前number个元素是有效事件
* MAX_EVENT_NUMBER仅仅是用来指示数组events的大小
* time = -1表示无限阻塞,否则等待time毫秒
*/
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if (number < 0)
{
printf("epoll failure\n");
break;
}
for (int i = 0; i < number; ++i)
{
int sockfd = events[i].data.fd;//事件对应的fd
if (sockfd == listenfd)
{
//触发的事件是tcp连接,为此accept之获取连接fd,并加入到内核事件表
struct sockaddr_in client_address;
socklen_t client_addr_len = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addr_len);
epoll_addfd(epollfd, connfd);
}
else if (sockfd == udpfd)
{
char buf[UDP_BUFFER_SIZE];
memset(buf, '\0', UDP_BUFFER_SIZE);
struct sockaddr_in client_address;
socklen_t client_addr_len = sizeof(client_address);
ret = recvfrom(udpfd, buf, UDP_BUFFER_SIZE - 1, 0, (struct sockaddr*)&client_address, &client_addr_len);
if (ret > 0)
{
sendto(udpfd, buf, UDP_BUFFER_SIZE - 1, 0, (struct sockaddr*)&client_address, client_addr_len);
}
}
else if (events[i].events & EPOLLIN)//tcp连接fd有可读事件
{
char buf[TCP_BUFFER_SIZE];
//epoll工作在ET模式,因此使用循环读完缓冲区的所有内容,否则下次不再触发
while (1)
{
memset(buf, '\0', TCP_BUFFER_SIZE);
ret = recv(sockfd, buf, TCP_BUFFER_SIZE - 1, 0);
if (ret < 0) //缓冲区数据读完
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
break;
}
}
else if (ret == 0) //对方关闭连接(收到RST报文)
{
close(sockfd);
}
else //成功读到数据
{
send(sockfd, buf, ret, 0);
}
}
}
else
{
printf("something else happened\n");
}
}
}
close(listenfd);
return 0;
}
一个sample学会使用epoll
最新推荐文章于 2024-09-12 20:58:18 发布