主要用一个例程来讲解poll,包含客户端和服务器端。

  poll函数没有FD_SETSIZE的限制

int poll(struct pollfd * fdarray, unsigned long nfds, int timeout)

  第一个参数:IO数组
第二个:检测的IO个数
struct pollfd
{
int fd;//文件描述符
short events;//请求的事件(感兴趣的事件)
short revents;//返回的事件
};

客户端程序:

  1 /*
2 #include <poll.h>
3 int poll(struct pollfd * fdarray,unsigned long nfds,int timeout)
4 第一个参数:IO数组
5 第二个:检测的IO个数
6 struct pollfd
7 {
8 int fd;//文件描述符
9 short events;//请求的事件
10 short revents;//返回的事件
11 };
12
13 */
14 #include<unistd.h>
15 #include<sys/types.h>
16 #include<sys/socket.h>
17 #include<string.h>
18 #include<stdlib.h>
19 #include<stdio.h>
20 #include<errno.h>
21 #include<netinet/in.h>
22 #include<arpa/inet.h>
23 #include<signal.h>
24 #include <sys/time.h>
25
26 #define ERR_EXIT(m)\
27 do\
28 {\
29 perror(m);\
30 exit(EXIT_FAILURE);\
31 }while(0)
32 ssize_t readn(int fd,void *buf,size_t count)
33 {
34 size_t nleft=count;
35 ssize_t nread;
36 char *bufp=(char*)buf;
37 while(nleft>0)
38 {
39 if((nread=read(fd,bufp,nleft))<0)
40 {
41 if(errno==EINTR)
42 continue;
43 else
44 return -1;
45 }
46 else if(nread==0)
47 return (count-nleft);
48 bufp+=nread;
49 nleft-=nread;
50 }
51 return count;
52 }
53 ssize_t writen(int fd, const void *buf, size_t count)
54 {
55 size_t nleft=count;
56 ssize_t nwritten;
57 char *bufp=(char*)buf;
58 while(nleft>0)
59 {
60 if((nwritten=write(fd,bufp,nleft))<=0)
61 {
62 if(errno==EINTR)
63 continue;
64 return -1;
65 }else if(nwritten==0)
66 continue;
67 bufp+=nwritten;
68 nleft-=nwritten;
69 }
70 return count;
71
72 }
73 ssize_t recv_peek(int sockfd,void *buf,size_t len)
74 {
75 while(1)
76 {
77 int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
78 if(ret==-1&&errno==EINTR)
79 continue;
80 return ret;
81 }
82 }
83 //偷窥方案实现readline避免一次读取一个字符
84 ssize_t readline(int sockfd,void * buf,size_t maxline)
85 {
86 int ret;
87 int nread;
88 size_t nleft=maxline;
89 char *bufp=(char*)buf;
90 while(1)
91 {
92 ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
93 if(ret<0)
94 return ret;
95 else if(ret==0)
96 return ret;
97 nread=ret;
98 int i;
99 for(i=0;i<nread;i++)
100 {
101 if(bufp[i]=='\n')
102 {
103 ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
104 if(ret!=i+1)
105 exit(EXIT_FAILURE);
106 return ret;
107 }
108 }
109 if(nread>nleft)
110 exit(EXIT_FAILURE);
111 nleft-=nread;
112 ret=readn(sockfd,bufp,nread);
113 if(ret!=nread)
114 exit(EXIT_FAILURE);
115 bufp+=nread;//移动指针继续窥看
116 }
117 return -1;
118 }
119 void echo_cli(int sock)
120 {
121 /*
122 char sendbuf[1024]={0};
123 char recvbuf[1024]={0};
124 while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默认有换行符
125 {
126
127 writen(sock,sendbuf,strlen(sendbuf));
128 int ret=readline(sock,recvbuf,1024);
129 if(ret==-1)
130 ERR_EXIT("readline");
131 else if(ret==0)
132 {
133 printf("service closed\n");
134 break;
135 }
136 fputs(recvbuf,stdout);
137 memset(sendbuf,0,sizeof(sendbuf));
138 memset(recvbuf,0,sizeof(recvbuf));
139 }
140 */
141 char sendbuf[1024]={0};
142 char recvbuf[1024]={0};
143 fd_set rset;
144 FD_ZERO(&rset);//初始化
145 int nready;//准备好的个数
146 int maxfd;
147 int fd=fileno(stdin);//防止STDIN_FILLENO被重定向
148 if(fd>sock)
149 maxfd=fd;
150 else
151 maxfd=sock;
152 while(1)
153 {
154 FD_SET(fd,&rset);//循环中
155 FD_SET(sock,&rset);
156 nready=select(maxfd+1,&rset,NULL,NULL,NULL);
157 if(nready==-1)
158 ERR_EXIT("select error");
159 if(nready==0)
160 continue;
161 if(FD_ISSET(sock,&rset))
162 {
163 int ret=readline(sock,recvbuf,sizeof(recvbuf));
164 if(ret==-1)
165 ERR_EXIT("readline error");
166 else if(ret==0)
167 {
168 ERR_EXIT("serve closed");
169 break;
170 }
171 fputs(recvbuf,stdout);
172 memset(recvbuf,0,sizeof(recvbuf));
173 }
174 if(FD_ISSET(fd,&rset))
175 {
176 if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
177 break;
178 writen(sock,sendbuf,strlen(sendbuf));
179 memset(sendbuf,0,sizeof(sendbuf));
180 }
181 }
182 close(sock);
183
184 }
185 void handle_sigpipe(int sig)
186 {
187 printf("recive a signal=%d\n",sig);
188
189 }
190 int main(void)
191 {
192 signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信号,默认终止进程
193 int sock;
194 if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
195 ERR_EXIT("socket error");
196
197 struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
198 memset(&servaddr,0,sizeof(servaddr));
199 servaddr.sin_family=AF_INET;
200 servaddr.sin_port=htons(5188);
201
202 servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址
203 //inet_aton("127.0.0.1",&servaddr.sin_addr);
204
205 if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
206 ERR_EXIT("connect");
207
208 //利用getsockname获取客户端本身地址和端口,即为对方accept中的对方套接口
209 struct sockaddr_in localaddr;
210 socklen_t addrlen=sizeof(localaddr);
211 if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0)
212 ERR_EXIT("getsockname error");
213 printf("local IP=%s, local port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
214 //使用getpeername获取对方地址
215 echo_cli(sock);//选择一个与服务器通信
216 return 0;
217 }

 

