Linux编程入门三网络编程二

网友投稿 629 2022-10-14

Linux编程入门三网络编程二

Linux编程入门三网络编程二

同步/异步IO

I/O模型

读写操作和阻塞阶段

同步

阻塞I/O

程序阻塞于读写函数

同步

I/O复用

程序阻塞于I/O复用系统调用,但可同时监听多个I/O事件。对I/O本身的读写操作是非阻塞的

同步

信号驱动I/O(SIGIO信号)

信号触发读写就绪事件,用户程序执行读写操作。程序没有阻塞阶段

异步

异步I/O

内核执行读写操作并触发读写完成事件。程序没有阻塞阶段

I/O复用

select、poll和epoll的区别

系统调用

select

poll

epoll

事件集合

用户通过3个fd_set参数分别传入感兴趣的可读、可写及异常等事件,内核通过对这些参数的在线修改来反馈其中的就绪事件。这使得用户每次调用select都要重置这3个参数

统一处理所有事件类型,因此只需一个事件集参数pollfd。用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents反馈其中就绪的事件

内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无须反复传入用户感兴趣的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件

应用程序索引就绪文件描述符的时间复杂度

O(n)

O(n)

O(1)

最大支持文件描述符数

一般有最大值限制

65535

65535

工作模式

LT

LT

支持ET高效模式

内核实现和工作效率

采用轮询方式来检测就绪事件,算法时间复杂度为O(n)

采用轮询方式来检测就绪事件,算法时间复杂度为O(n)

采用回调方式来检测就绪事件,算法时间复杂度为O(1)

select

#include int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

nfds参数指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。fd_set结构体的定义如下:

#include #define __FD_SETSIZE 1024 //fd_set能容纳的文件描述符数量#include #define FD_SETSIZE __FD_SETSIZEtypedef long int __fd_mask;#undef __NFDBITS#define __NFDBITS (8*(int)sizeof(__fd_mask)) //8->1byte为8bitstypedef 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 int listen(int sockfd, int backlog);

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

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 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 #include int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

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

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 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编程入门三网络编程一下面是改了的代码,但是代码有问题

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 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 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 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 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 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 //根据描述符类型和事件类型进行chuli105 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 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高性能服务器编程 游双 后台开发核心技术与应用实践 徐晓鑫

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Perfect:Swift 服务端框架
下一篇:Firestorm- firestore 的 ORM 框架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~