Linux编程入门三网络编程二

同步/异步IOI/O模型读写操作和阻塞阶段
同步阻塞I/O程序阻塞于读写函数
同步I/O复用程序阻塞于I/O复用系统调用,但可同时监听多个I/O事件。对I/O本身的读写操作是非阻塞的
同步信号驱动I/O(SIGIO信号)信号触发读写就绪事件,用户程序执行读写操作。程序没有阻塞阶段
异步异步I/O内核执行读写操作并触发读写完成事件。程序没有阻塞阶段

I/O复用

select、poll和epoll的区别

系统调用selectpollepoll
事件集合用户通过3个fd_set参数分别传入感兴趣的可读、可写及异常等事件,内核通过对这些参数的在线修改来反馈其中的就绪事件。这使得用户每次调用select都要重置这3个参数统一处理所有事件类型,因此只需一个事件集参数pollfd。用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents反馈其中就绪的事件内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无须反复传入用户感兴趣的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件
应用程序索引就绪文件描述符的时间复杂度O(n)O(n)O(1)
最大支持文件描述符数一般有最大值限制6553565535
工作模式LTLT支持ET高效模式
内核实现和工作效率采用轮询方式来检测就绪事件,算法时间复杂度为O(n)采用轮询方式来检测就绪事件,算法时间复杂度为O(n)采用回调方式来检测就绪事件,算法时间复杂度为O(1)

select

#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
  • nfds参数指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。
  • fd_set结构体的定义如下:
#include <typesizes.h>
#define __FD_SETSIZE 1024  //fd_set能容纳的文件描述符数量
#include <sys/select.h>
#define FD_SETSIZE __FD_SETSIZE
typedef long int __fd_mask;
#undef __NFDBITS
#define __NFDBITS (8*(int)sizeof(__fd_mask))   //8->1byte为8bits
typedef struct
{
#ifdef __USE_XOPEN
	__fd_mask fds_bits[ __FD_SETSIZE/__NFDBITS]; //该数组每个元素的每一位标记一个文件描述符
#define __NFDS_BITS(set)  ((set)->fds_bits)
#else 
         __fd_mask fds_bits[ __FD_SETSIZE/__NFDBITS];
#define __NFDS_BITS(set)  ((set)->fds_bits)
#endif
} fd_set;
  • timeout参数用来设置select函数的超时时间

测试用例:

#include <sys/socket.h>
int listen(int sockfd, int backlog);