服务器端程序:

  1 /*
2 #include <poll.h>
3 int poll(struct pollfd * fdarray,unsigned long nfds,int timeout)
4 第一个参数:IO数组
5 第二个:检测的IO个数
6 struct pollfd
7 {
8 int fd;//文件描述符
9 short events;//请求的事件
10 short revents;//返回的事件
11 };
12
13 */
14 #include<unistd.h>
15 #include<sys/types.h>
16 #include<sys/socket.h>
17 #include<string.h>
18 #include<stdlib.h>
19 #include<stdio.h>
20 #include<errno.h>
21 #include<netinet/in.h>
22 #include<arpa/inet.h>
23 #include<signal.h>
24 #include<sys/wait.h>
25 #include<poll.h>
26 #define ERR_EXIT(m)\
27 do\
28 {\
29 perror(m);\
30 exit(EXIT_FAILURE);\
31 }while(0)
32 ssize_t readn(int fd,void *buf,size_t count)
33 {
34 size_t nleft=count;
35 ssize_t nread;
36 char *bufp=(char*)buf;
37 while(nleft>0)
38 {
39 if((nread=read(fd,bufp,nleft))<0)
40 {
41 if(errno==EINTR)
42 continue;
43 else
44 return -1;
45 }
46 else if(nread==0)
47 return (count-nleft);
48 bufp+=nread;
49 nleft-=nread;
50 }
51 return count;
52 }
53 ssize_t writen(int fd, const void *buf, size_t count)
54 {
55 size_t nleft=count;
56 ssize_t nwritten;
57 char *bufp=(char*)buf;
58 while(nleft>0)
59 {
60 if((nwritten=write(fd,bufp,nleft))<=0)
61 {
62 if(errno==EINTR)
63 continue;
64 return -1;
65 }else if(nwritten==0)
66 continue;
67 bufp+=nwritten;
68 nleft-=nwritten;
69 }
70 return count;
71
72 }
73 ssize_t recv_peek(int sockfd,void *buf,size_t len)
74 {
75 while(1)
76 {
77 int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
78 if(ret==-1&&errno==EINTR)
79 continue;
80 return ret;
81 }
82 }
83 //偷窥方案实现readline避免一次读取一个字符
84 ssize_t readline(int sockfd,void * buf,size_t maxline)
85 {
86 int ret;
87 int nread;
88 size_t nleft=maxline;
89 char *bufp=(char*)buf;
90 while(1)
91 {
92 ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
93 if(ret<0)
94 return ret;
95 else if(ret==0)
96 return ret;
97 nread=ret;
98 int i;
99 for(i=0;i<nread;i++)
100 {
101 if(bufp[i]=='\n')
102 {
103 ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
104 if(ret!=i+1)
105 exit(EXIT_FAILURE);
106 return ret;
107 }
108 }
109 if(nread>nleft)
110 exit(EXIT_FAILURE);
111 nleft-=nread;
112 ret=readn(sockfd,bufp,nread);
113 if(ret!=nread)
114 exit(EXIT_FAILURE);
115 bufp+=nread;//移动指针继续窥看
116 }
117 return -1;
118 }
119 /*
120 signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);
121
122 signal(SIGCHLD, SIG_IGN);
123
124 因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。(Linux Only)
125
126 对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
127
128 signal(SIGPIPE, SIG_IGN);
129
130 TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道,
131 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制,
132 一个端点无法获知对端的socket是调用了close还是shutdown.
133
134 对一个已经收到FIN包的socket调用read方法,
135 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送).
136 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以,
137 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.
138
139 为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数:
140
141 signal(SIGPIPE, SIG_IGN);
142
143 这样, 第二次调用write方法时, 会返回-1, 同时errno置为SIGPIPE. 程序便能知道对端已经关闭.
144
145 */
146 void handle_sigchld(int sig)
147 {
148
149 while(waitpid(-1,NULL, WNOHANG)>0)
150 ;
151
152 }
153 void handle_sigpipe(int sig)
154 {
155 printf("recevie a sig=%d\n",sig);//打印,不退出服务器进程
156 }
157 int main(void)
158 {
159 int count=0;//测试描述符限制
160 signal(SIGCHLD,handle_sigchld);//避免僵死进程
161 signal(SIGPIPE,handle_sigpipe);//忽略pipe信号。如果客户端关闭套接字close,而服务器端又调用一次write,服务器会接受到RST.如果服务器再调用write产生SIGPIPE
162 int listenfd;
163 if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
164 ERR_EXIT("socket error");
165 //本地协议地址赋给一个套接字
166 struct sockaddr_in servaddr;
167 memset(&servaddr,0,sizeof(servaddr));
168 servaddr.sin_family=AF_INET;
169 servaddr.sin_port=htons(5188);
170 servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址
171
172 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
173 int on=1;
174 if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
175 ERR_EXIT("setsockopt error");
176 //绑定本地套接字
177 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
178 ERR_EXIT("bind error");
179 if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
180 ERR_EXIT("listen error");
181
182 struct sockaddr_in peeraddr;//对方套接字地址
183 socklen_t peerlen=sizeof(peeraddr);
184
185 //poll没有fd_set的限制。若进程中最多打开2048个文件描述符
186 struct pollfd client[2048];
187 int i=0;
188 for(i=0;i<2048;i++)
189 {
190 client[i].fd=-1;//保存已连接套接字fd.
191 }
192 int conn;//已连接套接字(主动套接字)
193 int nready;//准备好的事件数,返回值
194 int maxi=0;//当前client数组中正在使用的最大下标值,关心的事件个数
195 client[0].fd=listenfd;
196 client[0].events=POLLIN;//只对监听套接口的可读事件感兴趣
197 while(1)
198 {
199 nready=poll(client,maxi+1,-1);//[0,maxi]
200 if(nready==-1)
201 {
202 if(errno==EINTR)
203 continue;
204 ERR_EXIT("select error");
205 }
206 if(nready==0)
207 continue;
208 //判断是不是监听套接口产生了事件
209 //返回的事件与上 POLLIN,如果等于1 表明监听套接字产生了可读事件
210 if(client[0].revents & POLLIN)
211 {
212 conn=accept(listenfd,(struct sockaddr *)&peeraddr,&peerlen);
213 if(conn==-1)
214 ERR_EXIT("accept");
215 for(i=0;i<2018;i++)
216 {
217 if(client[i].fd<0)
218 {
219 client[i].fd=conn;
220 client[i].events=POLLIN;
221 if(i>maxi)
222 maxi=i;
223 break;
224 }
225 }
226 if(i==2048)
227 {
228 fprintf(stderr,"too many clients\n");
229 exit(EXIT_FAILURE);
230 }
231 printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
232 printf("count=%d\n",++count);
233
234 if(--nready<=0)
235 continue;
236 }
237 //判断是不是已连接套接口产生了事件
238 for(i=1;i<=maxi;i++)
239 {
240 conn=client[i].fd;
241 if(conn==-1) continue;
242 if(client[i].revents & POLLIN)
243 {
244 char recvbuf[1024]={0};
245 int ret=readline(conn,recvbuf,1024);
246 if(ret==-1)
247 ERR_EXIT("readline");
248 if(ret==0)
249 {
250 printf("client close\n");
251 client[i].fd=-1;
252 close(conn);
253 }
254 fputs(recvbuf,stdout);
255 writen(conn,recvbuf,strlen(recvbuf));
256 if(--nready<=0)
257 break;
258 }
259 }
260 }
261 return 0;
262 }

