struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 联合体, 多个变量共用同一块内存
typedef union epoll_data {
void *ptr;
int fd; // 通常情况下使用这个成员, 和epoll_ctl的第三个参数相同即可
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_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
#include
#include
#include
#include
#include
int main()
{
// 1. 创建监听的fd
int lfd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
// 3. 设置监听
listen(lfd, 128);
// 将监听的fd的状态检测委托给内核检测
int maxfd = lfd;
// 初始化检测的读集合
fd_set rdset;
fd_set rdtemp;
// 清零
FD_ZERO(&rdset);
// 将监听的lfd设置到检测的读集合中
FD_SET(lfd, &rdset);
// 通过select委托内核检测读集合中的文件描述符状态, 检测read缓冲区有没有数据
// 如果有数据, select解除阻塞返回
// 应该让内核持续检测
while(1)
{
// 默认阻塞
// rdset 中是委托内核检测的所有的文件描述符
rdtemp = rdset;
int num = select(maxfd+1, &rdtemp, NULL, NULL, NULL);
// rdset中的数据被内核改写了, 只保留了发生变化的文件描述的标志位上的1, 没变化的改为0
// 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了
// 判断
// 有没有新连接
if(FD_ISSET(lfd, &rdtemp))
{
// 接受连接请求, 这个调用不阻塞
struct sockaddr_in cliaddr;
int cliLen = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &cliLen);
// 得到了有效的文件描述符
// 通信的文件描述符添加到读集合
// 在下一轮select检测的时候, 就能得到缓冲区的状态
FD_SET(cfd, &rdset);
// 重置最大的文件描述符
maxfd = cfd > maxfd ? cfd : maxfd;
}
// 没有新连接, 通信
for(int i=0; i<maxfd+1; ++i)
{
// 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据
if(i != lfd && FD_ISSET(i, &rdtemp))
{
// 接收数据
char buf[10] = {0};
// 一次只能接收10个字节, 客户端一次发送100个字节
// 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据
// 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次
// 循环会一直持续, 知道缓冲区数据被读完位置
int len = read(i, buf, sizeof(buf));
if(len == 0)
{
printf("客户端关闭了连接...\n");
// 将检测的文件描述符从读集合中删除
FD_CLR(i, &rdset);
close(i);
}
else if(len > 0)
{
// 收到了数据
// 发送数据
write(i, buf, strlen(buf)+1);
}
else
{
// 异常
perror("read");
}
}
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// server
int main(int argc, const char* argv[])
{
// 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket error");
exit(1);
}
// 绑定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地多有的IP
// 设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定端口
int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
// 监听
ret = listen(lfd, 64);
if(ret == -1)
{
perror("listen error");
exit(1);
}
// 现在只有监听的文件描述符
// 所有的文件描述符对应读写缓冲区状态都是委托内核进行检测的epoll
// 创建一个epoll模型
int epfd = epoll_create(100);
if(epfd == -1)
{
perror("epoll_create");
exit(0);
}
// 往epoll实例中添加需要检测的节点, 现在只有监听的文件描述符
struct epoll_event ev;
ev.events = EPOLLIN; // 检测lfd读读缓冲区是否有数据
ev.data.fd = lfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
if(ret == -1)
{
perror("epoll_ctl");
exit(0);
}
struct epoll_event evs[1024];
int size = sizeof(evs) / sizeof(struct epoll_event);
// 持续检测
while(1)
{
// 调用一次, 检测一次
int num = epoll_wait(epfd, evs, size, -1);
for(int i=0; i<num; ++i)
{
// 取出当前的文件描述符
int curfd = evs[i].data.fd;
// 判断这个文件描述符是不是用于监听的
if(curfd == lfd)
{
// 建立新的连接
int cfd = accept(curfd, NULL, NULL);
// 新得到的文件描述符添加到epoll模型中, 下一轮循环的时候就可以被检测了
ev.events = EPOLLIN; // 读缓冲区是否有数据
ev.data.fd = cfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if(ret == -1)
{
perror("epoll_ctl-accept");
exit(0);
}
}
else
{
// 处理通信的文件描述符
// 接收数据
char buf[5] = {0};
int len = recv(curfd, buf, sizeof(buf), 0);
if(len == 0)
{
printf("客户端已经断开了连接\n");
// 将这个文件描述符从epoll模型中删除
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
}
else if(len > 0)
{
printf("客户端say: %s\n", buf);
send(curfd, buf, len, 0);
}
else
{
perror("recv");
exit(0);
}
}
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// server
int main(int argc, const char* argv[])
{
// 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket error");
exit(1);
}
// 绑定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地多有的IP
// 127.0.0.1
// inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
// 设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定端口
int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
// 监听
ret = listen(lfd, 64);
if(ret == -1)
{
perror("listen error");
exit(1);
}
// 现在只有监听的文件描述符
// 所有的文件描述符对应读写缓冲区状态都是委托内核进行检测的epoll
// 创建一个epoll模型
int epfd = epoll_create(100);
if(epfd == -1)
{
perror("epoll_create");
exit(0);
}
// 往epoll实例中添加需要检测的节点, 现在只有监听的文件描述符
struct epoll_event ev;
ev.events = EPOLLIN; // 检测lfd读读缓冲区是否有数据
ev.data.fd = lfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
if(ret == -1)
{
perror("epoll_ctl");
exit(0);
}
struct epoll_event evs[1024];
int size = sizeof(evs) / sizeof(struct epoll_event);
// 持续检测
while(1)
{
// 调用一次, 检测一次
int num = epoll_wait(epfd, evs, size, -1);
printf("==== num: %d\n", num);
for(int i=0; i<num; ++i)
{
// 取出当前的文件描述符
int curfd = evs[i].data.fd;
// 判断这个文件描述符是不是用于监听的
if(curfd == lfd)
{
// 建立新的连接
int cfd = accept(curfd, NULL, NULL);
// 将文件描述符设置为非阻塞
// 得到文件描述符的属性
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
// 新得到的文件描述符添加到epoll模型中, 下一轮循环的时候就可以被检测了
// 通信的文件描述符检测读缓冲区数据的时候设置为边沿模式
ev.events = EPOLLIN | EPOLLET; // 读缓冲区是否有数据
ev.data.fd = cfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if(ret == -1)
{
perror("epoll_ctl-accept");
exit(0);
}
}
else
{
// 处理通信的文件描述符
// 接收数据
char buf[5] = {0};
// 循环读数据
while(1)
{
int len = recv(curfd, buf, sizeof(buf), 0);
if(len == 0)
{
// 非阻塞模式下和阻塞模式是一样的 => 判断对方是否断开连接
printf("客户端断开了连接...\n");
// 将这个文件描述符从epoll模型中删除
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
break;
}
else if(len > 0)
{
// 通信
// 接收的数据打印到终端
write(STDOUT_FILENO, buf, len);
// 发送数据
send(curfd, buf, len, 0);
}
else
{
// len == -1
if(errno == EAGAIN)
{
printf("数据读完了...\n");
break;
}
else
{
perror("recv");
exit(0);
}
}
}
}
}
}
return 0;
}