使用listen创建一个监听队列来存放待处理的客户连接。backlog参数提示内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不再受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。在内核2.2之前的Linux中,backlog参数是指所有处于半连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的socket的上限。自内核2.2之后,它只表示处于完全连接状态的socket的上限。

  1 #include <sys/socket.h>
  2 #include <netinet/in.h>
  3 #include <arpa/inet.h>
  4 #include <signal.h>
  5 #include <unistd.h>
  6 #include <stdlib.h>
  7 #include <assert.h>
  8 #include <stdio.h>
  9 #include <string.h>
 10 #include <stdbool.h>
 11 static bool stop = false;
 12 // SIGTERM信号的处理函数,触发时结束主程序中的循环
 13 static void handle_term(int sig)
 14 {
 15         stop = true;
 16 }
 17 int main(int argc, char* argv[])
 18 {
 19         signal(SIGTERM, handle_term);
 20         if(argc <= 3)
 21         {
 22                 printf("usage: %s ip_address port_number backlog\n", basename(argv[0]));
 23                 return 1;
 24         }
 25         const char* ip = argv[1];
 26         int port = atoi(argv[2]);
 27         int backlog = atoi(argv[3]);
 28 
 29         int sock = socket(PF_INET, SOCK_STREAM, 0);
 30         assert(sock >= 0);
 31         struct sockaddr_in address;
 32         bzero(&address, sizeof(address));
 33         address.sin_family = AF_INET;
 34         inet_pton(AF_INET, ip, &address.sin_addr);
 35         address.sin_port = htons(port);
 36 
 37         int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
 38         assert(ret!=-1);
 39 
 40         ret = listen(sock, backlog);
 41         assert(ret!=-1);
 42         //循环等待连接,直到有SIGTERM信号将它中断
 43         while(!stop)
 44         {
 45                 sleep(1);
 46         }
 47         close(sock);
 48         return 0;
 49 }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上图显示了该时刻listen监听队列的内容。可见在监听队列中,处于ESTABLISHED状态的连接只有6个(backlog值加1),其他连接都处于SYN_RCVD状态。

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept从listen监听队列中接受一个连接。accept成功返回连接句柄(该句柄唯一地标识了被接受的这个连接)

  1 #include <sys/types.h>
  2 #include <sys/socket.h>
  3 #include <netinet/in.h>
  4 #include <arpa/inet.h>
  5 #include <assert.h>
  6 #include <stdio.h>
  7 #include <unistd.h>
  8 #include <errno.h>
  9 #include <string.h>
 10 #include <fcntl.h>
 11 #include <stdlib.h>
 12 
 13 int main(int argc, char* argv[])
 14 {
 15         if(argc <= 2)
 16         {
 17                 printf("usage: %s ip_address port_number\n",basename(argv[0]));
 18                 return 1;
 19         }
 20         const char* ip = argv[1];
 21         int port = atoi(argv[2]);
 22         int ret = 0;
 23         struct sockaddr_in address;
 24         bzero(&address, sizeof(address));
 25         address.sin_family = AF_INET;
 26         inet_pton(AF_INET, ip, &address.sin_addr);
 27         address.sin_port = htons(port);
 28         int listenfd = socket(PF_INET, SOCK_STREAM, 0);
 29         assert(listenfd >= 0);
 30         ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
 31         assert(ret!= -1);
 32         ret = listen(listenfd, 4);
 33         assert(ret!= -1);
 34         struct sockaddr_in client_address;
 35         socklen_t client_addrlength = sizeof(client_address);
 36         int connfd = accept(listenfd, (struct sockaddr*)&client_address,&client_addrlength);
 37         if(connfd < 0)
 38         {
 39                 printf("errno is: %d\n", errno);
 40                 close(listenfd);
 41         }
 42 
 43         char buf[1024];
 44         fd_set read_fds;
 45         FD_ZERO(&read_fds);
 46         while(1)
 47         {
 48                 memset(buf, '\0', sizeof(buf));
 49                 //每次调用select前都要重新在read_fds中设置文件描述符connfd对应的位
 50                 //因为事件发生之后,文件描述符集合将被内核修改
 51                 FD_SET(connfd, &read_fds);
 52                 ret = select(connfd + 1, &read_fds, NULL, NULL, NULL);
 53                 if(ret<0)
 54                 {
 55                         printf("selection failure\n");
 56                         break;
 57                 }
 58 
 59                 //对于可读事件,检查connfd对应的位标志有没有被设置
 60                 if(FD_ISSET(connfd,&read_fds))
 61                 {
 62                         ret = recv(connfd, buf, sizeof(buf)-1, 0);
 63                         if(ret<=0)
 64                         {
 65                                 break;
 66                         }
 67                         printf("get %d bytes of normal data: %s\n", ret, buf);
 68                 }
 69         }
 70         close(connfd);
 71         close(listenfd);
 72         return 0;
 73 }

