对于一个文件描述符,默认都是阻塞IO
非阻塞IO
fcntl函数原型
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ..../* arg */ );
传入的cmd的值不同, 后⾯面追加的参数也不相同.
fcntl函数有5种功能:
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=FGETFD 或 FSETFD).
- 获得/设置文件状态标记(cmd=FGETFL 或 FSETFL).
- 获得/设置异步I/O所有权(cmd=FGETOWN 或 FSETOWN).
- 获得/设置记录锁(cmd=FGETLK,FSETLK或F_SETLKW).
这里我们只是用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞
该段代码是使用轮询方式读取标准输入
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <stdio.h>
6
7 void SetNonBlock(int fd)
8 {
9 int fl = fcntl(fd, F_GETFL);
10 if( fl < 0 ){
11 perror("fcntl");
12 return;
13 }
14 fcntl(fd, F_SETFL, fl | O_NONBLOCK);
15 }
16
17 int main()
18 {
19 SetNonBlock(0); //将标准输入文件描述符设置为非阻塞
20 while(1){ //循环读取,即轮询
21 char buf[1024];
22 ssize_t read_size = read(0, buf, sizeof(buf)-1);
23 if( read_size < 0 ){
24 perror("read");
25 sleep(1);
26 continue;
27 }
28 printf("input:%s\n", buf);
29 }
30 return 0;
31 }
那么此处我们可以引出一个概念,重定向
重定向
1.dup/dup2系统调用
函数原型
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
使用·dup将标准输出重定向到文件中
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <fcntl.h>
4
5 int main()
6 {
7 int fd = open("./log", O_CREAT | O_RDWR);
8 if( fd < 0 ){
9 perror("open");
10 return 1;
11 }
12 close(1);
13 int new_fd = dup(fd);
14 if( new_fd != 1 ){
15 perror("dup");
16 return 2;
17 }
18 printf("new_fd: %d\n", new_fd);
19 close(fd);
20
21 for(;;){
22 char buf[1024] = {0};
23 ssize_t read_size = read(0, buf, sizeof(buf)-1);
24 if(read_size < 0){
25 perror("read");
26 continue;
27 }
28 printf("%s", buf);
29 fflush(stdout);
30 }
31 close(new_fd);
32 return 0;
33 }
运行结果如下:
使用dup2将标准输出重定向到文件中
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = open("./log", O_CREAT | O_RDWR);
if( fd < 0 ){
perror("open");
return 1;
}
close(1);
dup2(fd, 1); //newfd为1,是fd的拷贝,所以1里面存放的是fd的内容
for(;;){
char buf[1024];
ssize_t read_size = read(0, buf, sizeof(buf));
if( read_size < 0 ){
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
close(fd);
return 0;
}
IO多路转接之select
首先我们要明确一点,无论它怎么工作,以何种方式工作,它的目的只有一个那就是等(等多个文件描述符),即它最根本的工作机制就是等待内核将数据准备好,直到被监视的多个文件描述符中的一个或者多个状态发生了改变
函数原型:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数解释:
- 参数nfds是需要监视的最大的⽂文件描述符值+1;
- rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
关于fd_set
其实这个结构就是一个整数数组, 更严格的说, 是一个 "位图". 使用位图中对应的位来表示要监视的文件描述 符. 提供了一组操作fd_set的接口, 来比较方便的操作位图
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
- 参数timeout为结构timeval,用来设置select()的等待时间
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
- 执行成功则返回文件描述词状态已改变的个数
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
- 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout 的值变成不可预测。
select使用实例1:检测标准输入
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
int main()
{
fd_set read_fds;
FD_ZERO(&read_fds); //将所有的文件描述符集合清空
FD_SET(0, &read_fds); //将文件描述符fd设置为0,表示关心标准输入上的读事件
for(;;){
printf(">");
fflush(stdout);
int ret = select(1, &read_fds, NULL, NULL, NULL);//其他为设置为空,表示只关心rfds上的读事件
if( ret < 0 ){
perror("select");
continue;
}
if(FD_ISSET(0, &read_fds)){ //测试当rfds的位为真时,表示可以读取
char buf[1024];
read(0, buf, sizeof(buf)-1);
printf("input:%s", buf);
}else{
printf("error! invalid fd\n");
continue;
}
FD_ZERO(&read_fds);
FD_SET(0, &read_fds);
}
return 0;
}
select使用示例:使用select编写网络服务器
服务器:
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void init(int* fd_list, int fd_list_size)
{
int i = 0;
for( ; i < fd_list_size; ++i){
fd_list[i] = -1;
}
}
void Reload(int sock,int* connect_list, int connect_list_size, fd_set* read_fds, int* max_fd)
{
int i = 0;
FD_ZERO(read_fds);
FD_SET(sock, read_fds);
int max = sock; //初始时只有sock,所以max等于sock
for(i = 0 ; i < connect_list_size; ++i){
if(connect_list[i] != -1){
FD_SET(connect_list[i], read_fds);//将connect_list这个数组中该元素指向的内容也就是对应的文件描述符加入read_fds
if(connect_list[i] > max)
max = connect_list[i]; //同时记得比较后更新max
}
}
*max_fd = max;
}
void Add(int connect_fd,int* connect_list, int connect_list_size)
{
int i = 0;
for(i = 0 ; i < connect_list_size; ++i)
{
if(connect_list[i] != -1){
connect_list[i] = connect_fd;
break;
}
}
}
int main( int argc, char* argv[] )
{
if(argc != 3){
printf("Usage: ./server [ip] [port]\n");
return 1;
}
struct sockaddr_in addr;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 1;
}
int bind_fd = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
if(bind_fd < 0){
perror("bind");
return 2;
}
bind_fd = listen(sock, 5);
if(bind_fd < 0){
perror("listen");
return 3;
}
fd_set read_fds;
int fd_list[1024];
init(fd_list, sizeof(fd_list)/sizeof(int));
for(;;){
int max_fd = sock;
Reload(sock, fd_list, sizeof(fd_list)/sizeof(int), &read_fds, &max_fd);
printf("before select: %d\n", FD_ISSET(sock, &read_fds)); //FD_ISSET测试read_fds中的sock是否为真
82,3-17 53%
int ret = select(max_fd+1, &read_fds, NULL, NULL, NULL);
printf("after select: %d\n", FD_ISSET(sock, &read_fds));
if(ret < 0){
perror("select");
continue;
}
if(ret == 0){
printf("select timeout\n");
}
//处理sock
if( FD_ISSET(sock, &read_fds)){
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int connect_fd = accept(sock, (struct sockaddr*)&client_addr, &len); //服务器端接受请求
if(connect_fd < 0){
perror("accept");
continue;
}
printf("client %s: %d connect\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
Add(connect_fd, fd_list, sizeof(fd_list)/sizeof(int));
}
//处理connect_fd
size_t i = 0;
for(i = 0; i < sizeof(fd_list)/sizeof(int); ++i){
if(fd_list[i] == -1){
continue;
}
if(!FD_ISSET(fd_list[i], &read_fds))
continue;
char buf[1024];
ssize_t read_size = read(fd_list[i], buf, sizeof(buf)-1);
if(read_size < 0){
perror("read");
continue;
}
if(read_size == 0){
printf("client say: goodbuye!\n");
fd_list[i] = -1;
}
printf("client say: %s", buf);
write(fd_list[i], buf, strlen(buf));
}
}
return 0;
}
客户端
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Usage()
{
printf("Usage: ./client [ip] [port] \n");
}
int main(int argc, char* argv[])
{
if(argc != 3){
Usage();
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 2;
}
int ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
perror("connect");
return 3;
}
for(;;){
printf(">");
fflush(stdout);
char buf[1024];
read(0, buf, sizeof(buf)-1);
ssize_t write_size = write(sock, buf, strlen(buf)-1);
if(write_size < 0){
perror("wirte");
continue;
}
ssize_t read_size = read(sock, buf, sizeof(buf)-1);
if(read_size < 0){
perror("read");
continue;
}
if(read_size == 0){
printf("server close\n");
break;
}
printf("server say: %s\n", buf);
}
close(sock);
return 0;
}
IO多路转接之poll
poll函数接口
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数说明:
- fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集 合, 返回的事件集合.
- nfds表示fds数组的长度.
- timeout表示poll函数的超时时间, 单位是毫秒(ms)
返回结果:
- 返回值小于0, 表示出错;
- 返回值等于0, 表示poll函数等待超时;
- 返回值大于0, 表⽰示poll由于监听的文件描述符就绪而返回.
poll使用实例1:检测标准输入
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
struct pollfd poll_fd;
poll_fd.fd = 0; //将文件描述符设置为0,即标准输入
poll_fd.events = POLLIN; //events事件设置为数据可读
for(;;){
int ret = poll(&poll_fd, 1, 1000);
if(ret < 0){
perror("poll");
continue;
}
if(ret == 0){
printf("poll timeour\n");
continue;
}
else{ //代表poll监听的文件描述符就绪
if(poll_fd.revents == POLLIN)
{
char buf[1024];
read(0, buf, sizeof(buf)-1);
printf("stdin: %s", buf);
}
}
}
pollt使用实例2:使用poll编写网络服务器
/*************************************************************************
> File Name: server.c
> Author:
> Mail:
> Created Time: Sun 22 Jul 2018 08:46:19 PM PDT
************************************************************************/
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct pollfd pollfd;
void Init(pollfd* fd_list, int size)
{
int i = 0;
for(i = 0 ; i < size; ++i )
{
fd_list[i].fd = -1;
fd_list[i].events = 0;
fd_list[i].revents = 0;
}
}
void Add(int fd, pollfd* fd_list, int size)
{
int i = 0;
for(i = 0 ; i < size; ++i)
{
if(fd_list[i].fd != -1)
{
fd_list[i].fd = fd;
fd_list[i].events = POLLIN;
break;
}
}
}
int main( int argc, char* argv[] )
{
if(argc != 3){
printf("Usage: ./server [ip] [port]\n");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 1;
}
int ret = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
perror("bind");
return 2;
}
ret = listen(sock, 5);
if(ret < 0){
perror("listen");
return 3;
}
pollfd fd_list[1024];
Init(fd_list, sizeof(fd_list)/sizeof(pollfd));
Add(sock, fd_list, sizeof(fd_list)/sizeof(pollfd));
for(;;){
int ret = poll(fd_list, sizeof(fd_list)/sizeof(pollfd), 1000);
if(ret < 0){
perror("poll");
continue;
}
if(ret == 0){
printf("poll timeout\n");
continue;
}
else
{
size_t i = 0;
for(i = 0; i < sizeof(fd_list)/sizeof(pollfd); ++i)
{
if(fd_list[i].fd == sock)
{//处理sock的情况
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int connect_fd = accept(sock, (struct sockaddr*)&client_addr, &len);
if(connect_fd < 0)
{
perror("accept");
continue;
}
Add(connect_fd, fd_list, sizeof(fd_list)/sizeof(pollfd));
}
else
{ // 处理connect_fd的情况
char buf[1024];
ssize_t read_size = read(fd_list[i].fd, buf, sizeof(buf)-1);
if(read_size < 0)
{
perror("read");
continue;
}
if(read_size == 0)
{
printf("client say : goodbye\n");
close(fd_list[i].fd);
fd_list[i].fd = -1
}
printf("client say: %s\n", buf);
write(fd_list[i].fd, buf, strlen(buf)-1);
}
}
}
}
return 0;
}
IO多路转接之epoll
- 根据man手册说,epoll是为处理大批量句柄而作了改进的poll.
epoll有三个相关的系统调用
epoll_create
int epoll_create(int size);
创建一个epoll的句柄.
- 自从linux2.6.8之后,size参数是被忽略的.
- ⽤完之后, 必须调用close()关闭.
epoll_ctl(epoll的事件注册函数. )
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件 类型.
- 第一个参数是epoll_create()的返回值(epoll的句柄).
- 第二个参数表示动作,用三个宏来表示.
第二个参数的宏:
1. EPOLL_CTL_ADD :注册新的fd到epfd中;
2.EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
3..EPOLL_CTL_DEL :从epfd中删除⼀一个fd;
- 第三个参数是需要监听的fd.
- 第四个参数是告诉内核需要监听什么事.
events可以是以下几个宏的集合:
- EPOLLIN : 表⽰对应的文件描述符可以读 (包括对端SOCKET正常关闭);
- EPOLLOUT : 表⽰对应的文件描述符可以写;
- EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这⾥里应该表示有带外数据到来);
- EPOLLERR : 表示对应的文件描述符发生错误;
- EPOLLHUP : 表示对应的文件描述符被挂断;
- EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered) 来说的.
- EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的 话, 需要再次把这个socket加⼊入到EPOLL队列里.
epoll_wait(收集在epoll监控的事件中已经发送的事件. )
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数:
- 参数events是分配好的epoll_event结构体数组.
- epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这 个events数组中,不会去帮助我们在用户态中分配内存).
- maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
- 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
- 如果函数调用成功,返回对应I/O上已准备好的⽂文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败.
epollt使用实例:使用epoll编写网络服务器
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
void ProcessConnect(int sock,int epoll_fd)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connect_fd = accept(sock, (struct sockaddr*)&client, &len);
if(connect_fd < 0){
perror("accept");
return;
}
printf("client %s: %d connect\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
struct epoll_event ev;
ev.data.fd = connect_fd;
ev.events = EPOLLIN;
int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, connect_fd, &ev);
if(ret < 0){
perror("epoll_ctl");
return;
}
return;
}
void ProcessRequest(int connect_fd, int epoll_fd)
{
char buf[1024];
ssize_t read_size = read(connect_fd, buf, sizeof(buf)-1);
if(read_size < 0){
perror("read");
return;
}
if(read_size == 0){
close(connect_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, connect_fd, NULL);
printf("client say: goodbye\n");
return;
}
else{
printf("client say; %s", buf);
write(connect_fd, buf, strlen(buf));
}
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage: ./server [ip] [port]\n");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 2;
}
int bind_fd = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
if(bind_fd < 0){
perror("bind");
return 3;
}
bind_fd = listen(sock, 5);
if(bind_fd < 0){
perror("listen");
return 4;
}
int epoll_fd = epoll_create(10);
if(epoll_fd < 0){
perror("epoll_create");
return 5;
}
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = sock;
bind_fd = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event);
if(bind_fd < 0){
perror("epoll_ctl");
return 6;
}
for(;;){
struct epoll_event events[10];
int size = epoll_wait(epoll_fd, events, sizeof(events)/sizeof(events[0]), -1);
if(size < 0){
perror("epoll_wait");
continue;
}
if(size == 0){
printf("epoll timeout\n");
continue;
}
else
{
int i = 0;
for(i = 0; i < size; ++i)
{
if(!(events[i].events & EPOLLIN))
continue;
if(events[i].data.fd == sock) //处理sock的情况
ProcessConnect(sock, epoll_fd);
else
ProcessRequest(events[i].data.fd, epoll_fd);
}
}
}
return 0;
}
- 实际上,epoll有2种工作方式-水平触发(LT)和边缘触发(ET)
- 假设我们有一个这样的例子:
- 我们已经把一个tcp socket添加到epoll描述符
- 这个时候socket的另一端被写入了2KB的数据
- 调用epoll_wait,并且它会返回. 说明它已经准备好读取操作
- 调用read, 只读取了1KB的数据
- 继续调用epoll_wait......
水平触发Level Triggered 工作模式
epoll默认状态下就是LT工作模式.
- 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
- 如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调⽤用 epoll_wait 时, epoll_wait 仍然会立刻返回并通知socket读事件就绪.
- 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.
- 支持阻塞读写和非阻塞读写
边缘触发Edge Triggered工作模式
如果我们在第1步将socket添加到epoll描述符的时候使⽤用了EPOLLET标志, epoll进⼊入ET⼯工作模式.
- 当epoll检测到socket上事件就绪时, 必须立刻处理.
- 如上面的例⼦子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调⽤用 epoll_wait 的时 候, epoll_wait 不会再返回
- 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会.
- ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采⽤用ET模式使用epoll
- 只支持非阻塞的读写
epoll的ET网络服务器
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
void SetNoBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if(fl < 0){
perror("fcntl");
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
void ProcessConnect(int sock,int epoll_fd)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connect_fd = accept(sock, (struct sockaddr*)&client, &len);
if(connect_fd < 0){
perror("accept");
return;
}
printf("client %s: %d connect\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
struct epoll_event ev;
ev.data.fd = connect_fd;
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, connect_fd, &ev);
if(ret < 0){
perror("epoll_ctl");
return;
}
return;
}
ssize_t NoBlockRead(int fd, char* buf, int size)
{
ssize_t total_size = 0;
for(;;){
ssize_t cur_size = read(fd, buf+total_size, 1024);
total_size += cur_size;
if(cur_size < 1024 || errno == EAGAIN)
break;
}
buf[total_size] = '\0';
return total_size;
}
void ProcessRequest(int connect_fd, int epoll_fd)
{
char buf[1024] = {0};
ssize_t read_size = NoBlockRead(connect_fd, buf, sizeof(buf)-1);
if(read_size < 0){
perror("read");
return;
}
if(read_size == 0){
close(connect_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, connect_fd, NULL);
printf("client say: goodbye\n");
return;
}
else{
printf("client say; %s", buf);
write(connect_fd, buf, strlen(buf));
}
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage: ./server [ip] [port]\n");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 2;
}
int bind_fd = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
if(bind_fd < 0){
perror("bind");
return 3;
}
bind_fd = listen(sock, 5);
if(bind_fd < 0){
perror("listen");
return 4;
}
int epoll_fd = epoll_create(10);
if(epoll_fd < 0){
perror("epoll_create");
return 5;
}
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = sock;
bind_fd = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event);
if(bind_fd < 0){
perror("epoll_ctl");
return 6;
}
for(;;){
struct epoll_event events[10];
int size = epoll_wait(epoll_fd, events, sizeof(events)/sizeof(events[0]), -1);
if(size < 0){
perror("epoll_wait");
continue;
}
if(size == 0){
printf("epoll timeout\n");
continue;
}
else
{
int i = 0;
for(i = 0; i < size; ++i)
{
if(!(events[i].events & EPOLLIN))
continue;
if(events[i].data.fd == sock) //处理sock的情况
ProcessConnect(sock, epoll_fd);
else
ProcessRequest(events[i].data.fd, epoll_fd);
}
}
}
return 0;
}
157,0-1 Bot
select、poll、epoll的的优缺点总结:
1.工作机制:
三者都是等,即都是就绪事件通知机制
2.默认工作方式
三者默认都是工作在LT模式下,只有poll既可以支持LT,也支持ET模式
select
- select的文件描述符有上限
- select代码编写比较麻烦
- select的服务器性能随着用户的增加有可能降低
- 每次调用select,都需要手动的设置fd集合,从接口使用角度来说非常不方便
- 每次调用select,都需要在在内核遍历传递进来的fd,这个开销在fd很多时很大
poll
优点:
- poll所关心的文件描述符没有上限
- poll将输入输出的参数进行了分离,即Poll结构包含了要监视的event和发生的revents,不再使用select“参数-值”传达的方式
缺点:
poll中监听的文件描述符数目增多时
- 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
- 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
- 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.
epoll
- ⽂文件描述符数目无上限: 通过epoll_ctl()来注册一个文件描述符, 内核中使用红黑树的数据结构来管理所有需要监控的文件描述符.
- 基于事件的就绪通知方式: ,一旦被监听的某个文件描述符就绪, 内核会采用类似于callback的回调机制, 迅速激活这个文件描述符. 这样随着文件描述符数量的增加, 也不会影响判定就绪的性能;
- 维护就绪队列: 当文件描述符就绪, 就会被放到内核中的一个就绪队列中. 这样调用epoll_wait获取 epoll的就绪文件描述符的时候, 只要取队列中的元素即可, 操作的时间复杂度是O(1);
epoll工作原理