Makefile文件

.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN= poll_cli  poll_ser
all:$(BIN)
%.o:%.c
    $(CC) $(CFLAGS)  -c $^ -o $@
clean:
    rm -f *.o $(BIN)

IO复用之poll的更多相关文章

  1. IO复用: select 和poll 到epoll

    linux 提供了select.poll和epoll三种接口来实现多路IO复用.下面总结下这三种接口. select 该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经 ...

  2. 第十六篇:初探IO复用

    前言 在之前的文章中,我具体实现了一个并发回射服务器并给它加载了僵尸子进程的自动清理信号机制.在正常情况下,它已经可以很好地工作了,但它能否合理应对一些特殊情况呢? 问题发现 先来看看当服务器的客户子 ...

  3. 初探IO复用

    前言 在之前的文章中,我具体实现了一个并发回射服务器并给它加载了僵尸子进程的自动清理信号机制.在正常情况下,它已经可以很好地工作了,但它能否合理应对一些特殊情况呢? 问题发现 先来看看当服务器的客户子 ...

  4. select、poll、epoll三组IO复用

    int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout)//其中n ...

  5. 【Unix网络编程】chapter6 IO复用:select和poll函数

    chapter6 6.1 概述 I/O复用典型使用在下列网络应用场合. (1):当客户处理多个描述符时,必须使用IO复用 (2):一个客户同时处理多个套接字是可能的,不过不叫少见. (3):如果一个T ...

  6. 多路IO复用模型--select, poll, epoll

    select 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 2.解决1024以下客户端时使用se ...

  7. Select、Poll、Epoll IO复用技术

    简介 目前多进程方式实现的服务器端,一次创建多个工作子进程来给客户端提供服务, 但是创建进程会耗费大量资源,导致系统资源不足 IO复用技术就是让一个进程同时为多个客户端端提供服务 IO复用技术 之 S ...

  8. IO复用的三种方法(select,poll,epoll)深入理解

    (一)IO复用是Linux中的IO模型之一,IO复用就是进程告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程处理,从而不会在单个IO上阻塞了,Linux中,提供 ...

  9. Linux网络编程-IO复用技术

    IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...