第一次telnet登陆到服务器程序,使用netstat可以见到listen监听队列有两条记录。输入hello world,可以看到服务器输出回应。再开一个终端使用telnet登陆到服务器程序,使用netstat可见四条记录。
在这里插入图片描述
在第二个终端中输入hello world one并回车,发现服务器程序没有返回消息,服务器程序中没有socket句柄绑定,所以服务器接收不到该终端发送的数据。运行netstat,可见第二个记录中发送缓冲区中有17字节数据。
在这里插入图片描述
在一个终端输入CTRL+],在第二个终端输入hello world one,服务器也不会显示。运行netstat还会发现4条记录。
在这里插入图片描述
在第一个终端输入quit退出telnet程序,可以发现服务器端退出运行,运行netstat可见只有一个记录,且状态变成了TIME_WAIT。
在这里插入图片描述
详细的select服务器程序见Linux编程入门三网络编程一:https://blog.csdn.net/asmartkiller/article/details/89352515
下面是改了的代码,但是代码有问题

  1 #include <sys/types.h>
  2 #include <sys/socket.h>
  3 #include <netinet/in.h>
  4 #include <arpa/inet.h>
  5 #include <unistd.h>
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <sys/wait.h>
 10 #include <strings.h>
 11 #include <errno.h>
 12 #define DEFAULT_PORT 6666
 13 
 14 int main(int argc, char ** argv)
 15 {
 16         //监听socket:serverfd 数据传输socket:acceptfd
 17         int serverfd,acceptfd;
 18         struct sockaddr_in my_addr;    //本机地址信息
 19         struct sockaddr_in their_addr; //客户地址信息
 20         unsigned int sin_size, myport = 6666, lisnum = 10;
 21         if((serverfd = socket(AF_INET, SOCK_STREAM, 0))==-1)
 22         {
 23                 perror("socket");
 24                 return -1;
 25         }
 26         printf("socket ok\n");
 27         my_addr.sin_family=AF_INET;
 28         my_addr.sin_port=htons(DEFAULT_PORT);
 29         my_addr.sin_addr.s_addr=INADDR_ANY;
 30         bzero(&(my_addr.sin_zero),0);
 31         if(bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))==-1)
 32         {
 33                 perror("bind");
 34                 return -2;
 35         }
 36         printf("bind ok\n");
 37         if(listen(serverfd,lisnum)==-1)
 38         {
 39                 perror("listen");
 40                 return -3;
 41         }
 42         printf("listen ok\n");
 43 
 44         fd_set client_fdset;  //监控文件描述符集合
 45         int maxsock = serverfd; //监控文件描述符中最大的文件号,初始最大为服务器socket文件号
 46         struct timeval tv;
 47         int client_sockfd[5]; //存放活动的sockfd
 48         bzero((void*)client_sockfd,sizeof(client_sockfd));
 49         int conn_amount = 0;    //用来记录描述符数量
 50 
 51         char buffer[1024];
 52         int ret = 0;
 53         while(1)
 54         {
 55                 //重置client_fdset集合
 56                 FD_ZERO(&client_fdset);
 57                 //加入服务器描述符到集合相应的位
 58                 printf("put server sockfd in fdset\n");
 59                 FD_SET(serverfd,&client_fdset);
 60 
 61                 //把活动的客户socket句柄加入到文件描述符中
 62                 printf("put client sockfd in fdset\n");
 63                 for(int i = 0; i < 5; ++i)
 64                 {
 65                         if(client_sockfd[i] != 0)
 66                         {
 67                                 FD_SET(client_sockfd[i],&client_fdset);
 68                         }
 69                 }
 70 
 71                 //设置超时时间
 72                 tv.tv_sec = 30; //30秒
 73                 tv.tv_usec = 0;
 74 
 75                 printf("select function\n");
 76                 ret = select(maxsock+1, &client_fdset, NULL, NULL, &tv);
 77                 if(ret<0)
 78                 {
 79                         perror("select error\n");
 80                         break;
 81                 }else if(ret == 0){
 82                         printf("timeout!\n");
 83                         continue;
 84                 }
 85 
 86                 //轮询各个文件描述符
 87                 int dis_flag = 0;
 88                 printf("loop check connect, conn_amount: %d\n", conn_amount);
 89                 for(int i = 0; i < conn_amount; ++i)
 90                 {
 91                         //FD_ISSET检查client_sockfd是否可读写,>0可读写
 92                         if(FD_ISSET(client_sockfd[i], &client_fdset))
 93                         {
 94                                 printf("start recv from client[%d]:\n", i);
 95                                 ret = recv(client_sockfd[i], buffer, 1024, 0);
 96                                 if(ret <= 0)
 97                                 {
 98                                         printf("client[%d] close\n", i);
 99                                         close(client_sockfd[i]);
100                                         //从client_fdset集去除client_sockfd对应的位
101                                         FD_CLR(client_sockfd[i], &client_fdset);
102                                         client_sockfd[i] = 0;
103                                         dis_flag = 1;
104                                 }else{
105                                         printf("recv from client[%d]:%s\n", i, buffer);
106                                 }
107                         }
108                 }
109                 if(dis_flag == 1)
110                 {
111                         dis_flag = 0;
112                         conn_amount--;
113                 }
114 
115 
116                 //检查是否有新连接,如果有,加入到client_sockfd中
117                 if(FD_ISSET(serverfd, &client_fdset)) //使用服务器sock句柄来检查是否有新连接
118                 {
119                         //接收连接
120                         struct sockaddr_in client_addr;
121                         size_t size = sizeof(struct sockaddr_in);
122                         //使用accept接收新连接,返回客户sock句柄
123                         printf("accept new client\n");
124                         int sock_client = accept(serverfd,(struct sockaddr*)(&client_addr),(un    signed int*)(&size));
125                         if(sock_client<0)
126                         {
127                                 perror("accept error!\n");
128                                 continue;
129                         }
130 
131                         if(conn_amount < 5)
132                         {
133                                 int j;
134                                 for(int j = 0; j < 5; ++j)
135                                 {
136                                         if(client_sockfd[j]==0)
137                                         {
138                                                 client_sockfd[j] = sock_client;
139                                                 conn_amount++;
140                                                 break;
141                                         }
142                                 }
143                                 bzero(buffer,1024);
144                                 strcpy(buffer, "this is server! welcome!\n");
145                                 send(sock_client, buffer, 1024, 0);
146                                 printf("new connection client[%d] %s:%d\n", conn_amount, inet_    ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
147                                 bzero(buffer,sizeof(buffer));
148                                 if(sock_client > maxsock)
149                                 {
150                                         maxsock = sock_client;
151                                 }else{
152                                         printf("maxsock is max\n");
153                                 }
154                         }else{
155                                 printf("too much connect, refuse this connect\n");
156                                 close(sock_client);
157                         }
158 
159                 }
160         }
161         for(int i = 0; i < 5; ++i)
162         {
163                 if(client_sockfd[i]!=0)
164                 {
165                         close(client_sockfd[i]);
166                 }
167         }
168         close(serverfd);
169         return 0;
170 }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

