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 socket
    int 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 it
    bzero(&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.listen
    if(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.xxx
            if( 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.close
    close(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 socket
    int 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 0
    client_addr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    if(( inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr)) != 1){
        perror("inet_pton");
        exit(1);
    }
#endif

    if(connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0){
        perror("connect");
        exit(1);
    }
    printf("client start....\n");

#if 0
    
#else
    fd_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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值