随机推荐

  1. 多测师讲解——python002.2练习题

    # # 作业:第二天# ## # 1.求出1 / 1 + 1 / 3 + 1 / 5--+1 / 99的和 (1分之一+1分之三+1分支5....)# # 2.用循环语句,计算2 - 10之间整数的循 ...

  2. day32 Pyhton 模块02复习 序列化

    一. 什么是序列化 在我们存储数据或者网络传输数据的时候. 需要对我们的对象进行处理. 把对象处理成方便存储和传输的数据格式. 这个过程叫序列化 不同的序列化, 结果也不同. 但是目的是一样的. 都是 ...

  3. File、Blob、ArrayBuffer等文件类的对象有什么区别和联系

    前言 在前端中处理文件时会经常遇到File.Blob.ArrayBuffer以及相关的处理方法或方式如FileReader.FormData等等这些名词,对于这些常见而又不常见的名词,我相信大多数人对 ...

  4. windows搭建SVN服务

    下载`TortoiseSVN 官网下载址:https://www.visualsvn.com/visualsvn/download/tortoisesvn/ 根据自己系统环境选择 安装Tortoise ...

  5. 利用Docker搭建开发环境

    一. 前言 随着平台的不断壮大,项目的研发对于开发人员而言,对于外部各类环境的依赖逐渐增加,特别是针对基础服务的依赖.这些现象导致开 发人员常常是为了简单从而直接使用公有的基础组件进行协同开发,在出现 ...

  6. MySQL死锁系列-线上死锁问题排查思路

    前言 MySQL 死锁异常是我们经常会遇到的线上异常类别,一旦线上业务日间复杂,各种业务操作之间往往会产生锁冲突,有些会导致死锁异常.这种死锁异常一般要在特定时间特定数据和特定业务操作才会复现,并且分 ...

  7. 三种方式获取SSMS连接密码

    内网渗透是有的时候会遇到对方SSMS没断开连接正连着别的机器的mssql此时有两种方法可以获取sa密码 当密码强度较弱时可以使用第一只方式,第一种方式解不开的情况下可以使用后面二种方式 1.直接查询s ...

  8. was 发布版本的步骤:

    was 发布版本的步骤:实际使用:1.备份应用 (备份应用下的war包,tar -czvf app.20200418.tar.gz app.war)2.停服务(was 控制台停,方便)3.替换该版本文 ...

  9. h5 语义话标签的意义

    使用语义话标签的意义 语义类标签对开发者更为友好,使用语义类标签增强了可读性,即便是在没有 CSS 的时 候,开发者也能够清晰地看出网页的结构,也更为便于团队的开发和维护. 除了对人类友好之外,语义类 ...

  10. Spring学习-Bean的基本概念知识

    4月份开始复习一遍spring相关知识.让自己巩固一下spring大法的深奥益处,所以就看了大佬的博客,转载留下来日后继续研读.认为重点的标记为红色 转载自:http://www.cnblogs.co ...