poll

poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。

#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
  • fds参数是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。每个pollfd结构体指定一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。
struct 
{
	int fd;              //文件描述符
	short events;  //注册的事件
	short revents; //实际发生的事件,由内核填充
};

fd成员指定文件描述符,events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;revents成员则由内核修改,以通知应用程序fd上实际发生了哪些事件。

  • nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:
typedef unsigned long int nfds_t;
  • timeout参数指定poll的超时值,单位是毫秒。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。

函数调用成功时,poll()返回结构体中revents域不为0的文件描述符个数:如果在超时前没有任何事件发生,poll()返回0。失败时,poll()返回-1,并设置errno。
serverpoll.cpp

  1 #include <sys/types.h>
  2 #include <sys/socket.h>
  3 #include <netinet/in.h>
  4 #include <arpa/inet.h>
  5 #include <unistd.h>
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <strings.h>
  9 #include <sys/wait.h>
 10 #include <string.h>
 11 #include <errno.h>
 12 #include <poll.h>
 13 #define IPADDRESS "127.0.0.1"
 14 #define PORT      6666
 15 #define MAXLINE   1024
 16 #define LISTENQ   5
 17 #define OPEN_MAX  1000
 18 #define INFTIM    -1
 19 
 20 int bind_and_listen()
 21 {
 22         int serverfd; //监听socket: serverfd
 23         struct sockaddr_in my_addr; //本机地址信息
 24         unsigned int sin_size;
 25         if((serverfd = socket(AF_INET, SOCK_STREAM, 0))==-1)
 26         {
 27                 perror("socket failure");
 28                 return -1;
 29         }
 30         printf("socket ok\n");
 31         my_addr.sin_family = AF_INET;
 32         my_addr.sin_port   = htons(PORT);
 33         my_addr.sin_addr.s_addr = INADDR_ANY;
 34         bzero(&(my_addr.sin_zero),0);
 35         if(bind(serverfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr))==-1)
 36         {
 37                 perror("bind failure");
 38                 return -2;
 39         }
 40         printf("bind ok\n");
 41         if(listen(serverfd,LISTENQ)==-1)
 42         {
 43                 perror("listen failure");
 44                 return -3;
 45         }
 46         printf("listen ok\n");
 47         return serverfd;
 48 }
 49 
 50 void do_poll(int listenfd)
 51 {
 52         struct pollfd clientfds[OPEN_MAX];
 53         //向pollfd中添加监听描述符
 54         clientfds[0].fd = listenfd;
 55         clientfds[0].events = POLLIN;
 56         //初始化pollfd中客户端连接描述符
 57         for(int i = 1;i < OPEN_MAX; ++i)
 58         {
 59                 clientfds[i].fd = -1;
 60         }
 61 
 62         int connfd, sockfd;
 63         struct sockaddr_in cliaddr;
 64         socklen_t cliaddrlen = sizeof(cliaddr);
 65         int maxi = 0;
 66         int nready;
 67 
 68         //循环处理
 69         while(1)
 70         {
 71                 //获取可用描述符的个数
 72                 nready = poll(clientfds,maxi+1,INFTIM);
 73                 if(nready == -1)
 74                 {
 75                         perror("poll error");
 76                         exit(1);
 77                 }
 78                 //测试监听描述符是否准备好
 79                 if(clientfds[0].revents & POLLIN)
 80                 {
 81                         //接受新连接
 82                         if((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen))=    =-1){
 83                                 if(errno == EINTR)
 84                                 {
 85                                         continue;
 86                                 }else{
 87                                         perror("accept error");
 88                                         exit(1);
 89                                 }
 90                         }
 91 
 92                         //接受新连接成功
 93                         fprintf(stdout, "accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_    addr),cliaddr.sin_port);
 94                         //将新连接描述符加到数组中
 95                         int j;
 96                         for(j = 1; j < OPEN_MAX; ++j)
 97                         {
 98                                 if(clientfds[j].fd<0)
 99                                 {
100                                         clientfds[j].fd = connfd;
101                                         break;
102                                 }
103                         }
104                         //过多连接,退出服务器程序
105                         if(j==OPEN_MAX)
106                         {
107                                 fprintf(stderr,"too many clients\n");
108                                 exit(1);
109                         }
110                         clientfds[j].events = POLLIN;
111                         //记录客户连接字的个数
112                         maxi = (j>maxi?j:maxi);
113                         if(--nready <= 0)
114                         {
115                                 continue;
116                         }
117                 }//处理新连接结束
118 
119                 //处理多个连接上客户端发来的包
120                 char buf[MAXLINE];
121                 memset(buf,0,MAXLINE);
122                 int readlen = 0;
123                 for(int i = 1; i <= maxi; i++)
124                 {
125                         if(clientfds[i].fd < 0)
126                         {
127                                 continue;
128                         }
129                         //测试客户描述符是否准备好
130                         if(clientfds[i].revents & POLLIN)
131                         {
132                                 //接受客户端发送的信息
133                                 readlen = read(clientfds[i].fd,buf,MAXLINE);
134                                 if(readlen == 0)
135                                 {
136                                         close(clientfds[i].fd);
137                                         clientfds[i].fd = -1;
138                                         continue;
139                                 }
140 
141                                 write(STDOUT_FILENO,buf,readlen);
142                                 write(clientfds[i].fd,buf,readlen);
143                         }
144                 }
145         }
146 }
147 
148 int main(int argc, char* argv[])
149 {
150         int listenfd = bind_and_listen();
151         if(listenfd<0)
152         {
153                 return 0;
154         }
155         do_poll(listenfd);
156         return 0;
157 }

