> 文章列表 > C语言非阻塞模式实现

C语言非阻塞模式实现

C语言非阻塞模式实现

背景:我一开始建立一个套接字的时候,发现系统内核将它设置为了阻塞IO模式。这时候我想将其设置为阻塞IO模式。使用fcntl()和iocntl()函数实现。

1、int fcntl(int fd, int cmd, ... /* arg */ );

int fcntl(int fd, int cmd, long arg);

      int flag

      flag = fcntl(sockfd, F_GETFL, 0);

      flag |= O_NONBLOCK;

      fcntl(sockfd, F_SETFL, flag);

2、iocnt

int b_on =1;

           ioctl(sock_fd, FIONBIO, &b_on);

多路复用I/O:

        accept会阻塞,新建立好的newfd里面read数据也会阻塞。可以使用多线程或者多进程解决。

        多路复用解决:把所有fd(文件描述符)放入集合内,监控关注的fd,一个或者几个有数据的时候,退出来,判断到底哪个是有数据了。

        linux下每个进程默认情况下最多可以打开1024个文件描述符fd,文件描述符的特点:

                非负整数;

                从最小的数字分配;

                每个启动进程默认打开0,1,2三个文件描述符。

     多路复用不仅支持套接字fd,也支持普通文件的fd。

        linux下如何实现多路复用?

                fd_set集合(数组):里面存放了该进程的文件描述符。0~1024

                        每一位代表一个文件描述符,  为了监控每个文件描述符,又开辟了一个fd_set类型数组,将fd_set集合中要监控的数组位置设置为1,监控的文件描述符放入集合里面,1024/8,每位表示一个文件描述符,新开辟的数组可以监控多少个文件描述符1024/8,这样算下来还是有些多了。

                再想一个办法,拿到最大的文件描述符maxfd,开辟一个maxfd+1大小的数组,但是在内核里面是32位的CPU,开辟数组的时候一般是四个字节的整数倍,比如开辟maxfd+1 = 0+1/3+1/7+1大小的数组。

        多路复用模型:

                把关心的文件描述符fd加入到监控集合中;

                调用select或者poll函数监控集合fd_set中那些文件描述符(阻塞等待集合中一个或多个文件描述符有数据);

                当有数据时,退出select或者poll阻塞;

                依次判断哪个文件描述符有数据;

                依次处理有数据的文件描述符的数据。

      实现过程:

                1、fd_set(内核中一个数组),调用一些函数围绕它操作。

                        设置一个读rset:读集合; FD_ZERO()清除集合里面的数据,FD_SET()把关心的fd加入集合,FD_CLR()从集合中清除fd,FD_ISSET()判断fd是否在set中。                   

                void FD_CLR(int fd, fd_set *set);
               int  FD_ISSET(int fd, fd_set *set);
               void FD_SET(int fd, fd_set *set);
               void FD_ZERO(fd_set *set);

                2、调用select监控集合

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

                      参数:nfds= maxfd+1(因为数组是从0)开始的,readfds读集合,writefds写集合,exceptfds异常集合,timeout超时:

Null:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回。

                      一般情况下:读结合填写rset, 写集合填NULL(不需要阻塞),异常集合填(带外数据填0)其它情况填NULL,timeout有结构体:

        struct timeval{

                long tv_sec; //秒

                long tv_usec; //微秒  

        }

        时间单位:1秒(s) = 10^3毫秒(ms)=10^6微秒(us) = 10^9纳秒(ns) = 10^12皮秒(ps)

         注意:select退出后,集合是有数据的集合(关心的数据已经加入集合)

        3、判断

                if(FD_ISSET(fd, rset)){

                        如果要监听的套接字fd有数据,则有新的客户端连接,则accept;

                        如果已经建立连接的套接字有数据,则read读取客户端数据。

                }        

实例:

       服务器:

void sig_child_handle(int signo){if(SIGCHLD == signo){//NULL:状态不许要,WNOHANG非阻塞方式waitpid(-1, NULL, WNOHANG);}
}void cli_data_handle(int *arg);int main(int argc, char *argv[])
{//1.create socketint sockfd;struct sockaddr_in sin;signal(SIGCHLD, sig_child_handle);if((sockfd = socket(AF_INET, SOCK_STREAM, 0))<0){perror("create socket");return -1;}//2.bind ip//2.define a struct, clear and fill itbzero(&sin, sizeof(sin));sin.sin_family = AF_INET;sin.sin_port = htons(SERV_PORT); //transform internet byte order//优化1:让服务程序绑定在任意IP上sin.sin_addr.s_addr = htonl(INADDR_ANY);if(inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr.s_addr) != 1){perror("inet_pton");return 1;}if( ( bind(sockfd, (struct sockaddr *)(&sin), sizeof(sin)) ) == -1){perror("bind");exit(1);}//3.listenif(listen(sockfd, BACKLOG)<0){perror("listen");exit(1);}printf("server start\\n");//4.accepet()阻塞客户端请求int newfd;struct sockaddr_in cin;socklen_t addrlen = sizeof(cin);while(1){pid_t pid = -1;if((newfd = accept(sockfd, (struct sockaddr *)&cin, &addrlen))<0){perror("accept");break;}//创建一个子进程,用于处理连接的客户端的交互数据if((pid = fork()) < 0){perror("fork");exit(1);}if(pid > 0){//父进程close(newfd);}else if(pid == 0){//子进程close(sockfd);char ipv4_addr[16]; //xxx.xxx.xxx.xxxif( inet_ntop(AF_INET, (void *)&cin.sin_addr, ipv4_addr, sizeof(cin)) == NULL){perror("inet_top");exit(1);}printf("client(%s:%d)\\n", ipv4_addr, htons(cin.sin_port));cli_data_handle(&newfd);return 0;}}//6.closeclose(sockfd);return 0;
}
void cli_data_handle(int *arg){int newfd = *(int *)arg;printf("child handling process:newfd=%d\\n", newfd);//5.write() 与newfd读写数据char buf[128];char resp_buf[138];int ret = -1; while(1){bzero(buf, strlen(buf));do{ ret = read(newfd, buf, 127);}while(ret<0 && errno==EINTR);if(ret < 0){ perror("read");exit(1);}   //客户端已经关闭if(!ret){break;}printf("recive data:%s\\n", buf);bzero(resp_buf, 138);strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));strcat(resp_buf, buf);do{ret = write(newfd, resp_buf, strlen(resp_buf));}while(ret <0 &&EINTR == errno);if( !strncasecmp(buf, QUIT_STR, strlen(buf)-1) ){printf("client(%d) exiting\\n", newfd);break;}}close(newfd);
}

客户端:

        

/*./client SERV_IP_ADDR  SERV_PORT */
#include "inet.h"void usage(char *s){printf("\\n%s serv_ip serv_port", s); printf("\\n\\t serv_ip: server ip address");printf("\\n\\t sert_port: server port(>5000)\\n\\n");
}int main(int argc, char *argv[])
{   int port;if(argc != 3){ usage(argv[0]);exit(1);}   //1.create socketint sockfd;if((sockfd = socket(AF_INET, SOCK_STREAM, 0))<0){perror("create socket");exit(1);}//2.连接服务器//填充sockaddr_in 结构体变量,port =atoi(argv[2]);if(port < 5000){usage(argv[0]);exit(1);}struct sockaddr_in sin;bzero(&sin, sizeof(sin));sin.sin_family = AF_INET;sin.sin_port = htons(port);//网络字节序端口号
#if 0client_addr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#elseif(( inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr)) != 1){perror("inet_pton");exit(1);}
#endifif(connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0){perror("connect");exit(1);}printf("client start....\\n");#if 0#elsefd_set rset;struct timeval tout;int maxfd = -1;char buf[128];int ret;while(1){FD_ZERO(&rset);FD_SET(0, &rset);FD_SET(sockfd, &rset);maxfd = sockfd;tout.tv_sec = 5;tout.tv_usec = 0;select(maxfd+1, &rset, NULL, NULL, &tout);if(FD_ISSET(0, &rset)){ //如果文件描述符在集合rset里面,表示键盘有输入//读取键盘输入,发送到网络套接字bzero(buf, 128);do{ret = read(0, buf, 127);}while(ret<0 && EINTR == errno);if(ret < 0 ){perror("read");continue;}if(!ret) break; /* 键盘没有输入数据*/if(write(sockfd, buf, strlen(buf)) < 0){perror("error");break;}if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)) ){printf("client(%d) is exiting\\n", sockfd);break;}}if(FD_ISSET(sockfd, &rset)){ //服务器给发送过来的数据//读取套接字内容bzero(buf, 127);do{ret = read(sockfd, buf, 127);}while(ret<0 && EINTR == errno);if(ret < 0 ){perror("read");continue;}if(!ret) break;printf("server said: %s\\n", buf);if((strlen(buf) > strlen(SERV_RESP_STR)) &&(!strncasecmp(buf+strlen(SERV_RESP_STR), QUIT_STR, strlen(QUIT_STR)))){printf("client is exiting!\\n");break;}}}
#endif//4.close()关闭套接字close(sockfd);return 0;
}