I/O多路复用(select,poll)函数与回射服务器实现
I/O多路复用
在网络编程中,不得不提的一个概念就是I/O多路复用机制,采用I/O多路复用机制,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,能够同时处理多个描述符。
目前Linux上的I/O多路复用机制主要有三种select,poll,epoll,因为select与poll比较相似,因此这篇文章先讲select和poll函数。
接下来分别从函数、结构、注意点分别进行说明:
CSDN博客地址:https://blog.csdn.net/aa123248135/article/details/96729425
select函数

select的异步阻塞I/O模型
- 应用进程向内核进行系统调用,由于内核中接受缓冲区没有可读内容,因此返回EAGEIN错误;
- 应用进程阻塞与select调用,等到多个套接字中的任意一个变为可读;
- 当内核的接收缓冲区中由数据读入时,内核向进程返回可读条件,应用程序再次进行系统调用,内核收到调用后,将内核接受缓冲区的数据复制到应用缓冲区;
- 内核向应用进程返回数据复制成功消息。
select的函数及结构
函数结构
int select(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout); 返回:若有就绪描述符则为其数目,超时则为0,若出错则为-1
函数参数及含义:
1.maxfdp1参数指定待测试的描述符个数,它的值是带测试的最大描述符加1。(在中限定了select的最大描述符个数,一般为1024,poll和epoll可以说是没有文件描述符的限制)
2.readfds,writefds,exceptfds指定我们要让内核测试读、写、异常的文件描述符;(fd_set是select特有的的数据结构,可以简单的理解为按位标记具柄的队列),select在处理fd_set是常用的四个宏如下所示
FD_ZERO(int fd, fd_set* fds) /*clear all bits in fdset*/ FD_SET(int fd, fd_set* fds) /*turn on the bit for fd in fdset*/ FD_ISSET(int fd, fd_set* fds) /*turn off the bit for fd in fdset*/ FD_CLR(int fd, fd_set* fds) /*is the bit for fd in fdset?*/
3.timeout参数内核等待的时间,timeval的结构如下,这个参数可以有以下三种情况
struct timeval{
long tv_sec; /*seconds*/
long tv_usec; /*microseconds*/
} - 永远等待(阻塞):当timeout为空指针时,仅当文件描述符准备好I/O才返回,否则将一直阻塞;
- 等待固定时间:当timeout设置为具体的时间,且不为0,当文件描述符准备好I/O,且不超过设定的时间;
- 不等待(非阻塞):timeout为0,检查文件描述符后理解返回,非阻塞轮询模式。
select的tcp回射服务器程序
/**server端
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1
//函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//IO多路复用select
static void do_select(int listenfd);
//处理多个连接
static void handle_connection(int* connfds, fd_set &allset, fd_set &rset, int num);
int main(int argc,char *argv[])
{
int listenfd,connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
listenfd = socket_bind(IPADDRESS,PORT);
listen(listenfd,LISTENQ);
do_select(listenfd);
return 0;
}
static int socket_bind(const char* ip,int port)
{
int listenfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (listenfd == -1){
perror("socket error:");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,ip,&servaddr.sin_addr);
servaddr.sin_port = htons(port);
if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
perror("bind error: ");
exit(1);
}
return listenfd;
}
static void do_select(int listenfd)
{
int connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
int maxfd, maxi, i, nready, client[FD_SETSIZE];
fd_set rset, allset;
//添加监听描述符
//初始化客户连接描述符
maxfd = listenfd;
maxi = -1;
for(int i=0;i<FD_SETSIZE;++i)
client[i] = -1;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
//循环处理
for ( ; ; )
{
rset = allset;
//获取可用描述符的个数
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready == -1){
perror("select error:");
exit(1);
}
//测试监听描述符是否准备好
if (FD_ISSET(listenfd, &rset)){
cliaddrlen = sizeof(cliaddr);
//接受新的连接
if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1){
if (errno == EINTR)
continue;
else{
perror("accept error:");
exit(1);
}
}
fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
FD_SET(connfd, &allset);
if(connfd > maxfd)
maxfd = connfd;
//将新的连接描述符添加到数组中
//这个是找到最小的一个没有用的文件描述符
for (i = 1;i < FD_SETSIZE;i++){
if (client[i] < 0){
client[i] = connfd;
break;
}
}
if (i == FD_SETSIZE){
fprintf(stderr,"too many clients.\n");
exit(1);
}
//记录客户连接套接字的个数
maxi = (i > maxi ? i : maxi);
if (--nready <= 0)
continue;
}
//处理客户连接
handle_connection(client, allset, rset, maxi);
}
}
static void handle_connection(int* connfds,fd_set &allset ,fd_set &rset, int num) {
int i, n;
char buf[MAXLINE];
memset(buf, 0, MAXLINE);
for (i = 1; i <= num; i++) {
if (connfds[i] < 0)
continue;
//测试客户描述符是否准备好
if (FD_ISSET(connfds[i], &rset)) {
//接收客户端发送的信息
n = read(connfds[i], buf, MAXLINE);
/*n == 0 就是说明子机与母机断开连接了,子机在那边不输入,直接敲回车,这边也是可以收到回车符的*/
if (n == 0) {
close(connfds[i]);
FD_CLR(connfds[i], &allset);
char buf2[20] = {"client closed"};
write(STDOUT_FILENO, buf2, strlen(buf2));
continue;
}
// printf("read msg is: ");
write(STDOUT_FILENO, buf, n);
//向客户端发送buf
write(connfds[i], buf, n);
}
}
} /**client端
*/
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include<arpa/inet.h>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787
#define max(a,b) (a > b) ? a : b
static void handle_connection(int sockfd);
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//处理连接描述符
handle_connection(sockfd);
return 0;
}
static void handle_connection(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
struct pollfd pfds[2];
int n;
//添加连接描述符
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
//添加标准输入描述符
pfds[1].fd = STDIN_FILENO;
pfds[1].events = POLLIN;
for (; ;){
poll(pfds,2,-1);
if (pfds[0].revents & POLLIN){
n = read(sockfd,recvline,MAXLINE);
if (n == 0){
fprintf(stderr,"client: server is closed.\n");
close(sockfd);
}
write(STDOUT_FILENO,recvline,n);
}
//测试标准输入是否准备好
if (pfds[1].revents & POLLIN){
n = read(STDIN_FILENO,sendline,MAXLINE);
if (n == 0)
{
shutdown(sockfd,SHUT_WR);
continue;
}
write(sockfd,sendline,n);
}
}
}
poll函数
poll函数的异步阻塞I/O模型与select的类似。但是poll与select不同的是,poll 不是为每个条件构造一个描述符集,而是构造一个 pollfd 结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。
poll的函数及结构
函数结构
#include /* poll函数 * fds指向监控pollfd的首指针 * nfds是poll监听的最大文件描述符的个数,使用时传入最大文件描述符+1 * timeout设置超时模式 * 返回值:0表示没有套接字发生变化 n(n>0)表示有n个套接字有变化 -1有错误 */ int poll(struct pollfd* fds, unsigned long nfds, int timeout); 返回:若有就绪描述符则为其数目,超时则为0,若出错则为-1
函数参数及含义:
1.fds参数是指向一个pollfd结构的第一个元素的指针。其中每个元素都为polled结构,用于定于及返回poll感兴趣的文件描述符。
struct pollfd{
int fd; /*descriptor to check*/
short events; /*events of interest on fd*/
short revents; /*events that occurred on fd*/
}; 其中events和reverts标志是系统定义的一些常值,常用的如下:
2.nfds参数指定pllfd中参数的个数;
3.timeout指定poll等待的时间,为一个毫秒级的正数;
poll的tcp回射服务器程序
/* server端
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1
//函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//IO多路复用poll
static void do_poll(int listenfd);
//处理多个连接
static void handle_connection(struct pollfd *connfds, int num, int nready);
int main(int argc,char *argv[])
{
int listenfd,connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
listenfd = socket_bind(IPADDRESS,PORT);
listen(listenfd,LISTENQ);
do_poll(listenfd);
return 0;
}
static int socket_bind(const char* ip,int port)
{
int listenfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (listenfd == -1){
perror("socket error:");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,ip,&servaddr.sin_addr);
servaddr.sin_port = htons(port);
if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
perror("bind error: ");
exit(1);
}
return listenfd;
}
static void do_poll(int listenfd)
{
int connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
struct pollfd clientfds[OPEN_MAX];
int maxi;
int i;
int nready;
//添加监听描述符
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
//初始化客户连接描述符
for (i = 1;i < OPEN_MAX;i++)
clientfds[i].fd = -1;
maxi = 0;
//循环处理
for ( ; ; ){
//获取可用描述符的个数
nready = poll(clientfds,maxi+1,INFTIM);
if (nready == -1){
perror("poll error:");
exit(1);
}
//测试监听描述符是否准备好
if (clientfds[0].revents & POLLIN){
cliaddrlen = sizeof(cliaddr);
//接受新的连接
if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1){
if (errno == EINTR)
continue;
else{
perror("accept error:");
exit(1);
}
}
fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
//将新的连接描述符添加到数组中
//这个是找到最小的一个没有用的文件描述符
for (i = 1;i < OPEN_MAX;i++){
if (clientfds[i].fd < 0){
clientfds[i].fd = connfd;
break;
}
}
if (i == OPEN_MAX){
fprintf(stderr,"too many clients.\n");
exit(1);
}
//将新的描述符添加到读描述符集合中
clientfds[i].events = POLLIN;
//记录客户连接套接字的个数
maxi = (i > maxi ? i : maxi);
if (--nready <= 0)
continue;
}
//处理客户连接
handle_connection(clientfds, maxi, nready);
}
}
static void handle_connection(struct pollfd *connfds, int num, int nready)
{
int i,n;
char buf[MAXLINE];
memset(buf,0,MAXLINE);
for (i = 1;i <= num;i++){
if (connfds[i].fd < 0)
continue;
//测试客户描述符是否准备好
if (connfds[i].revents & POLLIN){
//接收客户端发送的信息
n = read(connfds[i].fd,buf,MAXLINE);
/*n == 0 就是说明子机与母机断开连接了,子机在那边不输入,直接敲回车,这边也是可以收到回车符的*/
if (n == 0){
close(connfds[i].fd);
connfds[i].fd = -1;
char buf2[1024] = {"client closed"};
write(STDOUT_FILENO, buf2, strlen(buf2));
continue;
}
// printf("read msg is: ");
write(STDOUT_FILENO,buf,n);
//向客户端发送buf
write(connfds[i].fd,buf,n);
if(--nready <= 0)
break;
}
}
} /**client端
*/
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include<arpa/inet.h>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787
#define max(a,b) (a > b) ? a : b
static void handle_connection(int sockfd);
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//处理连接描述符
handle_connection(sockfd);
return 0;
}
static void handle_connection(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
struct pollfd pfds[2];
int n;
//添加连接描述符
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
//添加标准输入描述符
pfds[1].fd = STDIN_FILENO;
pfds[1].events = POLLIN;
for (; ;){
poll(pfds,2,-1);
if (pfds[0].revents & POLLIN){
n = read(sockfd,recvline,MAXLINE);
if (n == 0){
fprintf(stderr,"client: server is closed.\n");
close(sockfd);
}
write(STDOUT_FILENO,recvline,n);
}
//测试标准输入是否准备好
if (pfds[1].revents & POLLIN){
n = read(STDIN_FILENO,sendline,MAXLINE);
if (n == 0)
{
shutdown(sockfd,SHUT_WR);
continue;
}
write(sockfd,sendline,n);
}
}
}
#C/C++##笔记#
OPPO公司福利 1202人发布