clientpoll.cpp

  1 #include <sys/types.h>
  2 #include <sys/socket.h>
  3 #include <netinet/in.h>
  4 #include <arpa/inet.h>
  5 #include <unistd.h>
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <strings.h>
  9 #include <sys/wait.h>
 10 #include <string.h>
 11 #include <errno.h>
 12 #include <poll.h>
 13 #define MAXLINE 1024
 14 #define DEFAULT_PORT 6666
 15 #define max(a,b) (a>b)?a:b
 16 
 17 static void handle_connection(int sockfd);
 18 int main(int argc, char* argv[])
 19 {
 20         int connfd = 0;
 21         int cLen = 0;
 22         struct sockaddr_in client;
 23         if(argc < 2)
 24         {
 25                 printf("Usage: clientent [server IP address]\n");
 26                 return -1;
 27         }
 28         client.sin_family=AF_INET;
 29         client.sin_port=htons(DEFAULT_PORT);
 30         client.sin_addr.s_addr=inet_addr(argv[1]);
 31         connfd = socket(AF_INET,SOCK_STREAM,0);
 32         if(connfd<0)
 33         {
 34                 perror("socket error");
 35                 return -1;
 36         }
 37         if(connect(connfd, (struct sockaddr*)&client, sizeof(client))<0)
 38         {
 39                 perror("connect error");
 40                 return -1;
 41         }
 42         //处理连接描述符
 43         handle_connection(connfd);
 44         return 0;
 45 }
 46 static void handle_connection(int sockfd)
 47 {
 48         char sendline[MAXLINE],recvline[MAXLINE];
 49         struct pollfd pfds[2];
 50         int n;
 51         //添加连接描述符
 52         pfds[0].fd = sockfd;
 53         pfds[0].events = POLLIN;
 54         //添加标准输入描述符
 55         pfds[1].fd = STDIN_FILENO;
 56         pfds[1].events = POLLIN;
 57         while(1)
 58         {
 59                 poll(pfds,2,-1);
 60                 if(pfds[0].revents & POLLIN)
 61                 {
 62                         n = read(sockfd,recvline,MAXLINE);
 63                         if(n==0)
 64                         {
 65                                 fprintf(stderr, "client: server is closed.\n");
 66                                 close(sockfd);
 67                         }
 68                         write(STDOUT_FILENO,recvline,n);
 69                 }
 70                 //测试标准输入是否准备好
 71                 if(pfds[1].revents & POLLIN)
 72                 {
 73                         n = read(STDIN_FILENO,sendline,MAXLINE);
 74                         if(n==0)
 75                         {
 76                                 shutdown(sockfd,SHUT_WR);
 77                                 continue;
 78                         }
 79                         write(sockfd,sendline,n);
 80                 }
 81         }
 82 }

在这里插入图片描述

epoll

epoll()是在Linux 2.6内核中提出,是select和poll的增强版。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符或事件集,用户空间和内核空间之间的数据拷贝只需一次。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
epoll接口

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

创建一个epoll的句柄,size用来告诉内核要监听的数目。当创建好epoll句柄后,它就会占用一个fd值,在使用完epoll后,必须调用close()关闭。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。
epoll_ctl事件注册函数,第一个参数是epoll_create()的返回值,第三个参数是要操作的文件描述符,第二个参数指定操作:

  • EPOLL_CTL_ADD:往事件表中注册fd上的事件
  • EPOLL_CTL_MOD:修改fd上的注册事件
  • EPOLL_CTL_DEL:删除fd上的注册事件
    第四个参数是告诉内核需要监听的事件:
struct epoll_event
{
	__uint32_t events;  //epoll事件
	epoll_data_t data;  //用户数据
};
typeef union epoll_data
{
	void* ptr;
	inf fd;
	uint32_t u32;
	uint64_t u64; 
} epool_data_t;

其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上E。但是epoll有两个额外的事件类型-EPOLLET和EPOLLONESHOT。
data成员用于存储用户数据。epoll_data_t是一个联合体,其4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体,不能同时使用其ptr成员和fd成员,因此如果要将文件描述符和用户数据关联起来,以实现快速的数据访问,只能使用其他手段,比如放弃使用epoll_data_t的fd成员,而在ptr指向的用户数据中包含fd。
epoll_wait在一段超时时间内等待一组文件描述符上的事件,参数events用于从内核得到事件的集合,maxevents告诉内核这个events有多大,且maxevents的值不能大于创建epoll_create时的size。参数timeout是超时时间。该函数返回需要处理的事件数目,如返回0表示已超时,失败后返回-1并设置errno。
epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出检测到的就绪事件。极大提高应用程序索引就绪文件描述符的效率。

serverepoll.cpp代码:服务器会根据客户端发来的内容回包,所以读到数据后,要把事件转为可写状态,由写事件进行发包

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <errno.h>
  5 #include <netinet/in.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <sys/epoll.h>
  9 #include <unistd.h>
 10 #include <sys/types.h>
 11 #define IPADDRESS   "127.0.0.1"
 12 #define PORT        6666
 13 #define MAXSIZE     1024
 14 #define LISTENQ     5
 15 #define FDSIZE      1000
 16 #define EPOLLEVENTS 100
 17 
 18 //创建套接字并绑定
 19 int socket_bind(const char* ip, int port);
 20 //IO多路复用epoll
 21 void do_epoll(int listenfd);
 22 //事件处理函数
 23 void handle_events(int epollfd, struct epoll_event *events, int num, int listenfd, char* buf);
 24 //处理接收到的连接
 25 void handle_accpet(int epollfd, int listenfd);
 26 //读处理
 27 void do_read(int epollfd, int fd, char* buf);
 28 //写处理
 29 void do_write(int epollfd, int fd, char* buf);
 30 //添加事件
 31 void add_event(int epollfd, int fd, int state);
 32 //修改事件
 33 void modify_event(int epollfd, int fd, int state);
 34 //删除事件
 35 void delete_event(int epollfd, int fd, int state);
 36 
 37 int main(int argc, char* argv[])
 38 {
 39         int listenfd;
 40         listenfd = socket_bind(IPADDRESS, PORT);
 41         listen(listenfd, LISTENQ);
 42         do_epoll(listenfd);
 43         return 0;
 44 }
 45 
 46 int socket_bind(const char* ip, int port)
 47 {
 48         int listenfd;
 49         struct sockaddr_in servaddr;
 50         listenfd = socket(AF_INET,SOCK_STREAM,0);
 51         if(listenfd == -1)
 52         {
 53                 perror("socket error");
 54                 exit(1);
 55         }
 56         bzero(&servaddr,sizeof(servaddr));
 57         servaddr.sin_family = AF_INET;
 58         inet_pton(AF_INET,ip,&servaddr.sin_addr);
 59         servaddr.sin_port = htons(port);
 60         if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1)
 61         {
 62                 perror("bind error");
 63                 exit(1);
 64         }
 65         return listenfd;
 66 }
 67 
 68 void do_epoll(int listenfd)
 69 {
 70         int epollfd;
 71         struct epoll_event events[EPOLLEVENTS]; //用于从内核得到事件的集合
 72         int ret;
 73         char buf[MAXSIZE];
 74         memset(buf,0,MAXSIZE);
 75         //创建一个描述符
 76         epollfd = epoll_create(FDSIZE);
 77         //注册监听描述符事件
 78         add_event(epollfd,listenfd,EPOLLIN);
 79         while(1)
 80         {
 81                 //等待事件产生 阻塞方式
 82                 ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
 83                 handle_events(epollfd,events,ret,listenfd,buf);
 84         }
 85         close(epollfd);
 86 }
 87 
 88 void add_event(int epollfd, int fd, int state)
 89 {
 90         //向内核注册需监听的事件
 91         struct epoll_event ev;
 92         ev.events = state;
 93         ev.data.fd = fd;
 94         epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
 95 }
 96 
 97 void handle_events(int epollfd, struct epoll_event *events, int num, int listenfd, char *buf)
 98 {
 99         int fd;
100         //发生的事件进行遍历
101         for(int i = 0; i < num; i++)
102         {
103                 fd = events[i].data.fd;
104                 //根据描述符类型和事件类型进行chuli
105                 if((fd == listenfd) && (events[i].events & EPOLLIN))
106                 {
107                         handle_accpet(epollfd,listenfd);
108                 }else if(events[i].events & EPOLLIN){
109                         do_read(epollfd,fd,buf);
110                 }else if(events[i].events & EPOLLOUT){
111                         do_write(epollfd,fd,buf);
112                 }
113         }
114 }
115 
116 void handle_accpet(int epollfd, int listenfd)
117 {
118         int clifd;
119         struct sockaddr_in cliaddr;
120         socklen_t cliaddrlen;
121         //使用accept函数获取客户端socket句柄
122         clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
123         if(clifd == -1)
124         {
125                 perror("accpet error");
126         }else{
127                 printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_    port);
128                 //向内核注册新客户端socket描述符事件
129                 add_event(epollfd,clifd,EPOLLIN);
130         }
131 }
132 
133 void do_read(int epollfd, int fd, char* buf)
134 {
135         int nread;
136         nread = read(fd,buf,MAXSIZE);
137         if(nread == -1)
138         {
139                 //读客户端socket出错,关闭该描述符,并从内核监听事件中注销
140                 perror("read error");
141                 close(fd);
142                 delete_event(epollfd,fd,EPOLLIN);
143         }else if(nread == 0){
144                 fprintf(stderr, "client close.\n");
145                 close(fd);
146                 delete_event(epollfd,fd,EPOLLIN);
147         }else{
148                 printf("read message is: %s", buf);
149                 //服务器读到客户端发送的数据,修改描述符对应的事件,由读改为写
150                 //将数据回送给客户端
151                 modify_event(epollfd,fd,EPOLLOUT);
152         }
153 }
154 
155 void do_write(int epollfd, int fd, char* buf)
156 {
157         int nwrite;
158         nwrite = write(fd,buf,strlen(buf));
159         if(nwrite == -1)
160         {
161                 perror("write error");
162                 close(fd);
163                 delete_event(epollfd,fd,EPOLLOUT);
164         }else{
165                 //修改描述符对应的事件,由写改为读,继续监听客户端
166                 modify_event(epollfd,fd,EPOLLIN);
167         }
168         memset(buf,0,MAXSIZE);
169 }
170 
171 void delete_event(int epollfd,int fd,int state)
172 {
173         struct epoll_event ev;
174         ev.events = state;
175         ev.data.fd =fd;
176         epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
177 }
178 
179 void modify_event(int epollfd,int fd,int state)
180 {
181         struct epoll_event ev;
182         ev.events = state;
183         ev.data.fd = fd;
184         epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
185 }

clientepoll.cpp代码:控制STDIN_FILENO、STDOUT_FILENO和sockfd这3个描述符

  1 #include <netinet/in.h>
  2 #include <sys/socket.h>
  3 #include <stdio.h>
  4 #include <string.h>
  5 #include <stdlib.h>
  6 #include <sys/epoll.h>
  7 #include <time.h>
  8 #include <unistd.h>
  9 #include <sys/types.h>
 10 #include <arpa/inet.h>
 11 #define MAXSIZE      1024
 12 #define IPADDRESS    "127.0.0.1"
 13 #define SERV_PORT    6666
 14 #define FDSIZE       1024
 15 #define EPOLLEVENTS  20
 16 void handle_connection(int sockfd);
 17 void handle_events(int epollfd, struct epoll_event *events, int num, int sockfd, char* buf);
 18 void do_read(int epollfd, int fd, int sockfd, char* buf);
 19 void do_write(int epollfd, int fd, int sockfd, char* buf);
 20 void add_event(int epollfd, int fd, int state);
 21 void delete_event(int epollfd, int fd, int state);
 22 void modify_event(int epollfd, int fd, int state);
 23 int count = 0;
 24 
 25 int main(int argc, char* argv[])
 26 {
 27         int sockfd;
 28         struct sockaddr_in servaddr;
 29         sockfd = socket(AF_INET,SOCK_STREAM,0);
 30         bzero(&servaddr,sizeof(servaddr));
 31         servaddr.sin_family = AF_INET;
 32         servaddr.sin_port = htons(SERV_PORT);
 33         inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
 34         connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
 35 
 36         handle_connection(sockfd);
 37         close(sockfd);
 38         return 0;
 39 }
 40 void handle_connection(int sockfd)
 41 {
 42         int epollfd;
 43         struct epoll_event events[EPOLLEVENTS];
 44         char buf[MAXSIZE];
 45         int ret;
 46 
 47         epollfd = epoll_create(FDSIZE);
 48         //先向内核注册标准输入
 49         add_event(epollfd,STDIN_FILENO,EPOLLIN);
 50         while(1)
 51         {
 52                 ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
 53                 handle_events(epollfd,events,ret,sockfd,buf);
 54         }
 55         close(epollfd);
 56 }
 57 void handle_events(int epollfd, struct epoll_event *events, int num, int sockfd, char* buf)
 58 {
 59         int fd;
 60         for(int i = 0; i < num; i++)
 61         {
 62                 fd = events[i].data.fd;
 63                 if(events[i].events & EPOLLIN)
 64                 {
 65                         do_read(epollfd,fd,sockfd,buf);
 66                 }else if(events[i].events & EPOLLOUT){
 67                         do_write(epollfd,fd,sockfd,buf);
 68                 }
 69         }
 70 }
 71 void do_read(int epollfd,int fd,int sockfd,char* buf)
 72 {
 73         int nread;
 74         nread = read(fd,buf,MAXSIZE);
 75         if(nread == -1)
 76         {
 77                 perror("read error");
 78                 close(fd);
 79         }else if(nread==0){
 80                 fprintf(stderr,"server close.\n");
 81                 close(fd);
 82         }else{
 83                 //区分是用户输入还是服务器发送
 84                 if(fd == STDIN_FILENO){
 85                         //向内核注册socket文件描述符EPOLLOUT,表明需要向服务器发送数据
 86                         add_event(epollfd,sockfd,EPOLLOUT);
 87                 }else{
 88                         //向内核注销socket文件描述符FPOLLIN事件,表明接收完服务器回送的数据
 89                         delete_event(epollfd,sockfd,EPOLLIN);
 90                         //向内核注册标准输出文件描述符EPOLLOUT事件,向屏幕输出数据
 91                         add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
 92                 }
 93         }
 94 }
 95 void do_write(int epollfd,int fd,int sockfd,char* buf)
 96 {
 97         int nwrite;
 98         char temp[100];
 99         buf[strlen(buf)-1]='\0';
100         snprintf(temp,sizeof(temp),"%s_%02d\n",buf,count++);
101         nwrite = write(fd,temp,strlen(temp));
102         if(nwrite == -1)
103         {
104                 perror("write error");
105                 close(fd);
106         }else{
107                 if(fd==STDOUT_FILENO){
108                         delete_event(epollfd,fd,EPOLLOUT);
109                 }else{
110                         //当向服务器发送数据后,将事件改为EPOLLIN,以监听服务器数据
111                         modify_event(epollfd,fd,EPOLLIN);
112                 }
113         }
114         memset(buf,0,MAXSIZE);
115 }
116 void add_event(int epollfd,int fd,int state)
117 {
118         struct epoll_event ev;
119         ev.events = state;
120         ev.data.fd = fd;
121         epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
122 }
123 void delete_event(int epollfd,int fd,int state)
124 {
125         struct epoll_event ev;
126         ev.events = state;
127         ev.data.fd = fd;
128         epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
129 }
130 void modify_event(int epollfd,int fd,int state)
131 {
132         struct epoll_event ev;
133         ev.events = state;
134         ev.data.fd = fd;
135         epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
136 }

在这里插入图片描述

关于LT和ET模式和EPOLLONESHOT事件,请看网络编程三

参考:
Linux高性能服务器编程 游双
后台开发核心技术与应用实践 徐晓鑫

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值